BEGIN; -- Enable direct/manual sales orders (e.g. market booth daily capture). -- Direct orders are identifiable by order_source=direct and external_ref prefix DIR-. ALTER TABLE sales_order ADD COLUMN IF NOT EXISTS order_source TEXT NOT NULL DEFAULT 'wix'; ALTER TABLE sales_order ALTER COLUMN party_id DROP NOT NULL; UPDATE sales_order SET order_source = 'wix' WHERE order_source IS NULL; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'chk_sales_order_source' ) THEN ALTER TABLE sales_order ADD CONSTRAINT chk_sales_order_source CHECK (order_source IN ('wix', 'direct')); END IF; END; $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'chk_sales_order_direct_ref_prefix' ) THEN ALTER TABLE sales_order ADD CONSTRAINT chk_sales_order_direct_ref_prefix CHECK (order_source <> 'direct' OR external_ref LIKE 'DIR-%'); END IF; END; $$; CREATE INDEX IF NOT EXISTS idx_sales_order_source ON sales_order(order_source); CREATE SEQUENCE IF NOT EXISTS seq_sales_order_direct_ref; CREATE OR REPLACE FUNCTION fn_next_direct_sales_order_ref( p_order_date DATE DEFAULT CURRENT_DATE ) RETURNS TEXT LANGUAGE plpgsql AS $$ DECLARE v_seq BIGINT; BEGIN v_seq := nextval('seq_sales_order_direct_ref'); RETURN format( 'DIR-%s-%s', to_char(p_order_date, 'YYYYMMDD'), lpad(v_seq::TEXT, 5, '0') ); END; $$; CREATE OR REPLACE FUNCTION fn_sales_order_assign_direct_ref() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF NEW.order_source = 'direct' AND (NEW.external_ref IS NULL OR btrim(NEW.external_ref) = '') THEN NEW.external_ref := fn_next_direct_sales_order_ref(COALESCE(NEW.order_date::DATE, CURRENT_DATE)); END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_sales_order_assign_direct_ref ON sales_order; CREATE TRIGGER trg_sales_order_assign_direct_ref BEFORE INSERT OR UPDATE OF order_source, external_ref, order_date ON sales_order FOR EACH ROW EXECUTE FUNCTION fn_sales_order_assign_direct_ref(); CREATE OR REPLACE FUNCTION fn_enqueue_sales_order_events() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_event_key TEXT; BEGIN IF TG_OP = 'INSERT' THEN v_event_key := format('order.imported:%s', NEW.external_ref); PERFORM fn_enqueue_event( 'order.imported', v_event_key, 'sales_order', NEW.id::TEXT, jsonb_build_object( 'orderId', NEW.id, 'externalRef', NEW.external_ref, 'orderSource', NEW.order_source, 'orderStatus', NEW.order_status, 'paymentStatus', NEW.payment_status, 'occurredAt', NOW() ) ); ELSIF TG_OP = 'UPDATE' THEN IF OLD.order_status IS DISTINCT FROM NEW.order_status AND NEW.order_status = 'cancelled' THEN v_event_key := format('order.cancelled.full:%s:%s', NEW.external_ref, COALESCE(NEW.cancelled_at, NOW())); PERFORM fn_enqueue_event( 'order.cancelled.full', v_event_key, 'sales_order', NEW.id::TEXT, jsonb_build_object( 'orderId', NEW.id, 'externalRef', NEW.external_ref, 'orderSource', NEW.order_source, 'orderStatus', NEW.order_status, 'cancelledAt', COALESCE(NEW.cancelled_at, NOW()), 'cancelledReason', NEW.cancelled_reason ) ); END IF; END IF; RETURN NEW; END; $$; CREATE OR REPLACE FUNCTION fn_enqueue_partial_cancel_events() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_order_id BIGINT; v_external_ref TEXT; v_order_source TEXT; v_event_key TEXT; BEGIN IF NEW.line_status = 'partially_cancelled' AND OLD.line_status IS DISTINCT FROM NEW.line_status THEN SELECT so.id, so.external_ref, so.order_source INTO v_order_id, v_external_ref, v_order_source FROM sales_order so WHERE so.id = NEW.sales_order_id; v_event_key := format('order.cancelled.partial:%s:%s:%s', v_external_ref, NEW.id, NEW.qty_cancelled); PERFORM fn_enqueue_event( 'order.cancelled.partial', v_event_key, 'sales_order_line', NEW.id::TEXT, jsonb_build_object( 'orderId', v_order_id, 'externalRef', v_external_ref, 'orderSource', v_order_source, 'lineId', NEW.id, 'lineNo', NEW.line_no, 'qty', NEW.qty, 'qtyCancelled', NEW.qty_cancelled, 'lineStatus', NEW.line_status, 'occurredAt', NOW() ) ); END IF; RETURN NEW; END; $$; -- Payment methods used by direct/manual sales mask. INSERT INTO payment_method (code, label) VALUES ('cash', 'Barzahlung'), ('paypal', 'PayPal') ON CONFLICT (code) DO NOTHING; UPDATE payment_method SET label = 'Ueberweisung', updated_at = NOW() WHERE code = 'bank_transfer' AND label <> 'Ueberweisung'; COMMIT;