diff --git a/pos/blueprints/pos_bp.py b/pos/blueprints/pos_bp.py index 2a81748..3ef4ea6 100644 --- a/pos/blueprints/pos_bp.py +++ b/pos/blueprints/pos_bp.py @@ -19,6 +19,57 @@ from services.audit import log_action pos_bp = Blueprint('pos', __name__, url_prefix='/pos/api') +def _enrich_items(cur, items, customer_id=None): + """Look up inventory data for items that lack unit_price/tax_rate. + + Returns list of dicts with all fields needed by calculate_totals. + """ + enriched = [] + for item in items: + inv_id = item.get('inventory_id') + qty = int(item.get('quantity', 1)) + if qty <= 0: + raise ValueError(f"Invalid quantity for inventory_id {inv_id}") + + cur.execute(""" + SELECT id, part_number, name, cost, price_1, price_2, price_3, + tax_rate, branch_id + FROM inventory WHERE id = %s AND is_active = true + """, (inv_id,)) + inv = cur.fetchone() + if not inv: + raise ValueError(f"Inventory item {inv_id} not found or inactive") + + # Determine price tier from customer if provided + price_tier = 1 + if customer_id: + cur.execute("SELECT price_tier FROM customers WHERE id = %s", (customer_id,)) + cust = cur.fetchone() + if cust and cust[0]: + price_tier = int(cust[0]) + + # price_1=inv[4], price_2=inv[5], price_3=inv[6] + tier_prices = {1: inv[4], 2: inv[5], 3: inv[6]} + default_price = float(tier_prices.get(price_tier, inv[4]) or inv[4]) + + unit_price = float(item.get('unit_price', default_price)) + discount_pct = float(item.get('discount_pct', 0)) + tax_rate = float(item.get('tax_rate', inv[7] or 0.16)) + + enriched.append({ + 'inventory_id': inv_id, + 'part_number': inv[1], + 'name': inv[2], + 'quantity': qty, + 'unit_price': unit_price, + 'unit_cost': float(inv[3]) if inv[3] else 0, + 'discount_pct': discount_pct, + 'tax_rate': tax_rate, + 'branch_id': inv[8], + }) + return enriched + + # ─── Sales ─────────────────────────────────────── @pos_bp.route('/sales', methods=['POST']) @@ -362,8 +413,15 @@ def create_quotation(): conn = get_tenant_conn(g.tenant_id) cur = conn.cursor() + # Enrich items with inventory data (price, tax, etc.) + try: + enriched = _enrich_items(cur, items, data.get('customer_id')) + except ValueError as e: + cur.close(); conn.close() + return jsonify({'error': str(e)}), 400 + # Calculate totals - totals = calculate_totals(items) + totals = calculate_totals(enriched) valid_days = int(data.get('valid_days', 7)) valid_until = (date.today() + timedelta(days=valid_days)).isoformat() @@ -371,24 +429,21 @@ def create_quotation(): try: cur.execute(""" INSERT INTO quotations - (branch_id, customer_id, employee_id, subtotal, discount_total, + (branch_id, customer_id, employee_id, subtotal, tax_total, total, status, valid_until, notes) - VALUES (%s,%s,%s,%s,%s,%s,%s,'active',%s,%s) + VALUES (%s,%s,%s,%s,%s,%s,'active',%s,%s) RETURNING id, created_at """, ( g.branch_id, data.get('customer_id'), g.employee_id, - totals['subtotal'], totals['discount_total'], totals['tax_total'], + totals['subtotal'], totals['tax_total'], totals['total'], valid_until, data.get('notes') )) quot_id, created_at = cur.fetchone() # Insert quotation items for item in totals['items']: - # Look up part_number and name from inventory - cur.execute("SELECT part_number, name FROM inventory WHERE id = %s", (item['inventory_id'],)) - inv = cur.fetchone() - part_number = inv[0] if inv else item.get('part_number', '') - name = inv[1] if inv else item.get('name', '') + part_number = item.get('part_number', '') + name = item.get('name', '') line_subtotal = round( item['unit_price'] * item['quantity'] * (1 - item['discount_pct'] / 100), 2 @@ -670,8 +725,15 @@ def create_layaway(): conn = get_tenant_conn(g.tenant_id) cur = conn.cursor() + # Enrich items with inventory data + try: + enriched = _enrich_items(cur, items, customer_id) + except ValueError as e: + cur.close(); conn.close() + return jsonify({'error': str(e)}), 400 + # Calculate totals - totals = calculate_totals(items) + totals = calculate_totals(enriched) if initial_payment > totals['total']: cur.close(); conn.close() @@ -697,11 +759,9 @@ def create_layaway(): # Insert layaway items and reserve stock (table created by migration v1.1_pos_tables.sql) from services.inventory_engine import record_operation for item in totals['items']: - cur.execute("SELECT part_number, name, branch_id FROM inventory WHERE id = %s", (item['inventory_id'],)) - inv = cur.fetchone() - part_number = inv[0] if inv else item.get('part_number', '') - name = inv[1] if inv else item.get('name', '') - item_branch_id = inv[2] if inv else g.branch_id + part_number = item.get('part_number', '') + name = item.get('name', '') + item_branch_id = item.get('branch_id', g.branch_id) line_subtotal = round( item['unit_price'] * item['quantity'] * (1 - item['discount_pct'] / 100), 2 @@ -997,11 +1057,29 @@ def complete_layaway(layaway_id): if status != 'active': cur.close(); conn.close() return jsonify({'error': f'Layaway is {status}'}), 400 - if paid < total: - cur.close(); conn.close() - return jsonify({'error': f'Layaway not fully paid. Remaining: ${total - paid:.2f}'}), 400 + remaining = round(total - paid, 2) try: + # If there's a remaining balance, accept a final payment with the complete call + if remaining > 0: + final_method = data.get('payment_method', 'efectivo') + cur.execute(""" + INSERT INTO layaway_payments + (layaway_id, amount, payment_method, reference, employee_id) + VALUES (%s,%s,%s,%s,%s) + """, (layaway_id, remaining, final_method, + data.get('reference', 'Pago final al completar'), g.employee_id)) + cur.execute("UPDATE layaways SET amount_paid = total WHERE id = %s", (layaway_id,)) + paid = total + + # Record cash movement for final payment + register_id = data.get('register_id') + if register_id and final_method == 'efectivo': + cur.execute(""" + INSERT INTO cash_movements (register_id, type, amount, reason, employee_id) + VALUES (%s, 'in', %s, %s, %s) + """, (register_id, remaining, f'Apartado #{layaway_id} - pago final', g.employee_id)) + # Get layaway items cur.execute(""" SELECT inventory_id, quantity, unit_price, discount_pct, tax_rate