feat(pos): add 3 improvements — Spanish translations, PDF quotes, push notifications

1. Spanish translations for TecDoc catalog (translations.py) applied to
   catalog_service.py and dashboard server.py endpoints
2. Printable quotation HTML endpoint (/pos/api/quotations/<id>/pdf) with
   @media print CSS for clean browser-to-PDF output
3. Web Push notifications to owner/admin on sale cancellation, stock zero,
   and cash register differences > $500. Includes service worker, VAPID
   key management, and subscription endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 08:05:11 +00:00
parent c61e58ac6a
commit 5d5a2777eb
11 changed files with 848 additions and 14 deletions

View File

@@ -13,6 +13,7 @@ PERFORMANCE: vehicle_parts has 14B+ rows. Every query MUST:
import re
from services.na_models import is_na_model
from services.translations import translate_part_name, translate_category
def _clean_model_name(name):
@@ -185,7 +186,7 @@ def get_categories(master_conn, mye_id):
""", (mye_id,))
rows = cur.fetchall()
cur.close()
return [{'id_part_category': r[0], 'name': r[1], 'part_count': r[2]} for r in rows]
return [{'id_part_category': r[0], 'name': translate_category(r[1]), 'part_count': r[2]} for r in rows]
def get_groups(master_conn, mye_id, category_id):
@@ -273,10 +274,11 @@ def get_parts(master_conn, mye_id, group_id, tenant_conn, branch_id, page=1, per
local = local_map.get(oem) or local_map.get(f'cat:{part_id}')
# Prefer local inventory image over catalog image
image_url = (local.get('image_url') if local else None) or r[6]
raw_name = r[3] or r[2] # prefer Spanish name
items.append({
'id_part': part_id,
'oem_part_number': oem,
'name': r[3] or r[2], # prefer Spanish name
'name': translate_part_name(raw_name),
'description': r[5] or r[4],
'image_url': image_url,
'local_stock': local['stock'] if local else 0,
@@ -321,11 +323,11 @@ def get_part_detail(master_conn, part_id, tenant_conn, branch_id):
part_info = {
'id_part': row[0],
'oem_part_number': oem,
'name': row[3] or row[2],
'name': translate_part_name(row[3] or row[2]),
'description': row[5] or row[4],
'image_url': row[6],
'group_name': row[7],
'category_name': row[8],
'group_name': translate_category(row[7]) if row[7] else row[7],
'category_name': translate_category(row[8]) if row[8] else row[8],
}
# Bodegas with stock
@@ -517,7 +519,7 @@ def smart_search(master_conn, q, tenant_conn, branch_id, limit=50):
results.append({
'id_part': part_id,
'oem_part_number': oem,
'name': r[3] or r[2],
'name': translate_part_name(r[3] or r[2]),
'image_url': r[4],
'local_stock': local['stock'] if local else 0,
'local_price': local['price_1'] if local else None,