@fayz-ai/plugin-orders 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -599,6 +599,214 @@ var ordersRegistries = [
599
599
  }
600
600
  ];
601
601
 
602
+ // src/migrations/index.ts
603
+ var MIGRATION_001_AN_ORDER_ARRIVES_FROM_A_MARKETPLACE = `-- ============================================================================
604
+ -- 001 \u2014 an order arrives from a marketplace
605
+ --
606
+ -- The V2 sink of ADR 0003: the ONE door through which the integration service
607
+ -- hands a canonical delivery order to this product, and the two beside it to
608
+ -- apply a later status and to read the current one. No rule lives in the
609
+ -- service; what an order IS here is decided here, in one transaction.
610
+ --
611
+ -- There is no person behind these calls \u2014 the actor is the service, acting
612
+ -- for the tenant it names, which is why \`p_tenant\` is explicit and no
613
+ -- \`app.has_permission\` is asked (the same reasoning as service_authorize_for).
614
+ -- The functions are therefore closed to every signed-in user and opened only
615
+ -- to the service's own role, \`fayz_integration\`, when that role exists on the
616
+ -- cluster. Creating the role is an operator step; the grant below is guarded.
617
+ --
618
+ -- The order shape is @fayz-ai/contracts' CanonicalOrder v1: money in
619
+ -- centavos, items with options, totals that close. Everything that this
620
+ -- product has no column for travels in orders.metadata.external, so nothing
621
+ -- the marketplace said is lost.
622
+ -- ============================================================================
623
+
624
+ CREATE OR REPLACE FUNCTION public.orders_ingest_external(
625
+ p_tenant uuid,
626
+ p_unit uuid,
627
+ p_order jsonb,
628
+ p_status text
629
+ ) RETURNS uuid
630
+ LANGUAGE plpgsql SECURITY DEFINER
631
+ SET search_path TO ''
632
+ AS $$
633
+ DECLARE
634
+ v_provider text := p_order ->> 'provider';
635
+ v_external text := p_order ->> 'externalId';
636
+ v_ref text;
637
+ v_kind text;
638
+ v_channel text;
639
+ v_order uuid;
640
+ v_item jsonb;
641
+ v_n integer := 0;
642
+ v_qty numeric;
643
+ v_unit numeric;
644
+ v_total numeric;
645
+ v_sub numeric;
646
+ v_disc numeric;
647
+ v_ship numeric;
648
+ BEGIN
649
+ IF p_tenant IS NULL THEN
650
+ RAISE EXCEPTION 'orders_ingest_external: tenant is required' USING ERRCODE = '22023';
651
+ END IF;
652
+ IF v_provider IS NULL OR v_external IS NULL THEN
653
+ RAISE EXCEPTION 'orders_ingest_external: order needs provider and externalId' USING ERRCODE = '22023';
654
+ END IF;
655
+ IF p_unit IS NOT NULL AND NOT EXISTS (
656
+ SELECT 1 FROM public.companies c WHERE c.id = p_unit AND c.tenant_id = p_tenant) THEN
657
+ RAISE EXCEPTION 'orders_ingest_external: unit % is not of tenant %', p_unit, p_tenant USING ERRCODE = '22023';
658
+ END IF;
659
+
660
+ -- Once per (provider, externalId) within the tenant: a replay returns the
661
+ -- order it already made. The key lives in metadata, as order_create's does.
662
+ v_ref := v_provider || ':' || v_external;
663
+ SELECT o.id INTO v_order FROM public.orders o
664
+ WHERE o.tenant_id = p_tenant AND o.metadata ->> 'externalRef' = v_ref
665
+ LIMIT 1;
666
+ IF v_order IS NOT NULL THEN
667
+ RETURN v_order;
668
+ END IF;
669
+
670
+ -- The contract's kinds are this product's kinds; the channel is where the
671
+ -- order came in, in this product's vocabulary.
672
+ v_kind := CASE p_order ->> 'kind'
673
+ WHEN 'delivery' THEN 'delivery'
674
+ WHEN 'takeout' THEN 'takeout'
675
+ WHEN 'dine_in' THEN 'dine_in'
676
+ ELSE 'delivery' END;
677
+ v_channel := CASE v_kind WHEN 'delivery' THEN 'delivery' WHEN 'dine_in' THEN 'dine_in' ELSE 'balcao' END;
678
+
679
+ IF p_status IS NULL OR NOT EXISTS (
680
+ SELECT 1 FROM public.order_kinds k WHERE k.kind = v_kind AND p_status = ANY (k.statuses)) THEN
681
+ RAISE EXCEPTION 'orders_ingest_external: % is not a status of kind %', p_status, v_kind USING ERRCODE = '22023';
682
+ END IF;
683
+
684
+ v_sub := round(coalesce((p_order -> 'totals' ->> 'subtotal')::numeric, 0) / 100, 2);
685
+ v_disc := round(coalesce((p_order -> 'totals' ->> 'discount')::numeric, 0) / 100, 2);
686
+ v_ship := round(coalesce((p_order -> 'totals' ->> 'deliveryFee')::numeric, 0) / 100, 2);
687
+
688
+ INSERT INTO public.orders (tenant_id, unit_id, kind, channel, status, reference_number,
689
+ subtotal, discount, shipping, total, currency, notes, metadata)
690
+ VALUES (p_tenant, p_unit, v_kind, v_channel, p_status,
691
+ nullif(p_order ->> 'displayId', ''),
692
+ v_sub, v_disc, v_ship,
693
+ round(coalesce((p_order -> 'totals' ->> 'total')::numeric, 0) / 100, 2),
694
+ coalesce(p_order -> 'totals' ->> 'currency', 'BRL'),
695
+ nullif(p_order ->> 'notes', ''),
696
+ jsonb_build_object(
697
+ 'externalRef', v_ref,
698
+ 'idempotencyKey', v_ref,
699
+ 'external', jsonb_build_object(
700
+ 'provider', v_provider,
701
+ 'externalId', v_external,
702
+ 'displayId', p_order -> 'displayId',
703
+ 'merchantRef', p_order -> 'merchantRef',
704
+ 'placedAt', p_order -> 'placedAt',
705
+ 'timing', p_order -> 'timing',
706
+ 'customer', p_order -> 'customer',
707
+ 'payment', p_order -> 'payment',
708
+ 'fulfillment', p_order -> 'fulfillment',
709
+ 'totals', p_order -> 'totals',
710
+ 'lastStatusAt', NULL)))
711
+ RETURNING id INTO v_order;
712
+
713
+ FOR v_item IN SELECT * FROM jsonb_array_elements(coalesce(p_order -> 'items', '[]'::jsonb)) LOOP
714
+ v_n := v_n + 1;
715
+ v_qty := coalesce((v_item ->> 'quantity')::numeric, 1);
716
+ v_unit := round(coalesce((v_item ->> 'unitPrice')::numeric, 0) / 100, 2);
717
+ v_total := round(coalesce((v_item ->> 'total')::numeric, 0) / 100, 2);
718
+ INSERT INTO public.items (tenant_id, unit_id, order_id, name, description, quantity,
719
+ unit_price, discount, total, sort_order, metadata)
720
+ VALUES (p_tenant, p_unit, v_order,
721
+ coalesce(nullif(btrim(coalesce(v_item ->> 'name', '')), ''), 'Item'),
722
+ nullif(v_item ->> 'notes', ''),
723
+ v_qty, v_unit, 0, v_total, v_n - 1,
724
+ jsonb_build_object(
725
+ 'externalId', v_item -> 'externalId',
726
+ 'externalCode', v_item -> 'externalCode',
727
+ 'unit', v_item -> 'unit',
728
+ 'options', coalesce(v_item -> 'options', '[]'::jsonb)));
729
+ END LOOP;
730
+
731
+ RETURN v_order;
732
+ END $$;
733
+
734
+ COMMENT ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) IS
735
+ 'The integration service hands a canonical marketplace order to this product, once per (provider, externalId). Actor: the service, for the named tenant.';
736
+
737
+ -- A later status for an order the service ingested. Monotonic on the
738
+ -- service side already; here the only rules are "this order is yours" and
739
+ -- "that status exists for its kind".
740
+ CREATE OR REPLACE FUNCTION public.orders_apply_external_status(
741
+ p_tenant uuid,
742
+ p_order uuid,
743
+ p_status text,
744
+ p_occurred_at timestamptz
745
+ ) RETURNS void
746
+ LANGUAGE plpgsql SECURITY DEFINER
747
+ SET search_path TO ''
748
+ AS $$
749
+ DECLARE
750
+ v_kind text;
751
+ BEGIN
752
+ SELECT o.kind INTO v_kind FROM public.orders o
753
+ WHERE o.id = p_order AND o.tenant_id = p_tenant AND o.metadata ? 'externalRef';
754
+ IF v_kind IS NULL THEN
755
+ RAISE EXCEPTION 'orders_apply_external_status: order % is not an external order of tenant %', p_order, p_tenant
756
+ USING ERRCODE = '22023';
757
+ END IF;
758
+ IF NOT EXISTS (SELECT 1 FROM public.order_kinds k WHERE k.kind = v_kind AND p_status = ANY (k.statuses)) THEN
759
+ RAISE EXCEPTION 'orders_apply_external_status: % is not a status of kind %', p_status, v_kind USING ERRCODE = '22023';
760
+ END IF;
761
+
762
+ UPDATE public.orders
763
+ SET status = p_status,
764
+ completed_at = CASE WHEN p_status IN ('completed', 'delivered') THEN coalesce(p_occurred_at, now()) ELSE completed_at END,
765
+ metadata = jsonb_set(metadata, '{external,lastStatusAt}', to_jsonb(coalesce(p_occurred_at, now())), true),
766
+ updated_at = now()
767
+ WHERE id = p_order;
768
+ END $$;
769
+
770
+ COMMENT ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) IS
771
+ 'A later status from the marketplace for an order the service ingested. Actor: the service, for the named tenant.';
772
+
773
+ CREATE OR REPLACE FUNCTION public.orders_external_status(p_tenant uuid, p_order uuid)
774
+ RETURNS jsonb
775
+ LANGUAGE sql STABLE SECURITY DEFINER
776
+ SET search_path TO ''
777
+ AS $$
778
+ SELECT jsonb_build_object('status', o.status, 'updatedAt', o.updated_at)
779
+ FROM public.orders o
780
+ WHERE o.id = p_order AND o.tenant_id = p_tenant AND o.metadata ? 'externalRef';
781
+ $$;
782
+
783
+ COMMENT ON FUNCTION public.orders_external_status(uuid, uuid) IS
784
+ 'What this product thinks an external order is now. Actor: the service, for the named tenant.';
785
+
786
+ -- Closed to people, open to the service. The pools grant EXECUTE to
787
+ -- \`authenticated\` on every new public function by default; a definer function
788
+ -- that takes the tenant as a parameter must not be reachable by a session.
789
+ REVOKE ALL ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) FROM PUBLIC, anon, authenticated;
790
+ REVOKE ALL ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) FROM PUBLIC, anon, authenticated;
791
+ REVOKE ALL ON FUNCTION public.orders_external_status(uuid, uuid) FROM PUBLIC, anon, authenticated;
792
+ GRANT EXECUTE ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) TO service_role;
793
+ GRANT EXECUTE ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) TO service_role;
794
+ GRANT EXECUTE ON FUNCTION public.orders_external_status(uuid, uuid) TO service_role;
795
+
796
+ DO $$
797
+ BEGIN
798
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN
799
+ GRANT USAGE ON SCHEMA public TO fayz_integration;
800
+ GRANT EXECUTE ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) TO fayz_integration;
801
+ GRANT EXECUTE ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) TO fayz_integration;
802
+ GRANT EXECUTE ON FUNCTION public.orders_external_status(uuid, uuid) TO fayz_integration;
803
+ END IF;
804
+ END $$;
805
+ `;
806
+ var MIGRATIONS = [
807
+ { id: "001_an_order_arrives_from_a_marketplace", sql: MIGRATION_001_AN_ORDER_ARRIVES_FROM_A_MARKETPLACE }
808
+ ];
809
+
602
810
  // src/locales/en.ts
603
811
  var en = {
604
812
  "orders.title": "Orders",
@@ -2396,6 +2604,15 @@ function createOrdersPlugin(options) {
2396
2604
  // No `menu`: a sale is not a restaurant's, and a dependency lights up by
2397
2605
  // drag — every account with Orders would grow a Cardápio line.
2398
2606
  dependencies: [],
2607
+ // What installing this provisions, byte-for-byte the files under
2608
+ // src/migrations (embed barrel). The spine holds the orders tables; this
2609
+ // list holds the doors the plugin adds on top of them.
2610
+ migrations: MIGRATIONS.map((m) => ({
2611
+ id: `orders-${m.id}`,
2612
+ version: "1.0.0",
2613
+ sql: m.sql,
2614
+ description: m.id
2615
+ })),
2399
2616
  events: [
2400
2617
  {
2401
2618
  name: "orders.order_completed",