Fix order import mapping and lot auto-switch robustness

This commit is contained in:
2026-03-29 21:52:26 +02:00
parent bcf1f5523e
commit 3b8a8aeb69
4 changed files with 377 additions and 25 deletions

View File

@@ -6,6 +6,7 @@
2. `db/migrations/0002_phase1_seed_methods.sql`
3. `db/migrations/0003_phase1_inventory_forecast.sql`
4. `db/migrations/0004_phase1_direct_sales.sql`
5. `db/migrations/0005_phase1_auto_switch_fix.sql`
## Beispielausfuehrung
@@ -14,6 +15,7 @@ psql "$DATABASE_URL" -f db/migrations/0001_phase1_core.sql
psql "$DATABASE_URL" -f db/migrations/0002_phase1_seed_methods.sql
psql "$DATABASE_URL" -f db/migrations/0003_phase1_inventory_forecast.sql
psql "$DATABASE_URL" -f db/migrations/0004_phase1_direct_sales.sql
psql "$DATABASE_URL" -f db/migrations/0005_phase1_auto_switch_fix.sql
```
## Enthaltene Kernlogik in `0001`

View File

@@ -665,7 +665,10 @@ BEGIN
END IF;
UPDATE stock_lot
SET status = 'current', updated_at = NOW()
SET
status = 'current',
lot_number = COALESCE(lot_number, format('AUTO-%s-%s', v_product_id, v_open_lot_id)),
updated_at = NOW()
WHERE id = v_open_lot_id;
INSERT INTO stock_lot (product_id, lot_number, status)

View File

@@ -0,0 +1,99 @@
BEGIN;
-- Fix lot auto-switch when current lot reaches zero:
-- if the next open lot has no lot_number yet, assign one before switching to status=current.
-- This avoids violating chk_stock_lot_number_required_for_non_open.
CREATE OR REPLACE FUNCTION fn_auto_switch_lot_when_depleted()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
v_product_id BIGINT;
v_open_lot_id BIGINT;
v_current_net NUMERIC;
v_event_key TEXT;
BEGIN
-- only relevant when the updated lot is currently active
SELECT product_id
INTO v_product_id
FROM stock_lot
WHERE id = NEW.lot_id;
-- if lot is not current anymore, nothing to do
IF NOT EXISTS (
SELECT 1 FROM stock_lot WHERE id = NEW.lot_id AND status = 'current'
) THEN
RETURN NEW;
END IF;
SELECT qty_net
INTO v_current_net
FROM v_stock_lot_balance
WHERE stock_lot_id = NEW.lot_id;
IF COALESCE(v_current_net, 0) > 0 THEN
RETURN NEW;
END IF;
-- lock all lots for this product to avoid race conditions
PERFORM 1
FROM stock_lot
WHERE product_id = v_product_id
FOR UPDATE;
-- confirm current lot is still current after lock
IF NOT EXISTS (
SELECT 1 FROM stock_lot WHERE id = NEW.lot_id AND status = 'current'
) THEN
RETURN NEW;
END IF;
UPDATE stock_lot
SET status = 'closed', updated_at = NOW()
WHERE id = NEW.lot_id;
SELECT id
INTO v_open_lot_id
FROM stock_lot
WHERE product_id = v_product_id
AND status = 'open'
ORDER BY id
LIMIT 1
FOR UPDATE;
IF v_open_lot_id IS NULL THEN
RAISE EXCEPTION 'No open lot available for product % during auto switch', v_product_id;
END IF;
-- current/closed lots must have a lot_number due to chk_stock_lot_number_required_for_non_open.
UPDATE stock_lot
SET
status = 'current',
lot_number = COALESCE(lot_number, format('AUTO-%s-%s', v_product_id, v_open_lot_id)),
updated_at = NOW()
WHERE id = v_open_lot_id;
INSERT INTO stock_lot (product_id, lot_number, status)
VALUES (v_product_id, NULL, 'open');
v_event_key := format('lot.auto_switched:%s:%s', v_product_id, txid_current());
PERFORM fn_enqueue_event(
'lot.auto_switched',
v_event_key,
'stock_lot',
NEW.lot_id::TEXT,
jsonb_build_object(
'productId', v_product_id,
'closedLotId', NEW.lot_id,
'newCurrentLotId', v_open_lot_id,
'occurredAt', NOW()
)
);
RETURN NEW;
END;
$$;
COMMIT;