@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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +48 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +8 -3
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;
|
|
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",
|