@fayz-ai/plugin-shop 0.11.2 → 0.13.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.
Files changed (66) hide show
  1. package/dist/{ShopPage-YB5ZWECJ.js → ShopPage-QFIM2HDU.js} +1516 -406
  2. package/dist/ShopPage-QFIM2HDU.js.map +1 -0
  3. package/dist/{chunk-HAKXHHAG.js → chunk-2VMTPRGC.js} +202 -11
  4. package/dist/chunk-2VMTPRGC.js.map +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1814 -442
  8. package/dist/index.js.map +1 -1
  9. package/dist/integrations/abacatepay/connectorDef.d.ts.map +1 -1
  10. package/dist/integrations/abacatepay/data.d.ts +40 -0
  11. package/dist/integrations/abacatepay/data.d.ts.map +1 -0
  12. package/dist/integrations/abacatepay/limits.d.ts +26 -0
  13. package/dist/integrations/abacatepay/limits.d.ts.map +1 -0
  14. package/dist/integrations/abacatepay/sync.d.ts +88 -0
  15. package/dist/integrations/abacatepay/sync.d.ts.map +1 -0
  16. package/dist/integrations/bling/data.d.ts.map +1 -1
  17. package/dist/integrations/mercadopago/connectorDef.d.ts +41 -0
  18. package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -0
  19. package/dist/integrations/neochef/connectorDef.d.ts +12 -0
  20. package/dist/integrations/neochef/connectorDef.d.ts.map +1 -0
  21. package/dist/integrations/neochef/data.d.ts +54 -0
  22. package/dist/integrations/neochef/data.d.ts.map +1 -0
  23. package/dist/integrations/neochef/index.d.ts +8 -0
  24. package/dist/integrations/neochef/index.d.ts.map +1 -0
  25. package/dist/integrations/neochef/limits.d.ts +26 -0
  26. package/dist/integrations/neochef/limits.d.ts.map +1 -0
  27. package/dist/integrations/neochef/mapping.d.ts +31 -0
  28. package/dist/integrations/neochef/mapping.d.ts.map +1 -0
  29. package/dist/integrations/neochef/mark.d.ts +21 -0
  30. package/dist/integrations/neochef/mark.d.ts.map +1 -0
  31. package/dist/integrations/neochef/types.d.ts +71 -0
  32. package/dist/integrations/neochef/types.d.ts.map +1 -0
  33. package/dist/integrations/vindi/connectorDef.d.ts +67 -35
  34. package/dist/integrations/vindi/connectorDef.d.ts.map +1 -1
  35. package/dist/views/ContentPage.d.ts.map +1 -1
  36. package/dist/views/CustomerModal.d.ts +33 -0
  37. package/dist/views/CustomerModal.d.ts.map +1 -0
  38. package/dist/views/NewOrderCharge.d.ts +39 -3
  39. package/dist/views/NewOrderCharge.d.ts.map +1 -1
  40. package/dist/views/NewOrderPage.d.ts +10 -1
  41. package/dist/views/NewOrderPage.d.ts.map +1 -1
  42. package/dist/views/OrderDetailView.d.ts.map +1 -1
  43. package/dist/views/OrdersAgentSurface.d.ts +10 -0
  44. package/dist/views/OrdersAgentSurface.d.ts.map +1 -0
  45. package/dist/views/ProductPositionsTab.d.ts +7 -0
  46. package/dist/views/ProductPositionsTab.d.ts.map +1 -0
  47. package/dist/views/ProductStockTab.d.ts +84 -0
  48. package/dist/views/ProductStockTab.d.ts.map +1 -0
  49. package/dist/views/ShopPage.d.ts.map +1 -1
  50. package/dist/views/StockEntryModal.d.ts +32 -0
  51. package/dist/views/StockEntryModal.d.ts.map +1 -0
  52. package/dist/views/customerEntity.d.ts.map +1 -1
  53. package/dist/views/discount-agent.d.ts +2 -0
  54. package/dist/views/discount-agent.d.ts.map +1 -0
  55. package/dist/views/new-order-rules.d.ts +23 -1
  56. package/dist/views/new-order-rules.d.ts.map +1 -1
  57. package/dist/views/orders-agent.d.ts +11 -0
  58. package/dist/views/orders-agent.d.ts.map +1 -0
  59. package/dist/views/product-matching.d.ts +38 -0
  60. package/dist/views/product-matching.d.ts.map +1 -0
  61. package/dist/views/productEntity.d.ts.map +1 -1
  62. package/dist/views/stock-agent.d.ts +82 -0
  63. package/dist/views/stock-agent.d.ts.map +1 -0
  64. package/package.json +8 -6
  65. package/dist/ShopPage-YB5ZWECJ.js.map +0 -1
  66. package/dist/chunk-HAKXHHAG.js.map +0 -1
@@ -1,11 +1,13 @@
1
- import { errorMessage, shopCustomerLookup, listProductsDetailed, invalidateProductLists, buildStockLocationsRegistry } from './chunk-HAKXHHAG.js';
2
- import React2, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
3
- import { Thumbnail, Button, DataTable, toast, ConfirmDialog, formatPhone, SearchSelect, Modal, ModalPortal, ModalOverlay, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, Skeleton, PhoneField } from '@fayz-ai/ui';
4
- import { Minus, Plus, Trash2, CreditCard, XCircle, ShoppingBag, Package, Wallet, X, Pencil, MapPin, Search, Check, Copy, MessageCircle, CheckCircle2, Printer, RefreshCw, ExternalLink, Truck, AlertTriangle, Loader2 } from 'lucide-react';
5
- import { getShopProvider, formatDocument, resolveShippingLabelProvider, documentDigits, isValidDocument } from '@fayz-ai/shop';
6
- import { useAdminPath, adminNavigateTo, OrderFormBlock, OrderFormView, OrderDetailSkeleton, OrderDetailBlock, OrderDetailRow, PersonLink, OrderDetailView as OrderDetailView$1, OrderListView, RecordPickerModal, useConnectors, CrudRecordModal } from '@fayz-ai/admin';
1
+ import { errorMessage, shopCustomerLookup, listProductsDetailed, invalidateProductLists, buildShopCustomerEntity, buildStockLocationsRegistry, listProductsCached, resolveTerm, candidateMeta } from './chunk-2VMTPRGC.js';
2
+ import * as React2 from 'react';
3
+ import React2__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
4
+ import { useSaveBar, Thumbnail, Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, formatPhone, toast, DataTable, ConfirmDialog, SearchSelect, ModalPortal, ModalOverlay, ModalBody, Skeleton } from '@fayz-ai/ui';
5
+ import { Minus, Plus, Trash2, RotateCcw, QrCode, Pencil, Archive, Ban, CreditCard, XCircle, ShoppingBag, Package, Wallet, WandSparkles, X, MapPin, Search, Link2, HandCoins, Check, Copy, Share2, MessageCircle, CheckCircle2, Printer, RefreshCw, ExternalLink, Truck, AlertTriangle, Loader2 } from 'lucide-react';
6
+ import { CHARGEABLE_PROVIDERS, supportsPaymentLink, getShopProvider, formatDocument, resolveShippingLabelProvider, isValidDocument, documentDigits } from '@fayz-ai/shop';
7
+ import { useAdminPath, adminNavigateTo, OrderFormBlock, OrderFormView, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailRow, PersonLink, OrderDetailView as OrderDetailView$1, OrderListView, askAI, RecordPickerModal, useConnectors, CrudRecordModal, useAgentSurface, duplicateWrite } from '@fayz-ai/admin';
7
8
  import { usePluginRuntimeOptional, getSupabaseClientOptional } from '@fayz-ai/core';
8
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
+ import { listStockReasonCodes } from '@fayz-ai/shop/inventory';
9
11
 
10
12
  function ShipOrderForm({ orderId, provider, onShipped }) {
11
13
  const [open, setOpen] = useState(false);
@@ -136,8 +138,8 @@ function SenderLocationModal({
136
138
  locationId
137
139
  }) {
138
140
  const resolve = provider ?? (() => getShopProvider());
139
- const registry = React2.useMemo(() => buildStockLocationsRegistry(resolve), [provider]);
140
- const chosen = React2.useRef(null);
141
+ const registry = React2__default.useMemo(() => buildStockLocationsRegistry(resolve), [provider]);
142
+ const chosen = React2__default.useRef(null);
141
143
  return /* @__PURE__ */ jsx(
142
144
  CrudRecordModal,
143
145
  {
@@ -187,17 +189,24 @@ function validateNewOrder(draft, ending) {
187
189
  return `Para gerar a cobran\xE7a o adquirente exige ${missing.join(", ")} no cadastro do cliente.`;
188
190
  }
189
191
  }
192
+ if (ending === "link") {
193
+ const missing = [
194
+ !draft.customerEmail.trim() && "e-mail",
195
+ !draft.customerPhone.trim() && "telefone"
196
+ ].filter(Boolean);
197
+ if (missing.length) {
198
+ return `Para mandar o link o cadastro do cliente precisa de ${missing.join(" e ")}.`;
199
+ }
200
+ }
190
201
  return null;
191
202
  }
192
- function isDuplicateEmail(error) {
193
- const code = String(error?.code ?? "");
194
- if (code === "23505") return true;
195
- const message = String(error?.message ?? error ?? "");
196
- return /duplicate key value/iu.test(message) && /email/iu.test(message);
203
+ function insufficientStockProduct(message) {
204
+ const found = /insufficient stock for\s+(.+?)\s*$/iu.exec(String(message ?? ""));
205
+ return found ? found[1].trim() : null;
197
206
  }
198
207
  function isSenderRefusal(message) {
199
- const text = String(message ?? "");
200
- return /\bfrom\./iu.test(text) || /remetente/iu.test(text);
208
+ const text2 = String(message ?? "");
209
+ return /\bfrom\./iu.test(text2) || /remetente/iu.test(text2);
201
210
  }
202
211
  var money = (value) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(value) || 0);
203
212
  var eta = (option) => {
@@ -822,6 +831,408 @@ async function cancelShopOrder(provider, orderId, reason) {
822
831
  const { error } = await db.rpc("shop_cancel_order", { p_order_id: orderId, p_reason: reason ?? null });
823
832
  if (error) throw new Error(error.message);
824
833
  }
834
+ var POLL_MS = 4e3;
835
+ function ChargeChoice({ icon: Icon, title, hint, onClick, testId }) {
836
+ return /* @__PURE__ */ jsxs(
837
+ "button",
838
+ {
839
+ type: "button",
840
+ onClick,
841
+ "data-testid": testId,
842
+ className: "flex w-full items-start gap-3 rounded-md border px-3 py-2.5 text-left transition hover:border-primary hover:bg-muted/40",
843
+ children: [
844
+ /* @__PURE__ */ jsx(Icon, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }),
845
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
846
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: title }),
847
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: hint })
848
+ ] })
849
+ ]
850
+ }
851
+ );
852
+ }
853
+ function NewOrderCharge({
854
+ open,
855
+ provider,
856
+ sessionId,
857
+ emv,
858
+ qrcodeBase64,
859
+ qrcodeUrl,
860
+ paymentUrl,
861
+ acquirer,
862
+ total,
863
+ currency: currency2,
864
+ customerName,
865
+ customerPhone,
866
+ onPaid,
867
+ onClose,
868
+ onChoose,
869
+ opening,
870
+ onRegisterReceipt,
871
+ error
872
+ }) {
873
+ const [state, setState] = useState("waiting");
874
+ const [copied, setCopied] = useState(false);
875
+ const [checking, setChecking] = useState(false);
876
+ const settled = useRef(false);
877
+ const inFlight = useRef(false);
878
+ const money4 = new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(total);
879
+ const check = useCallback(async () => {
880
+ if (settled.current || !sessionId || inFlight.current) return;
881
+ inFlight.current = true;
882
+ setChecking(true);
883
+ try {
884
+ const result = await provider().settleCheckoutSession?.(sessionId, acquirer);
885
+ if (!result) return;
886
+ if (result.state === "paid") {
887
+ settled.current = true;
888
+ setState("paid");
889
+ setTimeout(() => onPaid(result.orderId ?? void 0), 1200);
890
+ return;
891
+ }
892
+ const outcome = chargeOutcome(result.state);
893
+ if (outcome !== "waiting") {
894
+ settled.current = true;
895
+ setState(outcome);
896
+ }
897
+ } catch {
898
+ } finally {
899
+ inFlight.current = false;
900
+ setChecking(false);
901
+ }
902
+ }, [provider, sessionId, acquirer, onPaid]);
903
+ useEffect(() => {
904
+ if (!open || !sessionId) return;
905
+ const timer = setInterval(() => {
906
+ void check();
907
+ }, POLL_MS);
908
+ return () => clearInterval(timer);
909
+ }, [open, sessionId, check]);
910
+ const isLink = Boolean(paymentUrl && !emv);
911
+ const payable = paymentUrl ?? emv ?? "";
912
+ const qrSrc = qrcodeBase64 ? `data:image/png;base64,${qrcodeBase64}` : qrcodeUrl || null;
913
+ const ready = Boolean(sessionId && payable);
914
+ const choosing = Boolean(onChoose) && !ready && !opening;
915
+ async function copy() {
916
+ if (!payable) return;
917
+ try {
918
+ await navigator.clipboard.writeText(payable);
919
+ setCopied(true);
920
+ setTimeout(() => setCopied(false), 2400);
921
+ } catch {
922
+ }
923
+ }
924
+ async function shareQr() {
925
+ if (!qrSrc) return;
926
+ let blob;
927
+ try {
928
+ blob = qrcodeBase64 ? new Blob([Uint8Array.from(atob(qrcodeBase64), (c) => c.charCodeAt(0))], { type: "image/png" }) : await (await fetch(qrSrc)).blob();
929
+ } catch {
930
+ toast.error("N\xE3o consegui pegar a imagem do QR", {
931
+ description: 'Use "Copiar c\xF3digo" \u2014 o cliente cola no app do banco.'
932
+ });
933
+ return;
934
+ }
935
+ const file = new File([blob], `pix-${money4.replace(/\D/gu, "")}.${blob.type.includes("svg") ? "svg" : "png"}`, { type: blob.type || "image/png" });
936
+ const share = navigator;
937
+ try {
938
+ if (share.canShare?.({ files: [file] })) {
939
+ await navigator.share({ files: [file], text: message });
940
+ return;
941
+ }
942
+ const Item = window.ClipboardItem;
943
+ if (!Item || !blob.type.includes("png")) throw new Error("sem \xE1rea de transfer\xEAncia de imagem");
944
+ await navigator.clipboard.write([new Item({ "image/png": blob })]);
945
+ toast.success("QR copiado \u2014 \xE9 s\xF3 colar na conversa do WhatsApp.");
946
+ } catch {
947
+ toast.error("Este navegador n\xE3o compartilha imagem", {
948
+ description: 'Use "Copiar c\xF3digo" \u2014 o cliente cola no app do banco.'
949
+ });
950
+ }
951
+ }
952
+ const greeting = customerName ? ` ${customerName.split(" ")[0]}` : "";
953
+ const message = isLink ? `Ol\xE1${greeting}! Aqui est\xE1 o link para pagar seu pedido de ${money4}:
954
+
955
+ ${payable}
956
+
957
+ Voc\xEA escolhe a forma de pagamento na p\xE1gina.` : `Ol\xE1${greeting}! Segue o Pix de ${money4} para o seu pedido:
958
+
959
+ ${payable}`;
960
+ const whatsapp = customerPhone && payable ? `https://wa.me/${customerPhone.length > 11 ? customerPhone : `55${customerPhone}`}?text=${encodeURIComponent(message)}` : null;
961
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
962
+ if (!next) onClose();
963
+ }, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", "data-testid": "new-order-charge-modal", children: [
964
+ /* @__PURE__ */ jsxs(ModalHeader, { className: "mb-4", children: [
965
+ /* @__PURE__ */ jsx(ModalTitle, { children: choosing || opening ? `Cobrar ${money4}` : isLink ? `Link de ${money4}` : `Pix de ${money4}` }),
966
+ choosing && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Como o cliente vai pagar?" }),
967
+ opening && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Abrindo a cobran\xE7a no adquirente\u2026" }),
968
+ ready && state === "waiting" && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: isLink ? "Mande para o cliente. O pedido \xE9 criado quando o pagamento cair \u2014 pode fechar esta janela." : "Vale por 15 minutos. O pedido \xE9 criado quando o pagamento cair." })
969
+ ] }),
970
+ /* @__PURE__ */ jsxs(ModalBody, { children: [
971
+ error && /* @__PURE__ */ jsx(
972
+ "p",
973
+ {
974
+ role: "alert",
975
+ "data-testid": "charge-error",
976
+ className: "mb-3 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800",
977
+ children: error
978
+ }
979
+ ),
980
+ choosing && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "charge-choices", children: [
981
+ /* @__PURE__ */ jsx(
982
+ ChargeChoice,
983
+ {
984
+ icon: QrCode,
985
+ title: "Cobrar por Pix",
986
+ hint: "QR e c\xF3digo na hora, para ler ou mandar agora",
987
+ onClick: () => onChoose?.(false),
988
+ testId: "charge-choose-pix"
989
+ }
990
+ ),
991
+ /* @__PURE__ */ jsx(
992
+ ChargeChoice,
993
+ {
994
+ icon: Link2,
995
+ title: "Gerar link de pagamento",
996
+ hint: "Uma p\xE1gina do adquirente \u2014 o cliente escolhe como pagar",
997
+ onClick: () => onChoose?.(true),
998
+ testId: "charge-choose-link"
999
+ }
1000
+ ),
1001
+ onRegisterReceipt && /* @__PURE__ */ jsx(
1002
+ ChargeChoice,
1003
+ {
1004
+ icon: HandCoins,
1005
+ title: "J\xE1 recebi",
1006
+ hint: "Dinheiro, maquininha, Pix na sua conta \u2014 s\xF3 registrar",
1007
+ onClick: onRegisterReceipt,
1008
+ testId: "charge-choose-received"
1009
+ }
1010
+ )
1011
+ ] }),
1012
+ opening && /* @__PURE__ */ jsxs(
1013
+ "div",
1014
+ {
1015
+ className: "flex flex-col items-center gap-4 sm:flex-row sm:items-start",
1016
+ "aria-busy": true,
1017
+ "data-testid": "charge-skeleton",
1018
+ children: [
1019
+ /* @__PURE__ */ jsx("div", { className: "h-40 w-40 shrink-0 animate-pulse rounded-md border bg-muted" }),
1020
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
1021
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-28 animate-pulse rounded bg-muted" }),
1022
+ /* @__PURE__ */ jsx("div", { className: "h-16 w-full animate-pulse rounded-md bg-muted" }),
1023
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1024
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-32 animate-pulse rounded-md bg-muted" }),
1025
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-40 animate-pulse rounded-md bg-muted" })
1026
+ ] })
1027
+ ] })
1028
+ ]
1029
+ }
1030
+ ),
1031
+ ready && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4 sm:flex-row sm:items-start", children: [
1032
+ qrSrc && /* @__PURE__ */ jsx(
1033
+ "img",
1034
+ {
1035
+ src: qrSrc,
1036
+ alt: "QR code do Pix",
1037
+ "data-testid": "new-order-qr",
1038
+ className: "h-40 w-40 shrink-0 rounded-md border bg-white p-2"
1039
+ }
1040
+ ),
1041
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
1042
+ /* @__PURE__ */ jsxs("div", { children: [
1043
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: isLink ? "Link de pagamento" : "Pix copia e cola" }),
1044
+ /* @__PURE__ */ jsx(
1045
+ "p",
1046
+ {
1047
+ "data-testid": isLink ? "new-order-payment-link" : "new-order-emv",
1048
+ className: "mt-1 max-h-20 overflow-auto break-all rounded-md border bg-muted/30 p-2 font-mono text-[11px]",
1049
+ children: payable
1050
+ }
1051
+ ),
1052
+ isLink && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: "O cliente escolhe Pix, boleto ou cart\xE3o na p\xE1gina do adquirente." })
1053
+ ] }),
1054
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
1055
+ /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: copy, "data-testid": "new-order-copy", children: [
1056
+ copied ? /* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "mr-1.5 h-3.5 w-3.5" }),
1057
+ copied ? "Copiado" : isLink ? "Copiar link" : "Copiar c\xF3digo"
1058
+ ] }),
1059
+ qrSrc && /* @__PURE__ */ jsxs(
1060
+ Button,
1061
+ {
1062
+ size: "sm",
1063
+ variant: "outline",
1064
+ onClick: () => void shareQr(),
1065
+ "data-testid": "new-order-share-qr",
1066
+ children: [
1067
+ /* @__PURE__ */ jsx(Share2, { className: "mr-1.5 h-3.5 w-3.5" }),
1068
+ "Compartilhar QR"
1069
+ ]
1070
+ }
1071
+ ),
1072
+ whatsapp && /* @__PURE__ */ jsxs(
1073
+ "a",
1074
+ {
1075
+ href: whatsapp,
1076
+ target: "_blank",
1077
+ rel: "noreferrer",
1078
+ "data-testid": "new-order-whatsapp",
1079
+ className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1080
+ children: [
1081
+ /* @__PURE__ */ jsx(MessageCircle, { className: "mr-1.5 h-3.5 w-3.5" }),
1082
+ "Mandar no WhatsApp"
1083
+ ]
1084
+ }
1085
+ )
1086
+ ] })
1087
+ ] })
1088
+ ] }),
1089
+ ready && /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-md border bg-muted/30 px-3 py-2 text-sm", children: [
1090
+ state === "waiting" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-muted-foreground", "data-testid": "new-order-waiting", children: [
1091
+ /* @__PURE__ */ jsx("span", { className: "h-2 w-2 animate-pulse rounded-full bg-amber-500" }),
1092
+ "Aguardando o pagamento\u2026 a tela confirma sozinha."
1093
+ ] }),
1094
+ state === "paid" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 font-medium text-emerald-700", "data-testid": "new-order-paid", children: [
1095
+ /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
1096
+ " Pagamento confirmado \u2014 abrindo o pedido\u2026"
1097
+ ] }),
1098
+ (state === "expired" || state === "failed") && /* @__PURE__ */ jsx("p", { className: "text-red-700", "data-testid": "new-order-dead", children: state === "expired" ? `${isLink ? "O link expirou" : "O c\xF3digo expirou"} sem pagamento. O pedido continua aqui \u2014 d\xE1 para cobrar de outro jeito.` : "O pagamento n\xE3o foi conclu\xEDdo. O pedido continua aqui \u2014 d\xE1 para cobrar de outro jeito." })
1099
+ ] })
1100
+ ] }),
1101
+ /* @__PURE__ */ jsxs(ModalFooter, { className: "mt-4 gap-2", children: [
1102
+ ready && state === "waiting" && /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void check(), disabled: checking, "data-testid": "new-order-check", children: checking ? "Conferindo\u2026" : "J\xE1 pagou? Conferir" }),
1103
+ /* @__PURE__ */ jsx(
1104
+ Button,
1105
+ {
1106
+ variant: state === "waiting" ? "ghost" : "default",
1107
+ onClick: onClose,
1108
+ "data-testid": "new-order-charge-close",
1109
+ children: state === "waiting" ? "Fechar" : "Voltar ao pedido"
1110
+ }
1111
+ )
1112
+ ] })
1113
+ ] }) }) }) });
1114
+ }
1115
+ var onlyDigits = (value) => value.replace(/\D/gu, "");
1116
+ function CustomerModal({ open, initialName, customerId, initial, provider, onClose, onSaved }) {
1117
+ const entityDef = useMemo(() => buildShopCustomerEntity(), []);
1118
+ const [conflict, setConflict] = useState(null);
1119
+ const [refused, setRefused] = useState(null);
1120
+ const [forcing, setForcing] = useState(false);
1121
+ const initialData = useMemo(() => {
1122
+ const [head, ...rest] = String(initial?.name ?? initialName ?? "").trim().split(/\s+/u);
1123
+ return {
1124
+ firstName: head ?? "",
1125
+ lastName: rest.join(" "),
1126
+ email: initial?.email ?? "",
1127
+ phone: initial?.phone ?? "",
1128
+ document: initial?.document ?? ""
1129
+ };
1130
+ }, [initial, initialName]);
1131
+ useEffect(() => {
1132
+ if (open) {
1133
+ setConflict(null);
1134
+ setRefused(null);
1135
+ }
1136
+ }, [open]);
1137
+ async function saveAnyway() {
1138
+ if (!refused || !conflict) return;
1139
+ setForcing(true);
1140
+ try {
1141
+ const fields = { ...refused, [conflict.field]: void 0 };
1142
+ const saved = customerId ? await provider().updateCustomer(customerId, fields) : await provider().createCustomer(fields);
1143
+ toast.success(`Cadastro salvo sem o ${conflict.label} \u2014 ele j\xE1 pertence a outro cadastro.`);
1144
+ onSaved(saved);
1145
+ } catch (err) {
1146
+ toast.error("N\xE3o consegui salvar", { description: errorMessage(err) });
1147
+ } finally {
1148
+ setForcing(false);
1149
+ }
1150
+ }
1151
+ async function save(data) {
1152
+ const document = documentDigits(String(data.document ?? ""));
1153
+ if (document && !isValidDocument(document)) throw new Error("Esse CPF/CNPJ n\xE3o confere.");
1154
+ const email = String(data.email ?? "").trim().toLowerCase();
1155
+ const fields = {
1156
+ firstName: String(data.firstName ?? "").trim(),
1157
+ lastName: String(data.lastName ?? "").trim(),
1158
+ email: email || void 0,
1159
+ phone: onlyDigits(String(data.phone ?? "")) || void 0,
1160
+ document: document || void 0,
1161
+ notes: data.notes ?? void 0
1162
+ };
1163
+ try {
1164
+ const saved = customerId ? await provider().updateCustomer(customerId, fields) : await provider().createCustomer(fields);
1165
+ onSaved(saved);
1166
+ return saved;
1167
+ } catch (err) {
1168
+ const clash = duplicateWrite(err);
1169
+ if (!clash?.field) throw err;
1170
+ setRefused(fields);
1171
+ const hit = clash.field === "email" && email ? (await provider().listCustomers({ search: email, limit: 5 }).catch(() => [])).find((c) => (c.email ?? "").toLowerCase() === email) ?? null : null;
1172
+ setConflict({ field: clash.field, label: clash.label, hit });
1173
+ return;
1174
+ }
1175
+ }
1176
+ return /* @__PURE__ */ jsx(
1177
+ CrudRecordModal,
1178
+ {
1179
+ open,
1180
+ mode: customerId ? "edit" : "create",
1181
+ entityDef,
1182
+ initialData,
1183
+ title: customerId ? "Cadastro do cliente" : "Novo cliente",
1184
+ onClose,
1185
+ save,
1186
+ notice: conflict && /* @__PURE__ */ jsxs(
1187
+ "div",
1188
+ {
1189
+ role: "alert",
1190
+ "data-testid": "new-customer-duplicate",
1191
+ className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900",
1192
+ children: [
1193
+ /* @__PURE__ */ jsx("p", { children: conflict.hit ? /* @__PURE__ */ jsxs(Fragment, { children: [
1194
+ /* @__PURE__ */ jsxs("strong", { children: [
1195
+ conflict.hit.firstName,
1196
+ " ",
1197
+ conflict.hit.lastName
1198
+ ] }),
1199
+ " j\xE1 \xE9 cliente com esse ",
1200
+ conflict.label,
1201
+ "."
1202
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1203
+ "Esse ",
1204
+ conflict.label,
1205
+ " j\xE1 est\xE1 em outro cadastro desta loja."
1206
+ ] }) }),
1207
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-3", children: [
1208
+ conflict.hit && /* @__PURE__ */ jsx(
1209
+ "button",
1210
+ {
1211
+ type: "button",
1212
+ onClick: () => onSaved(conflict.hit),
1213
+ "data-testid": "new-customer-use-existing",
1214
+ className: "font-medium underline underline-offset-2",
1215
+ children: "Usar este cliente"
1216
+ }
1217
+ ),
1218
+ /* @__PURE__ */ jsx(
1219
+ "button",
1220
+ {
1221
+ type: "button",
1222
+ onClick: () => void saveAnyway(),
1223
+ disabled: forcing,
1224
+ "data-testid": "new-customer-save-anyway",
1225
+ className: "font-medium underline underline-offset-2 disabled:opacity-60",
1226
+ children: forcing ? "Salvando\u2026" : `Salvar sem o ${conflict.label}`
1227
+ }
1228
+ )
1229
+ ] })
1230
+ ]
1231
+ }
1232
+ )
1233
+ }
1234
+ );
1235
+ }
825
1236
  var money2 = (v, currency2 = "BRL") => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(Number(v) || 0);
826
1237
  var dateTime2 = (iso) => new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
827
1238
  var dateOnly = (iso) => new Date(iso).toLocaleDateString("pt-BR");
@@ -930,6 +1341,17 @@ function deliveryFromNotes(notes) {
930
1341
  return (match?.[1] ?? "").trim() || null;
931
1342
  }
932
1343
  var shopOrderCache = /* @__PURE__ */ new Map();
1344
+ function fromSession(session) {
1345
+ return {
1346
+ sessionId: session.sessionId,
1347
+ emv: session.charge?.emv ?? null,
1348
+ qrcode: session.charge?.qrcodeBase64 ?? null,
1349
+ qrcodeUrl: session.charge?.qrcodeUrl ?? null,
1350
+ paymentUrl: session.paymentUrl ?? null,
1351
+ total: session.total,
1352
+ provider: session.provider ?? null
1353
+ };
1354
+ }
933
1355
  var shopShipmentsCache = /* @__PURE__ */ new Map();
934
1356
  var shopReceivableCache = /* @__PURE__ */ new Map();
935
1357
  function OrderDetailView({ orderId, provider, onBack }) {
@@ -946,6 +1368,12 @@ function OrderDetailView({ orderId, provider, onBack }) {
946
1368
  const [statusError, setStatusError] = useState(null);
947
1369
  const [cancelReason, setCancelReason] = useState("");
948
1370
  const [receivable, setReceivable] = useState(() => shopReceivableCache.get(orderId) ?? null);
1371
+ const [charge, setCharge] = useState(null);
1372
+ const [chargeOpen, setChargeOpen] = useState(false);
1373
+ const [charging, setCharging] = useState(null);
1374
+ const [customer, setCustomer] = useState(null);
1375
+ const [editingCustomer, setEditingCustomer] = useState(false);
1376
+ const [chargeError, setChargeError] = useState(null);
949
1377
  useEffect(() => {
950
1378
  let cancelled = false;
951
1379
  const cached = shopOrderCache.get(orderId);
@@ -977,6 +1405,19 @@ function OrderDetailView({ orderId, provider, onBack }) {
977
1405
  cancelled = true;
978
1406
  };
979
1407
  }, [orderId, provider]);
1408
+ const providerRef = useRef(provider);
1409
+ providerRef.current = provider;
1410
+ const loadCustomer = useCallback(async () => {
1411
+ const id = shopOrderCache.get(orderId)?.customerId;
1412
+ if (!id) {
1413
+ setCustomer(null);
1414
+ return;
1415
+ }
1416
+ setCustomer(await providerRef.current().getCustomer(id).catch(() => null));
1417
+ }, [orderId]);
1418
+ useEffect(() => {
1419
+ void loadCustomer();
1420
+ }, [loadCustomer, order?.customerId]);
980
1421
  useEffect(() => {
981
1422
  let cancelled = false;
982
1423
  const db = getSupabaseClientOptional();
@@ -1014,7 +1455,20 @@ function OrderDetailView({ orderId, provider, onBack }) {
1014
1455
  useEffect(() => {
1015
1456
  void loadReceivable();
1016
1457
  }, [loadReceivable]);
1017
- if (loading) return /* @__PURE__ */ jsx(OrderDetailSkeleton, {});
1458
+ useEffect(() => {
1459
+ let cancelled = false;
1460
+ const shop = providerRef.current();
1461
+ const find = shop.findOpenOrderCharge;
1462
+ if (!find || !order || order.financialStatus !== "pending") return;
1463
+ void find.call(shop, orderId).then((standing) => {
1464
+ if (!cancelled && standing) setCharge(fromSession(standing));
1465
+ }).catch(() => {
1466
+ });
1467
+ return () => {
1468
+ cancelled = true;
1469
+ };
1470
+ }, [orderId, order?.financialStatus]);
1471
+ if (loading) return /* @__PURE__ */ jsx(OrderDetailSkeleton, { parentLabel: "Pedidos", onBack });
1018
1472
  if (error) {
1019
1473
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800", children: [
1020
1474
  /* @__PURE__ */ jsx("p", { className: "font-medium", children: "N\xE3o foi poss\xEDvel carregar o pedido." }),
@@ -1088,80 +1542,115 @@ function OrderDetailView({ orderId, provider, onBack }) {
1088
1542
  setStatusSaving(false);
1089
1543
  }
1090
1544
  }
1545
+ async function openCharge(asLink) {
1546
+ if (charge) return;
1547
+ setCharging(asLink ? "link" : "pix");
1548
+ setChargeError(null);
1549
+ try {
1550
+ const session = await provider().chargeExistingOrder(orderId, { asLink });
1551
+ setCharge(fromSession(session));
1552
+ } catch (err) {
1553
+ const message = errorMessage(err);
1554
+ setChargeError(message);
1555
+ toast.error("N\xE3o consegui abrir a cobran\xE7a", { description: message });
1556
+ } finally {
1557
+ setCharging(null);
1558
+ }
1559
+ }
1560
+ const missingOnCustomer = customer ? [
1561
+ !customer.document && { key: "cpf", label: "+ Adicionar CPF" },
1562
+ !customer.email && { key: "email", label: "+ Adicionar e-mail" },
1563
+ !customer.phone && { key: "phone", label: "+ Adicionar celular" }
1564
+ ].filter(Boolean) : [];
1091
1565
  const destructive = pendingStatus === "cancelled";
1092
- const statusActions = pendingStatus ? /* @__PURE__ */ jsxs("div", { className: `w-full space-y-2 rounded-md border px-3 py-2 ${destructive ? "border-red-300 bg-red-50" : "border-amber-300 bg-amber-50"}`, children: [
1093
- /* @__PURE__ */ jsx("p", { className: `text-xs ${destructive ? "text-red-900" : "text-amber-900"}`, children: LIFECYCLE_CONFIRM[pendingStatus].warning }),
1094
- destructive && order.financialStatus === "paid" && /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-red-900", children: "Este pedido est\xE1 pago. Cancelar N\xC3O devolve o dinheiro \u2014 o estorno \xE9 feito na m\xE3o, no Financeiro ou no seu meio de pagamento." }),
1095
- destructive && /* @__PURE__ */ jsx(
1096
- "input",
1097
- {
1098
- value: cancelReason,
1099
- onChange: (event) => setCancelReason(event.target.value),
1100
- placeholder: "Motivo (opcional) \u2014 fica no hist\xF3rico do pedido",
1101
- "data-testid": "order-cancel-reason",
1102
- className: "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary"
1103
- }
1104
- ),
1105
- statusError && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-white px-2.5 py-1.5 text-xs text-red-800", children: statusError }),
1106
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1566
+ function closeStatus() {
1567
+ setPendingStatus(null);
1568
+ setStatusError(null);
1569
+ setCancelReason("");
1570
+ }
1571
+ const statusModal = /* @__PURE__ */ jsx(Modal, { open: pendingStatus !== null, onOpenChange: (isOpen) => {
1572
+ if (!isOpen && !statusSaving) closeStatus();
1573
+ }, children: /* @__PURE__ */ jsx(ModalContent, { size: "sm", children: pendingStatus && /* @__PURE__ */ jsxs(Fragment, { children: [
1574
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
1575
+ /* @__PURE__ */ jsx(ModalTitle, { children: LIFECYCLE_CONFIRM[pendingStatus].cta }),
1576
+ /* @__PURE__ */ jsx(ModalDescription, { children: LIFECYCLE_CONFIRM[pendingStatus].warning })
1577
+ ] }),
1578
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1579
+ destructive && order.financialStatus === "paid" && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-red-50 px-2.5 py-1.5 text-xs font-medium text-red-900", children: "Este pedido est\xE1 pago. Cancelar N\xC3O devolve o dinheiro \u2014 o estorno \xE9 feito na m\xE3o, no Financeiro ou no seu meio de pagamento." }),
1580
+ destructive && /* @__PURE__ */ jsx(
1581
+ "input",
1582
+ {
1583
+ value: cancelReason,
1584
+ onChange: (event) => setCancelReason(event.target.value),
1585
+ placeholder: "Motivo (opcional) \u2014 fica no hist\xF3rico do pedido",
1586
+ "data-testid": "order-cancel-reason",
1587
+ className: "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary"
1588
+ }
1589
+ ),
1590
+ statusError && /* @__PURE__ */ jsx("p", { role: "alert", className: "rounded-md border border-red-200 bg-red-50 px-2.5 py-1.5 text-xs text-red-800", children: statusError })
1591
+ ] }),
1592
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
1593
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: closeStatus, disabled: statusSaving, children: "Voltar" }),
1107
1594
  /* @__PURE__ */ jsx(
1108
- "button",
1595
+ Button,
1109
1596
  {
1110
- type: "button",
1597
+ variant: destructive ? "destructive" : "default",
1111
1598
  onClick: () => applyStatus(pendingStatus),
1112
1599
  disabled: statusSaving,
1113
1600
  "data-testid": "order-status-confirm",
1114
- className: `rounded-md px-3 py-1.5 text-sm font-medium text-white transition disabled:opacity-60 ${destructive ? "bg-red-600 hover:bg-red-700" : "bg-primary hover:opacity-90"}`,
1115
1601
  children: statusSaving ? "Salvando\u2026" : LIFECYCLE_CONFIRM[pendingStatus].cta
1116
1602
  }
1117
- ),
1118
- /* @__PURE__ */ jsx(
1119
- "button",
1120
- {
1121
- type: "button",
1122
- onClick: () => {
1123
- setPendingStatus(null);
1124
- setStatusError(null);
1125
- setCancelReason("");
1126
- },
1127
- className: "rounded-md border bg-background px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1128
- children: "Voltar"
1129
- }
1130
1603
  )
1131
1604
  ] })
1132
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1133
- order.status !== "open" && /* @__PURE__ */ jsx(
1134
- "button",
1135
- {
1136
- type: "button",
1137
- onClick: () => setPendingStatus("open"),
1138
- "data-testid": "order-reopen",
1139
- className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1140
- children: "Reabrir pedido"
1141
- }
1142
- ),
1143
- order.status === "open" && /* @__PURE__ */ jsx(
1144
- "button",
1145
- {
1146
- type: "button",
1147
- onClick: () => setPendingStatus("archived"),
1148
- "data-testid": "order-archive",
1149
- className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1150
- children: "Arquivar"
1151
- }
1152
- ),
1153
- order.status !== "cancelled" && /* @__PURE__ */ jsx(
1154
- "button",
1605
+ ] }) }) });
1606
+ const statusActions = (
1607
+ // Three hand-written <button>s of equal weight, with the destructive one
1608
+ // last and loudest, was the shape this page had. Declared instead: editing
1609
+ // is what you came for, archiving and cancelling live behind the caret, and
1610
+ // the destructive tone comes from the token rather than `border-red-200`.
1611
+ /* @__PURE__ */ jsx(
1612
+ PageActions,
1155
1613
  {
1156
- type: "button",
1157
- onClick: () => setPendingStatus("cancelled"),
1158
- "data-testid": "order-cancel",
1159
- className: "rounded-md border border-red-200 px-3 py-1.5 text-sm font-medium text-red-700 transition hover:bg-red-50",
1160
- children: "Cancelar pedido"
1614
+ actions: [
1615
+ ...order.status !== "open" ? [{ id: "reopen", testId: "order-reopen", label: "Reabrir pedido", icon: RotateCcw, intent: "primary", onSelect: () => setPendingStatus("open") }] : [],
1616
+ // UMA porta. Pix, link e "já recebi" eram três entradas de menu dizendo
1617
+ // quase a mesma coisa — pôr o pedido em dia —, e a escolha entre elas é
1618
+ // do momento, não do menu. Ela mora dentro do modal, que abre no clique
1619
+ // e mostra o esqueleto enquanto o adquirente responde.
1620
+ ...order.status === "open" && order.financialStatus === "pending" && typeof provider().chargeExistingOrder === "function" ? [{
1621
+ id: "charge",
1622
+ testId: "order-charge",
1623
+ label: charge ? "Ver cobran\xE7a" : "Cobrar",
1624
+ icon: QrCode,
1625
+ intent: "primary",
1626
+ onSelect: () => {
1627
+ setChargeError(null);
1628
+ setChargeOpen(true);
1629
+ }
1630
+ }] : [],
1631
+ // Nothing paid, nothing shipped, and the STORE typed it — still a draft,
1632
+ // so items, customer and address can change without cancelling the sale.
1633
+ // An order from the storefront is out: that cart belongs to the buyer.
1634
+ ...order.status === "open" && order.financialStatus === "pending" && order.fulfillmentStatus === "unfulfilled" && order.origin !== "storefront" && typeof provider().reviseOrder === "function" ? [{
1635
+ id: "edit",
1636
+ testId: "order-edit",
1637
+ label: "Editar pedido",
1638
+ icon: Pencil,
1639
+ // Secundária enquanto há o que cobrar: a casca pinta UMA primária,
1640
+ // e num pedido em aberto ela é a que põe dinheiro na loja.
1641
+ intent: typeof provider().chargeExistingOrder === "function" ? "secondary" : "primary",
1642
+ onSelect: () => adminNavigateTo(`/orders/${order.id}/edit`)
1643
+ }] : [],
1644
+ // Cobrar um pedido que já existe (0073). Salvar virou a ação principal
1645
+ // da venda, e até aqui a única saída depois era "registrar recebimento"
1646
+ // — que é dizer que o dinheiro entrou, não pedi-lo. Pix e link são a
1647
+ // MESMA cobrança: muda por onde ela chega ao cliente.
1648
+ ...order.status === "open" ? [{ id: "archive", testId: "order-archive", label: "Arquivar", icon: Archive, intent: "secondary", onSelect: () => setPendingStatus("archived") }] : [],
1649
+ ...order.status !== "cancelled" ? [{ id: "cancel", testId: "order-cancel", label: "Cancelar pedido", icon: Ban, intent: "danger", onSelect: () => setPendingStatus("cancelled") }] : []
1650
+ ]
1161
1651
  }
1162
- ),
1163
- statusError && /* @__PURE__ */ jsx("p", { className: "w-full rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-800", children: statusError })
1164
- ] });
1652
+ )
1653
+ );
1165
1654
  const installments = receivable && receivable.bills.length > 0 ? receivable.bills.map((b) => ({
1166
1655
  number: b.installment_number ?? 0,
1167
1656
  amount: `${money2(Number(b.paid_amount ?? 0), cur)} / ${money2(Number(b.amount), cur)}`,
@@ -1170,8 +1659,20 @@ function OrderDetailView({ orderId, provider, onBack }) {
1170
1659
  dueDate: `vence ${dateOnly(b.due_date)}`
1171
1660
  })) : [];
1172
1661
  const data = {
1662
+ // No topo, onde o layout compartilhado já reserva lugar para uma recusa.
1663
+ errorBanner: chargeError && !chargeOpen ? /* @__PURE__ */ jsx(
1664
+ "div",
1665
+ {
1666
+ role: "alert",
1667
+ "data-testid": "order-charge-error",
1668
+ className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800",
1669
+ children: chargeError
1670
+ }
1671
+ ) : void 0,
1173
1672
  title: `Pedido #${order.orderNumber}`,
1174
- subtitle: dateTime2(order.createdAt),
1673
+ // A data saiu do topo para o rodapé de procedência, que é onde toda ficha
1674
+ // do app guarda "criado em / atualizado em".
1675
+ meta: order,
1175
1676
  badges: [
1176
1677
  { label: ORDER_STATUS_LABEL[order.financialStatus] ?? order.financialStatus, tone: toneOf(order.financialStatus) },
1177
1678
  { label: ORDER_STATUS_LABEL[order.fulfillmentStatus] ?? order.fulfillmentStatus, tone: toneOf(order.fulfillmentStatus) }
@@ -1189,7 +1690,20 @@ function OrderDetailView({ orderId, provider, onBack }) {
1189
1690
  className: "font-medium"
1190
1691
  }
1191
1692
  ) : /* @__PURE__ */ jsx("p", { className: "font-medium", children: order.customerName ?? "\u2014" }),
1192
- order.customerEmail && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: order.customerEmail }),
1693
+ (customer?.email ?? order.customerEmail) && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: customer?.email ?? order.customerEmail }),
1694
+ customer?.phone && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: formatPhone(customer.phone) }),
1695
+ customer?.document && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: formatDocument(customer.document) }),
1696
+ order.customerId && missingOnCustomer.length > 0 && /* @__PURE__ */ jsx("p", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 pt-1", children: missingOnCustomer.map((field) => /* @__PURE__ */ jsx(
1697
+ "button",
1698
+ {
1699
+ type: "button",
1700
+ onClick: () => setEditingCustomer(true),
1701
+ "data-testid": `order-customer-add-${field.key}`,
1702
+ className: "text-xs font-medium text-muted-foreground underline decoration-dashed decoration-from-font underline-offset-4 transition hover:text-foreground",
1703
+ children: field.label
1704
+ },
1705
+ field.key
1706
+ )) }),
1193
1707
  !order.customerId && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Compra sem cadastro \u2014 n\xE3o h\xE1 ficha de cliente para abrir." })
1194
1708
  ] }),
1195
1709
  items: order.items.map((it) => ({
@@ -1221,17 +1735,7 @@ function OrderDetailView({ orderId, provider, onBack }) {
1221
1735
  ],
1222
1736
  installments,
1223
1737
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [
1224
- receivable.balance.balance > 0 && receivable.balance.status !== "cancelled" && /* @__PURE__ */ jsx(
1225
- "button",
1226
- {
1227
- type: "button",
1228
- onClick: registerReceipt,
1229
- disabled: confirming,
1230
- "data-testid": "order-register-receipt",
1231
- className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
1232
- children: confirming ? "Registrando\u2026" : "Registrar recebimento"
1233
- }
1234
- ),
1738
+ receivable.balance.balance > 0 && receivable.balance.status !== "cancelled" && /* @__PURE__ */ jsx(Button, { onClick: registerReceipt, disabled: confirming, "data-testid": "order-register-receipt", children: confirming ? "Registrando\u2026" : "Registrar recebimento" }),
1235
1739
  hasFinancial && /* @__PURE__ */ jsx(
1236
1740
  "button",
1237
1741
  {
@@ -1299,156 +1803,220 @@ function OrderDetailView({ orderId, provider, onBack }) {
1299
1803
  ] })
1300
1804
  ] }) }),
1301
1805
  /* @__PURE__ */ jsx(OrderProviderLinks, { orderId: order.id }),
1302
- /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Hist\xF3rico", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-sm", children: [
1303
- /* @__PURE__ */ jsx(OrderDetailRow, { label: "Criado", value: dateTime2(order.createdAt) }),
1304
- /* @__PURE__ */ jsx(OrderDetailRow, { label: "Atualizado", value: dateTime2(order.updatedAt) }),
1305
- /* @__PURE__ */ jsx(OrderDetailRow, { label: "Situa\xE7\xE3o", value: /* @__PURE__ */ jsx(InlineBadge, { value: order.status }) })
1306
- ] }) }),
1806
+ /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Situa\xE7\xE3o", children: /* @__PURE__ */ jsx(InlineBadge, { value: order.status }) }),
1307
1807
  order.notes && /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Observa\xE7\xF5es", children: /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: order.notes }) })
1308
1808
  ] }),
1309
1809
  onBack,
1310
1810
  backLabel: "Pedidos"
1311
1811
  };
1312
- return /* @__PURE__ */ jsx(OrderDetailView$1, { ...data });
1812
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1813
+ /* @__PURE__ */ jsx(OrderDetailView$1, { ...data }),
1814
+ statusModal,
1815
+ order.customerId && /* @__PURE__ */ jsx(
1816
+ CustomerModal,
1817
+ {
1818
+ open: editingCustomer,
1819
+ customerId: order.customerId,
1820
+ initial: {
1821
+ name: [customer?.firstName, customer?.lastName].filter(Boolean).join(" ") || (order.customerName ?? ""),
1822
+ email: customer?.email ?? order.customerEmail ?? "",
1823
+ phone: customer?.phone ?? "",
1824
+ document: customer?.document ?? ""
1825
+ },
1826
+ provider,
1827
+ onClose: () => setEditingCustomer(false),
1828
+ onSaved: (saved) => {
1829
+ setCustomer(saved);
1830
+ setEditingCustomer(false);
1831
+ toast.success("Cadastro atualizado.");
1832
+ }
1833
+ }
1834
+ ),
1835
+ chargeOpen && /* @__PURE__ */ jsx(
1836
+ NewOrderCharge,
1837
+ {
1838
+ open: chargeOpen,
1839
+ provider,
1840
+ sessionId: charge?.sessionId ?? null,
1841
+ emv: charge?.emv ?? null,
1842
+ qrcodeBase64: charge?.qrcode ?? null,
1843
+ qrcodeUrl: charge?.qrcodeUrl ?? null,
1844
+ paymentUrl: charge?.paymentUrl ?? null,
1845
+ acquirer: charge?.provider ?? null,
1846
+ opening: charging !== null,
1847
+ error: chargeError,
1848
+ onChoose: (asLink) => {
1849
+ void openCharge(asLink);
1850
+ },
1851
+ onRegisterReceipt: () => {
1852
+ setChargeOpen(false);
1853
+ void registerReceipt();
1854
+ },
1855
+ total: charge?.total ?? order.total,
1856
+ currency: cur,
1857
+ customerName: order.customerName ?? "",
1858
+ customerPhone: (order.shippingAddress?.phone ?? "").replace(/\D/gu, ""),
1859
+ onPaid: () => {
1860
+ setChargeOpen(false);
1861
+ shopOrderCache.delete(orderId);
1862
+ void provider().getOrder(orderId).then((o) => {
1863
+ if (o) {
1864
+ shopOrderCache.set(orderId, o);
1865
+ setOrder(o);
1866
+ }
1867
+ });
1868
+ void loadReceivable(true);
1869
+ },
1870
+ onClose: () => setChargeOpen(false)
1871
+ }
1872
+ )
1873
+ ] });
1313
1874
  }
1314
- var POLL_MS = 4e3;
1315
- function NewOrderCharge({
1875
+ function today() {
1876
+ const d = /* @__PURE__ */ new Date();
1877
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1878
+ }
1879
+ function occurredAtFor(date) {
1880
+ if (!date || date === today()) return void 0;
1881
+ const [y, m, d] = date.split("-").map(Number);
1882
+ if (!y || !m || !d) return void 0;
1883
+ return new Date(y, m - 1, d, 12, 0, 0).toISOString();
1884
+ }
1885
+ function StockEntryModal({
1316
1886
  open,
1887
+ onClose,
1317
1888
  provider,
1318
- sessionId,
1319
- emv,
1320
- qrcodeBase64,
1321
- total,
1322
- currency: currency2,
1323
- customerName,
1324
- customerPhone,
1325
- onPaid,
1326
- onClose
1889
+ variantId,
1890
+ productId,
1891
+ productName,
1892
+ sku,
1893
+ available,
1894
+ needed,
1895
+ onReceived
1327
1896
  }) {
1328
- const [state, setState] = useState("waiting");
1329
- const [copied, setCopied] = useState(false);
1330
- const [checking, setChecking] = useState(false);
1331
- const settled = useRef(false);
1332
- const money4 = new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(total);
1333
- const check = useCallback(async () => {
1334
- if (settled.current) return;
1335
- setChecking(true);
1336
- try {
1337
- const result = await provider().settleCheckoutSession?.(sessionId);
1338
- if (!result) return;
1339
- if (result.state === "paid") {
1340
- settled.current = true;
1341
- setState("paid");
1342
- setTimeout(() => onPaid(result.orderId ?? void 0), 1200);
1343
- return;
1344
- }
1345
- const outcome = chargeOutcome(result.state);
1346
- if (outcome !== "waiting") {
1347
- settled.current = true;
1348
- setState(outcome);
1349
- }
1350
- } finally {
1351
- setChecking(false);
1352
- }
1353
- }, [provider, sessionId, onPaid]);
1897
+ const reasons = useMemo(() => listStockReasonCodes("receipt"), []);
1898
+ const shortfall = Math.max(1, (needed ?? 1) - Math.max(0, available ?? 0));
1899
+ const [quantity, setQuantity] = useState(shortfall);
1900
+ const [reasonCode, setReasonCode] = useState("purchase");
1901
+ const [note, setNote] = useState("");
1902
+ const [arrivedOn, setArrivedOn] = useState(today());
1903
+ const [saving, setSaving] = useState(false);
1904
+ const [error, setError] = useState(null);
1354
1905
  useEffect(() => {
1355
1906
  if (!open) return;
1356
- const timer = setInterval(() => {
1357
- void check();
1358
- }, POLL_MS);
1359
- return () => clearInterval(timer);
1360
- }, [open, check]);
1361
- async function copy() {
1907
+ setQuantity(shortfall);
1908
+ setReasonCode("purchase");
1909
+ setNote("");
1910
+ setArrivedOn(today());
1911
+ setError(null);
1912
+ }, [open, shortfall]);
1913
+ const receive = provider().receiveProductStock;
1914
+ const newBalance = available == null ? null : Math.max(0, available) + quantity;
1915
+ async function submit() {
1916
+ if (quantity <= 0) {
1917
+ setError("Informe quantas unidades entraram.");
1918
+ return;
1919
+ }
1920
+ setSaving(true);
1921
+ setError(null);
1362
1922
  try {
1363
- await navigator.clipboard.writeText(emv);
1364
- setCopied(true);
1365
- setTimeout(() => setCopied(false), 2400);
1366
- } catch {
1923
+ const now = await provider().receiveProductStock(productId, quantity, {
1924
+ variantId,
1925
+ reasonCode,
1926
+ note: note.trim() || void 0,
1927
+ occurredAt: occurredAtFor(arrivedOn)
1928
+ });
1929
+ onReceived(now);
1930
+ } catch (err) {
1931
+ setError(errorMessage(err));
1932
+ } finally {
1933
+ setSaving(false);
1367
1934
  }
1368
1935
  }
1369
- const whatsapp = customerPhone ? `https://wa.me/${customerPhone.length > 11 ? customerPhone : `55${customerPhone}`}?text=${encodeURIComponent(
1370
- `Ol\xE1${customerName ? ` ${customerName.split(" ")[0]}` : ""}! Segue o Pix de ${money4} para o seu pedido:
1371
-
1372
- ${emv}`
1373
- )}` : null;
1936
+ const inputClass = "mt-1 w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary";
1374
1937
  return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
1375
1938
  if (!next) onClose();
1376
- }, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", "data-testid": "new-order-charge-modal", children: [
1939
+ }, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", "data-testid": "stock-entry-modal", children: [
1377
1940
  /* @__PURE__ */ jsxs(ModalHeader, { className: "mb-4", children: [
1378
- /* @__PURE__ */ jsxs(ModalTitle, { children: [
1379
- "Pix de ",
1380
- money4
1941
+ /* @__PURE__ */ jsx(ModalTitle, { children: "Dar entrada no estoque" }),
1942
+ /* @__PURE__ */ jsx(ModalDescription, { children: "Entra no hist\xF3rico como recebimento \u2014 o pedido segue logo depois." })
1943
+ ] }),
1944
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
1945
+ /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2 text-sm", children: [
1946
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: productName }),
1947
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1948
+ sku ? `${sku} \xB7 ` : "",
1949
+ available == null ? "Estoque desconhecido" : `${available} em estoque`,
1950
+ needed != null ? ` \xB7 o pedido precisa de ${needed}` : ""
1951
+ ] })
1952
+ ] }),
1953
+ !receive && /* @__PURE__ */ jsx("p", { role: "alert", className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900", children: "Esta loja n\xE3o registra entradas por aqui. Lance a entrada pela tela de estoque." }),
1954
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1955
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Quantidade recebida" }),
1956
+ /* @__PURE__ */ jsx(
1957
+ "input",
1958
+ {
1959
+ type: "number",
1960
+ min: 1,
1961
+ step: 1,
1962
+ autoFocus: true,
1963
+ value: quantity,
1964
+ onChange: (event) => setQuantity(Math.max(0, Math.trunc(Number(event.target.value) || 0))),
1965
+ "data-testid": "stock-entry-quantity",
1966
+ className: inputClass
1967
+ }
1968
+ ),
1969
+ newBalance != null && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-[11px] text-muted-foreground", children: [
1970
+ "Saldo depois da entrada: ",
1971
+ newBalance
1972
+ ] })
1973
+ ] }),
1974
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1975
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Data de entrada" }),
1976
+ /* @__PURE__ */ jsx(
1977
+ "input",
1978
+ {
1979
+ type: "date",
1980
+ value: arrivedOn,
1981
+ max: today(),
1982
+ onChange: (event) => setArrivedOn(event.target.value || today()),
1983
+ "data-testid": "stock-entry-date",
1984
+ className: inputClass
1985
+ }
1986
+ ),
1987
+ /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-[11px] text-muted-foreground", children: "Quando a mercadoria chegou. O lan\xE7amento fica com a data de hoje." })
1988
+ ] }),
1989
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1990
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Motivo" }),
1991
+ /* @__PURE__ */ jsx(
1992
+ "select",
1993
+ {
1994
+ value: reasonCode,
1995
+ onChange: (event) => setReasonCode(event.target.value),
1996
+ "data-testid": "stock-entry-reason",
1997
+ className: inputClass,
1998
+ children: reasons.map((reason) => /* @__PURE__ */ jsx("option", { value: reason.key, children: reason.label }, reason.key))
1999
+ }
2000
+ )
1381
2001
  ] }),
1382
- state === "waiting" && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Vale por 15 minutos. O pedido \xE9 criado quando o pagamento cair." })
1383
- ] }),
1384
- /* @__PURE__ */ jsxs(ModalBody, { children: [
1385
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4 sm:flex-row sm:items-start", children: [
1386
- qrcodeBase64 && /* @__PURE__ */ jsx(
1387
- "img",
2002
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
2003
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
2004
+ /* @__PURE__ */ jsx(
2005
+ "input",
1388
2006
  {
1389
- src: `data:image/png;base64,${qrcodeBase64}`,
1390
- alt: "QR code do Pix",
1391
- "data-testid": "new-order-qr",
1392
- className: "h-40 w-40 shrink-0 rounded-md border bg-white p-2"
2007
+ value: note,
2008
+ onChange: (event) => setNote(event.target.value),
2009
+ placeholder: "Nota fiscal, fornecedor \u2014 fica no hist\xF3rico",
2010
+ "data-testid": "stock-entry-note",
2011
+ className: inputClass
1393
2012
  }
1394
- ),
1395
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
1396
- /* @__PURE__ */ jsxs("div", { children: [
1397
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Pix copia e cola" }),
1398
- /* @__PURE__ */ jsx(
1399
- "p",
1400
- {
1401
- "data-testid": "new-order-emv",
1402
- className: "mt-1 max-h-20 overflow-auto break-all rounded-md border bg-muted/30 p-2 font-mono text-[11px]",
1403
- children: emv
1404
- }
1405
- )
1406
- ] }),
1407
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
1408
- /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: copy, "data-testid": "new-order-copy", children: [
1409
- copied ? /* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "mr-1.5 h-3.5 w-3.5" }),
1410
- copied ? "Copiado" : "Copiar c\xF3digo"
1411
- ] }),
1412
- whatsapp && /* @__PURE__ */ jsxs(
1413
- "a",
1414
- {
1415
- href: whatsapp,
1416
- target: "_blank",
1417
- rel: "noreferrer",
1418
- "data-testid": "new-order-whatsapp",
1419
- className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1420
- children: [
1421
- /* @__PURE__ */ jsx(MessageCircle, { className: "mr-1.5 h-3.5 w-3.5" }),
1422
- "Mandar no WhatsApp"
1423
- ]
1424
- }
1425
- )
1426
- ] })
1427
- ] })
2013
+ )
1428
2014
  ] }),
1429
- /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-md border bg-muted/30 px-3 py-2 text-sm", children: [
1430
- state === "waiting" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-muted-foreground", "data-testid": "new-order-waiting", children: [
1431
- /* @__PURE__ */ jsx("span", { className: "h-2 w-2 animate-pulse rounded-full bg-amber-500" }),
1432
- "Aguardando o pagamento\u2026 a tela confirma sozinha."
1433
- ] }),
1434
- state === "paid" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 font-medium text-emerald-700", "data-testid": "new-order-paid", children: [
1435
- /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
1436
- " Pagamento confirmado \u2014 abrindo o pedido\u2026"
1437
- ] }),
1438
- (state === "expired" || state === "failed") && /* @__PURE__ */ jsx("p", { className: "text-red-700", "data-testid": "new-order-dead", children: state === "expired" ? "O c\xF3digo expirou sem pagamento. O pedido continua aqui \u2014 d\xE1 para cobrar de outro jeito." : "O pagamento n\xE3o foi conclu\xEDdo. O pedido continua aqui \u2014 d\xE1 para cobrar de outro jeito." })
1439
- ] })
2015
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error })
1440
2016
  ] }),
1441
2017
  /* @__PURE__ */ jsxs(ModalFooter, { className: "mt-4 gap-2", children: [
1442
- state === "waiting" && /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void check(), disabled: checking, "data-testid": "new-order-check", children: checking ? "Conferindo\u2026" : "J\xE1 pagou? Conferir" }),
1443
- /* @__PURE__ */ jsx(
1444
- Button,
1445
- {
1446
- variant: state === "waiting" ? "ghost" : "default",
1447
- onClick: onClose,
1448
- "data-testid": "new-order-charge-close",
1449
- children: state === "waiting" ? "Fechar" : "Voltar ao pedido"
1450
- }
1451
- )
2018
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: saving, children: "Cancelar" }),
2019
+ /* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || !receive, "data-testid": "stock-entry-save", children: saving ? "Registrando\u2026" : "Registrar entrada" })
1452
2020
  ] })
1453
2021
  ] }) }) }) });
1454
2022
  }
@@ -1470,12 +2038,11 @@ var PAYMENT_METHODS = [
1470
2038
  { id: "boleto", label: "Boleto" },
1471
2039
  { id: "other", label: "Outro" }
1472
2040
  ];
1473
- var CHARGEABLE_PROVIDERS = ["abacatepay"];
1474
2041
  var money3 = (value, currency2 = "BRL") => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(Number(value) || 0);
1475
- var onlyDigits = (value) => value.replace(/\D/gu, "");
2042
+ var onlyDigits2 = (value) => value.replace(/\D/gu, "");
1476
2043
  var maskPostal = (value) => {
1477
- const digits = onlyDigits(value).slice(0, 8);
1478
- return digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
2044
+ const digits2 = onlyDigits2(value).slice(0, 8);
2045
+ return digits2.length > 5 ? `${digits2.slice(0, 5)}-${digits2.slice(5)}` : digits2;
1479
2046
  };
1480
2047
  var fieldClass = "mt-1 w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary";
1481
2048
  function Field({ label, hint, children }) {
@@ -1485,7 +2052,10 @@ function Field({ label, hint, children }) {
1485
2052
  hint && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-[11px] text-muted-foreground", children: hint })
1486
2053
  ] });
1487
2054
  }
1488
- function NewOrderPage({ provider, onDone, onCancel }) {
2055
+ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
2056
+ const editing = Boolean(orderId);
2057
+ const [loading, setLoading] = useState(Boolean(orderId));
2058
+ const [orderNumber, setOrderNumber] = useState(null);
1489
2059
  const [lines, setLines] = useState([]);
1490
2060
  const [customer, setCustomer] = useState(EMPTY_CUSTOMER);
1491
2061
  const [address, setAddress] = useState(EMPTY_ADDRESS);
@@ -1501,7 +2071,68 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1501
2071
  const [error, setError] = useState(null);
1502
2072
  const [session, setSession] = useState(null);
1503
2073
  const [chargeOpen, setChargeOpen] = useState(false);
2074
+ const [charging, setCharging] = useState(false);
2075
+ const [lastEnding, setLastEnding] = useState(null);
2076
+ const [entryOpen, setEntryOpen] = useState(false);
1504
2077
  const [editingCustomer, setEditingCustomer] = useState(false);
2078
+ useEffect(() => {
2079
+ if (!orderId) return;
2080
+ let cancelled = false;
2081
+ setLoading(true);
2082
+ void (async () => {
2083
+ try {
2084
+ const order = await provider().getOrder(orderId);
2085
+ if (cancelled || !order) return;
2086
+ setOrderNumber(order.orderNumber);
2087
+ setLines(order.items.map((item, index) => ({
2088
+ key: `${item.productId ?? item.id}:${index}`,
2089
+ productId: item.productId ?? "",
2090
+ name: item.name,
2091
+ sku: item.sku,
2092
+ imageUrl: item.imageUrl,
2093
+ unitPrice: item.unitPrice,
2094
+ quantity: item.quantity,
2095
+ // A lista de variações não é recarregada: a linha já sabe qual foi
2096
+ // vendida, e quem quiser trocar de variação tira a linha e escolhe o
2097
+ // produto de novo — que é o mesmo caminho de sempre.
2098
+ variants: [],
2099
+ variantId: item.variantId ?? null,
2100
+ optionsLabel: null,
2101
+ stock: null
2102
+ })));
2103
+ setNotes(order.notes ?? "");
2104
+ setDiscountCode(order.discountCode ?? "");
2105
+ const customerRow = order.customerId ? await provider().getCustomer(order.customerId).catch(() => null) : null;
2106
+ if (cancelled) return;
2107
+ setCustomer({
2108
+ id: order.customerId,
2109
+ name: customerRow ? `${customerRow.firstName} ${customerRow.lastName}`.trim() : order.customerName ?? "",
2110
+ email: customerRow?.email ?? order.customerEmail ?? "",
2111
+ phone: customerRow?.phone ?? "",
2112
+ document: customerRow?.document ?? ""
2113
+ });
2114
+ const addr = order.shippingAddress;
2115
+ if (addr) {
2116
+ setAddress({
2117
+ postalCode: addr.postal_code ?? "",
2118
+ street: addr.street ?? "",
2119
+ number: addr.number ?? "",
2120
+ complement: addr.complement ?? "",
2121
+ district: addr.district ?? "",
2122
+ city: addr.city ?? "",
2123
+ state: addr.state ?? ""
2124
+ });
2125
+ }
2126
+ } catch (err) {
2127
+ if (!cancelled) setError(errorMessage(err));
2128
+ } finally {
2129
+ if (!cancelled) setLoading(false);
2130
+ }
2131
+ })();
2132
+ return () => {
2133
+ cancelled = true;
2134
+ };
2135
+ }, [orderId, provider]);
1505
2136
  useEffect(() => {
1506
2137
  let cancelled = false;
1507
2138
  provider().getStoreSettings?.().then((value) => {
@@ -1514,12 +2145,13 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1514
2145
  }, [provider]);
1515
2146
  const acquirer = settings?.paymentProvider ?? null;
1516
2147
  const canCharge = Boolean(acquirer && CHARGEABLE_PROVIDERS.includes(acquirer));
2148
+ const canLink = canCharge && supportsPaymentLink(acquirer);
1517
2149
  const currency2 = settings?.currency ?? "BRL";
1518
2150
  const subtotal = useMemo(
1519
2151
  () => lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0),
1520
2152
  [lines]
1521
2153
  );
1522
- const postalDigits = onlyDigits(address.postalCode);
2154
+ const postalDigits = onlyDigits2(address.postalCode);
1523
2155
  const delivers = postalDigits.length === 8;
1524
2156
  const shipping = delivers ? freight?.price ?? 0 : 0;
1525
2157
  const quoteFreight = useCallback(async (postal) => {
@@ -1556,7 +2188,8 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1556
2188
  quantity: 1,
1557
2189
  variants: sellable.length > 1 ? sellable : [],
1558
2190
  variantId: only?.id ?? null,
1559
- optionsLabel: null
2191
+ optionsLabel: null,
2192
+ stock: only?.inventoryCount ?? product.inventoryCount ?? null
1560
2193
  }];
1561
2194
  });
1562
2195
  }
@@ -1582,8 +2215,8 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1582
2215
  state: address.state.trim().toUpperCase(),
1583
2216
  country: "BR",
1584
2217
  recipient: customer.name.trim() || void 0,
1585
- phone: onlyDigits(customer.phone) || void 0,
1586
- document: onlyDigits(customer.document) || void 0
2218
+ phone: onlyDigits2(customer.phone) || void 0,
2219
+ document: onlyDigits2(customer.document) || void 0
1587
2220
  } : void 0;
1588
2221
  function validate(ending) {
1589
2222
  return validateNewOrder({
@@ -1591,14 +2224,14 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1591
2224
  unresolvedVariants: lines.filter((line) => line.variants.length > 0 && !line.variantId).length,
1592
2225
  customerName: customer.name,
1593
2226
  customerEmail: customer.email,
1594
- customerPhone: onlyDigits(customer.phone),
2227
+ customerPhone: onlyDigits2(customer.phone),
1595
2228
  customerDocument: customer.document.trim(),
1596
2229
  documentValid: isValidDocument(customer.document),
1597
2230
  delivers,
1598
2231
  addressComplete: Boolean(address.street.trim() && address.city.trim() && address.state.trim())
1599
2232
  }, ending);
1600
2233
  }
1601
- async function resolveCustomerId() {
2234
+ async function resolveCustomerId2() {
1602
2235
  if (customer.id) return customer.id;
1603
2236
  const email = customer.email.trim().toLowerCase();
1604
2237
  const [first, ...rest] = customer.name.trim().split(/\s+/u);
@@ -1611,7 +2244,7 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1611
2244
  firstName: first ?? customer.name.trim(),
1612
2245
  lastName: rest.join(" "),
1613
2246
  email: email || void 0,
1614
- phone: onlyDigits(customer.phone) || void 0
2247
+ phone: onlyDigits2(customer.phone) || void 0
1615
2248
  });
1616
2249
  return created.id;
1617
2250
  }
@@ -1621,10 +2254,11 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1621
2254
  setError(invalid);
1622
2255
  return;
1623
2256
  }
2257
+ setLastEnding(ending);
1624
2258
  setSaving(true);
1625
2259
  setError(null);
1626
2260
  try {
1627
- if (ending === "charge") {
2261
+ if (ending === "charge" || ending === "link") {
1628
2262
  const opened = await provider().openCheckoutSession?.({
1629
2263
  items: lines.map((line) => ({
1630
2264
  productId: line.productId,
@@ -1634,20 +2268,32 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1634
2268
  })),
1635
2269
  customer: { name: customer.name.trim(), email: customer.email.trim().toLowerCase() },
1636
2270
  shippingAddress,
1637
- discountCode: discountCode.trim() || null
2271
+ discountCode: discountCode.trim() || null,
2272
+ // The one difference between the two endings, all the way down: same
2273
+ // repricing, same frozen session, same settlement by webhook. What
2274
+ // changes is whether the acquirer answers with a code to read out or
2275
+ // an address to send.
2276
+ asLink: ending === "link"
1638
2277
  });
1639
- if (!opened?.charge?.emv) throw new Error("A cobran\xE7a foi aberta sem c\xF3digo Pix. Tente de novo.");
2278
+ if (ending === "link" && !opened?.paymentUrl) {
2279
+ throw new Error("O adquirente n\xE3o devolveu o link de pagamento. Tente de novo.");
2280
+ }
2281
+ if (ending === "charge" && !opened?.charge?.emv) {
2282
+ throw new Error("A cobran\xE7a foi aberta sem c\xF3digo Pix. Tente de novo.");
2283
+ }
1640
2284
  setSession({
1641
2285
  sessionId: opened.sessionId,
1642
- emv: opened.charge.emv,
1643
- qrcode: opened.charge.qrcodeBase64,
2286
+ emv: opened.charge?.emv ?? null,
2287
+ qrcode: opened.charge?.qrcodeBase64 ?? null,
2288
+ qrcodeUrl: opened.charge?.qrcodeUrl ?? null,
2289
+ paymentUrl: opened.paymentUrl ?? null,
1644
2290
  total: opened.total
1645
2291
  });
1646
2292
  setChargeOpen(true);
1647
2293
  return;
1648
2294
  }
1649
- const customerId = await resolveCustomerId();
1650
- const order = await provider().placeOrder({
2295
+ const customerId = await resolveCustomerId2();
2296
+ const payload = {
1651
2297
  customerId,
1652
2298
  customer: { name: customer.name.trim(), email: customer.email.trim().toLowerCase() || void 0 },
1653
2299
  items: lines.map((line) => ({
@@ -1660,10 +2306,14 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1660
2306
  discountCode: discountCode.trim() || void 0,
1661
2307
  paymentMethod: ending === "received" ? method : void 0,
1662
2308
  notes: notes.trim() || void 0
1663
- });
2309
+ };
2310
+ const order = orderId ? await provider().reviseOrder(orderId, payload) : await provider().placeOrder(payload);
1664
2311
  if (ending === "received") {
1665
2312
  await provider().confirmPayment?.(order.id, reference.trim() || `manual:${method}`);
1666
2313
  }
2314
+ toast.success(
2315
+ orderId ? `Pedido #${order.orderNumber} atualizado.` : ending === "received" ? `Pedido #${order.orderNumber} salvo e marcado como pago.` : `Pedido #${order.orderNumber} salvo.`
2316
+ );
1667
2317
  onDone(order.id);
1668
2318
  } catch (err) {
1669
2319
  setError(errorMessage(err));
@@ -1671,6 +2321,35 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1671
2321
  setSaving(false);
1672
2322
  }
1673
2323
  }
2324
+ useSaveBar({
2325
+ dirty: lines.length > 0 && !loading && !session && !settling && !charging,
2326
+ saving,
2327
+ onSave: () => {
2328
+ void finish("later");
2329
+ },
2330
+ onDiscard: () => {
2331
+ if (orderId) {
2332
+ onCancel();
2333
+ return;
2334
+ }
2335
+ setLines([]);
2336
+ setCustomer(EMPTY_CUSTOMER);
2337
+ setAddress(EMPTY_ADDRESS);
2338
+ setNotes("");
2339
+ setDiscountCode("");
2340
+ setCouponOpen(false);
2341
+ setError(null);
2342
+ },
2343
+ message: orderId ? "Altera\xE7\xF5es n\xE3o salvas" : "Pedido n\xE3o salvo",
2344
+ saveLabel: orderId ? "Salvar altera\xE7\xF5es" : "Salvar pedido",
2345
+ discardLabel: orderId ? "Sair sem salvar" : "Descartar"
2346
+ });
2347
+ const blockedLine = useMemo(() => {
2348
+ const name = insufficientStockProduct(error);
2349
+ if (!name) return null;
2350
+ const wanted = name.trim().toLowerCase();
2351
+ return lines.find((line) => line.name.trim().toLowerCase() === wanted) ?? null;
2352
+ }, [error, lines]);
1674
2353
  const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
1675
2354
  /* @__PURE__ */ jsx(OrderFormBlock, { title: "Cliente", children: /* @__PURE__ */ jsx(
1676
2355
  CustomerPicker,
@@ -1707,10 +2386,10 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1707
2386
  /* @__PURE__ */ jsxs(
1708
2387
  OrderFormView,
1709
2388
  {
1710
- title: "Novo pedido",
2389
+ title: orderId ? `Editar pedido${orderNumber ? ` #${orderNumber}` : ""}` : "Novo pedido",
1711
2390
  parentLabel: "Pedidos",
1712
2391
  onBack: onCancel,
1713
- subtitle: "Venda por telefone, WhatsApp ou balc\xE3o",
2392
+ subtitle: orderId ? "Enquanto ningu\xE9m pagou, o pedido ainda \xE9 rascunho" : "Venda por telefone, WhatsApp ou balc\xE3o",
1714
2393
  sideSections,
1715
2394
  children: [
1716
2395
  /* @__PURE__ */ jsxs(OrderFormBlock, { title: "Produtos", children: [
@@ -1752,7 +2431,7 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1752
2431
  "input",
1753
2432
  {
1754
2433
  value: line.quantity,
1755
- onChange: (event) => setQuantity(line.key, Number(onlyDigits(event.target.value) || 0)),
2434
+ onChange: (event) => setQuantity(line.key, Number(onlyDigits2(event.target.value) || 0)),
1756
2435
  "aria-label": "Quantidade",
1757
2436
  "data-testid": "new-order-quantity",
1758
2437
  className: "fz-tap w-14 rounded-md border bg-background px-1 py-1.5 text-center text-sm tabular-nums outline-none focus:border-primary"
@@ -1865,47 +2544,85 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1865
2544
  customer.name
1866
2545
  ]
1867
2546
  }
2547
+ ),
2548
+ blockedLine && /* @__PURE__ */ jsxs(
2549
+ "button",
2550
+ {
2551
+ type: "button",
2552
+ onClick: () => setEntryOpen(true),
2553
+ "data-testid": "new-order-stock-entry",
2554
+ className: "mt-1 font-medium underline underline-offset-2",
2555
+ children: [
2556
+ "Dar entrada no estoque de ",
2557
+ blockedLine.name
2558
+ ]
2559
+ }
1868
2560
  )
1869
2561
  ]
1870
2562
  }
1871
2563
  ),
1872
- lines.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center gap-2", children: settling ? /* @__PURE__ */ jsxs(Fragment, { children: [
2564
+ lines.length > 0 && !editing && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center gap-2", children: settling ? /* @__PURE__ */ jsxs(Fragment, { children: [
1873
2565
  /* @__PURE__ */ jsx(Button, { onClick: () => void finish("received"), disabled: saving, "data-testid": "new-order-confirm-received", children: saving ? "Criando\u2026" : "Confirmar pagamento" }),
1874
2566
  /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setSettling(false), children: "Voltar" })
1875
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2567
+ ] }) : charging ? (
2568
+ /* Pix e link são a MESMA cobrança — muda só por onde ela chega ao
2569
+ cliente. A escolha só aparece para quem tem as duas. */
2570
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2571
+ /* @__PURE__ */ jsx(Button, { onClick: () => void finish("charge"), disabled: saving, "data-testid": "new-order-charge-pix", children: saving ? "Abrindo\u2026" : "Cobran\xE7a Pix" }),
2572
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void finish("link"), disabled: saving, "data-testid": "new-order-link", children: saving ? "Gerando\u2026" : "Link de pagamento" }),
2573
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setCharging(false), children: "Voltar" })
2574
+ ] })
2575
+ ) : session ? (
2576
+ /* Com cobrança aberta o pedido nasce do pagamento, não daqui:
2577
+ salvar agora criaria um segundo pedido quando o cliente pagar. */
2578
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2579
+ /* @__PURE__ */ jsx(Button, { onClick: () => setChargeOpen(true), "data-testid": "new-order-charge", children: "Ver cobran\xE7a" }),
2580
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "A cobran\xE7a est\xE1 aberta \u2014 o pedido entra sozinho quando ela for paga." })
2581
+ ] })
2582
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1876
2583
  canCharge && /* @__PURE__ */ jsx(
1877
2584
  Button,
1878
2585
  {
1879
- onClick: () => session ? setChargeOpen(true) : void finish("charge"),
2586
+ onClick: () => canLink ? setCharging(true) : void finish("charge"),
1880
2587
  disabled: saving,
1881
2588
  "data-testid": "new-order-charge",
1882
- children: saving ? "Abrindo\u2026" : session ? "Ver cobran\xE7a" : "Gerar cobran\xE7a Pix"
2589
+ children: saving ? "Abrindo\u2026" : canLink ? "Cobrar" : "Cobrar por Pix"
1883
2590
  }
1884
2591
  ),
1885
2592
  /* @__PURE__ */ jsx(
1886
2593
  Button,
1887
2594
  {
1888
- variant: canCharge ? "outline" : "default",
2595
+ variant: canCharge ? "ghost" : "default",
1889
2596
  onClick: () => setSettling(true),
1890
2597
  "data-testid": "new-order-received",
1891
2598
  children: "J\xE1 recebi"
1892
2599
  }
1893
- ),
1894
- /* @__PURE__ */ jsx(
1895
- Button,
1896
- {
1897
- variant: "ghost",
1898
- onClick: () => void finish("later"),
1899
- disabled: saving,
1900
- "data-testid": "new-order-later",
1901
- children: "Cobrar depois"
1902
- }
1903
2600
  )
1904
2601
  ] }) })
1905
2602
  ] })
1906
2603
  ]
1907
2604
  }
1908
2605
  ),
2606
+ blockedLine && /* @__PURE__ */ jsx(
2607
+ StockEntryModal,
2608
+ {
2609
+ open: entryOpen,
2610
+ provider,
2611
+ variantId: blockedLine.variantId,
2612
+ productId: blockedLine.productId,
2613
+ productName: blockedLine.name,
2614
+ sku: blockedLine.sku,
2615
+ available: blockedLine.stock,
2616
+ needed: blockedLine.quantity,
2617
+ onClose: () => setEntryOpen(false),
2618
+ onReceived: (now) => {
2619
+ setLines((current) => current.map((line) => line.key === blockedLine.key ? { ...line, stock: now } : line));
2620
+ setEntryOpen(false);
2621
+ setError(null);
2622
+ if (lastEnding) void finish(lastEnding);
2623
+ }
2624
+ }
2625
+ ),
1909
2626
  session && /* @__PURE__ */ jsx(
1910
2627
  NewOrderCharge,
1911
2628
  {
@@ -1914,9 +2631,12 @@ function NewOrderPage({ provider, onDone, onCancel }) {
1914
2631
  sessionId: session.sessionId,
1915
2632
  emv: session.emv,
1916
2633
  qrcodeBase64: session.qrcode,
2634
+ qrcodeUrl: session.qrcodeUrl,
2635
+ paymentUrl: session.paymentUrl,
2636
+ acquirer,
1917
2637
  total: session.total,
1918
2638
  currency: currency2,
1919
- customerPhone: onlyDigits(customer.phone),
2639
+ customerPhone: onlyDigits2(customer.phone),
1920
2640
  customerName: customer.name,
1921
2641
  onPaid: onDone,
1922
2642
  onClose: () => setChargeOpen(false)
@@ -2113,147 +2833,6 @@ function CustomerPicker({ provider, value, onChange, editing, onEditing }) {
2113
2833
  )
2114
2834
  ] });
2115
2835
  }
2116
- function CustomerModal({ open, initialName, customerId, initial, provider, onClose, onSaved }) {
2117
- const [first, setFirst] = useState("");
2118
- const [last, setLast] = useState("");
2119
- const [email, setEmail] = useState("");
2120
- const [phone, setPhone] = useState("");
2121
- const [document, setDocument] = useState("");
2122
- const [saving, setSaving] = useState(false);
2123
- const [error, setError] = useState(null);
2124
- const [duplicate, setDuplicate] = useState(null);
2125
- useEffect(() => {
2126
- if (!open) return;
2127
- const [head, ...rest] = String(initial?.name ?? initialName ?? "").trim().split(/\s+/u);
2128
- setFirst(head ?? "");
2129
- setLast(rest.join(" "));
2130
- setEmail(initial?.email ?? "");
2131
- setPhone(initial?.phone ?? "");
2132
- setDocument(initial?.document ?? "");
2133
- setError(null);
2134
- setDuplicate(null);
2135
- }, [open, initialName, initial]);
2136
- async function save() {
2137
- if (!first.trim()) {
2138
- setError("O nome \xE9 obrigat\xF3rio.");
2139
- return;
2140
- }
2141
- if (document && !isValidDocument(document)) {
2142
- setError("Esse CPF/CNPJ n\xE3o confere.");
2143
- return;
2144
- }
2145
- setSaving(true);
2146
- setError(null);
2147
- setDuplicate(null);
2148
- try {
2149
- const fields = {
2150
- firstName: first.trim(),
2151
- lastName: last.trim(),
2152
- email: email.trim().toLowerCase() || void 0,
2153
- phone: onlyDigits(phone) || void 0,
2154
- document: documentDigits(document) || void 0
2155
- };
2156
- const saved = customerId ? await provider().updateCustomer(customerId, fields) : await provider().createCustomer(fields);
2157
- onSaved(saved);
2158
- } catch (err) {
2159
- if (isDuplicateEmail(err)) {
2160
- const existing = await provider().listCustomers({ search: email.trim().toLowerCase(), limit: 5 }).catch(() => []);
2161
- const hit = existing.find((c) => (c.email ?? "").toLowerCase() === email.trim().toLowerCase());
2162
- if (hit) {
2163
- setDuplicate(hit);
2164
- return;
2165
- }
2166
- setError("J\xE1 existe um cliente com esse e-mail nesta loja. Procure por ele na busca.");
2167
- return;
2168
- }
2169
- setError(errorMessage(err));
2170
- } finally {
2171
- setSaving(false);
2172
- }
2173
- }
2174
- return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
2175
- if (!next) onClose();
2176
- }, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", children: [
2177
- /* @__PURE__ */ jsx(ModalHeader, { className: "mb-4", children: /* @__PURE__ */ jsx(ModalTitle, { children: customerId ? "Cadastro do cliente" : "Novo cliente" }) }),
2178
- /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
2179
- /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-3", children: [
2180
- /* @__PURE__ */ jsx(Field, { label: "Nome", children: /* @__PURE__ */ jsx(
2181
- "input",
2182
- {
2183
- value: first,
2184
- onChange: (e) => setFirst(e.target.value),
2185
- autoFocus: true,
2186
- "data-testid": "new-customer-first",
2187
- className: fieldClass
2188
- }
2189
- ) }),
2190
- /* @__PURE__ */ jsx(Field, { label: "Sobrenome", children: /* @__PURE__ */ jsx(
2191
- "input",
2192
- {
2193
- value: last,
2194
- onChange: (e) => setLast(e.target.value),
2195
- "data-testid": "new-customer-last",
2196
- className: fieldClass
2197
- }
2198
- ) })
2199
- ] }),
2200
- /* @__PURE__ */ jsx(Field, { label: "Telefone", children: /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(PhoneField, { value: phone, onChange: setPhone, "data-testid": "new-customer-phone" }) }) }),
2201
- /* @__PURE__ */ jsx(Field, { label: "E-mail", children: /* @__PURE__ */ jsx(
2202
- "input",
2203
- {
2204
- type: "email",
2205
- value: email,
2206
- onChange: (e) => setEmail(e.target.value),
2207
- "data-testid": "new-customer-email",
2208
- className: fieldClass
2209
- }
2210
- ) }),
2211
- /* @__PURE__ */ jsx(Field, { label: "CPF / CNPJ", hint: "Necess\xE1rio para cobran\xE7a online e para a nota fiscal", children: /* @__PURE__ */ jsx(
2212
- "input",
2213
- {
2214
- value: formatDocument(document),
2215
- onChange: (e) => setDocument(documentDigits(e.target.value)),
2216
- placeholder: "000.000.000-00",
2217
- "data-testid": "new-customer-document",
2218
- className: fieldClass
2219
- }
2220
- ) }),
2221
- duplicate && /* @__PURE__ */ jsxs(
2222
- "div",
2223
- {
2224
- role: "alert",
2225
- "data-testid": "new-customer-duplicate",
2226
- className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900",
2227
- children: [
2228
- /* @__PURE__ */ jsxs("p", { children: [
2229
- /* @__PURE__ */ jsxs("strong", { children: [
2230
- duplicate.firstName,
2231
- " ",
2232
- duplicate.lastName
2233
- ] }),
2234
- " j\xE1 \xE9 cliente com esse e-mail."
2235
- ] }),
2236
- /* @__PURE__ */ jsx(
2237
- "button",
2238
- {
2239
- type: "button",
2240
- onClick: () => onSaved(duplicate),
2241
- "data-testid": "new-customer-use-existing",
2242
- className: "mt-1 font-medium underline underline-offset-2",
2243
- children: "Usar este cliente"
2244
- }
2245
- )
2246
- ]
2247
- }
2248
- ),
2249
- error && /* @__PURE__ */ jsx("p", { role: "alert", className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error })
2250
- ] }),
2251
- /* @__PURE__ */ jsxs(ModalFooter, { className: "mt-4 gap-2", children: [
2252
- /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, children: "Cancelar" }),
2253
- /* @__PURE__ */ jsx(Button, { onClick: save, disabled: saving, "data-testid": "new-customer-save", children: saving ? "Salvando\u2026" : customerId ? "Salvar" : "Criar cliente" })
2254
- ] })
2255
- ] }) }) }) });
2256
- }
2257
2836
  function DeliveryAddress({ provider, customerId, address, onChange }) {
2258
2837
  const [saved, setSaved] = useState([]);
2259
2838
  const [chosen, setChosen] = useState(null);
@@ -2424,14 +3003,14 @@ function PostalField({ value, onChange, onResolved }) {
2424
3003
  const [looking, setLooking] = useState(false);
2425
3004
  const [missing, setMissing] = useState(false);
2426
3005
  useEffect(() => {
2427
- const digits = onlyDigits(value);
2428
- if (digits.length !== 8) {
3006
+ const digits2 = onlyDigits2(value);
3007
+ if (digits2.length !== 8) {
2429
3008
  setMissing(false);
2430
3009
  return;
2431
3010
  }
2432
3011
  let cancelled = false;
2433
3012
  setLooking(true);
2434
- fetch(`https://viacep.com.br/ws/${digits}/json/`).then((response) => response.json()).then((found) => {
3013
+ fetch(`https://viacep.com.br/ws/${digits2}/json/`).then((response) => response.json()).then((found) => {
2435
3014
  if (cancelled) return;
2436
3015
  if (found?.erro) {
2437
3016
  setMissing(true);
@@ -2464,6 +3043,496 @@ function PostalField({ value, onChange, onResolved }) {
2464
3043
  }
2465
3044
  ) });
2466
3045
  }
3046
+
3047
+ // src/views/orders-agent.ts
3048
+ var ORDER_TAKING_INSTRUCTIONS = `
3049
+ Esta tela \xE9 a lista de Pedidos da loja. O lojista recebe pedido por WhatsApp e cola a mensagem aqui.
3050
+
3051
+ Como tirar um pedido:
3052
+ 1. Leia a mensagem e separe tr\xEAs coisas: os itens com quantidade, quem \xE9 o cliente, e os recados de entrega. Item sem n\xFAmero \xE9 quantidade 1.
3053
+ 2. Chame \`findShopProducts\` UMA vez com todos os termos de item juntos. Nunca invente produto, pre\xE7o ou SKU.
3054
+ \xB7 Ela DECIDE por voc\xEA. Termo que volta com \`product\` est\xE1 resolvido: use e siga, sem confirmar, sem listar alternativa. Um vencedor folgado n\xE3o \xE9 uma d\xFAvida.
3055
+ \xB7 Termo que volta com \`needsChoice\` j\xE1 virou uma pergunta com bot\xF5es na tela do lojista. N\xC3O repita as op\xE7\xF5es no texto, n\xE3o escreva SKU, pre\xE7o nem estoque em lista: no m\xE1ximo uma linha curta, e espere ele tocar. A resposta dele volta como "Nome (id)" \u2014 o id entre par\xEAnteses \xE9 o produto escolhido.
3056
+ \xB7 Termo que volta com \`notFound\` \xE9 o \xFAnico que se pergunta por texto: diga que a loja n\xE3o tem esse item e pergunte o que usar no lugar.
3057
+ \xB7 \`outOfStock\` \xE9 aviso, n\xE3o bloqueio: diga numa frase antes de montar o pedido.
3058
+ \xB7 Produto com varia\xE7\xF5es: use a que a mensagem indicar; se ela n\xE3o indicar, pergunte.
3059
+ 3. Cliente: \`findShopCustomers\` com o nome (ou o telefone). Um candidato claro, use o \`customerId\`. V\xE1rios, pergunte. Nenhum, deixe o pedido criar o cadastro com o nome que veio \u2014 n\xE3o pe\xE7a e-mail nem CPF.
3060
+ 4. Endere\xE7o \xE9 OPCIONAL e nunca \xE9 adivinhado. S\xF3 preencha \`shippingAddress\` se a mensagem trouxer CEP ou rua/n\xFAmero/cidade. Ponto de refer\xEAncia ("Bruna recantinho"), dia ou hora ("entregar sexta") v\xE3o em \`notes\`, do jeito que o cliente escreveu.
3061
+ 5. Chame \`createShopOrder\`. O lojista confirma e corrige no cart\xE3o antes de gravar \u2014 n\xE3o pe\xE7a confirma\xE7\xE3o por texto antes, monte e chame.
3062
+ 6. Criado o pedido, mostre o link dele e ofere\xE7a em uma linha o que vem depois: gerar cobran\xE7a (Pix ou link), adicionar endere\xE7o, ou tirar o pr\xF3ximo.
3063
+
3064
+ Regras:
3065
+ \xB7 Voc\xEA n\xE3o \xE9 um cat\xE1logo. Nunca imprima lista numerada de produto com SKU, pre\xE7o e estoque \u2014 isso \xE9 despejo de banco de dados, e quem l\xEA est\xE1 no balc\xE3o com o cliente esperando.
3066
+ \xB7 Pre\xE7o, frete e total s\xE3o do servidor. N\xE3o cite total antes de o pedido existir.
3067
+ \xB7 V\xE1rias mensagens coladas de uma vez s\xE3o v\xE1rios pedidos: fa\xE7a um de cada vez.
3068
+ \xB7 Portugu\xEAs do Brasil, curto, como quem est\xE1 no balc\xE3o.
3069
+ `.trim();
3070
+ var FIND_PRODUCTS = {
3071
+ id: "shop.orders.find-products",
3072
+ name: "findShopProducts",
3073
+ description: 'Resolves the words a customer wrote ("3 costela su\xEDna", "arroz de costela") into catalog products. Send EVERY item term of the message in one call \u2014 one call resolves the whole order. Answers candidates per term with id, price and stock; it never picks for you and never invents a product. When a term has no candidate, or two candidates are equally close, ask the merchant which one it is.',
3074
+ icon: "Search",
3075
+ mode: "read",
3076
+ execution: { plane: "client" },
3077
+ category: "Shop",
3078
+ role: "operations",
3079
+ parameters: {
3080
+ type: "object",
3081
+ properties: {
3082
+ terms: {
3083
+ type: "array",
3084
+ description: "The item words as the customer wrote them, without the quantity.",
3085
+ items: { type: "string" }
3086
+ }
3087
+ },
3088
+ required: ["terms"]
3089
+ }
3090
+ };
3091
+ var FIND_CUSTOMERS = {
3092
+ id: "shop.orders.find-customers",
3093
+ name: "findShopCustomers",
3094
+ description: "Looks a customer up by name, phone or e-mail. Use the id it answers when one candidate is clearly the person; ask the merchant when there are several; place the order without an id when there is none \u2014 it creates the record.",
3095
+ icon: "Users",
3096
+ mode: "read",
3097
+ execution: { plane: "client" },
3098
+ category: "Shop",
3099
+ role: "operations",
3100
+ parameters: {
3101
+ type: "object",
3102
+ properties: { query: { type: "string", description: "Name, phone or e-mail." } },
3103
+ required: ["query"]
3104
+ }
3105
+ };
3106
+ var CREATE_ORDER = {
3107
+ id: "shop.orders.create",
3108
+ name: "createShopOrder",
3109
+ description: 'Places the order the message describes. The merchant reviews and corrects it on the confirmation card before anything is written \u2014 build it and call, do not ask for confirmation in text first. Items carry ids and quantities only: the server re-reads every price, so never send money. Leave `shippingAddress` out unless the message gave a real address; delivery notes ("entregar sexta") belong in `notes`.',
3110
+ icon: "ShoppingCart",
3111
+ mode: "persist",
3112
+ execution: { plane: "client" },
3113
+ category: "Shop",
3114
+ role: "operations",
3115
+ permission: { feature: "shop", action: "create" },
3116
+ parameters: {
3117
+ type: "object",
3118
+ properties: {
3119
+ customerName: { type: "string", description: "Who is buying, as the message names them." },
3120
+ customerId: { type: "string", description: "Id from findShopCustomers, when the person already exists." },
3121
+ customerPhone: { type: "string", description: "Only if the message carries it." },
3122
+ customerEmail: { type: "string", description: "Only if the message carries it." },
3123
+ items: {
3124
+ type: "array",
3125
+ description: "One entry per line of the order.",
3126
+ items: {
3127
+ type: "object",
3128
+ properties: {
3129
+ productId: { type: "string", description: "From findShopProducts." },
3130
+ variantId: { type: "string", description: "Required when the product has variants." },
3131
+ name: { type: "string", description: "Product name, so the merchant reads the card without ids." },
3132
+ quantity: { type: "number", description: "How many. Absent in the message means 1." }
3133
+ },
3134
+ required: ["productId", "name", "quantity"]
3135
+ }
3136
+ },
3137
+ notes: {
3138
+ type: "string",
3139
+ description: "Delivery day, reference point, anything the customer said that is not an item or an address."
3140
+ },
3141
+ shippingAddress: {
3142
+ type: "object",
3143
+ description: "Only when the message gave one. Keys: postalCode, street, number, complement, district, city, state.",
3144
+ properties: {
3145
+ postalCode: { type: "string" },
3146
+ street: { type: "string" },
3147
+ number: { type: "string" },
3148
+ complement: { type: "string" },
3149
+ district: { type: "string" },
3150
+ city: { type: "string" },
3151
+ state: { type: "string", description: "Two letters (SP, RJ)." }
3152
+ }
3153
+ }
3154
+ },
3155
+ required: ["customerName", "items"]
3156
+ }
3157
+ };
3158
+ var CHARGE_ORDER = {
3159
+ id: "shop.orders.charge",
3160
+ name: "chargeShopOrder",
3161
+ description: "Opens the charge for an order that already exists \u2014 a Pix code to read out, or a payment link to send on WhatsApp. Safe to repeat: an order that already has an open charge answers with the same one instead of a second.",
3162
+ icon: "CreditCard",
3163
+ mode: "persist",
3164
+ execution: { plane: "client" },
3165
+ category: "Shop",
3166
+ role: "operations",
3167
+ parameters: {
3168
+ type: "object",
3169
+ properties: {
3170
+ orderId: { type: "string", description: "The order to charge." },
3171
+ asLink: { type: "boolean", description: "True for a link to send, false for a Pix code to read out." }
3172
+ },
3173
+ required: ["orderId"]
3174
+ }
3175
+ };
3176
+ var SET_ADDRESS = {
3177
+ id: "shop.orders.set-address",
3178
+ name: "setShopOrderAddress",
3179
+ description: "Writes the delivery address onto an order that has none. Rewrites the order through the same trusted path that created it, so it only works while nobody has paid it.",
3180
+ icon: "MapPin",
3181
+ mode: "persist",
3182
+ execution: { plane: "client" },
3183
+ category: "Shop",
3184
+ role: "operations",
3185
+ parameters: {
3186
+ type: "object",
3187
+ properties: {
3188
+ orderId: { type: "string" },
3189
+ postalCode: { type: "string", description: "CEP." },
3190
+ street: { type: "string" },
3191
+ number: { type: "string" },
3192
+ complement: { type: "string" },
3193
+ district: { type: "string" },
3194
+ city: { type: "string" },
3195
+ state: { type: "string", description: "Two letters (SP, RJ)." },
3196
+ recipient: { type: "string", description: "Who receives, when it is not the buyer." }
3197
+ },
3198
+ required: ["orderId", "street", "city", "state"]
3199
+ }
3200
+ };
3201
+ var powersOf = (shop) => shop;
3202
+ var digits = (value) => String(value ?? "").replace(/\D/gu, "");
3203
+ var text = (value) => typeof value === "string" ? value.trim() : "";
3204
+ var orderRef = (order) => ({
3205
+ id: order.id,
3206
+ label: `Pedido #${order.orderNumber}`,
3207
+ entityKey: "shop_order",
3208
+ path: `/orders/${order.id}`
3209
+ });
3210
+ async function resolveCustomerId(shop, args) {
3211
+ if (args.customerId) return args.customerId;
3212
+ const email = (args.customerEmail ?? "").trim().toLowerCase();
3213
+ const phone = digits(args.customerPhone);
3214
+ if (email) {
3215
+ const found = await shop.listCustomers({ search: email, limit: 5 });
3216
+ const hit = found.find((c) => (c.email ?? "").toLowerCase() === email);
3217
+ if (hit) return hit.id;
3218
+ }
3219
+ if (phone.length >= 10) {
3220
+ const found = await shop.listCustomers({ search: phone, limit: 5 });
3221
+ const hit = found.find((c) => digits(c.phone).endsWith(phone.slice(-10)));
3222
+ if (hit) return hit.id;
3223
+ }
3224
+ const [first, ...rest] = args.customerName.trim().split(/\s+/u);
3225
+ const created = await shop.createCustomer({
3226
+ firstName: first ?? args.customerName.trim(),
3227
+ lastName: rest.join(" "),
3228
+ email: email || void 0,
3229
+ phone: phone || void 0
3230
+ });
3231
+ return created.id;
3232
+ }
3233
+ function readAddress(raw) {
3234
+ if (!raw || typeof raw !== "object") return void 0;
3235
+ const a = raw;
3236
+ const street = text(a.street);
3237
+ const city = text(a.city);
3238
+ const state = text(a.state);
3239
+ if (!street || !city || !state) return void 0;
3240
+ return {
3241
+ postalCode: digits(a.postalCode),
3242
+ street,
3243
+ city,
3244
+ state: state.toUpperCase().slice(0, 2),
3245
+ ...text(a.number) ? { number: text(a.number) } : {},
3246
+ ...text(a.complement) ? { complement: text(a.complement) } : {},
3247
+ ...text(a.district) ? { district: text(a.district) } : {},
3248
+ ...text(a.recipient) ? { recipient: text(a.recipient) } : {}
3249
+ };
3250
+ }
3251
+ function createOrderTakingTools({ getProvider, onChanged }) {
3252
+ const failed = (error) => ({ ok: false, error: errorMessage(error) });
3253
+ return [
3254
+ {
3255
+ tool: FIND_PRODUCTS,
3256
+ execute: async (args) => {
3257
+ const terms = (Array.isArray(args.terms) ? args.terms : []).map(text).filter(Boolean).slice(0, 20);
3258
+ if (terms.length === 0) return { ok: false, error: "no_terms" };
3259
+ try {
3260
+ const catalog = await listProductsCached(getProvider, { status: "active", limit: 500 });
3261
+ const variantsOf = /* @__PURE__ */ new Map();
3262
+ const matches = [];
3263
+ const choices = [];
3264
+ for (const [index, term] of terms.entries()) {
3265
+ const answer = resolveTerm(term, catalog);
3266
+ if (!answer.resolved) {
3267
+ if (answer.options?.length) {
3268
+ choices.push({
3269
+ id: `product-${index}`,
3270
+ question: `Qual "${term}"?`,
3271
+ options: answer.options.map((candidate) => ({
3272
+ id: candidate.productId,
3273
+ label: candidate.name,
3274
+ meta: candidateMeta(candidate)
3275
+ }))
3276
+ });
3277
+ matches.push({ term, needsChoice: true });
3278
+ } else {
3279
+ matches.push({ term, notFound: true });
3280
+ }
3281
+ continue;
3282
+ }
3283
+ const product = answer.resolved;
3284
+ if (!variantsOf.has(product.productId)) {
3285
+ let list = [];
3286
+ try {
3287
+ list = (await getProvider().getProductVariants?.(product.productId))?.variants ?? [];
3288
+ } catch {
3289
+ }
3290
+ variantsOf.set(product.productId, list);
3291
+ }
3292
+ const variants = variantsOf.get(product.productId) ?? [];
3293
+ if (variants.length > 0) {
3294
+ product.variants = variants.map((v) => ({
3295
+ variantId: v.id,
3296
+ options: v.options,
3297
+ price: v.price ?? product.price,
3298
+ stock: v.inventoryCount ?? 0
3299
+ }));
3300
+ }
3301
+ matches.push({
3302
+ term,
3303
+ product,
3304
+ // O servidor recusa o pedido por falta de estoque; dizer antes é a
3305
+ // diferença entre avisar e ser recusado na frente do cliente.
3306
+ ...product.stock <= 0 ? { outOfStock: true } : {}
3307
+ });
3308
+ }
3309
+ return {
3310
+ ok: true,
3311
+ // O marcador que o painel usa para desenhar as perguntas — só
3312
+ // aparece quando existe dúvida de verdade.
3313
+ ...choices.length > 0 ? { kind: "record_choice", choices } : {},
3314
+ matches,
3315
+ catalogSize: catalog.length
3316
+ };
3317
+ } catch (error) {
3318
+ return failed(error);
3319
+ }
3320
+ }
3321
+ },
3322
+ {
3323
+ tool: FIND_CUSTOMERS,
3324
+ execute: async (args) => {
3325
+ const query = text(args.query);
3326
+ if (!query) return { ok: false, error: "no_query" };
3327
+ try {
3328
+ const rows = await getProvider().listCustomers({ search: query, limit: 5 });
3329
+ return {
3330
+ ok: true,
3331
+ customers: rows.map((c) => {
3332
+ const name = [c.firstName, c.lastName].filter(Boolean).join(" ").trim();
3333
+ return {
3334
+ customerId: c.id,
3335
+ name,
3336
+ phone: c.phone,
3337
+ email: c.email,
3338
+ ordersCount: c.ordersCount,
3339
+ ref: { id: c.id, label: name || c.email || "Cliente", entityKey: "shop_customer" }
3340
+ };
3341
+ })
3342
+ };
3343
+ } catch (error) {
3344
+ return failed(error);
3345
+ }
3346
+ }
3347
+ },
3348
+ {
3349
+ tool: CREATE_ORDER,
3350
+ execute: async (args) => {
3351
+ const customerName = text(args.customerName);
3352
+ const rawItems = Array.isArray(args.items) ? args.items : [];
3353
+ const items = rawItems.map((raw) => {
3354
+ const item = raw ?? {};
3355
+ const quantity = Math.max(1, Math.round(Number(item.quantity) || 1));
3356
+ return {
3357
+ productId: text(item.productId),
3358
+ variantId: text(item.variantId) || void 0,
3359
+ quantity
3360
+ };
3361
+ }).filter((item) => item.productId);
3362
+ if (items.length === 0) return { ok: false, error: "no_items", message: "Nenhum item com produto do cat\xE1logo." };
3363
+ if (!customerName) return { ok: false, error: "no_customer", message: "Falta o nome de quem est\xE1 comprando." };
3364
+ const shop = getProvider();
3365
+ try {
3366
+ const customerId = await resolveCustomerId(shop, {
3367
+ customerId: text(args.customerId) || void 0,
3368
+ customerName,
3369
+ customerPhone: text(args.customerPhone),
3370
+ customerEmail: text(args.customerEmail)
3371
+ });
3372
+ const payload = {
3373
+ customerId,
3374
+ customer: { name: customerName, email: text(args.customerEmail).toLowerCase() || void 0 },
3375
+ items,
3376
+ ...readAddress(args.shippingAddress) ? { shippingAddress: readAddress(args.shippingAddress) } : {},
3377
+ ...text(args.notes) ? { notes: text(args.notes) } : {}
3378
+ };
3379
+ const order = await shop.placeOrder(payload);
3380
+ onChanged?.();
3381
+ return {
3382
+ ok: true,
3383
+ orderId: order.id,
3384
+ orderNumber: order.orderNumber,
3385
+ total: order.total,
3386
+ currency: order.currency,
3387
+ hasAddress: Boolean(order.shippingAddress),
3388
+ ref: orderRef(order),
3389
+ nextSteps: [
3390
+ "chargeShopOrder \u2014 Pix ou link para mandar no WhatsApp",
3391
+ ...order.shippingAddress ? [] : ["setShopOrderAddress \u2014 endere\xE7o de entrega"]
3392
+ ]
3393
+ };
3394
+ } catch (error) {
3395
+ return failed(error);
3396
+ }
3397
+ }
3398
+ },
3399
+ {
3400
+ tool: CHARGE_ORDER,
3401
+ execute: async (args) => {
3402
+ const orderId = text(args.orderId);
3403
+ if (!orderId) return { ok: false, error: "no_order" };
3404
+ const shop = powersOf(getProvider());
3405
+ if (typeof shop.chargeExistingOrder !== "function") {
3406
+ return { ok: false, error: "charge_unavailable", message: "Esta loja ainda n\xE3o tem um meio de cobran\xE7a conectado." };
3407
+ }
3408
+ try {
3409
+ const open = await shop.findOpenOrderCharge?.(orderId);
3410
+ const session = open ?? await shop.chargeExistingOrder(orderId, { asLink: args.asLink === true });
3411
+ onChanged?.();
3412
+ return {
3413
+ ok: true,
3414
+ reused: Boolean(open),
3415
+ total: session.total,
3416
+ paymentUrl: session.paymentUrl ?? null,
3417
+ pixCode: session.charge?.emv ?? null,
3418
+ expiresAt: session.charge?.expiresAt ?? null
3419
+ };
3420
+ } catch (error) {
3421
+ return failed(error);
3422
+ }
3423
+ }
3424
+ },
3425
+ {
3426
+ tool: SET_ADDRESS,
3427
+ execute: async (args) => {
3428
+ const orderId = text(args.orderId);
3429
+ const address = readAddress(args);
3430
+ if (!orderId) return { ok: false, error: "no_order" };
3431
+ if (!address) return { ok: false, error: "incomplete_address", message: "Endere\xE7o precisa de rua, cidade e estado." };
3432
+ const shop = getProvider();
3433
+ const revise = powersOf(shop).reviseOrder;
3434
+ if (typeof revise !== "function") {
3435
+ return { ok: false, error: "revise_unavailable", message: "Esta loja n\xE3o edita pedido por aqui." };
3436
+ }
3437
+ try {
3438
+ const order = await shop.getOrder(orderId);
3439
+ if (!order) return { ok: false, error: "not_found", message: "Pedido n\xE3o encontrado." };
3440
+ if (order.financialStatus === "paid" || order.status === "cancelled") {
3441
+ return {
3442
+ ok: false,
3443
+ error: "not_editable",
3444
+ message: "Pedido pago ou cancelado n\xE3o \xE9 reescrito \u2014 o endere\xE7o muda no envio."
3445
+ };
3446
+ }
3447
+ const revised = await revise(orderId, {
3448
+ ...order.customerId ? { customerId: order.customerId } : {},
3449
+ ...order.customerName ? { customer: { name: order.customerName, email: order.customerEmail ?? void 0 } } : {},
3450
+ items: order.items.filter((line) => line.productId).map((line) => ({
3451
+ productId: line.productId,
3452
+ // A variação só existe no pedido depois de 0029; um pedido mais
3453
+ // velho não tem o campo, e reescrever sem ele é o servidor
3454
+ // adivinhando qual tamanho foi vendido.
3455
+ variantId: line.variantId ?? void 0,
3456
+ quantity: line.quantity
3457
+ })),
3458
+ shippingAddress: address,
3459
+ ...order.notes ? { notes: order.notes } : {},
3460
+ ...order.discountCode ? { discountCode: order.discountCode } : {}
3461
+ });
3462
+ onChanged?.();
3463
+ return { ok: true, orderId: revised.id, orderNumber: revised.orderNumber, ref: orderRef(revised) };
3464
+ } catch (error) {
3465
+ return failed(error);
3466
+ }
3467
+ }
3468
+ }
3469
+ ];
3470
+ }
3471
+
3472
+ // src/views/OrdersAgentSurface.tsx
3473
+ var ORDER_TAKING_INTRO = {
3474
+ role: "operations",
3475
+ persona: "Balconista",
3476
+ pitch: "Cole a mensagem do cliente que eu monto o pedido.",
3477
+ description: "Leio os itens e as quantidades da mensagem, acho cada um no cat\xE1logo, resolvo o cliente e monto o pedido para voc\xEA conferir e confirmar. Depois gero a cobran\xE7a ou anoto o endere\xE7o.",
3478
+ suggestions: [
3479
+ { label: "O que voc\xEA precisa para montar um pedido?" },
3480
+ { label: "Quais pedidos est\xE3o esperando pagamento?" }
3481
+ ]
3482
+ };
3483
+ function OrdersAgentSurface({
3484
+ provider,
3485
+ orders,
3486
+ onChanged
3487
+ }) {
3488
+ const live = React2.useRef({ provider, orders, onChanged });
3489
+ live.current = { provider, orders, onChanged };
3490
+ const contribution = React2.useMemo(() => ({
3491
+ id: "shop.orders.list",
3492
+ pluginId: "shop",
3493
+ role: "operations",
3494
+ instructions: ORDER_TAKING_INSTRUCTIONS,
3495
+ describe: () => {
3496
+ const { orders: rows, provider: get } = live.current;
3497
+ let canCharge = false;
3498
+ let canRevise = false;
3499
+ try {
3500
+ const shop = get();
3501
+ canCharge = typeof shop.chargeExistingOrder === "function";
3502
+ canRevise = typeof shop.reviseOrder === "function";
3503
+ } catch {
3504
+ }
3505
+ return {
3506
+ title: "Lista \u2014 Pedidos",
3507
+ state: {
3508
+ screen: "/orders",
3509
+ canCharge,
3510
+ canRevise,
3511
+ ordersOnScreen: rows.length,
3512
+ // Os últimos, para "cobra o pedido da Fran" achar de qual pedido se
3513
+ // fala sem uma busca a mais.
3514
+ recentOrders: rows.slice(0, 8).map((o) => ({
3515
+ orderNumber: o.orderNumber,
3516
+ customer: o.customerName ?? o.customerEmail ?? null,
3517
+ total: o.total,
3518
+ currency: o.currency,
3519
+ financialStatus: o.financialStatus,
3520
+ fulfillmentStatus: o.fulfillmentStatus,
3521
+ hasAddress: Boolean(o.shippingAddress),
3522
+ createdAt: o.createdAt,
3523
+ ref: { id: o.id, label: `Pedido #${o.orderNumber}`, path: `/orders/${o.id}` }
3524
+ }))
3525
+ }
3526
+ };
3527
+ },
3528
+ tools: createOrderTakingTools({
3529
+ getProvider: () => live.current.provider(),
3530
+ onChanged: () => live.current.onChanged()
3531
+ })
3532
+ }), []);
3533
+ useAgentSurface(contribution);
3534
+ return null;
3535
+ }
2467
3536
  function currency(value, code = "BRL", locale = "pt-BR") {
2468
3537
  return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(value);
2469
3538
  }
@@ -2740,10 +3809,49 @@ function OrdersTab({ provider }) {
2740
3809
  }, [orders]);
2741
3810
  if (error) return /* @__PURE__ */ jsx(ErrorState, { message: error });
2742
3811
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3812
+ /* @__PURE__ */ jsx(
3813
+ OrdersAgentSurface,
3814
+ {
3815
+ provider: () => resolveShopProvider(provider),
3816
+ orders,
3817
+ onChanged: reload
3818
+ }
3819
+ ),
2743
3820
  /* @__PURE__ */ jsx(
2744
3821
  OrderListView,
2745
3822
  {
2746
- actions: /* @__PURE__ */ jsx(Button, { onClick: () => adminNavigateTo("/orders/new"), "data-testid": "orders-create", children: "+ Criar pedido" }),
3823
+ actions: /* @__PURE__ */ jsx(
3824
+ PageActions,
3825
+ {
3826
+ actions: [{
3827
+ id: "new-order",
3828
+ label: "Criar pedido",
3829
+ icon: Plus,
3830
+ intent: "primary",
3831
+ creates: true,
3832
+ testId: "orders-create",
3833
+ onSelect: () => adminNavigateTo("/orders/new")
3834
+ }, {
3835
+ // O pedido chega escrito — a outra porta para a mesma coisa, para
3836
+ // quem tem a mensagem do cliente na mão em vez de um formulário.
3837
+ //
3838
+ // `creates` porque ela CRIA um pedido, e é isso que decide a cara
3839
+ // do círculo no celular: o conjunto todo inserindo é um "+", e sem
3840
+ // esta linha a varinha rebaixava a tela inteira para o "⋮" neutro
3841
+ // — a lista de pedidos deixava de parecer uma tela em que se põe.
3842
+ id: "new-order-ai",
3843
+ label: "Criar com IA",
3844
+ description: "Cole a mensagem do cliente e eu monto o pedido.",
3845
+ icon: WandSparkles,
3846
+ creates: true,
3847
+ testId: "orders-create-ai",
3848
+ onSelect: () => askAI({
3849
+ hint: "Cole a mensagem do cliente\u2026",
3850
+ intro: ORDER_TAKING_INTRO
3851
+ })
3852
+ }]
3853
+ }
3854
+ ),
2747
3855
  stats,
2748
3856
  columns,
2749
3857
  rows: orders,
@@ -2873,13 +3981,15 @@ var ShopPage = ({ provider, section }) => {
2873
3981
  const active = section ?? tab;
2874
3982
  const path = useAdminPath();
2875
3983
  const orderId = section === "orders" ? path.match(/^\/orders\/([^/]+)$/)?.[1] : void 0;
2876
- if (section === "orders" && orderId === "new") {
3984
+ const editingId = section === "orders" ? path.match(/^\/orders\/([^/]+)\/edit$/)?.[1] : void 0;
3985
+ if (section === "orders" && (orderId === "new" || editingId)) {
2877
3986
  return /* @__PURE__ */ jsx(
2878
3987
  NewOrderPage,
2879
3988
  {
2880
3989
  provider: () => resolveShopProvider(provider),
3990
+ orderId: editingId,
2881
3991
  onDone: (id) => adminNavigateTo(id ? `/orders/${id}` : "/orders"),
2882
- onCancel: () => adminNavigateTo("/orders")
3992
+ onCancel: () => adminNavigateTo(editingId ? `/orders/${editingId}` : "/orders")
2883
3993
  }
2884
3994
  );
2885
3995
  }
@@ -2922,5 +4032,5 @@ var ShopPage = ({ provider, section }) => {
2922
4032
  ShopPage.displayName = "ShopPage";
2923
4033
 
2924
4034
  export { ShopPage };
2925
- //# sourceMappingURL=ShopPage-YB5ZWECJ.js.map
2926
- //# sourceMappingURL=ShopPage-YB5ZWECJ.js.map
4035
+ //# sourceMappingURL=ShopPage-QFIM2HDU.js.map
4036
+ //# sourceMappingURL=ShopPage-QFIM2HDU.js.map