diff --git a/pos/blueprints/invoicing_bp.py b/pos/blueprints/invoicing_bp.py index 0e2bd1b..2b4dba2 100644 --- a/pos/blueprints/invoicing_bp.py +++ b/pos/blueprints/invoicing_bp.py @@ -85,8 +85,8 @@ def _get_sale_with_items(cur, sale_id): 'tax_rate': float(r[9]) if r[9] else 0.16, 'tax_amount': float(r[10]) if r[10] else 0, 'subtotal': float(r[11]) if r[11] else 0, - 'clave_prod_serv': r[12], - 'clave_unidad': r[13], + 'clave_prod_serv': r[12] or '25174800', + 'clave_unidad': r[13] or 'H87', }) return sale diff --git a/pos/migrations/v1.0_initial.sql b/pos/migrations/v1.0_initial.sql index ad65641..b6f8719 100644 --- a/pos/migrations/v1.0_initial.sql +++ b/pos/migrations/v1.0_initial.sql @@ -375,5 +375,14 @@ CREATE TABLE IF NOT EXISTS physical_count_lines ( difference INTEGER NOT NULL ); +-- ===================== +-- TENANT CONFIGURATION (key-value store for CFDI, Horux, etc.) +-- ===================== +CREATE TABLE IF NOT EXISTS tenant_config ( + key VARCHAR(100) PRIMARY KEY, + value TEXT, + updated_at TIMESTAMPTZ DEFAULT NOW() +); + -- Barcode sequence CREATE SEQUENCE IF NOT EXISTS barcode_seq START 1; diff --git a/pos/seed/sat_accounts.sql b/pos/seed/sat_accounts.sql index b6e1a1c..a16a6d9 100644 --- a/pos/seed/sat_accounts.sql +++ b/pos/seed/sat_accounts.sql @@ -1,50 +1,69 @@ --- SAT Chart of Accounts (Catálogo de Cuentas SAT) +-- SAT Chart of Accounts (Catalogo de Cuentas SAT) -- All accounts are system accounts (is_system = true) --- Parent IDs resolved via subqueries to avoid hardcoded integer dependencies - -INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) VALUES +-- Individual INSERTs so parent_id subqueries can resolve correctly. -- ============================================================ -- ACTIVO (100) -- ============================================================ -('100', 'Activo', NULL, 'activo', '100', true, true), -('110', 'Caja', (SELECT id FROM accounts WHERE code = '100'), 'activo', '110', true, true), -('111', 'Bancos', (SELECT id FROM accounts WHERE code = '100'), 'activo', '111', true, true), -('120', 'Clientes', (SELECT id FROM accounts WHERE code = '100'), 'activo', '120', true, true), -('130', 'Inventarios', (SELECT id FROM accounts WHERE code = '100'), 'activo', '130', true, true), -('140', 'IVA Acreditable', (SELECT id FROM accounts WHERE code = '100'), 'activo', '140', true, true), +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('100', 'Activo', NULL, 'activo', '100', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('110', 'Caja', (SELECT id FROM accounts WHERE code = '100'), 'activo', '110', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('111', 'Bancos', (SELECT id FROM accounts WHERE code = '100'), 'activo', '111', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('120', 'Clientes', (SELECT id FROM accounts WHERE code = '100'), 'activo', '120', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('130', 'Inventarios', (SELECT id FROM accounts WHERE code = '100'), 'activo', '130', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('140', 'IVA Acreditable', (SELECT id FROM accounts WHERE code = '100'), 'activo', '140', true, true); -- ============================================================ -- PASIVO (200) -- ============================================================ -('200', 'Pasivo', NULL, 'pasivo', '200', true, true), -('210', 'Proveedores', (SELECT id FROM accounts WHERE code = '200'), 'pasivo', '210', true, true), -('220', 'IVA Trasladado', (SELECT id FROM accounts WHERE code = '200'), 'pasivo', '220', true, true), -('230', 'ISR por Pagar', (SELECT id FROM accounts WHERE code = '200'), 'pasivo', '230', true, true), +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('200', 'Pasivo', NULL, 'pasivo', '200', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('210', 'Proveedores', (SELECT id FROM accounts WHERE code = '200'), 'pasivo', '210', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('220', 'IVA Trasladado', (SELECT id FROM accounts WHERE code = '200'), 'pasivo', '220', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('230', 'ISR por Pagar', (SELECT id FROM accounts WHERE code = '200'), 'pasivo', '230', true, true); -- ============================================================ -- CAPITAL (300) -- ============================================================ -('300', 'Capital', NULL, 'capital', '300', true, true), -('310', 'Capital Social', (SELECT id FROM accounts WHERE code = '300'), 'capital', '310', true, true), -('320', 'Resultados del Ejercicio', (SELECT id FROM accounts WHERE code = '300'), 'capital', '320', true, true), +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('300', 'Capital', NULL, 'capital', '300', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('310', 'Capital Social', (SELECT id FROM accounts WHERE code = '300'), 'capital', '310', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('320', 'Resultados del Ejercicio', (SELECT id FROM accounts WHERE code = '300'), 'capital', '320', true, true); -- ============================================================ -- INGRESOS (400) -- ============================================================ -('400', 'Ingresos', NULL, 'ingreso', '400', true, true), -('410', 'Ventas', (SELECT id FROM accounts WHERE code = '400'), 'ingreso', '410', true, true), -('420', 'Devoluciones sobre Ventas', (SELECT id FROM accounts WHERE code = '400'), 'ingreso', '420', true, true), +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('400', 'Ingresos', NULL, 'ingreso', '400', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('410', 'Ventas', (SELECT id FROM accounts WHERE code = '400'), 'ingreso', '410', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('420', 'Devoluciones sobre Ventas', (SELECT id FROM accounts WHERE code = '400'), 'ingreso', '420', true, true); -- ============================================================ -- COSTOS (500) -- ============================================================ -('500', 'Costos', NULL, 'costo', '500', true, true), -('510', 'Costo de Mercancia Vendida', (SELECT id FROM accounts WHERE code = '500'), 'costo', '510', true, true), +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('500', 'Costos', NULL, 'costo', '500', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('510', 'Costo de Mercancia Vendida', (SELECT id FROM accounts WHERE code = '500'), 'costo', '510', true, true); -- ============================================================ -- GASTOS (600) -- ============================================================ -('600', 'Gastos', NULL, 'gasto', '600', true, true), -('610', 'Gastos Operativos', (SELECT id FROM accounts WHERE code = '600'), 'gasto', '610', true, true), -('620', 'Gastos Financieros', (SELECT id FROM accounts WHERE code = '600'), 'gasto', '620', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('600', 'Gastos', NULL, 'gasto', '600', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('610', 'Gastos Operativos', (SELECT id FROM accounts WHERE code = '600'), 'gasto', '610', true, true); +INSERT INTO accounts (code, name, parent_id, type, sat_code, is_system, is_active) +VALUES ('620', 'Gastos Financieros', (SELECT id FROM accounts WHERE code = '600'), 'gasto', '620', true, true); diff --git a/pos/services/cfdi_builder.py b/pos/services/cfdi_builder.py index c2bcf4c..ad7beed 100644 --- a/pos/services/cfdi_builder.py +++ b/pos/services/cfdi_builder.py @@ -167,12 +167,12 @@ def build_ingreso_xml(sale, tenant_config, customer=None): base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP) concepto = _make_element(conceptos, 'Concepto') - concepto.set('ClaveProdServ', item.get('clave_prod_serv', '25174800')) # Default: autopartes - concepto.set('NoIdentificacion', item.get('part_number', '')) + concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800') + concepto.set('NoIdentificacion', item.get('part_number') or '') concepto.set('Cantidad', str(qty)) - concepto.set('ClaveUnidad', item.get('clave_unidad', 'H87')) # H87 = Pieza + concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87') concepto.set('Unidad', 'PZA') - concepto.set('Descripcion', item.get('name', 'Autoparte')) + concepto.set('Descripcion', item.get('name') or 'Autoparte') concepto.set('ValorUnitario', _format_amount(unit_price)) concepto.set('Importe', _format_amount(importe)) concepto.set('ObjetoImp', '02') # Si objeto de impuesto @@ -286,12 +286,12 @@ def build_egreso_xml(sale, tenant_config, customer, original_uuid): base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP) concepto = _make_element(conceptos, 'Concepto') - concepto.set('ClaveProdServ', item.get('clave_prod_serv', '25174800')) - concepto.set('NoIdentificacion', item.get('part_number', '')) + concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800') + concepto.set('NoIdentificacion', item.get('part_number') or '') concepto.set('Cantidad', str(qty)) - concepto.set('ClaveUnidad', item.get('clave_unidad', 'H87')) + concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87') concepto.set('Unidad', 'PZA') - concepto.set('Descripcion', item.get('name', 'Autoparte')) + concepto.set('Descripcion', item.get('name') or 'Autoparte') concepto.set('ValorUnitario', _format_amount(unit_price)) concepto.set('Importe', _format_amount(importe)) concepto.set('ObjetoImp', '02') diff --git a/pos/services/pos_engine.py b/pos/services/pos_engine.py index dcc6962..c43d981 100644 --- a/pos/services/pos_engine.py +++ b/pos/services/pos_engine.py @@ -481,14 +481,19 @@ def cancel_sale(conn, sale_id, reason): # Reverse accounting entry (non-blocking) try: - # Fetch tax_total for the reversal entry - cur.execute("SELECT tax_total FROM sales WHERE id = %s", (sale_id,)) - _tax_row = cur.fetchone() - record_cancellation_entry(conn, { - 'id': sale_id, - 'total': float(s_total), - 'tax_total': float(_tax_row[0]) if _tax_row else 0.0, - }) + # Fetch full sale data for the reversal entry + cur.execute("""SELECT subtotal, tax_total, total, sale_type, payment_method + FROM sales WHERE id = %s""", (sale_id,)) + _sale_row = cur.fetchone() + if _sale_row: + record_cancellation_entry(conn, { + 'id': sale_id, + 'subtotal': float(_sale_row[0]) if _sale_row[0] else 0.0, + 'tax_total': float(_sale_row[1]) if _sale_row[1] else 0.0, + 'total': float(_sale_row[2]) if _sale_row[2] else 0.0, + 'sale_type': _sale_row[3] or 'cash', + 'payment_method': _sale_row[4] or 'efectivo', + }) except Exception: pass # Accounting errors never block cancellations diff --git a/pos/services/tenant_manager.py b/pos/services/tenant_manager.py index 8fc0fdd..0b7fd2a 100644 --- a/pos/services/tenant_manager.py +++ b/pos/services/tenant_manager.py @@ -165,6 +165,18 @@ def provision_tenant(name, rfc=None, owner_name="Admin", owner_email=None, owner (owner_id, perm) ) + # Seed tenant_config with RFC and defaults + if rfc: + tenant_cur.execute(""" + INSERT INTO tenant_config (key, value) VALUES + ('tenant_rfc', %s), + ('tenant_razon_social', %s), + ('tenant_cp', '00000'), + ('cfdi_regimen_fiscal', '601'), + ('cfdi_serie', 'A') + ON CONFLICT (key) DO NOTHING + """, (rfc, name)) + tenant_conn.commit() tenant_cur.close() tenant_conn.close()