feat: cashier/counter reports, service-order & remission flows, Rached migration utils
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- Add "Mis cortes de caja" report for cashiers/counters with sales detail.
- Cash register history scoped to own cuts for non-admin roles; new /register/<id>/sales endpoint.
- Remove dashboard from cashier menu; add Reports to cashier/counter.
- Service orders: assign mechanic, budget field, invoice flag, counter/cashier can add items/remissions, convert to remission.
- Remission notes module (UI, CSS, courier, counter remissions).
- Customer hard-delete and vehicle/customer linkage in workshop.
- POS: always show search results, compact payment grid, credit validation, tier pricing (5%/10%), ticket with customer/folio.
- Inventory: CSV template with sku_secondary, alias import.
- Rached migration scripts and DB migrations.
- Version-bump cached JS/CSS query strings.

Excludes local Rached session tokens/captures (rached_*.json / rached_*.txt).
This commit is contained in:
2026-07-02 12:51:56 +00:00
parent 483498cfcc
commit f42910f4f6
71 changed files with 5388 additions and 626 deletions

View File

@@ -57,6 +57,19 @@ MIGRATIONS = {
"v4.8": "v4.8_workshop_permissions.sql",
"v4.9": "v4.9_workshop_customers_view.sql",
"v4.10": "v4.10_fleet_permissions.sql",
"v4.11": "v4.11_clean_workshop_mechanic_permissions.sql",
"v4.12": "v4.12_service_order_soft_delete.sql",
"v4.13": "v4.13_fleet_vehicle_customer.sql",
"v4.14": "v4.14_service_order_delivery_cleanup.sql",
"v4.15": "v4.15_counter_remission.sql",
"v4.16": "v4.16_remission_courier.sql",
"v4.17": "v4.17_service_order_invoice.sql",
"v4.18": "v4.18_rached_workshop.sql",
"v4.19": "v4.19_counter_inventory_create.sql",
"v4.20": "v4.20_cashier_sell_permissions.sql",
"v4.21": "v4.21_cashier_workshop_permissions.sql",
"v4.22": "v4.22_cashier_invoicing_permissions.sql",
"v4.23": "v4.23_service_order_mechanic_name.sql",
}
@@ -112,7 +125,10 @@ def apply_migration(db_name, version):
def run_migrations():
"""Apply pending migrations to all tenants."""
tenants = get_all_tenants()
sorted_versions = sorted(MIGRATIONS.keys())
def _version_key(v):
return tuple(int(x) for x in v.lstrip('v').split('.'))
sorted_versions = sorted(MIGRATIONS.keys(), key=_version_key)
print(f"Found {len(tenants)} active tenants")
print(f"Available migrations: {sorted_versions}")
@@ -120,8 +136,9 @@ def run_migrations():
for tenant_id, db_name, name, current_version in tenants:
print(f"\n[{name}] (db={db_name}, current={current_version})")
current_key = _version_key(current_version)
for version in sorted_versions:
if version <= current_version:
if _version_key(version) <= current_key:
continue
print(f" Applying {version}...", end=" ")

View File

@@ -0,0 +1,10 @@
-- Normalize service order delivery options to only "pickup" (mostrador) and "delivery" (a domicilio).
-- Convert legacy "courier" records to pickup and clear courier_id when delivery is not "delivery".
UPDATE service_orders
SET delivery_method = 'pickup'
WHERE delivery_method = 'courier';
UPDATE service_orders
SET courier_id = NULL
WHERE delivery_method IS NULL OR delivery_method != 'delivery';

View File

@@ -0,0 +1,18 @@
-- v4.15: Counter remission note support
-- Ensures employees with role 'counter' have the base permissions needed to
-- generate remission notes from the POS.
INSERT INTO employee_permissions (employee_id, permission)
SELECT e.id, p.permission
FROM employees e
CROSS JOIN (
VALUES
('pos.remission'),
('pos.view'),
('catalog.view'),
('inventory.view'),
('customers.view'),
('customers.create')
) AS p(permission)
WHERE e.role = 'counter'
ON CONFLICT (employee_id, permission) DO NOTHING;

View File

@@ -0,0 +1,18 @@
-- v4.16: Add courier assignment to counter remission notes.
ALTER TABLE sales
ADD COLUMN IF NOT EXISTS courier_id INTEGER;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'sales_courier_id_fkey'
) THEN
ALTER TABLE sales
ADD CONSTRAINT sales_courier_id_fkey
FOREIGN KEY (courier_id) REFERENCES couriers(id);
END IF;
END $$;
CREATE INDEX IF NOT EXISTS idx_sales_courier_id ON sales(courier_id);

View File

@@ -0,0 +1,6 @@
-- Service order invoice requirement flag
ALTER TABLE service_orders
ADD COLUMN IF NOT EXISTS requires_invoice BOOLEAN DEFAULT FALSE;
CREATE INDEX IF NOT EXISTS idx_service_orders_requires_invoice
ON service_orders(requires_invoice);

View File

@@ -0,0 +1,32 @@
-- v4.18 Rached workshop fields
-- Extends service orders and items to match the Rached legacy workshop flow.
-- Applied to all tenants.
-- ═════════════════════════════════════════════════════════════════════════════
-- 1. SERVICE_ORDERS: capture free-text customer/vehicle/workshop data
-- ═════════════════════════════════════════════════════════════════════════════
ALTER TABLE service_orders
ADD COLUMN IF NOT EXISTS workshop_name VARCHAR(200),
ADD COLUMN IF NOT EXISTS customer_address TEXT,
ADD COLUMN IF NOT EXISTS customer_phone VARCHAR(50),
ADD COLUMN IF NOT EXISTS vehicle_description VARCHAR(300);
COMMENT ON COLUMN service_orders.workshop_name IS 'Free-text workshop/customer alias (Rached "Taller")';
COMMENT ON COLUMN service_orders.customer_address IS 'Address captured or imported for the service order';
COMMENT ON COLUMN service_orders.customer_phone IS 'Phone captured or imported for the service order';
COMMENT ON COLUMN service_orders.vehicle_description IS 'Free-text vehicle description (alternative to fleet_vehicles)';
CREATE INDEX IF NOT EXISTS idx_service_orders_workshop_name ON service_orders(workshop_name);
CREATE INDEX IF NOT EXISTS idx_service_orders_vehicle_description ON service_orders(vehicle_description);
-- ═════════════════════════════════════════════════════════════════════════════
-- 2. SERVICE_ORDER_ITEMS: mechanic per item + explicit observations
-- ═════════════════════════════════════════════════════════════════════════════
ALTER TABLE service_order_items
ADD COLUMN IF NOT EXISTS mechanic_id INTEGER REFERENCES employees(id),
ADD COLUMN IF NOT EXISTS observations TEXT;
COMMENT ON COLUMN service_order_items.mechanic_id IS 'Mechanic assigned to this specific line item';
COMMENT ON COLUMN service_order_items.observations IS 'Line-item observations (Rached detail notes)';
CREATE INDEX IF NOT EXISTS idx_service_order_items_mechanic_id ON service_order_items(mechanic_id);

View File

@@ -0,0 +1,11 @@
-- v4.19 — Grant inventory.create permission to existing counter employees
-- so they can create items and record purchase entries.
INSERT INTO employee_permissions (employee_id, permission)
SELECT e.id, 'inventory.create'
FROM employees e
WHERE e.role = 'counter'
AND e.is_active = true
AND NOT EXISTS (
SELECT 1 FROM employee_permissions ep
WHERE ep.employee_id = e.id AND ep.permission = 'inventory.create'
);

View File

@@ -0,0 +1,18 @@
-- v4.20 — Ensure existing cashier employees can sell and view quotations.
-- The cashier default set already includes these permissions, but employees
-- created before the fix may be missing them.
INSERT INTO employee_permissions (employee_id, permission)
SELECT e.id, p.perm
FROM employees e
CROSS JOIN (VALUES
('pos.sell'),
('pos.discount'),
('pos.cancel'),
('pos.view')
) AS p(perm)
WHERE e.role = 'cashier'
AND e.is_active = true
AND NOT EXISTS (
SELECT 1 FROM employee_permissions ep
WHERE ep.employee_id = e.id AND ep.permission = p.perm
);

View File

@@ -0,0 +1,15 @@
-- v4.21 — Allow existing cashier employees to create and edit service orders.
INSERT INTO employee_permissions (employee_id, permission)
SELECT e.id, p.perm
FROM employees e
CROSS JOIN (VALUES
('workshop.view'),
('workshop.edit'),
('workshop.add_items')
) AS p(perm)
WHERE e.role = 'cashier'
AND e.is_active = true
AND NOT EXISTS (
SELECT 1 FROM employee_permissions ep
WHERE ep.employee_id = e.id AND ep.permission = p.perm
);

View File

@@ -0,0 +1,15 @@
-- v4.22 -- Enable invoicing for existing cashier employees.
INSERT INTO employee_permissions (employee_id, permission)
SELECT e.id, p.perm
FROM employees e
CROSS JOIN (VALUES
('invoicing.view'),
('invoicing.create'),
('invoicing.cancel')
) AS p(perm)
WHERE e.role = 'cashier'
AND e.is_active = true
AND NOT EXISTS (
SELECT 1 FROM employee_permissions ep
WHERE ep.employee_id = e.id AND ep.permission = p.perm
);

View File

@@ -0,0 +1,2 @@
-- v4.23 -- Add free-text mechanic name for service orders.
ALTER TABLE service_orders ADD COLUMN IF NOT EXISTS mechanic_name TEXT;