from flask import Flask from json_provider import OrjsonProvider def create_app(): app = Flask(__name__) app.json = OrjsonProvider(app) # Tenant subdomain resolver (before every request) from middleware_tenant import resolve_tenant app.before_request(resolve_tenant) # ─── PWA: Service Worker must be served from /pos/ scope ────── @app.route('/pos/sw.js') def pos_sw(): from flask import send_from_directory return send_from_directory('static/pwa', 'sw.js', mimetype='application/javascript') # Register blueprints from blueprints.auth_bp import auth_bp app.register_blueprint(auth_bp) from blueprints.config_bp import config_bp app.register_blueprint(config_bp) from blueprints.inventory_bp import inventory_bp app.register_blueprint(inventory_bp) from blueprints.catalog_bp import catalog_bp app.register_blueprint(catalog_bp) from blueprints.pos_bp import pos_bp app.register_blueprint(pos_bp) from blueprints.customers_bp import customers_bp app.register_blueprint(customers_bp) from blueprints.cashregister_bp import cashregister_bp app.register_blueprint(cashregister_bp) from blueprints.invoicing_bp import invoicing_bp app.register_blueprint(invoicing_bp) from blueprints.accounting_bp import accounting_bp app.register_blueprint(accounting_bp) from blueprints.chat_bp import chat_bp app.register_blueprint(chat_bp) from blueprints.fleet_bp import fleet_bp app.register_blueprint(fleet_bp) from blueprints.whatsapp_bp import whatsapp_bp app.register_blueprint(whatsapp_bp) from blueprints.marketplace_bp import marketplace_bp app.register_blueprint(marketplace_bp) from blueprints.peer_bp import peer_bp app.register_blueprint(peer_bp) from blueprints.supplier_bp import supplier_bp app.register_blueprint(supplier_bp) from blueprints.warranty_bp import warranty_bp app.register_blueprint(warranty_bp) from blueprints.crm_bp import crm_bp app.register_blueprint(crm_bp) from blueprints.service_order_bp import service_order_bp app.register_blueprint(service_order_bp) from blueprints.image_bp import image_bp app.register_blueprint(image_bp) from blueprints.notification_bp import notification_bp app.register_blueprint(notification_bp) from blueprints.savings_bp import savings_bp app.register_blueprint(savings_bp) from blueprints.logistics_bp import logistics_bp app.register_blueprint(logistics_bp) from blueprints.public_api_bp import public_api_bp app.register_blueprint(public_api_bp) from blueprints.tasks_bp import tasks_bp app.register_blueprint(tasks_bp) # Health check @app.route('/pos/health') def health(): return {'status': 'ok'} from flask import render_template, send_from_directory, jsonify, g @app.route('/favicon.ico') def favicon(): return send_from_directory('static/pwa', 'icon-192.png', mimetype='image/png') @app.route('/pos/login') def pos_login(): return render_template('login.html', tenant_id=getattr(g, 'tenant_id', None), tenant_name=getattr(g, 'tenant_name', None), tenant_subdomain=getattr(g, 'tenant_subdomain', None)) @app.route('/pos/catalog') def pos_catalog(): return render_template('catalog.html') @app.route('/pos/inventory') def pos_inventory(): return render_template('inventory.html') @app.route('/pos/sale') def pos_sale(): return render_template('pos.html') @app.route('/pos/customers') def pos_customers(): return render_template('customers.html') @app.route('/pos/invoicing') def pos_invoicing(): return render_template('invoicing.html') @app.route('/pos/accounting') def pos_accounting(): return render_template('accounting.html') @app.route('/pos/dashboard') def pos_dashboard(): return render_template('dashboard.html') @app.route('/pos/config') def pos_config(): return render_template('config.html') @app.route('/pos/reports') def pos_reports(): return render_template('reports.html') @app.route('/pos/fleet') def pos_fleet(): return render_template('fleet.html') @app.route('/pos/quotations') def pos_quotations(): return render_template('quotations.html') @app.route('/pos/whatsapp') def pos_whatsapp(): return render_template('whatsapp.html') @app.route('/pos/marketplace') def pos_marketplace(): return render_template('marketplace.html') @app.route('/pos/static/') def pos_static(filename): return send_from_directory('static', filename) # ─── Sync: full inventory for offline cache ─────────────────── from middleware import require_auth as _require_auth @app.route('/pos/api/sync/inventory', methods=['GET']) @_require_auth() def sync_full_inventory(): """Download full inventory for offline cache.""" from tenant_db import get_tenant_conn conn = get_tenant_conn(g.tenant_id) cur = conn.cursor() branch_id = g.branch_id cur.execute(""" SELECT i.id, i.part_number, i.barcode, i.name, i.brand, i.unit, i.price_1, i.price_2, i.price_3, i.tax_rate, COALESCE(s.stock, 0) AS stock FROM inventory i LEFT JOIN ( SELECT inventory_id, COALESCE(SUM(quantity), 0) AS stock FROM inventory_operations GROUP BY inventory_id ) s ON s.inventory_id = i.id WHERE i.is_active = true AND i.branch_id = %s ORDER BY i.name """, [branch_id]) items = [] for r in cur.fetchall(): items.append({ 'item_id': r[0], 'sku': r[1], 'barcode': r[2], 'name': r[3], 'brand': r[4], 'unit': r[5], 'price_1': float(r[6]) if r[6] else 0, 'price_2': float(r[7]) if r[7] else 0, 'price_3': float(r[8]) if r[8] else 0, 'tax_rate': float(r[9]) if r[9] else 0.16, 'stock': r[10] }) cur.close() conn.close() return jsonify({'items': items, 'count': len(items)}) @app.route('/pos/api/sync/top-parts', methods=['GET']) @_require_auth() def sync_top_parts(): """Get top 500 most-sold parts for offline catalog cache.""" from tenant_db import get_tenant_conn conn = get_tenant_conn(g.tenant_id) cur = conn.cursor() branch_id = g.branch_id cur.execute(""" SELECT i.part_number, i.name, i.brand, i.price_1, i.tax_rate, i.category, COALESCE(s.stock, 0) AS stock, COALESCE(sv.total_sold, 0) AS total_sold FROM inventory i LEFT JOIN ( SELECT inventory_id, COALESCE(SUM(quantity), 0) AS stock FROM inventory_operations GROUP BY inventory_id ) s ON s.inventory_id = i.id LEFT JOIN ( SELECT si.inventory_id, SUM(si.quantity) AS total_sold FROM sale_items si JOIN sales sa ON si.sale_id = sa.id WHERE sa.status IN ('completed', 'partially_returned') GROUP BY si.inventory_id ) sv ON sv.inventory_id = i.id WHERE i.is_active = true AND i.branch_id = %s ORDER BY COALESCE(sv.total_sold, 0) DESC LIMIT 500 """, [branch_id]) parts = [] for r in cur.fetchall(): parts.append({ 'part_number': r[0], 'name': r[1], 'brand': r[2], 'price': float(r[3]) if r[3] else 0, 'tax_rate': float(r[4]) if r[4] else 0.16, 'category': r[5] or '', 'stock': r[6], 'total_sold': r[7] }) cur.close() conn.close() return jsonify({'parts': parts, 'count': len(parts)}) return app # Expose at module level for Gunicorn: gunicorn -w 2 -b 0.0.0.0:5001 app:app app = create_app() if __name__ == '__main__': app.run(host='0.0.0.0', port=5001, debug=True)