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

@@ -12,8 +12,10 @@ import urllib.request
from datetime import datetime, timedelta
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'pos'))
from config import DB_URL
from auth import hash_password, check_password, create_access_token, create_refresh_token, decode_token, require_auth
from services.translations import translate_part_name, translate_category
app = Flask(__name__, static_folder='.')
@@ -402,7 +404,7 @@ def api_catalog_categories():
ORDER BY name
"""), {'mye_id': mye_id}).mappings().all()
return jsonify([{'id_part_category': r['id_part_category'],
'name': r['name'], 'part_count': r['part_count']} for r in rows])
'name': translate_category(r['name']), 'part_count': r['part_count']} for r in rows])
finally:
session.close()
@@ -428,7 +430,7 @@ def api_catalog_groups():
ORDER BY name
"""), {'mye_id': mye_id, 'category_id': category_id}).mappings().all()
return jsonify([{'id_part_group': r['id_part_group'],
'name': r['name'], 'part_count': r['part_count']} for r in rows])
'name': translate_category(r['name']), 'part_count': r['part_count']} for r in rows])
finally:
session.close()
@@ -464,7 +466,7 @@ def api_catalog_parts():
items = [{
'id_part': r['id_part'],
'oem_part_number': r['oem_part_number'],
'name': r['name_es'] or r['name_part'],
'name': translate_part_name(r['name_es'] or r['name_part']),
'description': r['description_es'] or r['description'],
'image_url': r['image_url'],
} for r in rows]
@@ -497,11 +499,11 @@ def api_catalog_part_detail(part_id):
part = {
'id_part': row['id_part'],
'oem_part_number': row['oem_part_number'],
'name': row['name_es'] or row['name_part'],
'name': translate_part_name(row['name_es'] or row['name_part']),
'description': row['description_es'] or row['description'],
'image_url': row['image_url'],
'group_name': row['group_name'],
'category_name': row['category_name'],
'group_name': translate_category(row['group_name']) if row['group_name'] else row['group_name'],
'category_name': translate_category(row['category_name']) if row['category_name'] else row['category_name'],
}
# Cross-references
@@ -615,7 +617,7 @@ def api_catalog_search():
results.append({
'id_part': r['id_part'],
'oem_part_number': r['oem_part_number'],
'name': r['name_es'] or r['name_part'],
'name': translate_part_name(r['name_es'] or r['name_part']),
'image_url': r['image_url'],
'vehicle_info': vmap.get(r['id_part'], ''),
})