feat: complete session — catalog, marketplace, WhatsApp, peer-to-peer, install scripts
Major features: - Pixel-Perfect glassmorphism design (landing + POS + public catalog) - OEM/Local catalog toggle with Nexpart taxonomy (14 groups, 108 subgroups, 558 part types) - Marketplace B2B Phase 1 (bodegas, POs, status machine, WA+email notifications) - Peer-to-peer inventory (multi-instance, LAN discovery) - WhatsApp: photo→Vision AI, voice→Whisper, conversational quotations - Smart unified search (VIN/plate/part_number/keyword auto-detect) - Shop Supplies tab (vehicle-independent parts) - Chatbot AI fallback chain (5 models) + response cache - CSV inventory import tool + setup_instance.sh installer - Tablet-responsive CSS + sidebar toggle - Filters, export CSV, employee edit, business data save - Quotation system (WA→POS) with auto-print on confirmation - Live stats on landing page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,61 @@ def create_employee():
|
||||
return jsonify({'id': emp_id, 'message': 'Employee created'}), 201
|
||||
|
||||
|
||||
@config_bp.route('/employees/<int:emp_id>', methods=['PUT'])
|
||||
@require_auth('config.edit')
|
||||
def update_employee(emp_id):
|
||||
"""Update an existing employee's name, email, role, branch, discount, active status.
|
||||
If PIN is provided, it gets re-hashed. Otherwise PIN stays unchanged."""
|
||||
import bcrypt
|
||||
data = request.get_json() or {}
|
||||
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Check employee exists
|
||||
cur.execute("SELECT id FROM employees WHERE id = %s", (emp_id,))
|
||||
if not cur.fetchone():
|
||||
cur.close(); conn.close()
|
||||
return jsonify({'error': 'Employee not found'}), 404
|
||||
|
||||
# Build SET clause dynamically — only update provided fields
|
||||
updates = []
|
||||
params = []
|
||||
field_map = {
|
||||
'name': 'name', 'email': 'email', 'phone': 'phone',
|
||||
'role': 'role', 'branch_id': 'branch_id',
|
||||
'max_discount_pct': 'max_discount_pct', 'is_active': 'is_active',
|
||||
}
|
||||
for json_key, col in field_map.items():
|
||||
if json_key in data:
|
||||
updates.append(f"{col} = %s")
|
||||
params.append(data[json_key])
|
||||
|
||||
# PIN update (only if provided and non-empty)
|
||||
if data.get('pin') and len(str(data['pin'])) >= 4:
|
||||
pin_hash = bcrypt.hashpw(str(data['pin']).encode(), bcrypt.gensalt()).decode()
|
||||
updates.append("pin = %s")
|
||||
params.append(pin_hash)
|
||||
updates.append("password_hash = %s")
|
||||
params.append(pin_hash)
|
||||
|
||||
if not updates:
|
||||
cur.close(); conn.close()
|
||||
return jsonify({'error': 'Nothing to update'}), 400
|
||||
|
||||
params.append(emp_id)
|
||||
cur.execute(f"UPDATE employees SET {', '.join(updates)} WHERE id = %s", params)
|
||||
|
||||
from services.audit import log_action
|
||||
log_action(conn, 'EMPLOYEE_UPDATE', 'employee', emp_id,
|
||||
new_value={k: v for k, v in data.items() if k != 'pin'})
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
return jsonify({'ok': True, 'message': 'Employee updated'})
|
||||
|
||||
|
||||
@config_bp.route('/currency', methods=['GET'])
|
||||
@require_auth()
|
||||
def get_currency():
|
||||
@@ -244,6 +299,42 @@ def get_business():
|
||||
})
|
||||
|
||||
|
||||
@config_bp.route('/business', methods=['PUT'])
|
||||
@require_auth('config.edit')
|
||||
def update_business():
|
||||
"""Save tenant business info to tenant_config."""
|
||||
data = request.get_json() or {}
|
||||
field_map = {
|
||||
'razon_social': 'tenant_razon_social',
|
||||
'nombre': 'tenant_nombre',
|
||||
'rfc': 'tenant_rfc',
|
||||
'regimen_fiscal': 'tenant_regimen_fiscal',
|
||||
'direccion': 'tenant_direccion',
|
||||
'telefono': 'tenant_telefono',
|
||||
'email': 'tenant_email',
|
||||
# Tax params
|
||||
'tax_iva': 'tax_iva',
|
||||
'tax_ieps': 'tax_ieps',
|
||||
'invoice_serie': 'invoice_serie',
|
||||
'invoice_folio': 'invoice_folio',
|
||||
'default_currency': 'default_currency',
|
||||
'default_payment_method': 'default_payment_method',
|
||||
}
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
cur = conn.cursor()
|
||||
for field, key in field_map.items():
|
||||
val = data.get(field)
|
||||
if val is not None:
|
||||
cur.execute("""
|
||||
INSERT INTO tenant_config (key, value) VALUES (%s, %s)
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value
|
||||
""", (key, str(val).strip()))
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
return jsonify({'ok': True})
|
||||
|
||||
|
||||
@config_bp.route('/theme', methods=['GET'])
|
||||
@require_auth()
|
||||
def get_theme():
|
||||
|
||||
Reference in New Issue
Block a user