feat(pos): add accounting engine — auto journal entries for sales/purchases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
604
pos/services/accounting_engine.py
Normal file
604
pos/services/accounting_engine.py
Normal file
@@ -0,0 +1,604 @@
|
||||
# /home/Autopartes/pos/services/accounting_engine.py
|
||||
"""Accounting engine: automatic journal entry generation for business operations.
|
||||
|
||||
Every sale, purchase, cash cut, credit payment, and cancellation produces
|
||||
balanced journal entries (polizas). The engine looks up SAT account IDs
|
||||
by code and creates journal_entries + journal_entry_lines records.
|
||||
|
||||
Account codes (from SAT seed):
|
||||
110 = Caja
|
||||
111 = Bancos
|
||||
120 = Clientes
|
||||
130 = Inventarios
|
||||
140 = IVA Acreditable
|
||||
210 = Proveedores
|
||||
220 = IVA Trasladado
|
||||
410 = Ventas
|
||||
420 = Devoluciones sobre Ventas
|
||||
510 = Costo de Mercancia Vendida
|
||||
|
||||
All functions receive a psycopg2 connection (caller controls commit).
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
from flask import g
|
||||
|
||||
|
||||
def _to_dec(val):
|
||||
"""Convert a value to Decimal for precise arithmetic."""
|
||||
if val is None:
|
||||
return Decimal('0')
|
||||
return Decimal(str(val))
|
||||
|
||||
|
||||
TWO = Decimal('0.01')
|
||||
|
||||
|
||||
def _get_account_id(cur, code):
|
||||
"""Look up account ID by code. Raises ValueError if not found."""
|
||||
cur.execute("SELECT id FROM accounts WHERE code = %s AND is_active = true", (code,))
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
raise ValueError(f"Account with code '{code}' not found")
|
||||
return row[0]
|
||||
|
||||
|
||||
def _get_account_ids(cur, codes):
|
||||
"""Look up multiple account IDs by code. Returns dict {code: id}."""
|
||||
result = {}
|
||||
for code in codes:
|
||||
result[code] = _get_account_id(cur, code)
|
||||
return result
|
||||
|
||||
|
||||
def get_next_entry_number(conn):
|
||||
"""Get the next sequential journal entry number.
|
||||
|
||||
Uses a simple MAX+1 approach. For high-concurrency environments this
|
||||
could be replaced with a sequence, but for single-tenant refaccionarias
|
||||
the transaction-level lock from the INSERT is sufficient.
|
||||
|
||||
Args:
|
||||
conn: psycopg2 connection to tenant DB
|
||||
|
||||
Returns:
|
||||
int: next entry number (starts at 1)
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT COALESCE(MAX(entry_number), 0) + 1 FROM journal_entries")
|
||||
number = cur.fetchone()[0]
|
||||
cur.close()
|
||||
return number
|
||||
|
||||
|
||||
def _check_period_open(cur, entry_date):
|
||||
"""Verify the fiscal period for the given date is open.
|
||||
|
||||
If no fiscal_periods row exists for the month, it is considered open
|
||||
(periods are only created when explicitly closed).
|
||||
|
||||
Args:
|
||||
cur: psycopg2 cursor
|
||||
entry_date: date object
|
||||
|
||||
Raises:
|
||||
ValueError: if the period is closed
|
||||
"""
|
||||
cur.execute("""
|
||||
SELECT status FROM fiscal_periods
|
||||
WHERE year = %s AND month = %s
|
||||
""", (entry_date.year, entry_date.month))
|
||||
row = cur.fetchone()
|
||||
if row and row[0] == 'closed':
|
||||
raise ValueError(
|
||||
f"Fiscal period {entry_date.year}-{entry_date.month:02d} is closed. "
|
||||
f"Cannot create journal entries in a closed period."
|
||||
)
|
||||
|
||||
|
||||
def _create_entry(cur, entry_number, entry_date, entry_type, description,
|
||||
reference_type, reference_id, lines, is_auto=True):
|
||||
"""Create a journal entry with its lines.
|
||||
|
||||
Validates that total debits == total credits before inserting.
|
||||
|
||||
Args:
|
||||
cur: psycopg2 cursor
|
||||
entry_number: int sequential number
|
||||
entry_date: date
|
||||
entry_type: 'ingreso' | 'egreso' | 'diario' | 'poliza'
|
||||
description: str
|
||||
reference_type: 'sale' | 'purchase' | 'cash_register' | 'payment' | None
|
||||
reference_id: int or None
|
||||
lines: list of dicts with keys: account_id, debit, credit, description
|
||||
is_auto: bool (True for system-generated entries)
|
||||
|
||||
Returns:
|
||||
int: journal entry ID
|
||||
|
||||
Raises:
|
||||
ValueError: if debits != credits
|
||||
"""
|
||||
# Validate balance
|
||||
total_debit = sum(_to_dec(line['debit']) for line in lines)
|
||||
total_credit = sum(_to_dec(line['credit']) for line in lines)
|
||||
|
||||
if total_debit.quantize(TWO, ROUND_HALF_UP) != total_credit.quantize(TWO, ROUND_HALF_UP):
|
||||
raise ValueError(
|
||||
f"Unbalanced entry: debits={total_debit} credits={total_credit}"
|
||||
)
|
||||
|
||||
created_by = getattr(g, 'employee_id', None)
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO journal_entries
|
||||
(entry_number, date, type, description, reference_type, reference_id,
|
||||
status, created_by, is_auto)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, 'posted', %s, %s)
|
||||
RETURNING id
|
||||
""", (
|
||||
entry_number, entry_date, entry_type, description,
|
||||
reference_type, reference_id, created_by, is_auto
|
||||
))
|
||||
entry_id = cur.fetchone()[0]
|
||||
|
||||
for line in lines:
|
||||
debit = float(_to_dec(line['debit']).quantize(TWO, ROUND_HALF_UP))
|
||||
credit = float(_to_dec(line['credit']).quantize(TWO, ROUND_HALF_UP))
|
||||
if debit == 0 and credit == 0:
|
||||
continue # skip zero lines
|
||||
cur.execute("""
|
||||
INSERT INTO journal_entry_lines
|
||||
(journal_entry_id, account_id, debit, credit, description)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
""", (entry_id, line['account_id'], debit, credit, line.get('description', '')))
|
||||
|
||||
return entry_id
|
||||
|
||||
|
||||
def record_sale_entry(conn, sale):
|
||||
"""Generate journal entries for a completed sale.
|
||||
|
||||
For a cash sale:
|
||||
Debit 110 Caja (or 111 Bancos for transferencia/tarjeta) = total
|
||||
Credit 410 Ventas = subtotal
|
||||
Credit 220 IVA Trasladado = tax_total
|
||||
Debit 510 Costo de Mercancia Vendida = sum(unit_cost * qty)
|
||||
Credit 130 Inventarios = sum(unit_cost * qty)
|
||||
|
||||
For a credit sale:
|
||||
Debit 120 Clientes = total
|
||||
Credit 410 Ventas = subtotal
|
||||
Credit 220 IVA Trasladado = tax_total
|
||||
Debit 510 Costo de Mercancia Vendida = sum(unit_cost * qty)
|
||||
Credit 130 Inventarios = sum(unit_cost * qty)
|
||||
|
||||
Args:
|
||||
conn: psycopg2 connection to tenant DB
|
||||
sale: dict from process_sale() with keys:
|
||||
id, sale_type, payment_method, subtotal, discount_total,
|
||||
tax_total, total, items (with unit_cost, quantity)
|
||||
|
||||
Returns:
|
||||
int: journal entry ID
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
entry_date = date.today()
|
||||
_check_period_open(cur, entry_date)
|
||||
|
||||
entry_number = get_next_entry_number(conn)
|
||||
sale_id = sale['id']
|
||||
sale_type = sale.get('sale_type', 'cash')
|
||||
payment_method = sale.get('payment_method', 'efectivo')
|
||||
subtotal = _to_dec(sale['subtotal'])
|
||||
tax_total = _to_dec(sale['tax_total'])
|
||||
total = _to_dec(sale['total'])
|
||||
|
||||
# Calculate total cost of goods sold
|
||||
cost_total = Decimal('0')
|
||||
for item in sale.get('items', []):
|
||||
item_cost = _to_dec(item.get('unit_cost', 0)) * int(item.get('quantity', 1))
|
||||
cost_total += item_cost.quantize(TWO, ROUND_HALF_UP)
|
||||
|
||||
# Determine debit account for payment received
|
||||
accounts = _get_account_ids(cur, ['110', '111', '120', '130', '220', '410', '510'])
|
||||
|
||||
if sale_type == 'credit':
|
||||
payment_account_id = accounts['120'] # Clientes
|
||||
payment_desc = f'Clientes - Venta a credito #{sale_id}'
|
||||
elif payment_method in ('transferencia', 'tarjeta'):
|
||||
payment_account_id = accounts['111'] # Bancos
|
||||
payment_desc = f'Bancos - Venta #{sale_id} ({payment_method})'
|
||||
else:
|
||||
payment_account_id = accounts['110'] # Caja
|
||||
payment_desc = f'Caja - Venta #{sale_id} (efectivo)'
|
||||
|
||||
lines = [
|
||||
# Payment received (debit)
|
||||
{
|
||||
'account_id': payment_account_id,
|
||||
'debit': float(total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': payment_desc,
|
||||
},
|
||||
# Revenue (credit)
|
||||
{
|
||||
'account_id': accounts['410'],
|
||||
'debit': 0,
|
||||
'credit': float(subtotal.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': f'Ventas - Venta #{sale_id}',
|
||||
},
|
||||
# IVA Trasladado (credit)
|
||||
{
|
||||
'account_id': accounts['220'],
|
||||
'debit': 0,
|
||||
'credit': float(tax_total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': f'IVA Trasladado - Venta #{sale_id}',
|
||||
},
|
||||
]
|
||||
|
||||
# Cost of goods sold entries (only if cost > 0)
|
||||
if cost_total > 0:
|
||||
lines.append({
|
||||
'account_id': accounts['510'],
|
||||
'debit': float(cost_total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': f'Costo mercancia - Venta #{sale_id}',
|
||||
})
|
||||
lines.append({
|
||||
'account_id': accounts['130'],
|
||||
'debit': 0,
|
||||
'credit': float(cost_total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': f'Inventarios - Venta #{sale_id}',
|
||||
})
|
||||
|
||||
entry_id = _create_entry(
|
||||
cur, entry_number, entry_date, 'ingreso',
|
||||
f'Venta #{sale_id} - {payment_method}',
|
||||
'sale', sale_id, lines
|
||||
)
|
||||
|
||||
cur.close()
|
||||
return entry_id
|
||||
|
||||
|
||||
def record_purchase_entry(conn, purchase_data):
|
||||
"""Generate journal entries for a purchase (inventory receipt).
|
||||
|
||||
Debit 130 Inventarios = subtotal (cost of goods)
|
||||
Debit 140 IVA Acreditable = tax amount
|
||||
Credit 210 Proveedores = total (subtotal + tax)
|
||||
|
||||
Args:
|
||||
conn: psycopg2 connection
|
||||
purchase_data: dict with keys:
|
||||
reference_id: int (purchase order or operation ID)
|
||||
subtotal: float (cost of goods before tax)
|
||||
tax_amount: float (IVA 16%)
|
||||
total: float (subtotal + tax)
|
||||
supplier_name: str (optional, for description)
|
||||
|
||||
Returns:
|
||||
int: journal entry ID
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
entry_date = date.today()
|
||||
_check_period_open(cur, entry_date)
|
||||
|
||||
entry_number = get_next_entry_number(conn)
|
||||
ref_id = purchase_data.get('reference_id')
|
||||
subtotal = _to_dec(purchase_data['subtotal'])
|
||||
tax_amount = _to_dec(purchase_data.get('tax_amount', 0))
|
||||
total = _to_dec(purchase_data['total'])
|
||||
supplier = purchase_data.get('supplier_name', 'Proveedor')
|
||||
|
||||
accounts = _get_account_ids(cur, ['130', '140', '210'])
|
||||
|
||||
lines = [
|
||||
{
|
||||
'account_id': accounts['130'],
|
||||
'debit': float(subtotal.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': f'Inventarios - Compra {supplier}',
|
||||
},
|
||||
{
|
||||
'account_id': accounts['210'],
|
||||
'debit': 0,
|
||||
'credit': float(total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': f'Proveedores - Compra {supplier}',
|
||||
},
|
||||
]
|
||||
|
||||
# IVA Acreditable (only if tax > 0)
|
||||
if tax_amount > 0:
|
||||
lines.append({
|
||||
'account_id': accounts['140'],
|
||||
'debit': float(tax_amount.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': f'IVA Acreditable - Compra {supplier}',
|
||||
})
|
||||
|
||||
entry_id = _create_entry(
|
||||
cur, entry_number, entry_date, 'diario',
|
||||
f'Compra - {supplier}',
|
||||
'purchase', ref_id, lines
|
||||
)
|
||||
|
||||
cur.close()
|
||||
return entry_id
|
||||
|
||||
|
||||
def record_cash_cut_entry(conn, register):
|
||||
"""Generate journal entry for a cash register close (corte Z).
|
||||
|
||||
Moves cash from register (Caja) to bank (Bancos):
|
||||
Debit 111 Bancos = closing_amount
|
||||
Credit 110 Caja = closing_amount
|
||||
|
||||
Args:
|
||||
conn: psycopg2 connection
|
||||
register: dict with keys:
|
||||
id: int (cash register ID)
|
||||
closing_amount: float
|
||||
register_number: int
|
||||
|
||||
Returns:
|
||||
int: journal entry ID
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
entry_date = date.today()
|
||||
_check_period_open(cur, entry_date)
|
||||
|
||||
entry_number = get_next_entry_number(conn)
|
||||
reg_id = register['id']
|
||||
amount = _to_dec(register['closing_amount'])
|
||||
reg_num = register.get('register_number', '?')
|
||||
|
||||
if amount <= 0:
|
||||
cur.close()
|
||||
return None # No entry for zero/negative close
|
||||
|
||||
accounts = _get_account_ids(cur, ['110', '111'])
|
||||
|
||||
lines = [
|
||||
{
|
||||
'account_id': accounts['111'],
|
||||
'debit': float(amount.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': f'Bancos - Corte caja #{reg_num}',
|
||||
},
|
||||
{
|
||||
'account_id': accounts['110'],
|
||||
'debit': 0,
|
||||
'credit': float(amount.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': f'Caja - Corte caja #{reg_num}',
|
||||
},
|
||||
]
|
||||
|
||||
entry_id = _create_entry(
|
||||
cur, entry_number, entry_date, 'diario',
|
||||
f'Corte de caja #{reg_num} (registro #{reg_id})',
|
||||
'cash_register', reg_id, lines
|
||||
)
|
||||
|
||||
cur.close()
|
||||
return entry_id
|
||||
|
||||
|
||||
def record_credit_payment_entry(conn, payment):
|
||||
"""Generate journal entry for a customer credit payment.
|
||||
|
||||
Debit 111 Bancos (or 110 Caja) = amount
|
||||
Credit 120 Clientes = amount
|
||||
|
||||
Args:
|
||||
conn: psycopg2 connection
|
||||
payment: dict with keys:
|
||||
customer_id: int
|
||||
customer_name: str (optional)
|
||||
amount: float
|
||||
payment_method: 'efectivo' | 'transferencia' | 'tarjeta'
|
||||
reference_id: int (optional, e.g. sale_id being paid)
|
||||
|
||||
Returns:
|
||||
int: journal entry ID
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
entry_date = date.today()
|
||||
_check_period_open(cur, entry_date)
|
||||
|
||||
entry_number = get_next_entry_number(conn)
|
||||
amount = _to_dec(payment['amount'])
|
||||
customer_name = payment.get('customer_name', f"Cliente #{payment['customer_id']}")
|
||||
method = payment.get('payment_method', 'efectivo')
|
||||
ref_id = payment.get('reference_id')
|
||||
|
||||
accounts = _get_account_ids(cur, ['110', '111', '120'])
|
||||
|
||||
if method in ('transferencia', 'tarjeta'):
|
||||
debit_account = accounts['111'] # Bancos
|
||||
debit_desc = f'Bancos - Cobro credito {customer_name}'
|
||||
else:
|
||||
debit_account = accounts['110'] # Caja
|
||||
debit_desc = f'Caja - Cobro credito {customer_name}'
|
||||
|
||||
lines = [
|
||||
{
|
||||
'account_id': debit_account,
|
||||
'debit': float(amount.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': debit_desc,
|
||||
},
|
||||
{
|
||||
'account_id': accounts['120'],
|
||||
'debit': 0,
|
||||
'credit': float(amount.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': f'Clientes - Cobro credito {customer_name}',
|
||||
},
|
||||
]
|
||||
|
||||
entry_id = _create_entry(
|
||||
cur, entry_number, entry_date, 'ingreso',
|
||||
f'Cobro credito - {customer_name} ({method})',
|
||||
'payment', ref_id, lines
|
||||
)
|
||||
|
||||
cur.close()
|
||||
return entry_id
|
||||
|
||||
|
||||
def record_cancellation_entry(conn, sale):
|
||||
"""Generate reverse journal entry for a cancelled sale.
|
||||
|
||||
This is the exact reverse of record_sale_entry():
|
||||
Credit payment account (Caja/Bancos/Clientes) = total
|
||||
Debit 410 Ventas = subtotal
|
||||
Debit 220 IVA Trasladado = tax_total
|
||||
Credit 510 Costo de Mercancia Vendida = cost_total
|
||||
Debit 130 Inventarios = cost_total
|
||||
|
||||
Args:
|
||||
conn: psycopg2 connection
|
||||
sale: dict with the same structure as record_sale_entry() expects.
|
||||
If items are not present, looks up from DB.
|
||||
|
||||
Returns:
|
||||
int: journal entry ID
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
entry_date = date.today()
|
||||
_check_period_open(cur, entry_date)
|
||||
|
||||
entry_number = get_next_entry_number(conn)
|
||||
sale_id = sale['id']
|
||||
sale_type = sale.get('sale_type', 'cash')
|
||||
payment_method = sale.get('payment_method', 'efectivo')
|
||||
subtotal = _to_dec(sale.get('subtotal', 0))
|
||||
tax_total = _to_dec(sale.get('tax_total', 0))
|
||||
total = _to_dec(sale.get('total', 0))
|
||||
|
||||
# If sale dict lacks items, look up from DB
|
||||
items = sale.get('items')
|
||||
if not items:
|
||||
cur.execute("""
|
||||
SELECT unit_cost, quantity FROM sale_items WHERE sale_id = %s
|
||||
""", (sale_id,))
|
||||
items = [{'unit_cost': float(r[0]) if r[0] else 0, 'quantity': r[1]}
|
||||
for r in cur.fetchall()]
|
||||
|
||||
# If sale dict lacks totals, look up from DB
|
||||
if total == 0:
|
||||
cur.execute("""
|
||||
SELECT subtotal, tax_total, total, sale_type, payment_method
|
||||
FROM sales WHERE id = %s
|
||||
""", (sale_id,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
subtotal = _to_dec(row[0])
|
||||
tax_total = _to_dec(row[1])
|
||||
total = _to_dec(row[2])
|
||||
sale_type = row[3]
|
||||
payment_method = row[4]
|
||||
|
||||
cost_total = Decimal('0')
|
||||
for item in items:
|
||||
item_cost = _to_dec(item.get('unit_cost', 0)) * int(item.get('quantity', 1))
|
||||
cost_total += item_cost.quantize(TWO, ROUND_HALF_UP)
|
||||
|
||||
accounts = _get_account_ids(cur, ['110', '111', '120', '130', '220', '410', '510'])
|
||||
|
||||
# Determine which payment account to credit (reverse of debit in sale)
|
||||
if sale_type == 'credit':
|
||||
payment_account_id = accounts['120']
|
||||
payment_desc = f'Clientes - Cancelacion venta #{sale_id}'
|
||||
elif payment_method in ('transferencia', 'tarjeta'):
|
||||
payment_account_id = accounts['111']
|
||||
payment_desc = f'Bancos - Cancelacion venta #{sale_id}'
|
||||
else:
|
||||
payment_account_id = accounts['110']
|
||||
payment_desc = f'Caja - Cancelacion venta #{sale_id}'
|
||||
|
||||
lines = [
|
||||
# Reverse payment (credit the account that was debited)
|
||||
{
|
||||
'account_id': payment_account_id,
|
||||
'debit': 0,
|
||||
'credit': float(total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': payment_desc,
|
||||
},
|
||||
# Reverse revenue (debit Ventas)
|
||||
{
|
||||
'account_id': accounts['410'],
|
||||
'debit': float(subtotal.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': f'Ventas - Cancelacion venta #{sale_id}',
|
||||
},
|
||||
# Reverse IVA (debit IVA Trasladado)
|
||||
{
|
||||
'account_id': accounts['220'],
|
||||
'debit': float(tax_total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': f'IVA Trasladado - Cancelacion venta #{sale_id}',
|
||||
},
|
||||
]
|
||||
|
||||
# Reverse COGS entries (only if cost > 0)
|
||||
if cost_total > 0:
|
||||
lines.append({
|
||||
'account_id': accounts['510'],
|
||||
'debit': 0,
|
||||
'credit': float(cost_total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'description': f'Costo mercancia - Cancelacion venta #{sale_id}',
|
||||
})
|
||||
lines.append({
|
||||
'account_id': accounts['130'],
|
||||
'debit': float(cost_total.quantize(TWO, ROUND_HALF_UP)),
|
||||
'credit': 0,
|
||||
'description': f'Inventarios - Cancelacion venta #{sale_id}',
|
||||
})
|
||||
|
||||
entry_id = _create_entry(
|
||||
cur, entry_number, entry_date, 'egreso',
|
||||
f'Cancelacion venta #{sale_id}',
|
||||
'sale', sale_id, lines
|
||||
)
|
||||
|
||||
cur.close()
|
||||
return entry_id
|
||||
|
||||
|
||||
def create_manual_entry(conn, entry_data):
|
||||
"""Create a manual journal entry (type='diario').
|
||||
|
||||
Used by accountants for adjustments not tied to a specific operation.
|
||||
|
||||
Args:
|
||||
conn: psycopg2 connection
|
||||
entry_data: dict with keys:
|
||||
date: str 'YYYY-MM-DD'
|
||||
description: str
|
||||
lines: [{account_id, debit, credit, description}]
|
||||
|
||||
Returns:
|
||||
int: journal entry ID
|
||||
|
||||
Raises:
|
||||
ValueError: if entry is unbalanced or period is closed
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
entry_date = date.fromisoformat(entry_data['date'])
|
||||
_check_period_open(cur, entry_date)
|
||||
|
||||
entry_number = get_next_entry_number(conn)
|
||||
|
||||
entry_id = _create_entry(
|
||||
cur, entry_number, entry_date, 'diario',
|
||||
entry_data.get('description', 'Poliza manual'),
|
||||
None, None,
|
||||
entry_data['lines'],
|
||||
is_auto=False
|
||||
)
|
||||
|
||||
cur.close()
|
||||
return entry_id
|
||||
Reference in New Issue
Block a user