@fayz-ai/plugin-shop 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ShopPage-VVLOH3RF.js → ShopPage-QFIM2HDU.js} +1494 -423
- package/dist/ShopPage-QFIM2HDU.js.map +1 -0
- package/dist/{chunk-HAKXHHAG.js → chunk-2VMTPRGC.js} +202 -11
- package/dist/chunk-2VMTPRGC.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1312 -298
- package/dist/index.js.map +1 -1
- package/dist/integrations/bling/data.d.ts.map +1 -1
- package/dist/integrations/mercadopago/connectorDef.d.ts +41 -0
- package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -0
- package/dist/integrations/neochef/data.d.ts.map +1 -1
- package/dist/integrations/vindi/connectorDef.d.ts +42 -29
- package/dist/integrations/vindi/connectorDef.d.ts.map +1 -1
- package/dist/lib/onboarding.d.ts +17 -0
- package/dist/lib/onboarding.d.ts.map +1 -0
- package/dist/views/CustomerModal.d.ts +33 -0
- package/dist/views/CustomerModal.d.ts.map +1 -0
- package/dist/views/NewOrderCharge.d.ts +26 -2
- package/dist/views/NewOrderCharge.d.ts.map +1 -1
- package/dist/views/NewOrderPage.d.ts +10 -1
- package/dist/views/NewOrderPage.d.ts.map +1 -1
- package/dist/views/OrderDetailView.d.ts.map +1 -1
- package/dist/views/OrdersAgentSurface.d.ts +10 -0
- package/dist/views/OrdersAgentSurface.d.ts.map +1 -0
- package/dist/views/ProductPositionsTab.d.ts +7 -0
- package/dist/views/ProductPositionsTab.d.ts.map +1 -0
- package/dist/views/ProductStockTab.d.ts +84 -0
- package/dist/views/ProductStockTab.d.ts.map +1 -0
- package/dist/views/ShopPage.d.ts.map +1 -1
- package/dist/views/StockEntryModal.d.ts +32 -0
- package/dist/views/StockEntryModal.d.ts.map +1 -0
- package/dist/views/customerEntity.d.ts.map +1 -1
- package/dist/views/discount-agent.d.ts +2 -0
- package/dist/views/discount-agent.d.ts.map +1 -0
- package/dist/views/new-order-rules.d.ts +13 -0
- package/dist/views/new-order-rules.d.ts.map +1 -1
- package/dist/views/orders-agent.d.ts +11 -0
- package/dist/views/orders-agent.d.ts.map +1 -0
- package/dist/views/product-matching.d.ts +38 -0
- package/dist/views/product-matching.d.ts.map +1 -0
- package/dist/views/productEntity.d.ts.map +1 -1
- package/dist/views/stock-agent.d.ts +82 -0
- package/dist/views/stock-agent.d.ts.map +1 -0
- package/package.json +8 -6
- package/dist/ShopPage-VVLOH3RF.js.map +0 -1
- package/dist/chunk-HAKXHHAG.js.map +0 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { errorMessage, shopCustomerLookup, listProductsDetailed, invalidateProductLists, buildStockLocationsRegistry } from './chunk-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 =
|
|
140
|
-
const chosen =
|
|
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
|
{
|
|
@@ -198,15 +200,13 @@ function validateNewOrder(draft, ending) {
|
|
|
198
200
|
}
|
|
199
201
|
return null;
|
|
200
202
|
}
|
|
201
|
-
function
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
const message = String(error?.message ?? error ?? "");
|
|
205
|
-
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;
|
|
206
206
|
}
|
|
207
207
|
function isSenderRefusal(message) {
|
|
208
|
-
const
|
|
209
|
-
return /\bfrom\./iu.test(
|
|
208
|
+
const text2 = String(message ?? "");
|
|
209
|
+
return /\bfrom\./iu.test(text2) || /remetente/iu.test(text2);
|
|
210
210
|
}
|
|
211
211
|
var money = (value) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(value) || 0);
|
|
212
212
|
var eta = (option) => {
|
|
@@ -831,6 +831,408 @@ async function cancelShopOrder(provider, orderId, reason) {
|
|
|
831
831
|
const { error } = await db.rpc("shop_cancel_order", { p_order_id: orderId, p_reason: reason ?? null });
|
|
832
832
|
if (error) throw new Error(error.message);
|
|
833
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
|
+
}
|
|
834
1236
|
var money2 = (v, currency2 = "BRL") => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(Number(v) || 0);
|
|
835
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));
|
|
836
1238
|
var dateOnly = (iso) => new Date(iso).toLocaleDateString("pt-BR");
|
|
@@ -939,6 +1341,17 @@ function deliveryFromNotes(notes) {
|
|
|
939
1341
|
return (match?.[1] ?? "").trim() || null;
|
|
940
1342
|
}
|
|
941
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
|
+
}
|
|
942
1355
|
var shopShipmentsCache = /* @__PURE__ */ new Map();
|
|
943
1356
|
var shopReceivableCache = /* @__PURE__ */ new Map();
|
|
944
1357
|
function OrderDetailView({ orderId, provider, onBack }) {
|
|
@@ -955,6 +1368,12 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
955
1368
|
const [statusError, setStatusError] = useState(null);
|
|
956
1369
|
const [cancelReason, setCancelReason] = useState("");
|
|
957
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);
|
|
958
1377
|
useEffect(() => {
|
|
959
1378
|
let cancelled = false;
|
|
960
1379
|
const cached = shopOrderCache.get(orderId);
|
|
@@ -986,6 +1405,19 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
986
1405
|
cancelled = true;
|
|
987
1406
|
};
|
|
988
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]);
|
|
989
1421
|
useEffect(() => {
|
|
990
1422
|
let cancelled = false;
|
|
991
1423
|
const db = getSupabaseClientOptional();
|
|
@@ -1023,6 +1455,19 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1023
1455
|
useEffect(() => {
|
|
1024
1456
|
void loadReceivable();
|
|
1025
1457
|
}, [loadReceivable]);
|
|
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]);
|
|
1026
1471
|
if (loading) return /* @__PURE__ */ jsx(OrderDetailSkeleton, { parentLabel: "Pedidos", onBack });
|
|
1027
1472
|
if (error) {
|
|
1028
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: [
|
|
@@ -1097,80 +1542,115 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1097
1542
|
setStatusSaving(false);
|
|
1098
1543
|
}
|
|
1099
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) : [];
|
|
1100
1565
|
const destructive = pendingStatus === "cancelled";
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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" }),
|
|
1116
1594
|
/* @__PURE__ */ jsx(
|
|
1117
|
-
|
|
1595
|
+
Button,
|
|
1118
1596
|
{
|
|
1119
|
-
|
|
1597
|
+
variant: destructive ? "destructive" : "default",
|
|
1120
1598
|
onClick: () => applyStatus(pendingStatus),
|
|
1121
1599
|
disabled: statusSaving,
|
|
1122
1600
|
"data-testid": "order-status-confirm",
|
|
1123
|
-
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"}`,
|
|
1124
1601
|
children: statusSaving ? "Salvando\u2026" : LIFECYCLE_CONFIRM[pendingStatus].cta
|
|
1125
1602
|
}
|
|
1126
|
-
),
|
|
1127
|
-
/* @__PURE__ */ jsx(
|
|
1128
|
-
"button",
|
|
1129
|
-
{
|
|
1130
|
-
type: "button",
|
|
1131
|
-
onClick: () => {
|
|
1132
|
-
setPendingStatus(null);
|
|
1133
|
-
setStatusError(null);
|
|
1134
|
-
setCancelReason("");
|
|
1135
|
-
},
|
|
1136
|
-
className: "rounded-md border bg-background px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
|
|
1137
|
-
children: "Voltar"
|
|
1138
|
-
}
|
|
1139
1603
|
)
|
|
1140
1604
|
] })
|
|
1141
|
-
] })
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
children: "Reabrir pedido"
|
|
1150
|
-
}
|
|
1151
|
-
),
|
|
1152
|
-
order.status === "open" && /* @__PURE__ */ jsx(
|
|
1153
|
-
"button",
|
|
1154
|
-
{
|
|
1155
|
-
type: "button",
|
|
1156
|
-
onClick: () => setPendingStatus("archived"),
|
|
1157
|
-
"data-testid": "order-archive",
|
|
1158
|
-
className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
|
|
1159
|
-
children: "Arquivar"
|
|
1160
|
-
}
|
|
1161
|
-
),
|
|
1162
|
-
order.status !== "cancelled" && /* @__PURE__ */ jsx(
|
|
1163
|
-
"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,
|
|
1164
1613
|
{
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
+
]
|
|
1170
1651
|
}
|
|
1171
|
-
)
|
|
1172
|
-
|
|
1173
|
-
] });
|
|
1652
|
+
)
|
|
1653
|
+
);
|
|
1174
1654
|
const installments = receivable && receivable.bills.length > 0 ? receivable.bills.map((b) => ({
|
|
1175
1655
|
number: b.installment_number ?? 0,
|
|
1176
1656
|
amount: `${money2(Number(b.paid_amount ?? 0), cur)} / ${money2(Number(b.amount), cur)}`,
|
|
@@ -1179,8 +1659,20 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1179
1659
|
dueDate: `vence ${dateOnly(b.due_date)}`
|
|
1180
1660
|
})) : [];
|
|
1181
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,
|
|
1182
1672
|
title: `Pedido #${order.orderNumber}`,
|
|
1183
|
-
|
|
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,
|
|
1184
1676
|
badges: [
|
|
1185
1677
|
{ label: ORDER_STATUS_LABEL[order.financialStatus] ?? order.financialStatus, tone: toneOf(order.financialStatus) },
|
|
1186
1678
|
{ label: ORDER_STATUS_LABEL[order.fulfillmentStatus] ?? order.fulfillmentStatus, tone: toneOf(order.fulfillmentStatus) }
|
|
@@ -1198,7 +1690,20 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1198
1690
|
className: "font-medium"
|
|
1199
1691
|
}
|
|
1200
1692
|
) : /* @__PURE__ */ jsx("p", { className: "font-medium", children: order.customerName ?? "\u2014" }),
|
|
1201
|
-
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
|
+
)) }),
|
|
1202
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." })
|
|
1203
1708
|
] }),
|
|
1204
1709
|
items: order.items.map((it) => ({
|
|
@@ -1230,17 +1735,7 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1230
1735
|
],
|
|
1231
1736
|
installments,
|
|
1232
1737
|
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1233
|
-
receivable.balance.balance > 0 && receivable.balance.status !== "cancelled" && /* @__PURE__ */ jsx(
|
|
1234
|
-
"button",
|
|
1235
|
-
{
|
|
1236
|
-
type: "button",
|
|
1237
|
-
onClick: registerReceipt,
|
|
1238
|
-
disabled: confirming,
|
|
1239
|
-
"data-testid": "order-register-receipt",
|
|
1240
|
-
className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
|
|
1241
|
-
children: confirming ? "Registrando\u2026" : "Registrar recebimento"
|
|
1242
|
-
}
|
|
1243
|
-
),
|
|
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" }),
|
|
1244
1739
|
hasFinancial && /* @__PURE__ */ jsx(
|
|
1245
1740
|
"button",
|
|
1246
1741
|
{
|
|
@@ -1308,163 +1803,220 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1308
1803
|
] })
|
|
1309
1804
|
] }) }),
|
|
1310
1805
|
/* @__PURE__ */ jsx(OrderProviderLinks, { orderId: order.id }),
|
|
1311
|
-
/* @__PURE__ */ jsx(OrderDetailBlock, { title: "
|
|
1312
|
-
/* @__PURE__ */ jsx(OrderDetailRow, { label: "Criado", value: dateTime2(order.createdAt) }),
|
|
1313
|
-
/* @__PURE__ */ jsx(OrderDetailRow, { label: "Atualizado", value: dateTime2(order.updatedAt) }),
|
|
1314
|
-
/* @__PURE__ */ jsx(OrderDetailRow, { label: "Situa\xE7\xE3o", value: /* @__PURE__ */ jsx(InlineBadge, { value: order.status }) })
|
|
1315
|
-
] }) }),
|
|
1806
|
+
/* @__PURE__ */ jsx(OrderDetailBlock, { title: "Situa\xE7\xE3o", children: /* @__PURE__ */ jsx(InlineBadge, { value: order.status }) }),
|
|
1316
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 }) })
|
|
1317
1808
|
] }),
|
|
1318
1809
|
onBack,
|
|
1319
1810
|
backLabel: "Pedidos"
|
|
1320
1811
|
};
|
|
1321
|
-
return /* @__PURE__ */
|
|
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
|
+
] });
|
|
1322
1874
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
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({
|
|
1325
1886
|
open,
|
|
1887
|
+
onClose,
|
|
1326
1888
|
provider,
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
customerName,
|
|
1335
|
-
customerPhone,
|
|
1336
|
-
onPaid,
|
|
1337
|
-
onClose
|
|
1889
|
+
variantId,
|
|
1890
|
+
productId,
|
|
1891
|
+
productName,
|
|
1892
|
+
sku,
|
|
1893
|
+
available,
|
|
1894
|
+
needed,
|
|
1895
|
+
onReceived
|
|
1338
1896
|
}) {
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
1341
|
-
const [
|
|
1342
|
-
const
|
|
1343
|
-
const
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
try {
|
|
1348
|
-
const result = await provider().settleCheckoutSession?.(sessionId, acquirer);
|
|
1349
|
-
if (!result) return;
|
|
1350
|
-
if (result.state === "paid") {
|
|
1351
|
-
settled.current = true;
|
|
1352
|
-
setState("paid");
|
|
1353
|
-
setTimeout(() => onPaid(result.orderId ?? void 0), 1200);
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
const outcome = chargeOutcome(result.state);
|
|
1357
|
-
if (outcome !== "waiting") {
|
|
1358
|
-
settled.current = true;
|
|
1359
|
-
setState(outcome);
|
|
1360
|
-
}
|
|
1361
|
-
} finally {
|
|
1362
|
-
setChecking(false);
|
|
1363
|
-
}
|
|
1364
|
-
}, [provider, sessionId, acquirer, 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);
|
|
1365
1905
|
useEffect(() => {
|
|
1366
1906
|
if (!open) return;
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
setTimeout(() => setCopied(false), 2400);
|
|
1380
|
-
} catch {
|
|
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;
|
|
1381
1919
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
/* @__PURE__ */ jsx(
|
|
1414
|
-
"p",
|
|
1415
|
-
{
|
|
1416
|
-
"data-testid": isLink ? "new-order-payment-link" : "new-order-emv",
|
|
1417
|
-
className: "mt-1 max-h-20 overflow-auto break-all rounded-md border bg-muted/30 p-2 font-mono text-[11px]",
|
|
1418
|
-
children: payable
|
|
1419
|
-
}
|
|
1420
|
-
),
|
|
1421
|
-
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." })
|
|
1422
|
-
] }),
|
|
1423
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
1424
|
-
/* @__PURE__ */ jsxs(Button, { size: "sm", onClick: copy, "data-testid": "new-order-copy", children: [
|
|
1425
|
-
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" }),
|
|
1426
|
-
copied ? "Copiado" : isLink ? "Copiar link" : "Copiar c\xF3digo"
|
|
1427
|
-
] }),
|
|
1428
|
-
whatsapp && /* @__PURE__ */ jsxs(
|
|
1429
|
-
"a",
|
|
1430
|
-
{
|
|
1431
|
-
href: whatsapp,
|
|
1432
|
-
target: "_blank",
|
|
1433
|
-
rel: "noreferrer",
|
|
1434
|
-
"data-testid": "new-order-whatsapp",
|
|
1435
|
-
className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
|
|
1436
|
-
children: [
|
|
1437
|
-
/* @__PURE__ */ jsx(MessageCircle, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1438
|
-
"Mandar no WhatsApp"
|
|
1439
|
-
]
|
|
1440
|
-
}
|
|
1441
|
-
)
|
|
1442
|
-
] })
|
|
1920
|
+
setSaving(true);
|
|
1921
|
+
setError(null);
|
|
1922
|
+
try {
|
|
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);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
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";
|
|
1937
|
+
return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
|
|
1938
|
+
if (!next) onClose();
|
|
1939
|
+
}, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", "data-testid": "stock-entry-modal", children: [
|
|
1940
|
+
/* @__PURE__ */ jsxs(ModalHeader, { className: "mb-4", children: [
|
|
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}` : ""
|
|
1443
1951
|
] })
|
|
1444
1952
|
] }),
|
|
1445
|
-
/* @__PURE__ */
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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
|
+
)
|
|
2001
|
+
] }),
|
|
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",
|
|
2006
|
+
{
|
|
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
|
|
2012
|
+
}
|
|
2013
|
+
)
|
|
2014
|
+
] }),
|
|
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 })
|
|
1456
2016
|
] }),
|
|
1457
2017
|
/* @__PURE__ */ jsxs(ModalFooter, { className: "mt-4 gap-2", children: [
|
|
1458
|
-
|
|
1459
|
-
/* @__PURE__ */ jsx(
|
|
1460
|
-
Button,
|
|
1461
|
-
{
|
|
1462
|
-
variant: state === "waiting" ? "ghost" : "default",
|
|
1463
|
-
onClick: onClose,
|
|
1464
|
-
"data-testid": "new-order-charge-close",
|
|
1465
|
-
children: state === "waiting" ? "Fechar" : "Voltar ao pedido"
|
|
1466
|
-
}
|
|
1467
|
-
)
|
|
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" })
|
|
1468
2020
|
] })
|
|
1469
2021
|
] }) }) }) });
|
|
1470
2022
|
}
|
|
@@ -1487,10 +2039,10 @@ var PAYMENT_METHODS = [
|
|
|
1487
2039
|
{ id: "other", label: "Outro" }
|
|
1488
2040
|
];
|
|
1489
2041
|
var money3 = (value, currency2 = "BRL") => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(Number(value) || 0);
|
|
1490
|
-
var
|
|
2042
|
+
var onlyDigits2 = (value) => value.replace(/\D/gu, "");
|
|
1491
2043
|
var maskPostal = (value) => {
|
|
1492
|
-
const
|
|
1493
|
-
return
|
|
2044
|
+
const digits2 = onlyDigits2(value).slice(0, 8);
|
|
2045
|
+
return digits2.length > 5 ? `${digits2.slice(0, 5)}-${digits2.slice(5)}` : digits2;
|
|
1494
2046
|
};
|
|
1495
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";
|
|
1496
2048
|
function Field({ label, hint, children }) {
|
|
@@ -1500,7 +2052,10 @@ function Field({ label, hint, children }) {
|
|
|
1500
2052
|
hint && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-[11px] text-muted-foreground", children: hint })
|
|
1501
2053
|
] });
|
|
1502
2054
|
}
|
|
1503
|
-
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);
|
|
1504
2059
|
const [lines, setLines] = useState([]);
|
|
1505
2060
|
const [customer, setCustomer] = useState(EMPTY_CUSTOMER);
|
|
1506
2061
|
const [address, setAddress] = useState(EMPTY_ADDRESS);
|
|
@@ -1516,7 +2071,68 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1516
2071
|
const [error, setError] = useState(null);
|
|
1517
2072
|
const [session, setSession] = useState(null);
|
|
1518
2073
|
const [chargeOpen, setChargeOpen] = useState(false);
|
|
2074
|
+
const [charging, setCharging] = useState(false);
|
|
2075
|
+
const [lastEnding, setLastEnding] = useState(null);
|
|
2076
|
+
const [entryOpen, setEntryOpen] = useState(false);
|
|
1519
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]);
|
|
1520
2136
|
useEffect(() => {
|
|
1521
2137
|
let cancelled = false;
|
|
1522
2138
|
provider().getStoreSettings?.().then((value) => {
|
|
@@ -1535,7 +2151,7 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1535
2151
|
() => lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0),
|
|
1536
2152
|
[lines]
|
|
1537
2153
|
);
|
|
1538
|
-
const postalDigits =
|
|
2154
|
+
const postalDigits = onlyDigits2(address.postalCode);
|
|
1539
2155
|
const delivers = postalDigits.length === 8;
|
|
1540
2156
|
const shipping = delivers ? freight?.price ?? 0 : 0;
|
|
1541
2157
|
const quoteFreight = useCallback(async (postal) => {
|
|
@@ -1572,7 +2188,8 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1572
2188
|
quantity: 1,
|
|
1573
2189
|
variants: sellable.length > 1 ? sellable : [],
|
|
1574
2190
|
variantId: only?.id ?? null,
|
|
1575
|
-
optionsLabel: null
|
|
2191
|
+
optionsLabel: null,
|
|
2192
|
+
stock: only?.inventoryCount ?? product.inventoryCount ?? null
|
|
1576
2193
|
}];
|
|
1577
2194
|
});
|
|
1578
2195
|
}
|
|
@@ -1598,8 +2215,8 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1598
2215
|
state: address.state.trim().toUpperCase(),
|
|
1599
2216
|
country: "BR",
|
|
1600
2217
|
recipient: customer.name.trim() || void 0,
|
|
1601
|
-
phone:
|
|
1602
|
-
document:
|
|
2218
|
+
phone: onlyDigits2(customer.phone) || void 0,
|
|
2219
|
+
document: onlyDigits2(customer.document) || void 0
|
|
1603
2220
|
} : void 0;
|
|
1604
2221
|
function validate(ending) {
|
|
1605
2222
|
return validateNewOrder({
|
|
@@ -1607,14 +2224,14 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1607
2224
|
unresolvedVariants: lines.filter((line) => line.variants.length > 0 && !line.variantId).length,
|
|
1608
2225
|
customerName: customer.name,
|
|
1609
2226
|
customerEmail: customer.email,
|
|
1610
|
-
customerPhone:
|
|
2227
|
+
customerPhone: onlyDigits2(customer.phone),
|
|
1611
2228
|
customerDocument: customer.document.trim(),
|
|
1612
2229
|
documentValid: isValidDocument(customer.document),
|
|
1613
2230
|
delivers,
|
|
1614
2231
|
addressComplete: Boolean(address.street.trim() && address.city.trim() && address.state.trim())
|
|
1615
2232
|
}, ending);
|
|
1616
2233
|
}
|
|
1617
|
-
async function
|
|
2234
|
+
async function resolveCustomerId2() {
|
|
1618
2235
|
if (customer.id) return customer.id;
|
|
1619
2236
|
const email = customer.email.trim().toLowerCase();
|
|
1620
2237
|
const [first, ...rest] = customer.name.trim().split(/\s+/u);
|
|
@@ -1627,7 +2244,7 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1627
2244
|
firstName: first ?? customer.name.trim(),
|
|
1628
2245
|
lastName: rest.join(" "),
|
|
1629
2246
|
email: email || void 0,
|
|
1630
|
-
phone:
|
|
2247
|
+
phone: onlyDigits2(customer.phone) || void 0
|
|
1631
2248
|
});
|
|
1632
2249
|
return created.id;
|
|
1633
2250
|
}
|
|
@@ -1637,6 +2254,7 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1637
2254
|
setError(invalid);
|
|
1638
2255
|
return;
|
|
1639
2256
|
}
|
|
2257
|
+
setLastEnding(ending);
|
|
1640
2258
|
setSaving(true);
|
|
1641
2259
|
setError(null);
|
|
1642
2260
|
try {
|
|
@@ -1667,14 +2285,15 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1667
2285
|
sessionId: opened.sessionId,
|
|
1668
2286
|
emv: opened.charge?.emv ?? null,
|
|
1669
2287
|
qrcode: opened.charge?.qrcodeBase64 ?? null,
|
|
2288
|
+
qrcodeUrl: opened.charge?.qrcodeUrl ?? null,
|
|
1670
2289
|
paymentUrl: opened.paymentUrl ?? null,
|
|
1671
2290
|
total: opened.total
|
|
1672
2291
|
});
|
|
1673
2292
|
setChargeOpen(true);
|
|
1674
2293
|
return;
|
|
1675
2294
|
}
|
|
1676
|
-
const customerId = await
|
|
1677
|
-
const
|
|
2295
|
+
const customerId = await resolveCustomerId2();
|
|
2296
|
+
const payload = {
|
|
1678
2297
|
customerId,
|
|
1679
2298
|
customer: { name: customer.name.trim(), email: customer.email.trim().toLowerCase() || void 0 },
|
|
1680
2299
|
items: lines.map((line) => ({
|
|
@@ -1687,10 +2306,14 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1687
2306
|
discountCode: discountCode.trim() || void 0,
|
|
1688
2307
|
paymentMethod: ending === "received" ? method : void 0,
|
|
1689
2308
|
notes: notes.trim() || void 0
|
|
1690
|
-
}
|
|
2309
|
+
};
|
|
2310
|
+
const order = orderId ? await provider().reviseOrder(orderId, payload) : await provider().placeOrder(payload);
|
|
1691
2311
|
if (ending === "received") {
|
|
1692
2312
|
await provider().confirmPayment?.(order.id, reference.trim() || `manual:${method}`);
|
|
1693
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
|
+
);
|
|
1694
2317
|
onDone(order.id);
|
|
1695
2318
|
} catch (err) {
|
|
1696
2319
|
setError(errorMessage(err));
|
|
@@ -1698,6 +2321,35 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1698
2321
|
setSaving(false);
|
|
1699
2322
|
}
|
|
1700
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]);
|
|
1701
2353
|
const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1702
2354
|
/* @__PURE__ */ jsx(OrderFormBlock, { title: "Cliente", children: /* @__PURE__ */ jsx(
|
|
1703
2355
|
CustomerPicker,
|
|
@@ -1734,10 +2386,10 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1734
2386
|
/* @__PURE__ */ jsxs(
|
|
1735
2387
|
OrderFormView,
|
|
1736
2388
|
{
|
|
1737
|
-
title: "Novo pedido",
|
|
2389
|
+
title: orderId ? `Editar pedido${orderNumber ? ` #${orderNumber}` : ""}` : "Novo pedido",
|
|
1738
2390
|
parentLabel: "Pedidos",
|
|
1739
2391
|
onBack: onCancel,
|
|
1740
|
-
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",
|
|
1741
2393
|
sideSections,
|
|
1742
2394
|
children: [
|
|
1743
2395
|
/* @__PURE__ */ jsxs(OrderFormBlock, { title: "Produtos", children: [
|
|
@@ -1779,7 +2431,7 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1779
2431
|
"input",
|
|
1780
2432
|
{
|
|
1781
2433
|
value: line.quantity,
|
|
1782
|
-
onChange: (event) => setQuantity(line.key, Number(
|
|
2434
|
+
onChange: (event) => setQuantity(line.key, Number(onlyDigits2(event.target.value) || 0)),
|
|
1783
2435
|
"aria-label": "Quantidade",
|
|
1784
2436
|
"data-testid": "new-order-quantity",
|
|
1785
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"
|
|
@@ -1892,57 +2544,85 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1892
2544
|
customer.name
|
|
1893
2545
|
]
|
|
1894
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
|
+
}
|
|
1895
2560
|
)
|
|
1896
2561
|
]
|
|
1897
2562
|
}
|
|
1898
2563
|
),
|
|
1899
|
-
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: [
|
|
1900
2565
|
/* @__PURE__ */ jsx(Button, { onClick: () => void finish("received"), disabled: saving, "data-testid": "new-order-confirm-received", children: saving ? "Criando\u2026" : "Confirmar pagamento" }),
|
|
1901
2566
|
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setSettling(false), children: "Voltar" })
|
|
1902
|
-
] }) :
|
|
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: [
|
|
1903
2583
|
canCharge && /* @__PURE__ */ jsx(
|
|
1904
2584
|
Button,
|
|
1905
2585
|
{
|
|
1906
|
-
onClick: () =>
|
|
2586
|
+
onClick: () => canLink ? setCharging(true) : void finish("charge"),
|
|
1907
2587
|
disabled: saving,
|
|
1908
2588
|
"data-testid": "new-order-charge",
|
|
1909
|
-
children: saving ? "Abrindo\u2026" :
|
|
1910
|
-
}
|
|
1911
|
-
),
|
|
1912
|
-
canLink && !session && /* @__PURE__ */ jsx(
|
|
1913
|
-
Button,
|
|
1914
|
-
{
|
|
1915
|
-
variant: "outline",
|
|
1916
|
-
onClick: () => void finish("link"),
|
|
1917
|
-
disabled: saving,
|
|
1918
|
-
"data-testid": "new-order-link",
|
|
1919
|
-
children: saving ? "Gerando\u2026" : "Gerar link de pagamento"
|
|
2589
|
+
children: saving ? "Abrindo\u2026" : canLink ? "Cobrar" : "Cobrar por Pix"
|
|
1920
2590
|
}
|
|
1921
2591
|
),
|
|
1922
2592
|
/* @__PURE__ */ jsx(
|
|
1923
2593
|
Button,
|
|
1924
2594
|
{
|
|
1925
|
-
variant: canCharge ? "
|
|
2595
|
+
variant: canCharge ? "ghost" : "default",
|
|
1926
2596
|
onClick: () => setSettling(true),
|
|
1927
2597
|
"data-testid": "new-order-received",
|
|
1928
2598
|
children: "J\xE1 recebi"
|
|
1929
2599
|
}
|
|
1930
|
-
),
|
|
1931
|
-
/* @__PURE__ */ jsx(
|
|
1932
|
-
Button,
|
|
1933
|
-
{
|
|
1934
|
-
variant: "ghost",
|
|
1935
|
-
onClick: () => void finish("later"),
|
|
1936
|
-
disabled: saving,
|
|
1937
|
-
"data-testid": "new-order-later",
|
|
1938
|
-
children: "Cobrar depois"
|
|
1939
|
-
}
|
|
1940
2600
|
)
|
|
1941
2601
|
] }) })
|
|
1942
2602
|
] })
|
|
1943
2603
|
]
|
|
1944
2604
|
}
|
|
1945
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
|
+
),
|
|
1946
2626
|
session && /* @__PURE__ */ jsx(
|
|
1947
2627
|
NewOrderCharge,
|
|
1948
2628
|
{
|
|
@@ -1951,11 +2631,12 @@ function NewOrderPage({ provider, onDone, onCancel }) {
|
|
|
1951
2631
|
sessionId: session.sessionId,
|
|
1952
2632
|
emv: session.emv,
|
|
1953
2633
|
qrcodeBase64: session.qrcode,
|
|
2634
|
+
qrcodeUrl: session.qrcodeUrl,
|
|
1954
2635
|
paymentUrl: session.paymentUrl,
|
|
1955
2636
|
acquirer,
|
|
1956
2637
|
total: session.total,
|
|
1957
2638
|
currency: currency2,
|
|
1958
|
-
customerPhone:
|
|
2639
|
+
customerPhone: onlyDigits2(customer.phone),
|
|
1959
2640
|
customerName: customer.name,
|
|
1960
2641
|
onPaid: onDone,
|
|
1961
2642
|
onClose: () => setChargeOpen(false)
|
|
@@ -2152,147 +2833,6 @@ function CustomerPicker({ provider, value, onChange, editing, onEditing }) {
|
|
|
2152
2833
|
)
|
|
2153
2834
|
] });
|
|
2154
2835
|
}
|
|
2155
|
-
function CustomerModal({ open, initialName, customerId, initial, provider, onClose, onSaved }) {
|
|
2156
|
-
const [first, setFirst] = useState("");
|
|
2157
|
-
const [last, setLast] = useState("");
|
|
2158
|
-
const [email, setEmail] = useState("");
|
|
2159
|
-
const [phone, setPhone] = useState("");
|
|
2160
|
-
const [document, setDocument] = useState("");
|
|
2161
|
-
const [saving, setSaving] = useState(false);
|
|
2162
|
-
const [error, setError] = useState(null);
|
|
2163
|
-
const [duplicate, setDuplicate] = useState(null);
|
|
2164
|
-
useEffect(() => {
|
|
2165
|
-
if (!open) return;
|
|
2166
|
-
const [head, ...rest] = String(initial?.name ?? initialName ?? "").trim().split(/\s+/u);
|
|
2167
|
-
setFirst(head ?? "");
|
|
2168
|
-
setLast(rest.join(" "));
|
|
2169
|
-
setEmail(initial?.email ?? "");
|
|
2170
|
-
setPhone(initial?.phone ?? "");
|
|
2171
|
-
setDocument(initial?.document ?? "");
|
|
2172
|
-
setError(null);
|
|
2173
|
-
setDuplicate(null);
|
|
2174
|
-
}, [open, initialName, initial]);
|
|
2175
|
-
async function save() {
|
|
2176
|
-
if (!first.trim()) {
|
|
2177
|
-
setError("O nome \xE9 obrigat\xF3rio.");
|
|
2178
|
-
return;
|
|
2179
|
-
}
|
|
2180
|
-
if (document && !isValidDocument(document)) {
|
|
2181
|
-
setError("Esse CPF/CNPJ n\xE3o confere.");
|
|
2182
|
-
return;
|
|
2183
|
-
}
|
|
2184
|
-
setSaving(true);
|
|
2185
|
-
setError(null);
|
|
2186
|
-
setDuplicate(null);
|
|
2187
|
-
try {
|
|
2188
|
-
const fields = {
|
|
2189
|
-
firstName: first.trim(),
|
|
2190
|
-
lastName: last.trim(),
|
|
2191
|
-
email: email.trim().toLowerCase() || void 0,
|
|
2192
|
-
phone: onlyDigits(phone) || void 0,
|
|
2193
|
-
document: documentDigits(document) || void 0
|
|
2194
|
-
};
|
|
2195
|
-
const saved = customerId ? await provider().updateCustomer(customerId, fields) : await provider().createCustomer(fields);
|
|
2196
|
-
onSaved(saved);
|
|
2197
|
-
} catch (err) {
|
|
2198
|
-
if (isDuplicateEmail(err)) {
|
|
2199
|
-
const existing = await provider().listCustomers({ search: email.trim().toLowerCase(), limit: 5 }).catch(() => []);
|
|
2200
|
-
const hit = existing.find((c) => (c.email ?? "").toLowerCase() === email.trim().toLowerCase());
|
|
2201
|
-
if (hit) {
|
|
2202
|
-
setDuplicate(hit);
|
|
2203
|
-
return;
|
|
2204
|
-
}
|
|
2205
|
-
setError("J\xE1 existe um cliente com esse e-mail nesta loja. Procure por ele na busca.");
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
setError(errorMessage(err));
|
|
2209
|
-
} finally {
|
|
2210
|
-
setSaving(false);
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
|
|
2214
|
-
if (!next) onClose();
|
|
2215
|
-
}, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", children: [
|
|
2216
|
-
/* @__PURE__ */ jsx(ModalHeader, { className: "mb-4", children: /* @__PURE__ */ jsx(ModalTitle, { children: customerId ? "Cadastro do cliente" : "Novo cliente" }) }),
|
|
2217
|
-
/* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
|
|
2218
|
-
/* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-3", children: [
|
|
2219
|
-
/* @__PURE__ */ jsx(Field, { label: "Nome", children: /* @__PURE__ */ jsx(
|
|
2220
|
-
"input",
|
|
2221
|
-
{
|
|
2222
|
-
value: first,
|
|
2223
|
-
onChange: (e) => setFirst(e.target.value),
|
|
2224
|
-
autoFocus: true,
|
|
2225
|
-
"data-testid": "new-customer-first",
|
|
2226
|
-
className: fieldClass
|
|
2227
|
-
}
|
|
2228
|
-
) }),
|
|
2229
|
-
/* @__PURE__ */ jsx(Field, { label: "Sobrenome", children: /* @__PURE__ */ jsx(
|
|
2230
|
-
"input",
|
|
2231
|
-
{
|
|
2232
|
-
value: last,
|
|
2233
|
-
onChange: (e) => setLast(e.target.value),
|
|
2234
|
-
"data-testid": "new-customer-last",
|
|
2235
|
-
className: fieldClass
|
|
2236
|
-
}
|
|
2237
|
-
) })
|
|
2238
|
-
] }),
|
|
2239
|
-
/* @__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" }) }) }),
|
|
2240
|
-
/* @__PURE__ */ jsx(Field, { label: "E-mail", children: /* @__PURE__ */ jsx(
|
|
2241
|
-
"input",
|
|
2242
|
-
{
|
|
2243
|
-
type: "email",
|
|
2244
|
-
value: email,
|
|
2245
|
-
onChange: (e) => setEmail(e.target.value),
|
|
2246
|
-
"data-testid": "new-customer-email",
|
|
2247
|
-
className: fieldClass
|
|
2248
|
-
}
|
|
2249
|
-
) }),
|
|
2250
|
-
/* @__PURE__ */ jsx(Field, { label: "CPF / CNPJ", hint: "Necess\xE1rio para cobran\xE7a online e para a nota fiscal", children: /* @__PURE__ */ jsx(
|
|
2251
|
-
"input",
|
|
2252
|
-
{
|
|
2253
|
-
value: formatDocument(document),
|
|
2254
|
-
onChange: (e) => setDocument(documentDigits(e.target.value)),
|
|
2255
|
-
placeholder: "000.000.000-00",
|
|
2256
|
-
"data-testid": "new-customer-document",
|
|
2257
|
-
className: fieldClass
|
|
2258
|
-
}
|
|
2259
|
-
) }),
|
|
2260
|
-
duplicate && /* @__PURE__ */ jsxs(
|
|
2261
|
-
"div",
|
|
2262
|
-
{
|
|
2263
|
-
role: "alert",
|
|
2264
|
-
"data-testid": "new-customer-duplicate",
|
|
2265
|
-
className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900",
|
|
2266
|
-
children: [
|
|
2267
|
-
/* @__PURE__ */ jsxs("p", { children: [
|
|
2268
|
-
/* @__PURE__ */ jsxs("strong", { children: [
|
|
2269
|
-
duplicate.firstName,
|
|
2270
|
-
" ",
|
|
2271
|
-
duplicate.lastName
|
|
2272
|
-
] }),
|
|
2273
|
-
" j\xE1 \xE9 cliente com esse e-mail."
|
|
2274
|
-
] }),
|
|
2275
|
-
/* @__PURE__ */ jsx(
|
|
2276
|
-
"button",
|
|
2277
|
-
{
|
|
2278
|
-
type: "button",
|
|
2279
|
-
onClick: () => onSaved(duplicate),
|
|
2280
|
-
"data-testid": "new-customer-use-existing",
|
|
2281
|
-
className: "mt-1 font-medium underline underline-offset-2",
|
|
2282
|
-
children: "Usar este cliente"
|
|
2283
|
-
}
|
|
2284
|
-
)
|
|
2285
|
-
]
|
|
2286
|
-
}
|
|
2287
|
-
),
|
|
2288
|
-
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 })
|
|
2289
|
-
] }),
|
|
2290
|
-
/* @__PURE__ */ jsxs(ModalFooter, { className: "mt-4 gap-2", children: [
|
|
2291
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, children: "Cancelar" }),
|
|
2292
|
-
/* @__PURE__ */ jsx(Button, { onClick: save, disabled: saving, "data-testid": "new-customer-save", children: saving ? "Salvando\u2026" : customerId ? "Salvar" : "Criar cliente" })
|
|
2293
|
-
] })
|
|
2294
|
-
] }) }) }) });
|
|
2295
|
-
}
|
|
2296
2836
|
function DeliveryAddress({ provider, customerId, address, onChange }) {
|
|
2297
2837
|
const [saved, setSaved] = useState([]);
|
|
2298
2838
|
const [chosen, setChosen] = useState(null);
|
|
@@ -2463,14 +3003,14 @@ function PostalField({ value, onChange, onResolved }) {
|
|
|
2463
3003
|
const [looking, setLooking] = useState(false);
|
|
2464
3004
|
const [missing, setMissing] = useState(false);
|
|
2465
3005
|
useEffect(() => {
|
|
2466
|
-
const
|
|
2467
|
-
if (
|
|
3006
|
+
const digits2 = onlyDigits2(value);
|
|
3007
|
+
if (digits2.length !== 8) {
|
|
2468
3008
|
setMissing(false);
|
|
2469
3009
|
return;
|
|
2470
3010
|
}
|
|
2471
3011
|
let cancelled = false;
|
|
2472
3012
|
setLooking(true);
|
|
2473
|
-
fetch(`https://viacep.com.br/ws/${
|
|
3013
|
+
fetch(`https://viacep.com.br/ws/${digits2}/json/`).then((response) => response.json()).then((found) => {
|
|
2474
3014
|
if (cancelled) return;
|
|
2475
3015
|
if (found?.erro) {
|
|
2476
3016
|
setMissing(true);
|
|
@@ -2503,6 +3043,496 @@ function PostalField({ value, onChange, onResolved }) {
|
|
|
2503
3043
|
}
|
|
2504
3044
|
) });
|
|
2505
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
|
+
}
|
|
2506
3536
|
function currency(value, code = "BRL", locale = "pt-BR") {
|
|
2507
3537
|
return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(value);
|
|
2508
3538
|
}
|
|
@@ -2779,10 +3809,49 @@ function OrdersTab({ provider }) {
|
|
|
2779
3809
|
}, [orders]);
|
|
2780
3810
|
if (error) return /* @__PURE__ */ jsx(ErrorState, { message: error });
|
|
2781
3811
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3812
|
+
/* @__PURE__ */ jsx(
|
|
3813
|
+
OrdersAgentSurface,
|
|
3814
|
+
{
|
|
3815
|
+
provider: () => resolveShopProvider(provider),
|
|
3816
|
+
orders,
|
|
3817
|
+
onChanged: reload
|
|
3818
|
+
}
|
|
3819
|
+
),
|
|
2782
3820
|
/* @__PURE__ */ jsx(
|
|
2783
3821
|
OrderListView,
|
|
2784
3822
|
{
|
|
2785
|
-
actions: /* @__PURE__ */ jsx(
|
|
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
|
+
),
|
|
2786
3855
|
stats,
|
|
2787
3856
|
columns,
|
|
2788
3857
|
rows: orders,
|
|
@@ -2912,13 +3981,15 @@ var ShopPage = ({ provider, section }) => {
|
|
|
2912
3981
|
const active = section ?? tab;
|
|
2913
3982
|
const path = useAdminPath();
|
|
2914
3983
|
const orderId = section === "orders" ? path.match(/^\/orders\/([^/]+)$/)?.[1] : void 0;
|
|
2915
|
-
|
|
3984
|
+
const editingId = section === "orders" ? path.match(/^\/orders\/([^/]+)\/edit$/)?.[1] : void 0;
|
|
3985
|
+
if (section === "orders" && (orderId === "new" || editingId)) {
|
|
2916
3986
|
return /* @__PURE__ */ jsx(
|
|
2917
3987
|
NewOrderPage,
|
|
2918
3988
|
{
|
|
2919
3989
|
provider: () => resolveShopProvider(provider),
|
|
3990
|
+
orderId: editingId,
|
|
2920
3991
|
onDone: (id) => adminNavigateTo(id ? `/orders/${id}` : "/orders"),
|
|
2921
|
-
onCancel: () => adminNavigateTo("/orders")
|
|
3992
|
+
onCancel: () => adminNavigateTo(editingId ? `/orders/${editingId}` : "/orders")
|
|
2922
3993
|
}
|
|
2923
3994
|
);
|
|
2924
3995
|
}
|
|
@@ -2961,5 +4032,5 @@ var ShopPage = ({ provider, section }) => {
|
|
|
2961
4032
|
ShopPage.displayName = "ShopPage";
|
|
2962
4033
|
|
|
2963
4034
|
export { ShopPage };
|
|
2964
|
-
//# sourceMappingURL=ShopPage-
|
|
2965
|
-
//# sourceMappingURL=ShopPage-
|
|
4035
|
+
//# sourceMappingURL=ShopPage-QFIM2HDU.js.map
|
|
4036
|
+
//# sourceMappingURL=ShopPage-QFIM2HDU.js.map
|