@fayz-ai/plugin-shop 0.21.6 → 0.22.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,12 +1,12 @@
1
1
  import { errorMessage, shopCustomerLookup, listProductsDetailed, invalidateProductLists, buildStockLocationsRegistry, listProductsCached, resolveTerm, candidateMeta } from './chunk-XV3KZRQX.js';
2
- import * as React2 from 'react';
3
- import React2__default, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
2
+ import * as React7 from 'react';
3
+ import React7__default, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
4
4
  import { Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, Input, ModalFooter, formatPhone, toast, Textarea, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DataTable, ConfirmDialog, Skeleton, ModalPortal, ModalOverlay, ModalBody, SearchSelect } from '@fayz-ai/ui';
5
- import { RotateCcw, QrCode, Pencil, Archive, Ban, CreditCard, XCircle, ShoppingBag, Package, Wallet, Plus, WandSparkles, CheckCircle2, Printer, RefreshCw, ExternalLink, Truck, AlertTriangle, Loader2, Check, Copy, Link2, HandCoins, Share2, MessageCircle, MapPin, X } from 'lucide-react';
5
+ import { RotateCcw, QrCode, Pencil, Archive, Ban, CreditCard, XCircle, ShoppingBag, Package, Wallet, Plus, WandSparkles, ChefHat, Bike, Check, X, CheckCircle2, Printer, RefreshCw, ExternalLink, Truck, AlertTriangle, Loader2, Copy, Link2, HandCoins, Share2, MessageCircle, MapPin } from 'lucide-react';
6
6
  import { formatDocument, getShopProvider, CHARGEABLE_PROVIDERS, supportsPaymentLink, resolveShippingLabelProvider, documentDigits, isValidDocument } from '@fayz-ai/shop';
7
7
  import { useAdminPath, adminNavigateTo, useOrderReceivable, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailRow, toOrderDetailReceivable, PersonLink, OrderDetailView as OrderDetailView$1, WidgetSlot, OrderFormBlock, OrderCreateScreen, OrderListView, askAI, useConnectors, CrudRecordModal, AddressFormModal, useAgentSurface, ClientOrdersTab, createClientOrdersProvider, duplicateWrite } from '@fayz-ai/admin';
8
8
  import { FINANCIAL_ZONES } from '@fayz-ai/financial/views/zones';
9
- import { usePluginRuntimeOptional, getWidgetsForZone, getSupabaseClientOptional, formatCurrency } from '@fayz-ai/core';
9
+ import { usePluginRuntimeOptional, getWidgetsForZone, getSupabaseClientOptional, formatCurrency, getActiveTenantId } from '@fayz-ai/core';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
  import { listStockReasonCodes } from '@fayz-ai/shop/inventory';
12
12
 
@@ -156,8 +156,8 @@ function SenderLocationModal({
156
156
  locationId
157
157
  }) {
158
158
  const resolve = provider ?? (() => getShopProvider());
159
- const registry = React2__default.useMemo(() => buildStockLocationsRegistry(resolve), [provider]);
160
- const chosen = React2__default.useRef(null);
159
+ const registry = React7__default.useMemo(() => buildStockLocationsRegistry(resolve), [provider]);
160
+ const chosen = React7__default.useRef(null);
161
161
  return /* @__PURE__ */ jsx(
162
162
  CrudRecordModal,
163
163
  {
@@ -859,6 +859,260 @@ async function cancelShopOrder(provider, orderId, reason) {
859
859
  const { error } = await db.rpc("shop_cancel_order", { p_order_id: orderId, p_reason: reason ?? null });
860
860
  if (error) throw new Error(error.message);
861
861
  }
862
+ async function refundShopOrder(orderId, reason, reject = false) {
863
+ const db = getSupabaseClientOptional();
864
+ if (!db?.functions) throw new Error("Estorno indispon\xEDvel nesta loja.");
865
+ const { data, error } = await db.functions.invoke("shop-refund", {
866
+ body: { tenantId: getActiveTenantId(), orderId, reason, reject }
867
+ });
868
+ let body = data;
869
+ if (error && error.context) {
870
+ body = await error.context.json().catch(() => null);
871
+ }
872
+ if (body?.status === "succeeded") return;
873
+ throw new Error(body?.error ?? "O estorno n\xE3o foi conclu\xEDdo. Tente de novo.");
874
+ }
875
+ async function advanceShopDelivery(orderId, to, reason) {
876
+ const db = getSupabaseClientOptional();
877
+ if (!db?.rpc) throw new Error("Delivery indispon\xEDvel nesta loja.");
878
+ const { error } = await db.rpc("shop_delivery_advance", { p_order_id: orderId, p_to: to, p_reason: reason ?? null });
879
+ if (error) throw new Error(error.message);
880
+ }
881
+ var STAGE_LABEL = {
882
+ new: "Novo \u2014 aguardando aceite",
883
+ accepted: "Em preparo",
884
+ out_for_delivery: "Saiu para entrega",
885
+ delivered: "Entregue",
886
+ rejected: "Recusado",
887
+ cancelled: "Cancelado"
888
+ };
889
+ var NOTICE_STAGE = {
890
+ new: "Pedido recebido",
891
+ accepted: "Aceito",
892
+ out_for_delivery: "Saiu para entrega",
893
+ delivered: "Entregue",
894
+ rejected: "Recusado",
895
+ cancelled: "Cancelado"
896
+ };
897
+ var NOTICE_STATUS = {
898
+ pending: "na fila",
899
+ sending: "enviando",
900
+ sent: "enviado",
901
+ failed: "falhou",
902
+ skipped: "n\xE3o enviado"
903
+ };
904
+ function when(iso) {
905
+ return iso ? new Date(iso).toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }) : "";
906
+ }
907
+ function OrderDeliveryPanel({ orderId, paid, total, money: money5, onChanged }) {
908
+ const [ext, setExt] = React7__default.useState(null);
909
+ const [notices, setNotices] = React7__default.useState([]);
910
+ const [refunds, setRefunds] = React7__default.useState([]);
911
+ const [nonce, setNonce] = React7__default.useState(0);
912
+ const [busy, setBusy] = React7__default.useState(false);
913
+ const [asking, setAsking] = React7__default.useState(null);
914
+ const [reason, setReason] = React7__default.useState("");
915
+ const [error, setError] = React7__default.useState(null);
916
+ React7__default.useEffect(() => {
917
+ const db = getSupabaseClientOptional();
918
+ if (!db) return;
919
+ let cancelled = false;
920
+ void Promise.all([
921
+ db.from("plg_shop_order_extensions").select("delivery_stage, accepted_at, dispatched_at, delivered_at, rejected_at, reject_reason, fulfillment_type").eq("order_id", orderId).maybeSingle(),
922
+ db.from("plg_shop_order_notifications").select("id, stage, channel, status, last_error, sent_at, created_at").eq("order_id", orderId).order("created_at"),
923
+ db.from("plg_shop_order_refunds").select("id, status, amount, reason, error, created_at, provider").eq("order_id", orderId).order("created_at", { ascending: false })
924
+ ]).then(([e, n, r]) => {
925
+ if (cancelled) return;
926
+ setExt(e?.data ?? null);
927
+ setNotices(n?.data ?? []);
928
+ setRefunds(r?.data ?? []);
929
+ }).catch(() => {
930
+ });
931
+ return () => {
932
+ cancelled = true;
933
+ };
934
+ }, [orderId, nonce]);
935
+ const reload = () => {
936
+ setNonce((x) => x + 1);
937
+ onChanged();
938
+ };
939
+ const stage = ext?.delivery_stage ?? null;
940
+ const pickup = ext?.fulfillment_type === "pickup";
941
+ const refunded = refunds.some((r) => r.status === "succeeded");
942
+ if (!stage && !paid && refunds.length === 0) return null;
943
+ const step = async (to, done) => {
944
+ setBusy(true);
945
+ try {
946
+ await advanceShopDelivery(orderId, to);
947
+ toast.success(done);
948
+ reload();
949
+ } catch (err) {
950
+ toast.error(err instanceof Error ? err.message : String(err));
951
+ } finally {
952
+ setBusy(false);
953
+ }
954
+ };
955
+ const confirm = async () => {
956
+ const text2 = reason.trim();
957
+ if (!text2) {
958
+ setError("Diga o motivo: ele vai para o cliente e para o financeiro.");
959
+ return;
960
+ }
961
+ setBusy(true);
962
+ setError(null);
963
+ try {
964
+ if (paid) await refundShopOrder(orderId, text2, asking === "reject");
965
+ else await advanceShopDelivery(orderId, "rejected", text2);
966
+ toast.success(paid ? "Estorno feito e pedido cancelado." : "Pedido recusado.");
967
+ setAsking(null);
968
+ setReason("");
969
+ reload();
970
+ } catch (err) {
971
+ setError(err instanceof Error ? err.message : String(err));
972
+ } finally {
973
+ setBusy(false);
974
+ }
975
+ };
976
+ const open = stage === "new" || stage === "accepted" || stage === "out_for_delivery";
977
+ return /* @__PURE__ */ jsxs(OrderDetailBlock, { title: "Delivery", hint: stage ? "O pedido na opera\xE7\xE3o, e o que o cliente j\xE1 sabe" : "Estorno", children: [
978
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 text-sm", "data-testid": "order-delivery-panel", children: [
979
+ stage && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
980
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: STAGE_LABEL[stage] ?? stage }),
981
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
982
+ stage === "accepted" && when(ext?.accepted_at ?? null),
983
+ stage === "out_for_delivery" && when(ext?.dispatched_at ?? null),
984
+ stage === "delivered" && when(ext?.delivered_at ?? null),
985
+ stage === "rejected" && when(ext?.rejected_at ?? null)
986
+ ] })
987
+ ] }),
988
+ ext?.reject_reason && (stage === "rejected" || stage === "cancelled") && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
989
+ "Motivo: ",
990
+ ext.reject_reason
991
+ ] }),
992
+ open && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1.5", children: [
993
+ stage === "new" && /* @__PURE__ */ jsxs(
994
+ Button,
995
+ {
996
+ size: "sm",
997
+ disabled: busy,
998
+ "data-testid": "order-delivery-accept",
999
+ onClick: () => {
1000
+ void step("accepted", "Pedido aceito.");
1001
+ },
1002
+ children: [
1003
+ /* @__PURE__ */ jsx(ChefHat, { className: "mr-1 h-3.5 w-3.5" }),
1004
+ " Aceitar"
1005
+ ]
1006
+ }
1007
+ ),
1008
+ stage === "accepted" && /* @__PURE__ */ jsxs(
1009
+ Button,
1010
+ {
1011
+ size: "sm",
1012
+ disabled: busy,
1013
+ "data-testid": "order-delivery-dispatch",
1014
+ onClick: () => {
1015
+ void step("out_for_delivery", pickup ? "Pronto para retirada." : "Saiu para entrega.");
1016
+ },
1017
+ children: [
1018
+ /* @__PURE__ */ jsx(Bike, { className: "mr-1 h-3.5 w-3.5" }),
1019
+ " ",
1020
+ pickup ? "Pronto para retirada" : "Saiu para entrega"
1021
+ ]
1022
+ }
1023
+ ),
1024
+ stage === "out_for_delivery" && /* @__PURE__ */ jsxs(
1025
+ Button,
1026
+ {
1027
+ size: "sm",
1028
+ disabled: busy,
1029
+ "data-testid": "order-delivery-delivered",
1030
+ onClick: () => {
1031
+ void step("delivered", "Pedido entregue.");
1032
+ },
1033
+ children: [
1034
+ /* @__PURE__ */ jsx(Check, { className: "mr-1 h-3.5 w-3.5" }),
1035
+ " Entregue"
1036
+ ]
1037
+ }
1038
+ ),
1039
+ (stage === "new" || stage === "accepted") && /* @__PURE__ */ jsxs(
1040
+ Button,
1041
+ {
1042
+ size: "sm",
1043
+ variant: "outline",
1044
+ disabled: busy,
1045
+ "data-testid": "order-delivery-reject",
1046
+ onClick: () => {
1047
+ setError(null);
1048
+ setAsking("reject");
1049
+ },
1050
+ children: [
1051
+ /* @__PURE__ */ jsx(X, { className: "mr-1 h-3.5 w-3.5" }),
1052
+ " Recusar"
1053
+ ]
1054
+ }
1055
+ )
1056
+ ] }),
1057
+ paid && !refunded && stage !== "new" && stage !== "accepted" && /* @__PURE__ */ jsxs(
1058
+ Button,
1059
+ {
1060
+ size: "sm",
1061
+ variant: "outline",
1062
+ disabled: busy,
1063
+ "data-testid": "order-refund",
1064
+ onClick: () => {
1065
+ setError(null);
1066
+ setAsking("refund");
1067
+ },
1068
+ children: [
1069
+ /* @__PURE__ */ jsx(RotateCcw, { className: "mr-1 h-3.5 w-3.5" }),
1070
+ " Estornar e cancelar"
1071
+ ]
1072
+ }
1073
+ ),
1074
+ refunds.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1 border-t pt-2", children: refunds.map((r) => /* @__PURE__ */ jsxs("p", { className: "text-xs", children: [
1075
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
1076
+ "Estorno ",
1077
+ money5(Number(r.amount))
1078
+ ] }),
1079
+ " \xB7 ",
1080
+ r.status === "succeeded" ? "devolvido ao cliente" : r.status === "failed" ? `recusado: ${r.error ?? ""}` : "em andamento",
1081
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1082
+ " \xB7 ",
1083
+ when(r.created_at)
1084
+ ] })
1085
+ ] }, r.id)) }),
1086
+ notices.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t pt-2", children: [
1087
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: "Avisos ao cliente" }),
1088
+ notices.map((n) => /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", title: n.last_error ?? void 0, children: [
1089
+ NOTICE_STAGE[n.stage] ?? n.stage,
1090
+ " \xB7 ",
1091
+ n.channel === "whatsapp" ? "WhatsApp" : "E-mail",
1092
+ " \xB7 ",
1093
+ NOTICE_STATUS[n.status] ?? n.status,
1094
+ n.status === "skipped" && n.last_error ? ` (${n.last_error})` : ""
1095
+ ] }, n.id))
1096
+ ] })
1097
+ ] }),
1098
+ /* @__PURE__ */ jsx(Modal, { open: asking !== null, onOpenChange: (isOpen) => {
1099
+ if (!isOpen && !busy) setAsking(null);
1100
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "sm", children: [
1101
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
1102
+ /* @__PURE__ */ jsx(ModalTitle, { children: asking === "reject" ? "Recusar o pedido?" : "Estornar e cancelar?" }),
1103
+ /* @__PURE__ */ jsx(ModalDescription, { children: paid ? `${money5(total)} volta para o cliente pelo gateway agora, o recebimento \xE9 estornado no Financeiro e o cliente recebe o aviso com o motivo.` : "O cliente recebe o aviso com o motivo." })
1104
+ ] }),
1105
+ /* @__PURE__ */ jsx(Input, { value: reason, onChange: (e) => setReason(e.target.value), placeholder: "Motivo", "data-testid": "order-refund-reason" }),
1106
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-2 text-xs text-destructive", children: error }),
1107
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
1108
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setAsking(null), disabled: busy, children: "Voltar" }),
1109
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => {
1110
+ void confirm();
1111
+ }, disabled: busy, "data-testid": "order-refund-confirm", children: busy ? "Aguarde\u2026" : asking === "reject" ? paid ? "Recusar e estornar" : "Recusar" : "Estornar e cancelar" })
1112
+ ] })
1113
+ ] }) })
1114
+ ] });
1115
+ }
862
1116
  var POLL_MS = 4e3;
863
1117
  function ChargeChoice({ icon: Icon, title, hint, onClick, testId }) {
864
1118
  return /* @__PURE__ */ jsxs(
@@ -1906,6 +2160,19 @@ function OrderDetailView({ orderId, provider, onBack, fallback }) {
1906
2160
  }
1907
2161
  ) ?? { rows: [], installments: [], emptyLabel: "Sem conta a receber para este pedido." },
1908
2162
  extraSections: /* @__PURE__ */ jsxs(Fragment, { children: [
2163
+ /* @__PURE__ */ jsx(
2164
+ OrderDeliveryPanel,
2165
+ {
2166
+ orderId: order.id,
2167
+ paid: order.financialStatus === "paid" || order.financialStatus === "partially_paid",
2168
+ total: order.total,
2169
+ money: (v) => money3(v, cur),
2170
+ onChanged: () => {
2171
+ void reloadShipments();
2172
+ void loadReceivable(true);
2173
+ }
2174
+ }
2175
+ ),
1909
2176
  /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Entrega", hint: "O que a log\xEDstica precisa", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-sm", children: [
1910
2177
  addr ? /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
1911
2178
  /* @__PURE__ */ jsxs("p", { children: [
@@ -3442,9 +3709,9 @@ function OrdersAgentSurface({
3442
3709
  orders,
3443
3710
  onChanged
3444
3711
  }) {
3445
- const live = React2.useRef({ provider, orders, onChanged });
3712
+ const live = React7.useRef({ provider, orders, onChanged });
3446
3713
  live.current = { provider, orders, onChanged };
3447
- const contribution = React2.useMemo(() => ({
3714
+ const contribution = React7.useMemo(() => ({
3448
3715
  id: "shop.orders.list",
3449
3716
  pluginId: "shop",
3450
3717
  role: "operations",
@@ -4042,5 +4309,5 @@ var ShopPage = ({ provider, section }) => {
4042
4309
  ShopPage.displayName = "ShopPage";
4043
4310
 
4044
4311
  export { ShopOrderDetailSlot, ShopPage };
4045
- //# sourceMappingURL=ShopPage-2CTRGQTX.js.map
4046
- //# sourceMappingURL=ShopPage-2CTRGQTX.js.map
4312
+ //# sourceMappingURL=ShopPage-7GU5LPKP.js.map
4313
+ //# sourceMappingURL=ShopPage-7GU5LPKP.js.map