@fayz-ai/plugin-shop 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ShopPage-YB5ZWECJ.js → ShopPage-QFIM2HDU.js} +1516 -406
- 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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1814 -442
- package/dist/index.js.map +1 -1
- package/dist/integrations/abacatepay/connectorDef.d.ts.map +1 -1
- package/dist/integrations/abacatepay/data.d.ts +40 -0
- package/dist/integrations/abacatepay/data.d.ts.map +1 -0
- package/dist/integrations/abacatepay/limits.d.ts +26 -0
- package/dist/integrations/abacatepay/limits.d.ts.map +1 -0
- package/dist/integrations/abacatepay/sync.d.ts +88 -0
- package/dist/integrations/abacatepay/sync.d.ts.map +1 -0
- 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/connectorDef.d.ts +12 -0
- package/dist/integrations/neochef/connectorDef.d.ts.map +1 -0
- package/dist/integrations/neochef/data.d.ts +54 -0
- package/dist/integrations/neochef/data.d.ts.map +1 -0
- package/dist/integrations/neochef/index.d.ts +8 -0
- package/dist/integrations/neochef/index.d.ts.map +1 -0
- package/dist/integrations/neochef/limits.d.ts +26 -0
- package/dist/integrations/neochef/limits.d.ts.map +1 -0
- package/dist/integrations/neochef/mapping.d.ts +31 -0
- package/dist/integrations/neochef/mapping.d.ts.map +1 -0
- package/dist/integrations/neochef/mark.d.ts +21 -0
- package/dist/integrations/neochef/mark.d.ts.map +1 -0
- package/dist/integrations/neochef/types.d.ts +71 -0
- package/dist/integrations/neochef/types.d.ts.map +1 -0
- package/dist/integrations/vindi/connectorDef.d.ts +67 -35
- package/dist/integrations/vindi/connectorDef.d.ts.map +1 -1
- package/dist/views/ContentPage.d.ts.map +1 -1
- package/dist/views/CustomerModal.d.ts +33 -0
- package/dist/views/CustomerModal.d.ts.map +1 -0
- package/dist/views/NewOrderCharge.d.ts +39 -3
- 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 +23 -1
- 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-YB5ZWECJ.js.map +0 -1
- package/dist/chunk-HAKXHHAG.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { shopCustomerLookup, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-
|
|
2
|
-
import
|
|
3
|
-
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, resolveDataProvider } from '@fayz-ai/core';
|
|
1
|
+
import { shopCustomerLookup, buildShopCustomerEntity, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-2VMTPRGC.js';
|
|
2
|
+
import React9, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
3
|
+
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, getActiveTenantId, emitDataChanged, resolveDataProvider } from '@fayz-ai/core';
|
|
4
4
|
import { getShopTenantId, setShopTenantResolver, buildShopMessageDefaults, registerShippingLabelProvider, getShopProvider, buildShopDomainEvents, axesFromOptions, planVariantMatrix, validateVariantAxes, hasOnlyDefaultVariant, MAX_VARIANT_AXES, leavesNothingSellable, productImageToMediaFile, formatVariantCombination, applyVariantMatrix } from '@fayz-ai/shop';
|
|
5
|
-
import { fayzConnectorOAuth, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo,
|
|
6
|
-
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Link2, MapPin, Music, Camera, MessageCircle, Globe, Plus, Loader2, ChevronLeft, ChevronRight,
|
|
5
|
+
import { fayzConnectorOAuth, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo, useAgentSurface, askAI, AskAIButton, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
|
|
6
|
+
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Link2, MapPin, Music, Camera, MessageCircle, Globe, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, ExternalLink, Check, X, AlertTriangle, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, Car, Truck, Info } from 'lucide-react';
|
|
7
7
|
import { PageHeaderActions, PageTabs, Card, Button, Thumbnail, PageHeader, toast, ConfirmDialog, Input, Hint, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Skeleton } from '@fayz-ai/ui';
|
|
8
8
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import { listStockReasonCodes, resolveStockStore, createSupabaseStockStore } from '@fayz-ai/shop/inventory';
|
|
10
|
+
import { normalizeMovementType } from '@fayz-ai/plugin-inventory';
|
|
9
11
|
import { advanceWindow, createRateLimiter, sliceWindow, paginate, reconcileMirror, serializeCursor } from '@fayz-ai/core/connector-runtime';
|
|
10
12
|
|
|
11
13
|
// src/integrations/melhor-envio/labelProvider.ts
|
|
@@ -170,20 +172,20 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
170
172
|
const product = item;
|
|
171
173
|
const shop = getProvider();
|
|
172
174
|
const supported = typeof shop.getProductVariants === "function" && typeof shop.saveProductOptions === "function" && typeof shop.createProductVariant === "function" && typeof shop.updateProductVariant === "function";
|
|
173
|
-
const [axes, setAxes] =
|
|
174
|
-
const [variants, setVariants] =
|
|
175
|
-
const [busy, setBusy] =
|
|
176
|
-
const [error, setError] =
|
|
177
|
-
const [selected, setSelected] =
|
|
178
|
-
const [dirty, setDirty] =
|
|
179
|
-
const reload =
|
|
175
|
+
const [axes, setAxes] = React9.useState([]);
|
|
176
|
+
const [variants, setVariants] = React9.useState([]);
|
|
177
|
+
const [busy, setBusy] = React9.useState(false);
|
|
178
|
+
const [error, setError] = React9.useState(null);
|
|
179
|
+
const [selected, setSelected] = React9.useState(/* @__PURE__ */ new Set());
|
|
180
|
+
const [dirty, setDirty] = React9.useState(false);
|
|
181
|
+
const reload = React9.useCallback(async () => {
|
|
180
182
|
const set = await getProvider().getProductVariants(product.id, { includeArchived: true });
|
|
181
183
|
setAxes(axesFromOptions(set.options));
|
|
182
184
|
setVariants(set.variants);
|
|
183
185
|
setSelected(/* @__PURE__ */ new Set());
|
|
184
186
|
setDirty(false);
|
|
185
187
|
}, [getProvider, product.id]);
|
|
186
|
-
|
|
188
|
+
React9.useEffect(() => {
|
|
187
189
|
if (!supported) return;
|
|
188
190
|
void reload().catch((e) => setError(message(e)));
|
|
189
191
|
}, [reload, supported]);
|
|
@@ -199,7 +201,7 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
199
201
|
setBusy(false);
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
|
-
const plan =
|
|
204
|
+
const plan = React9.useMemo(() => planVariantMatrix(axes, variants), [axes, variants]);
|
|
203
205
|
const axisError = validateVariantAxes(axes);
|
|
204
206
|
const onlyDefault = hasOnlyDefaultVariant(variants);
|
|
205
207
|
const rows = plan.rows;
|
|
@@ -300,7 +302,7 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
300
302
|
] });
|
|
301
303
|
}
|
|
302
304
|
function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }) {
|
|
303
|
-
const [draft, setDraft] =
|
|
305
|
+
const [draft, setDraft] = React9.useState("");
|
|
304
306
|
function commit(raw) {
|
|
305
307
|
const values = raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
306
308
|
if (values.length === 0) return;
|
|
@@ -309,7 +311,7 @@ function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }
|
|
|
309
311
|
onChange({ ...axis, values: merged });
|
|
310
312
|
setDraft("");
|
|
311
313
|
}
|
|
312
|
-
return /* @__PURE__ */ jsxs("li", { className: "rounded-
|
|
314
|
+
return /* @__PURE__ */ jsxs("li", { className: "rounded-card border bg-card p-3", children: [
|
|
313
315
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
314
316
|
/* @__PURE__ */ jsx(
|
|
315
317
|
"input",
|
|
@@ -447,9 +449,9 @@ function VariantRow({ row, axes, product, busy, checked, onToggle, onSave, onSto
|
|
|
447
449
|
const variant = row.variant;
|
|
448
450
|
const pending = !variant;
|
|
449
451
|
const label = formatVariantCombination(row.options, axes);
|
|
450
|
-
const commit = (
|
|
452
|
+
const commit = (field2, value, previous) => {
|
|
451
453
|
if (!variant || value === previous) return;
|
|
452
|
-
onSave(variant.id, { [
|
|
454
|
+
onSave(variant.id, { [field2]: value });
|
|
453
455
|
};
|
|
454
456
|
return /* @__PURE__ */ jsxs("tr", { className: pending ? "bg-muted/20 text-muted-foreground" : void 0, children: [
|
|
455
457
|
/* @__PURE__ */ jsx("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsx(
|
|
@@ -573,9 +575,9 @@ function VariantRow({ row, axes, product, busy, checked, onToggle, onSave, onSto
|
|
|
573
575
|
] });
|
|
574
576
|
}
|
|
575
577
|
function BulkBar({ count, disabled, onApply, onClear }) {
|
|
576
|
-
const [price, setPrice] =
|
|
577
|
-
const [weight, setWeight] =
|
|
578
|
-
const [unit, setUnit] =
|
|
578
|
+
const [price, setPrice] = React9.useState("");
|
|
579
|
+
const [weight, setWeight] = React9.useState("");
|
|
580
|
+
const [unit, setUnit] = React9.useState("");
|
|
579
581
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border bg-muted/30 px-3 py-2 text-xs", children: [
|
|
580
582
|
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
581
583
|
count,
|
|
@@ -654,8 +656,8 @@ function parseNumber(raw) {
|
|
|
654
656
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
655
657
|
}
|
|
656
658
|
function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", integer }) {
|
|
657
|
-
const [draft, setDraft] =
|
|
658
|
-
|
|
659
|
+
const [draft, setDraft] = React9.useState(value == null ? "" : String(value));
|
|
660
|
+
React9.useEffect(() => {
|
|
659
661
|
setDraft(value == null ? "" : String(value));
|
|
660
662
|
}, [value]);
|
|
661
663
|
return /* @__PURE__ */ jsx(
|
|
@@ -674,8 +676,8 @@ function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", in
|
|
|
674
676
|
);
|
|
675
677
|
}
|
|
676
678
|
function TextCell({ value, disabled, placeholder, onCommit }) {
|
|
677
|
-
const [draft, setDraft] =
|
|
678
|
-
|
|
679
|
+
const [draft, setDraft] = React9.useState(value);
|
|
680
|
+
React9.useEffect(() => {
|
|
679
681
|
setDraft(value);
|
|
680
682
|
}, [value]);
|
|
681
683
|
return /* @__PURE__ */ jsx(
|
|
@@ -722,6 +724,613 @@ function describePlan(plan) {
|
|
|
722
724
|
function message(error) {
|
|
723
725
|
return error instanceof Error ? error.message : String(error);
|
|
724
726
|
}
|
|
727
|
+
var KIND_OF = {
|
|
728
|
+
// `sign: 0` = o razão aceita os dois lados (ajuste corrige para cima e para baixo).
|
|
729
|
+
entry: { kind: "receipt", sign: 1, reason: "purchase" },
|
|
730
|
+
exit: { kind: "adjustment", sign: -1, reason: "correction" },
|
|
731
|
+
adjustment: { kind: "adjustment", sign: 0, reason: "correction" },
|
|
732
|
+
loss: { kind: "loss", sign: -1, reason: "breakage" },
|
|
733
|
+
// Transferência é DOIS movimentos amarrados, com origem e destino. Metade de
|
|
734
|
+
// uma transferência é estoque sumindo de um lugar e não aparecendo no outro.
|
|
735
|
+
transfer: { kind: "transfer_out", sign: -1, reason: "transfer" }
|
|
736
|
+
};
|
|
737
|
+
function planMovement(rawType, quantity, reasonCode) {
|
|
738
|
+
const type = normalizeMovementType(rawType);
|
|
739
|
+
if (!type) {
|
|
740
|
+
return { error: "unknown_movement_type", message: "Use entrada, sa\xEDda, ajuste ou perda." };
|
|
741
|
+
}
|
|
742
|
+
if (type === "transfer") {
|
|
743
|
+
return {
|
|
744
|
+
error: "transfer_unsupported",
|
|
745
|
+
message: "Transfer\xEAncia entre locais precisa de origem e destino \u2014 ainda n\xE3o fa\xE7o por aqui."
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const magnitude = Math.abs(Number(quantity) || 0);
|
|
749
|
+
if (magnitude === 0) return { error: "zero_quantity", message: "Movimento de zero n\xE3o \xE9 movimento." };
|
|
750
|
+
const fallback = KIND_OF[type];
|
|
751
|
+
const declared = reasonCode ? listStockReasonCodes().find((r) => r.key === reasonCode) : void 0;
|
|
752
|
+
const kind = declared?.movementKind ?? fallback.kind;
|
|
753
|
+
const sign = fallback.sign === 0 ? Number(quantity) < 0 ? -1 : 1 : fallback.sign;
|
|
754
|
+
return { kind, quantity: magnitude * sign, reasonCode: declared?.key ?? fallback.reason };
|
|
755
|
+
}
|
|
756
|
+
var WHERE_IS = {
|
|
757
|
+
id: "shop.stock.position",
|
|
758
|
+
name: "getStockPosition",
|
|
759
|
+
description: 'Answers "where is my X" for a product the merchant names in words: how many are on hand, how many are held for orders, and how that splits across locations and lots. Resolves the words itself \u2014 send the term as the merchant said it. When two products fit equally it asks with a card instead of guessing.',
|
|
760
|
+
icon: "Boxes",
|
|
761
|
+
mode: "read",
|
|
762
|
+
execution: { plane: "client" },
|
|
763
|
+
category: "Shop",
|
|
764
|
+
role: "operations",
|
|
765
|
+
parameters: {
|
|
766
|
+
type: "object",
|
|
767
|
+
properties: { term: { type: "string", description: "The product, in the merchant's words." } },
|
|
768
|
+
required: ["term"]
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
var RECORD = {
|
|
772
|
+
id: "shop.stock.record",
|
|
773
|
+
name: "recordStockMovement",
|
|
774
|
+
description: "Writes one line in the stock ledger: entrada, sa\xEDda, ajuste or perda. Takes the `variantId` from getStockPosition \u2014 never a name. Send the quantity as a plain positive number and say which movement it is; the sign belongs to the ledger, not to you. The ledger is append-only: a mistake is corrected by an opposing movement, never by editing. `occurredAt` is WHEN the goods moved, which is not when they were typed \u2014 Friday's delivery entered on Monday.",
|
|
775
|
+
icon: "ArrowDownToLine",
|
|
776
|
+
mode: "persist",
|
|
777
|
+
execution: { plane: "client" },
|
|
778
|
+
category: "Shop",
|
|
779
|
+
role: "operations",
|
|
780
|
+
permission: { feature: "shop", action: "update" },
|
|
781
|
+
parameters: {
|
|
782
|
+
type: "object",
|
|
783
|
+
properties: {
|
|
784
|
+
variantId: { type: "string", description: "From getStockPosition." },
|
|
785
|
+
productName: { type: "string", description: "So the merchant reads the card without ids." },
|
|
786
|
+
movementType: {
|
|
787
|
+
type: "string",
|
|
788
|
+
description: "entrada | sa\xEDda | ajuste | perda",
|
|
789
|
+
enum: ["entrada", "saida", "ajuste", "perda"]
|
|
790
|
+
},
|
|
791
|
+
quantity: { type: "number", description: "How many, as a positive number." },
|
|
792
|
+
reasonCode: { type: "string", description: "Reason key when the merchant gave one (purchase, breakage, expiry, theft\u2026)." },
|
|
793
|
+
locationId: { type: "string", description: "Which location. Omitted means the store default." },
|
|
794
|
+
note: { type: "string", description: "Anything the merchant said that is not a field \u2014 the invoice number, who delivered." },
|
|
795
|
+
occurredAt: { type: "string", description: "ISO date when it actually moved. Omitted means now." }
|
|
796
|
+
},
|
|
797
|
+
required: ["variantId", "productName", "movementType", "quantity"]
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
function isLedgerMissing(error) {
|
|
801
|
+
const raw = error instanceof Error ? error.message : String(error?.message ?? error);
|
|
802
|
+
return /plg_shop_stock_|\btracking\b.*does not exist/i.test(raw);
|
|
803
|
+
}
|
|
804
|
+
var LEDGER_MISSING_MESSAGE = "O controle de estoque ainda n\xE3o foi aplicado nesta loja (migration 0034). Enquanto isso o saldo vive s\xF3 no n\xFAmero do produto.";
|
|
805
|
+
function occurredAtFor(chosenDay, today) {
|
|
806
|
+
if (!chosenDay || chosenDay === today) return void 0;
|
|
807
|
+
const parsed = /* @__PURE__ */ new Date(`${chosenDay}T12:00:00`);
|
|
808
|
+
return Number.isNaN(parsed.getTime()) ? void 0 : parsed.toISOString();
|
|
809
|
+
}
|
|
810
|
+
async function commitMovement(args) {
|
|
811
|
+
const plan = planMovement(args.movementType, args.quantity, args.reasonCode);
|
|
812
|
+
if ("error" in plan) return { ok: false, ...plan };
|
|
813
|
+
const store7 = stockStore();
|
|
814
|
+
if (plan.quantity < 0) {
|
|
815
|
+
const available = await store7.getAvailable(args.variantId);
|
|
816
|
+
if (Math.abs(plan.quantity) > available) {
|
|
817
|
+
return {
|
|
818
|
+
ok: false,
|
|
819
|
+
error: "insufficient_stock",
|
|
820
|
+
available,
|
|
821
|
+
message: `S\xF3 h\xE1 ${available} em estoque \u2014 a sa\xEDda de ${Math.abs(plan.quantity)} deixaria negativo.`
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const movement = await store7.recordMovement({
|
|
826
|
+
variantId: args.variantId,
|
|
827
|
+
kind: plan.kind,
|
|
828
|
+
quantity: plan.quantity,
|
|
829
|
+
reasonCode: plan.reasonCode,
|
|
830
|
+
...args.locationId ? { locationId: args.locationId } : {},
|
|
831
|
+
...args.note ? { note: args.note } : {},
|
|
832
|
+
...args.occurredAt ? { occurredAt: args.occurredAt } : {}
|
|
833
|
+
});
|
|
834
|
+
for (const table of [
|
|
835
|
+
"plg_shop_products",
|
|
836
|
+
// a Lista, e é a que o usuário estava vendo
|
|
837
|
+
"plg_shop_product_variants",
|
|
838
|
+
// a matriz de variantes
|
|
839
|
+
"plg_shop_stock_balances",
|
|
840
|
+
// as Posições
|
|
841
|
+
"products"
|
|
842
|
+
// o arquétipo, que as telas de Estoque leem
|
|
843
|
+
]) {
|
|
844
|
+
emitDataChanged({ table, op: "update" });
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
ok: true,
|
|
848
|
+
movementId: movement.id,
|
|
849
|
+
kind: plan.kind,
|
|
850
|
+
quantity: plan.quantity,
|
|
851
|
+
reasonCode: plan.reasonCode,
|
|
852
|
+
available: await store7.getAvailable(args.variantId)
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
var text = (value) => typeof value === "string" ? value.trim() : "";
|
|
856
|
+
var stockStore = () => resolveStockStore(getActiveTenantId() ?? "", () => createSupabaseStockStore());
|
|
857
|
+
function summarize(balances, locations) {
|
|
858
|
+
const nameOf = (id) => locations.find((l) => l.id === id)?.name ?? id;
|
|
859
|
+
const total = balances.reduce(
|
|
860
|
+
(acc, b) => ({
|
|
861
|
+
onHand: acc.onHand + b.onHand,
|
|
862
|
+
reserved: acc.reserved + b.reserved,
|
|
863
|
+
available: acc.available + b.available
|
|
864
|
+
}),
|
|
865
|
+
{ onHand: 0, reserved: 0, available: 0 }
|
|
866
|
+
);
|
|
867
|
+
const byLocation = /* @__PURE__ */ new Map();
|
|
868
|
+
for (const b of balances) {
|
|
869
|
+
const row = byLocation.get(b.locationId) ?? { location: nameOf(b.locationId), onHand: 0, available: 0 };
|
|
870
|
+
row.onHand += b.onHand;
|
|
871
|
+
row.available += b.available;
|
|
872
|
+
byLocation.set(b.locationId, row);
|
|
873
|
+
}
|
|
874
|
+
return { total, byLocation: [...byLocation.values()] };
|
|
875
|
+
}
|
|
876
|
+
function createStockAgentTools({ getProvider, onChanged }) {
|
|
877
|
+
const failed = (error) => isLedgerMissing(error) ? { ok: false, error: "ledger_not_installed", message: LEDGER_MISSING_MESSAGE } : { ok: false, error: errorMessage(error) };
|
|
878
|
+
const variantsOf = async (product) => {
|
|
879
|
+
try {
|
|
880
|
+
const set = await getProvider().getProductVariants?.(product.id);
|
|
881
|
+
const ids = (set?.variants ?? []).map((v) => v.id);
|
|
882
|
+
if (ids.length > 0) return ids;
|
|
883
|
+
} catch {
|
|
884
|
+
}
|
|
885
|
+
return [];
|
|
886
|
+
};
|
|
887
|
+
return [
|
|
888
|
+
{
|
|
889
|
+
tool: WHERE_IS,
|
|
890
|
+
execute: async (args) => {
|
|
891
|
+
const term = text(args.term);
|
|
892
|
+
if (!term) return { ok: false, error: "no_term" };
|
|
893
|
+
try {
|
|
894
|
+
const catalog = await listProductsCached(getProvider, { status: "active", limit: 500 });
|
|
895
|
+
const answer = resolveTerm(term, catalog);
|
|
896
|
+
if (!answer.resolved) {
|
|
897
|
+
if (!answer.options?.length) return { ok: true, term, notFound: true };
|
|
898
|
+
return {
|
|
899
|
+
ok: true,
|
|
900
|
+
kind: "record_choice",
|
|
901
|
+
choices: [{
|
|
902
|
+
id: "stock-product",
|
|
903
|
+
question: `Qual "${term}"?`,
|
|
904
|
+
options: answer.options.map((c) => ({ id: c.productId, label: c.name, meta: candidateMeta(c) }))
|
|
905
|
+
}],
|
|
906
|
+
term,
|
|
907
|
+
needsChoice: true
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
const product = catalog.find((p) => p.id === answer.resolved.productId);
|
|
911
|
+
const variantIds = await variantsOf(product);
|
|
912
|
+
const store7 = stockStore();
|
|
913
|
+
const [balances, locations] = await Promise.all([
|
|
914
|
+
store7.listBalances(variantIds.length > 0 ? { variantIds } : {}),
|
|
915
|
+
store7.listLocations()
|
|
916
|
+
]);
|
|
917
|
+
const mine = variantIds.length > 0 ? balances : balances.filter(() => false);
|
|
918
|
+
const { total, byLocation } = summarize(mine, locations);
|
|
919
|
+
return {
|
|
920
|
+
ok: true,
|
|
921
|
+
product: { productId: product.id, name: product.name, sku: product.sku },
|
|
922
|
+
// A variante é o que o lançamento precisa; sem ela o razão não tem
|
|
923
|
+
// onde postar, e é melhor dizer isso do que gravar no lugar errado.
|
|
924
|
+
variantId: variantIds[0] ?? null,
|
|
925
|
+
variants: variantIds,
|
|
926
|
+
total,
|
|
927
|
+
byLocation,
|
|
928
|
+
locations: locations.map((l) => ({ locationId: l.id, name: l.name, isDefault: l.isDefault }))
|
|
929
|
+
};
|
|
930
|
+
} catch (error) {
|
|
931
|
+
return failed(error);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
tool: RECORD,
|
|
937
|
+
execute: async (args) => {
|
|
938
|
+
const variantId = text(args.variantId);
|
|
939
|
+
if (!variantId) return { ok: false, error: "no_variant", message: "Preciso da variante \u2014 pergunte a posi\xE7\xE3o primeiro." };
|
|
940
|
+
try {
|
|
941
|
+
const answer = await commitMovement({
|
|
942
|
+
variantId,
|
|
943
|
+
movementType: text(args.movementType),
|
|
944
|
+
quantity: Number(args.quantity),
|
|
945
|
+
reasonCode: text(args.reasonCode) || void 0,
|
|
946
|
+
locationId: text(args.locationId) || void 0,
|
|
947
|
+
note: text(args.note) || void 0,
|
|
948
|
+
occurredAt: text(args.occurredAt) || void 0
|
|
949
|
+
});
|
|
950
|
+
if (answer.ok) onChanged?.();
|
|
951
|
+
return answer;
|
|
952
|
+
} catch (error) {
|
|
953
|
+
return failed(error);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
];
|
|
958
|
+
}
|
|
959
|
+
var STOCK_INSTRUCTIONS = `
|
|
960
|
+
Esta tela \xE9 o estoque da loja. O lojista lan\xE7a o que entrou e o que saiu, e pergunta onde est\xE3o as coisas.
|
|
961
|
+
|
|
962
|
+
\xB7 "Cad\xEA minha costela", "quanto tem de X": \`getStockPosition\` com as palavras dele. Ela resolve o produto sozinha e devolve o saldo por local. Responda com o n\xFAmero e o local, uma linha.
|
|
963
|
+
\xB7 Lan\xE7ar entrada ou sa\xEDda: pegue o \`variantId\` na posi\xE7\xE3o e chame \`recordStockMovement\`. Quantidade sempre positiva \u2014 o sinal \xE9 do raz\xE3o, n\xE3o seu.
|
|
964
|
+
\xB7 A esp\xE9cie vem do que ele disse: "chegaram 20" \xE9 entrada, "quebrou 3" \xE9 perda com raz\xE3o \`breakage\`, "vencerem 2" \xE9 perda com \`expiry\`, "contei e tem 18" \xE9 ajuste.
|
|
965
|
+
\xB7 A nota de sexta lan\xE7ada na segunda: se ele disser QUANDO chegou, mande em \`occurredAt\`. Sem isso o raz\xE3o registra hoje, e o custo cai no m\xEAs errado.
|
|
966
|
+
\xB7 O raz\xE3o n\xE3o se edita. Errou, lan\xE7a o movimento contr\xE1rio \u2014 nunca prometa "corrigir" o lan\xE7amento.
|
|
967
|
+
\xB7 Nunca invente produto nem variante. Termo amb\xEDguo j\xE1 virou pergunta com bot\xF5es: n\xE3o repita as op\xE7\xF5es no texto.
|
|
968
|
+
`.trim();
|
|
969
|
+
var stockStore2 = () => resolveStockStore(getActiveTenantId() ?? "", () => createSupabaseStockStore());
|
|
970
|
+
var MOVEMENT_TYPES = [
|
|
971
|
+
{ value: "entrada", label: "Entrada" },
|
|
972
|
+
{ value: "saida", label: "Sa\xEDda" },
|
|
973
|
+
{ value: "ajuste", label: "Ajuste" },
|
|
974
|
+
{ value: "perda", label: "Perda" }
|
|
975
|
+
];
|
|
976
|
+
var KIND_FOR = {
|
|
977
|
+
entrada: "receipt",
|
|
978
|
+
saida: "adjustment",
|
|
979
|
+
ajuste: "adjustment",
|
|
980
|
+
perda: "loss"
|
|
981
|
+
};
|
|
982
|
+
var todayISO = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
983
|
+
var field = "mt-1 w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary";
|
|
984
|
+
var STOCK_INTRO = {
|
|
985
|
+
role: "operations",
|
|
986
|
+
persona: "Estoquista",
|
|
987
|
+
pitch: "Diga o que entrou ou saiu que eu lan\xE7o.",
|
|
988
|
+
description: "Lan\xE7o entrada, sa\xEDda, ajuste ou perda no raz\xE3o deste produto, e respondo quanto tem e onde. Voc\xEA confirma antes de gravar.",
|
|
989
|
+
suggestions: [
|
|
990
|
+
{ label: "Chegaram 20 unidades" },
|
|
991
|
+
{ label: "Quebraram 2" }
|
|
992
|
+
]
|
|
993
|
+
};
|
|
994
|
+
function ProductPositionsTab({ item, getProvider }) {
|
|
995
|
+
const product = item;
|
|
996
|
+
const [variants, setVariants] = useState([]);
|
|
997
|
+
const [balances, setBalances] = useState([]);
|
|
998
|
+
const [locations, setLocations] = useState([]);
|
|
999
|
+
const [movements, setMovements] = useState([]);
|
|
1000
|
+
const [loading, setLoading] = useState(true);
|
|
1001
|
+
const [error, setError] = useState(null);
|
|
1002
|
+
const [variantId, setVariantId] = useState("");
|
|
1003
|
+
const [type, setType] = useState(null);
|
|
1004
|
+
const [quantity, setQuantity] = useState("");
|
|
1005
|
+
const [reasonCode, setReasonCode] = useState("");
|
|
1006
|
+
const [note, setNote] = useState("");
|
|
1007
|
+
const [noteOpen, setNoteOpen] = useState(false);
|
|
1008
|
+
const [occurredOn, setOccurredOn] = useState(() => todayISO());
|
|
1009
|
+
const [saving, setSaving] = useState(false);
|
|
1010
|
+
const quantityRef = React9.useRef(null);
|
|
1011
|
+
const load = useCallback(async () => {
|
|
1012
|
+
setLoading(true);
|
|
1013
|
+
try {
|
|
1014
|
+
const set = await getProvider().getProductVariants?.(product.id);
|
|
1015
|
+
const list = set?.variants ?? [];
|
|
1016
|
+
setVariants(list);
|
|
1017
|
+
const ids = list.map((v) => v.id);
|
|
1018
|
+
setVariantId((current) => current || ids[0] || "");
|
|
1019
|
+
const store7 = stockStore2();
|
|
1020
|
+
const [b, l] = await Promise.all([
|
|
1021
|
+
ids.length > 0 ? store7.listBalances({ variantIds: ids }) : Promise.resolve([]),
|
|
1022
|
+
store7.listLocations()
|
|
1023
|
+
]);
|
|
1024
|
+
setBalances(b);
|
|
1025
|
+
setLocations(l);
|
|
1026
|
+
setMovements(ids[0] ? await store7.listMovements({ variantId: ids[0], limit: 10 }) : []);
|
|
1027
|
+
setError(null);
|
|
1028
|
+
} catch (err) {
|
|
1029
|
+
setError(isLedgerMissing(err) ? LEDGER_MISSING_MESSAGE : errorMessage(err));
|
|
1030
|
+
} finally {
|
|
1031
|
+
setLoading(false);
|
|
1032
|
+
}
|
|
1033
|
+
}, [getProvider, product.id]);
|
|
1034
|
+
useEffect(() => {
|
|
1035
|
+
void load();
|
|
1036
|
+
}, [load]);
|
|
1037
|
+
const nameOf = useCallback(
|
|
1038
|
+
(id) => locations.find((l) => l.id === id)?.name ?? id,
|
|
1039
|
+
[locations]
|
|
1040
|
+
);
|
|
1041
|
+
const rows = useMemo(() => {
|
|
1042
|
+
const mine = variantId ? balances.filter((b) => b.variantId === variantId) : balances;
|
|
1043
|
+
return mine.map((b) => ({
|
|
1044
|
+
key: b.id,
|
|
1045
|
+
location: nameOf(b.locationId),
|
|
1046
|
+
onHand: b.onHand,
|
|
1047
|
+
reserved: b.reserved,
|
|
1048
|
+
available: b.available
|
|
1049
|
+
}));
|
|
1050
|
+
}, [balances, variantId, nameOf]);
|
|
1051
|
+
const total = useMemo(
|
|
1052
|
+
() => rows.reduce(
|
|
1053
|
+
(acc, r) => ({
|
|
1054
|
+
onHand: acc.onHand + r.onHand,
|
|
1055
|
+
reserved: acc.reserved + r.reserved,
|
|
1056
|
+
available: acc.available + r.available
|
|
1057
|
+
}),
|
|
1058
|
+
{ onHand: 0, reserved: 0, available: 0 }
|
|
1059
|
+
),
|
|
1060
|
+
[rows]
|
|
1061
|
+
);
|
|
1062
|
+
const reasons = useMemo(
|
|
1063
|
+
() => listStockReasonCodes().filter(
|
|
1064
|
+
(r) => !r.movementKind || r.movementKind === KIND_FOR[type ?? "entrada"]
|
|
1065
|
+
),
|
|
1066
|
+
[type]
|
|
1067
|
+
);
|
|
1068
|
+
useEffect(() => {
|
|
1069
|
+
setReasonCode("");
|
|
1070
|
+
}, [type]);
|
|
1071
|
+
const open = (next) => {
|
|
1072
|
+
setType(next);
|
|
1073
|
+
setQuantity("");
|
|
1074
|
+
setNote("");
|
|
1075
|
+
setNoteOpen(false);
|
|
1076
|
+
setOccurredOn(todayISO());
|
|
1077
|
+
window.setTimeout(() => quantityRef.current?.focus(), 0);
|
|
1078
|
+
};
|
|
1079
|
+
const close = () => {
|
|
1080
|
+
setType(null);
|
|
1081
|
+
setQuantity("");
|
|
1082
|
+
setNote("");
|
|
1083
|
+
setNoteOpen(false);
|
|
1084
|
+
setOccurredOn(todayISO());
|
|
1085
|
+
};
|
|
1086
|
+
const amount = Math.abs(Number(quantity) || 0);
|
|
1087
|
+
const outgoing = type === "saida" || type === "perda";
|
|
1088
|
+
const nextBalance = amount > 0 ? outgoing ? total.available - amount : total.available + amount : null;
|
|
1089
|
+
const live = React9.useRef({ product, variantId, total, getProvider, load });
|
|
1090
|
+
live.current = { product, variantId, total, getProvider, load };
|
|
1091
|
+
const contribution = useMemo(() => ({
|
|
1092
|
+
id: "shop.product.positions",
|
|
1093
|
+
pluginId: "shop",
|
|
1094
|
+
role: "operations",
|
|
1095
|
+
instructions: STOCK_INSTRUCTIONS,
|
|
1096
|
+
describe: () => {
|
|
1097
|
+
const o = live.current;
|
|
1098
|
+
return {
|
|
1099
|
+
title: `Estoque \u2014 ${o.product.name}`,
|
|
1100
|
+
record: { entity: "shop_product", id: o.product.id, label: o.product.name },
|
|
1101
|
+
state: {
|
|
1102
|
+
screen: "/products/:id#posicoes",
|
|
1103
|
+
productId: o.product.id,
|
|
1104
|
+
productName: o.product.name,
|
|
1105
|
+
variantId: o.variantId || null,
|
|
1106
|
+
onHand: o.total.onHand,
|
|
1107
|
+
reserved: o.total.reserved,
|
|
1108
|
+
available: o.total.available
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
},
|
|
1112
|
+
tools: createStockAgentTools({
|
|
1113
|
+
getProvider: () => live.current.getProvider(),
|
|
1114
|
+
onChanged: () => {
|
|
1115
|
+
void live.current.load();
|
|
1116
|
+
}
|
|
1117
|
+
})
|
|
1118
|
+
}), []);
|
|
1119
|
+
useAgentSurface(contribution);
|
|
1120
|
+
const submit = async () => {
|
|
1121
|
+
if (!variantId || !type) return;
|
|
1122
|
+
const amount2 = Number(quantity);
|
|
1123
|
+
if (!amount2) {
|
|
1124
|
+
toast.error("Diga quantas unidades.");
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
setSaving(true);
|
|
1128
|
+
try {
|
|
1129
|
+
const answer = await commitMovement({
|
|
1130
|
+
variantId,
|
|
1131
|
+
movementType: type,
|
|
1132
|
+
quantity: amount2,
|
|
1133
|
+
reasonCode: reasonCode || void 0,
|
|
1134
|
+
note: note.trim() || void 0,
|
|
1135
|
+
occurredAt: occurredAtFor(occurredOn, todayISO())
|
|
1136
|
+
});
|
|
1137
|
+
if (!answer.ok) {
|
|
1138
|
+
toast.error(answer.message ?? answer.error);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
toast.success(`Lan\xE7ado. Saldo agora: ${answer.available}.`);
|
|
1142
|
+
close();
|
|
1143
|
+
await load();
|
|
1144
|
+
} catch (err) {
|
|
1145
|
+
toast.error(errorMessage(err));
|
|
1146
|
+
} finally {
|
|
1147
|
+
setSaving(false);
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
if (loading) {
|
|
1151
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1152
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1153
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1154
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-24 animate-pulse rounded bg-muted" }),
|
|
1155
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-64 animate-pulse rounded bg-muted" })
|
|
1156
|
+
] }),
|
|
1157
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 w-32 animate-pulse rounded-md bg-muted" })
|
|
1158
|
+
] }),
|
|
1159
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-2", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card p-3", children: [
|
|
1160
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 flex-1 animate-pulse rounded bg-muted" }),
|
|
1161
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-12 animate-pulse rounded bg-muted" })
|
|
1162
|
+
] }, i)) })
|
|
1163
|
+
] });
|
|
1164
|
+
}
|
|
1165
|
+
if (error) {
|
|
1166
|
+
const missing = error === LEDGER_MISSING_MESSAGE;
|
|
1167
|
+
return /* @__PURE__ */ jsx("p", { className: missing ? "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground" : "rounded-lg border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive", children: error });
|
|
1168
|
+
}
|
|
1169
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1170
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
1171
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1172
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Estoque do produto" }),
|
|
1173
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
1174
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.onHand }),
|
|
1175
|
+
" em m\xE3os \xB7",
|
|
1176
|
+
" ",
|
|
1177
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.reserved }),
|
|
1178
|
+
" reservado para pedidos \xB7",
|
|
1179
|
+
" ",
|
|
1180
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available }),
|
|
1181
|
+
" dispon\xEDvel. O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio."
|
|
1182
|
+
] })
|
|
1183
|
+
] }),
|
|
1184
|
+
variantId && type === null && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1185
|
+
/* @__PURE__ */ jsxs(
|
|
1186
|
+
Button,
|
|
1187
|
+
{
|
|
1188
|
+
variant: "ghost",
|
|
1189
|
+
size: "sm",
|
|
1190
|
+
onClick: () => askAI({
|
|
1191
|
+
intro: STOCK_INTRO,
|
|
1192
|
+
hint: "O que entrou ou saiu\u2026",
|
|
1193
|
+
draft: `Entrada de unidades de ${product.name}`
|
|
1194
|
+
}),
|
|
1195
|
+
children: [
|
|
1196
|
+
/* @__PURE__ */ jsx(WandSparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1197
|
+
"Com IA"
|
|
1198
|
+
]
|
|
1199
|
+
}
|
|
1200
|
+
),
|
|
1201
|
+
/* @__PURE__ */ jsxs(Button, { onClick: () => open("entrada"), children: [
|
|
1202
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
1203
|
+
"Dar entrada"
|
|
1204
|
+
] })
|
|
1205
|
+
] })
|
|
1206
|
+
] }),
|
|
1207
|
+
!variantId && // Nunca desabilitar um botão sem dizer por quê. Sem variante o razão não
|
|
1208
|
+
// tem onde postar, e um "Lançar" apagado sem explicação faz o lojista
|
|
1209
|
+
// clicar três vezes e concluir que a tela está quebrada.
|
|
1210
|
+
/* @__PURE__ */ jsxs("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: [
|
|
1211
|
+
"Este produto ainda n\xE3o tem uma variante para o estoque apontar. Crie uma em ",
|
|
1212
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Variantes" }),
|
|
1213
|
+
" e volte aqui."
|
|
1214
|
+
] }),
|
|
1215
|
+
variants.length > 1 && /* @__PURE__ */ jsxs("label", { className: "block max-w-xs", children: [
|
|
1216
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Variante" }),
|
|
1217
|
+
/* @__PURE__ */ jsx("select", { value: variantId, onChange: (e) => setVariantId(e.target.value), className: field, children: variants.map((v) => /* @__PURE__ */ jsx("option", { value: v.id, children: Object.values(v.options ?? {}).join(" \xB7 ") || v.sku || v.id.slice(0, 8) }, v.id)) })
|
|
1218
|
+
] }),
|
|
1219
|
+
type !== null && /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card p-4 motion-safe:animate-zoom-in", children: [
|
|
1220
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: MOVEMENT_TYPES.map((t) => /* @__PURE__ */ jsx(
|
|
1221
|
+
"button",
|
|
1222
|
+
{
|
|
1223
|
+
type: "button",
|
|
1224
|
+
onClick: () => setType(t.value),
|
|
1225
|
+
className: `fz-tap rounded-button px-3 py-1.5 text-xs font-medium transition-colors ${type === t.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
1226
|
+
children: t.label
|
|
1227
|
+
},
|
|
1228
|
+
t.value
|
|
1229
|
+
)) }),
|
|
1230
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-end gap-4", children: [
|
|
1231
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1232
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Quantas?" }),
|
|
1233
|
+
/* @__PURE__ */ jsx(
|
|
1234
|
+
"input",
|
|
1235
|
+
{
|
|
1236
|
+
ref: quantityRef,
|
|
1237
|
+
type: "number",
|
|
1238
|
+
inputMode: "numeric",
|
|
1239
|
+
min: 1,
|
|
1240
|
+
value: quantity,
|
|
1241
|
+
onChange: (e) => setQuantity(e.target.value),
|
|
1242
|
+
onKeyDown: (e) => {
|
|
1243
|
+
if (e.key === "Enter") {
|
|
1244
|
+
e.preventDefault();
|
|
1245
|
+
void submit();
|
|
1246
|
+
}
|
|
1247
|
+
if (e.key === "Escape") {
|
|
1248
|
+
e.preventDefault();
|
|
1249
|
+
close();
|
|
1250
|
+
}
|
|
1251
|
+
},
|
|
1252
|
+
className: "mt-1 h-12 w-28 rounded-md border bg-background px-3 text-2xl font-semibold tabular-nums outline-none focus:border-primary"
|
|
1253
|
+
}
|
|
1254
|
+
)
|
|
1255
|
+
] }),
|
|
1256
|
+
/* @__PURE__ */ jsx("p", { "aria-live": "polite", className: "pb-2 text-sm text-muted-foreground", children: nextBalance === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1257
|
+
"Fica em ",
|
|
1258
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available })
|
|
1259
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1260
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.available }),
|
|
1261
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1.5", children: "\u2192" }),
|
|
1262
|
+
/* @__PURE__ */ jsx("span", { className: `text-base font-semibold tabular-nums ${nextBalance < 0 ? "text-destructive" : "text-foreground"}`, children: nextBalance }),
|
|
1263
|
+
nextBalance < 0 && /* @__PURE__ */ jsx("span", { className: "ml-2 text-destructive", children: "n\xE3o h\xE1 tudo isso" })
|
|
1264
|
+
] }) }),
|
|
1265
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1266
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1267
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Motivo" }),
|
|
1268
|
+
/* @__PURE__ */ jsxs("select", { value: reasonCode, onChange: (e) => setReasonCode(e.target.value), className: field, children: [
|
|
1269
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Padr\xE3o" }),
|
|
1270
|
+
reasons.map((r) => /* @__PURE__ */ jsx("option", { value: r.key, children: r.label }, r.key))
|
|
1271
|
+
] })
|
|
1272
|
+
] }),
|
|
1273
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1274
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: type === "entrada" ? "Chegou em" : "Quando" }),
|
|
1275
|
+
/* @__PURE__ */ jsx(
|
|
1276
|
+
"input",
|
|
1277
|
+
{
|
|
1278
|
+
type: "date",
|
|
1279
|
+
value: occurredOn,
|
|
1280
|
+
max: todayISO(),
|
|
1281
|
+
onChange: (e) => setOccurredOn(e.target.value || todayISO()),
|
|
1282
|
+
className: field
|
|
1283
|
+
}
|
|
1284
|
+
)
|
|
1285
|
+
] })
|
|
1286
|
+
] }),
|
|
1287
|
+
noteOpen ? /* @__PURE__ */ jsxs("label", { className: "mt-3 block", children: [
|
|
1288
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
|
|
1289
|
+
/* @__PURE__ */ jsx(
|
|
1290
|
+
"input",
|
|
1291
|
+
{
|
|
1292
|
+
autoFocus: true,
|
|
1293
|
+
value: note,
|
|
1294
|
+
onChange: (e) => setNote(e.target.value),
|
|
1295
|
+
className: field,
|
|
1296
|
+
placeholder: "Nota fiscal, quem entregou\u2026"
|
|
1297
|
+
}
|
|
1298
|
+
)
|
|
1299
|
+
] }) : /* @__PURE__ */ jsx(
|
|
1300
|
+
"button",
|
|
1301
|
+
{
|
|
1302
|
+
type: "button",
|
|
1303
|
+
onClick: () => setNoteOpen(true),
|
|
1304
|
+
className: "mt-2 text-xs font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
|
|
1305
|
+
children: "+ observa\xE7\xE3o"
|
|
1306
|
+
}
|
|
1307
|
+
),
|
|
1308
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2", children: [
|
|
1309
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || amount === 0, children: saving ? "Lan\xE7ando\u2026" : "Lan\xE7ar" }),
|
|
1310
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: close, disabled: saving, children: "Cancelar" }),
|
|
1311
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1312
|
+
amount === 0 && // O motivo de o botão estar apagado, ao lado dele.
|
|
1313
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Diga quantas unidades" })
|
|
1314
|
+
] })
|
|
1315
|
+
] }),
|
|
1316
|
+
variantId && (rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "Nenhuma posi\xE7\xE3o ainda \u2014 a primeira entrada abre o saldo." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: rows.map((r) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card p-3", children: [
|
|
1317
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium", children: r.location }),
|
|
1318
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
1319
|
+
r.reserved,
|
|
1320
|
+
" reservado"
|
|
1321
|
+
] }),
|
|
1322
|
+
/* @__PURE__ */ jsx("span", { className: "w-16 text-right text-sm font-semibold tabular-nums", children: r.available })
|
|
1323
|
+
] }, r.key)) })),
|
|
1324
|
+
movements.length > 0 && /* @__PURE__ */ jsxs("section", { className: "space-y-2 pt-2", children: [
|
|
1325
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "\xDAltimos movimentos" }),
|
|
1326
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-2", children: movements.map((m) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card px-3 py-2", children: [
|
|
1327
|
+
/* @__PURE__ */ jsx("span", { className: `w-14 text-sm font-semibold tabular-nums ${m.quantity < 0 ? "text-destructive" : "text-emerald-700"}`, children: m.quantity > 0 ? `+${m.quantity}` : m.quantity }),
|
|
1328
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm", children: m.reasonCode ?? m.kind }),
|
|
1329
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(m.occurredAt ?? m.createdAt).toLocaleDateString("pt-BR") })
|
|
1330
|
+
] }, m.id)) })
|
|
1331
|
+
] })
|
|
1332
|
+
] });
|
|
1333
|
+
}
|
|
725
1334
|
|
|
726
1335
|
// src/data/registry-routes.ts
|
|
727
1336
|
var SHIPPING_SETTINGS_TAB = "shop-shipping";
|
|
@@ -743,20 +1352,20 @@ function shippingRegistryIdFromHash(hash) {
|
|
|
743
1352
|
}
|
|
744
1353
|
var PACKAGES_ROUTE = shippingRegistryPath(PACKAGES_REGISTRY);
|
|
745
1354
|
function PackageSelect({ value, onChange, getProvider }) {
|
|
746
|
-
const shop =
|
|
747
|
-
const [rows, setRows] =
|
|
748
|
-
const [open, setOpen] =
|
|
749
|
-
const [creating, setCreating] =
|
|
750
|
-
const ref =
|
|
751
|
-
const load =
|
|
1355
|
+
const shop = React9.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
|
|
1356
|
+
const [rows, setRows] = React9.useState(null);
|
|
1357
|
+
const [open, setOpen] = React9.useState(false);
|
|
1358
|
+
const [creating, setCreating] = React9.useState(false);
|
|
1359
|
+
const ref = React9.useRef(null);
|
|
1360
|
+
const load = React9.useCallback(() => {
|
|
752
1361
|
if (typeof shop.listShippingPackages !== "function") {
|
|
753
1362
|
setRows([]);
|
|
754
1363
|
return;
|
|
755
1364
|
}
|
|
756
1365
|
void shop.listShippingPackages().then(setRows).catch(() => setRows([]));
|
|
757
1366
|
}, [shop]);
|
|
758
|
-
|
|
759
|
-
|
|
1367
|
+
React9.useEffect(load, [load]);
|
|
1368
|
+
React9.useEffect(() => {
|
|
760
1369
|
if (!open) return;
|
|
761
1370
|
const away = (event) => {
|
|
762
1371
|
if (ref.current && !ref.current.contains(event.target)) setOpen(false);
|
|
@@ -851,8 +1460,8 @@ function PackageSelect({ value, onChange, getProvider }) {
|
|
|
851
1460
|
] });
|
|
852
1461
|
}
|
|
853
1462
|
function NewPackageModal({ onClose, onCreated }) {
|
|
854
|
-
const [error, setError] =
|
|
855
|
-
const provider =
|
|
1463
|
+
const [error, setError] = React9.useState(null);
|
|
1464
|
+
const provider = React9.useMemo(
|
|
856
1465
|
() => resolveDataProvider(packagesRegistry.entity),
|
|
857
1466
|
[]
|
|
858
1467
|
);
|
|
@@ -898,9 +1507,9 @@ function Option({ label, hint, active, onClick }) {
|
|
|
898
1507
|
);
|
|
899
1508
|
}
|
|
900
1509
|
function ShippingProfileSelect({ value, onChange, getProvider }) {
|
|
901
|
-
const shop =
|
|
902
|
-
const [rows, setRows] =
|
|
903
|
-
|
|
1510
|
+
const shop = React9.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
|
|
1511
|
+
const [rows, setRows] = React9.useState(null);
|
|
1512
|
+
React9.useEffect(() => {
|
|
904
1513
|
let cancelled = false;
|
|
905
1514
|
if (typeof shop.listShippingProfiles !== "function") {
|
|
906
1515
|
setRows([]);
|
|
@@ -1077,14 +1686,27 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1077
1686
|
searchable: true,
|
|
1078
1687
|
showInTable: false
|
|
1079
1688
|
},
|
|
1689
|
+
// Não se DIGITA saldo.
|
|
1690
|
+
//
|
|
1691
|
+
// Este campo era um número editável, e um número editável convida a
|
|
1692
|
+
// resposta errada: alguém conta a prateleira, acha 18, escreve 18, e o
|
|
1693
|
+
// razão perde POR QUE mudou — se chegaram seis ou se sumiram quatro. É
|
|
1694
|
+
// exatamente o que um razão existe para impedir, e a 0036 já tratava a
|
|
1695
|
+
// escrita direta aqui como um ajuste às cegas para não perder o movimento.
|
|
1696
|
+
//
|
|
1697
|
+
// Vira reflexo: mostra o saldo, e o ato acontece em Posições, onde ele tem
|
|
1698
|
+
// espécie, motivo e data.
|
|
1080
1699
|
{
|
|
1081
1700
|
key: "inventoryCount",
|
|
1082
1701
|
label: "Estoque",
|
|
1083
1702
|
type: "number",
|
|
1084
1703
|
group: "inventory",
|
|
1085
1704
|
min: 0,
|
|
1086
|
-
|
|
1087
|
-
|
|
1705
|
+
// `showInForm: false` e não um campo desabilitado: some do formulário de
|
|
1706
|
+
// edição, continua na tabela e na ficha. Mostra-se o saldo; digita-se o
|
|
1707
|
+
// movimento.
|
|
1708
|
+
showInForm: false,
|
|
1709
|
+
hint: "Saldo do raz\xE3o. Entrada, sa\xEDda e perda se lan\xE7am na aba Posi\xE7\xF5es.",
|
|
1088
1710
|
showInTable: true,
|
|
1089
1711
|
sortable: true,
|
|
1090
1712
|
renderCell: (v) => {
|
|
@@ -1182,7 +1804,7 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1182
1804
|
group: "shipping",
|
|
1183
1805
|
min: 0,
|
|
1184
1806
|
placeholder: "0,4",
|
|
1185
|
-
hint: "Do produto embalado.
|
|
1807
|
+
hint: "Do produto, j\xE1 embalado. As medidas v\xEAm da embalagem escolhida acima.",
|
|
1186
1808
|
showInTable: false,
|
|
1187
1809
|
visibleWhen: (values) => values.isPhysical !== false
|
|
1188
1810
|
},
|
|
@@ -1199,9 +1821,19 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1199
1821
|
// Em centímetros, sem seletor de unidade: a coluna é `*_cm` e a caixa da
|
|
1200
1822
|
// loja também. Duas unidades para a mesma medida é como o peso passou
|
|
1201
1823
|
// quatro anos sendo lido errado.
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1824
|
+
// A medida é da EMBALAGEM, não do produto.
|
|
1825
|
+
//
|
|
1826
|
+
// O seletor logo acima já associa o produto a uma embalagem cadastrada da
|
|
1827
|
+
// loja (0047) — e é lá que altura, largura e comprimento vivem. Repetir os
|
|
1828
|
+
// três aqui pedia a mesma informação duas vezes por caminhos diferentes, e
|
|
1829
|
+
// as duas divergiam no dia em que alguém trocasse a caixa: a cotação lê a
|
|
1830
|
+
// embalagem, o formulário mostrava outro número.
|
|
1831
|
+
//
|
|
1832
|
+
// As colunas continuam existindo — um catálogo anterior à 0047 tem produto
|
|
1833
|
+
// medido e sem embalagem, e a cotação ainda os lê. O que sai é a pergunta.
|
|
1834
|
+
{ key: "heightCm", label: "Altura (cm)", type: "number", group: "shipping", min: 0, showInForm: false, showInTable: false },
|
|
1835
|
+
{ key: "widthCm", label: "Largura (cm)", type: "number", group: "shipping", min: 0, showInForm: false, showInTable: false },
|
|
1836
|
+
{ key: "lengthCm", label: "Comprimento (cm)", type: "number", group: "shipping", min: 0, showInForm: false, showInTable: false },
|
|
1205
1837
|
{
|
|
1206
1838
|
// Declarado aqui para poder viajar — para a transportadora, para o motoboy
|
|
1207
1839
|
// ou para quem embala. O que a loja FAZ com um carrinho refrigerado é uma
|
|
@@ -1228,6 +1860,17 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1228
1860
|
namePlural: "Produtos",
|
|
1229
1861
|
icon: "Package",
|
|
1230
1862
|
displayField: "name",
|
|
1863
|
+
// Qual tabela esta tela É. Sem isto, `deriveEntityKey` cai no rótulo — e a
|
|
1864
|
+
// chave da entidade vira `produto`, que muda com uma tradução. Quem avisa
|
|
1865
|
+
// "escrevi aqui" nomeia a TABELA (é o que emitDataChanged invalida no cache),
|
|
1866
|
+
// então sem esta linha o aviso não encontra a lista: o barramento casa
|
|
1867
|
+
// tabela-com-tabela, e o lado do assinante estava vazio.
|
|
1868
|
+
//
|
|
1869
|
+
// Não muda o caminho do dado: o índice do plugin passa um
|
|
1870
|
+
// `dataProvider: createShopProductDataProvider(...)` explícito, que vence o
|
|
1871
|
+
// provider genérico que `data.table` acionaria — mesma forma da entidade de
|
|
1872
|
+
// cliente logo ao lado.
|
|
1873
|
+
data: { table: "plg_shop_products" },
|
|
1231
1874
|
fields,
|
|
1232
1875
|
fieldGroups: [
|
|
1233
1876
|
// imageSlot renders the scaffolding's image uploader alongside the group.
|
|
@@ -1238,7 +1881,7 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1238
1881
|
{
|
|
1239
1882
|
id: "shipping",
|
|
1240
1883
|
label: "Envio",
|
|
1241
|
-
description: "
|
|
1884
|
+
description: "Em que caixa este produto sai e quanto ele pesa. A medida \xE9 da embalagem; o peso \xE9 do produto. Sem embalagem escolhida a transportadora \xE9 consultada com a caixa padr\xE3o da loja, e o frete cotado pode n\xE3o ser o frete pago.",
|
|
1242
1885
|
columns: 3
|
|
1243
1886
|
}
|
|
1244
1887
|
],
|
|
@@ -1262,139 +1905,21 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1262
1905
|
icon: "Layers",
|
|
1263
1906
|
component: ProductVariantsTab,
|
|
1264
1907
|
props: { getProvider }
|
|
1908
|
+
},
|
|
1909
|
+
// Separada de Variantes de propósito: aquela edita O QUE se vende,
|
|
1910
|
+
// esta responde ONDE estão as unidades — e a ficha só sabia dizer um
|
|
1911
|
+
// número só, que não distingue a loja do depósito nem o que já está
|
|
1912
|
+
// prometido a um pedido.
|
|
1913
|
+
{
|
|
1914
|
+
id: "positions",
|
|
1915
|
+
label: "Posi\xE7\xF5es",
|
|
1916
|
+
icon: "Boxes",
|
|
1917
|
+
component: ProductPositionsTab,
|
|
1918
|
+
props: { getProvider }
|
|
1265
1919
|
}
|
|
1266
1920
|
] : void 0
|
|
1267
1921
|
};
|
|
1268
1922
|
}
|
|
1269
|
-
function money2(value) {
|
|
1270
|
-
const n = Number(value);
|
|
1271
|
-
if (!Number.isFinite(n)) return "R$ 0,00";
|
|
1272
|
-
return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(n);
|
|
1273
|
-
}
|
|
1274
|
-
function buildShopCustomerEntity() {
|
|
1275
|
-
const fields = [
|
|
1276
|
-
// — Contato —
|
|
1277
|
-
{
|
|
1278
|
-
key: "firstName",
|
|
1279
|
-
label: "Nome",
|
|
1280
|
-
type: "text",
|
|
1281
|
-
required: true,
|
|
1282
|
-
group: "contact",
|
|
1283
|
-
placeholder: "Maria",
|
|
1284
|
-
searchable: true,
|
|
1285
|
-
showInTable: true,
|
|
1286
|
-
sortable: true,
|
|
1287
|
-
renderCell: (_v, row) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1288
|
-
/* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: [row.firstName, row.lastName].filter(Boolean).join(" ") }),
|
|
1289
|
-
row.email && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: row.email })
|
|
1290
|
-
] })
|
|
1291
|
-
},
|
|
1292
|
-
{ key: "lastName", label: "Sobrenome", type: "text", group: "contact", placeholder: "Silva", searchable: true, showInTable: false },
|
|
1293
|
-
{
|
|
1294
|
-
key: "email",
|
|
1295
|
-
label: "E-mail",
|
|
1296
|
-
type: "text",
|
|
1297
|
-
group: "contact",
|
|
1298
|
-
placeholder: "maria@exemplo.com",
|
|
1299
|
-
searchable: true,
|
|
1300
|
-
hint: "\xC9 por ele que o pedido \xE9 vinculado ao cliente no checkout.",
|
|
1301
|
-
showInTable: false
|
|
1302
|
-
},
|
|
1303
|
-
{
|
|
1304
|
-
key: "phone",
|
|
1305
|
-
label: "Telefone",
|
|
1306
|
-
type: "text",
|
|
1307
|
-
group: "contact",
|
|
1308
|
-
placeholder: "(21) 99999-0000",
|
|
1309
|
-
searchable: true,
|
|
1310
|
-
showInTable: true,
|
|
1311
|
-
sortable: false,
|
|
1312
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: v || "\u2014" })
|
|
1313
|
-
},
|
|
1314
|
-
// The column, the type and the provider all carried `document` already; the
|
|
1315
|
-
// form was the only place it could not be typed. Which meant a store could
|
|
1316
|
-
// see "cliente sem CPF/CNPJ" on every Bling run and have nowhere to fix it.
|
|
1317
|
-
{
|
|
1318
|
-
key: "document",
|
|
1319
|
-
label: "CPF / CNPJ",
|
|
1320
|
-
type: "text",
|
|
1321
|
-
group: "contact",
|
|
1322
|
-
placeholder: "000.000.000-00",
|
|
1323
|
-
searchable: true,
|
|
1324
|
-
hint: "Necess\xE1rio para emitir nota \u2014 sem ele o cliente n\xE3o sobe para o ERP.",
|
|
1325
|
-
showInTable: false
|
|
1326
|
-
},
|
|
1327
|
-
// — Histórico (somente leitura) —
|
|
1328
|
-
{
|
|
1329
|
-
key: "ordersCount",
|
|
1330
|
-
label: "Pedidos",
|
|
1331
|
-
type: "number",
|
|
1332
|
-
showInForm: false,
|
|
1333
|
-
showInTable: true,
|
|
1334
|
-
sortable: true,
|
|
1335
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right", children: Number(v) || 0 })
|
|
1336
|
-
},
|
|
1337
|
-
{
|
|
1338
|
-
key: "totalSpent",
|
|
1339
|
-
label: "Total gasto",
|
|
1340
|
-
type: "number",
|
|
1341
|
-
showInForm: false,
|
|
1342
|
-
showInTable: true,
|
|
1343
|
-
sortable: true,
|
|
1344
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money2(v) })
|
|
1345
|
-
},
|
|
1346
|
-
// — Interno —
|
|
1347
|
-
{
|
|
1348
|
-
key: "notes",
|
|
1349
|
-
label: "Observa\xE7\xF5es",
|
|
1350
|
-
type: "textarea",
|
|
1351
|
-
group: "internal",
|
|
1352
|
-
span: 2,
|
|
1353
|
-
placeholder: "Prefer\xEAncias, restri\xE7\xF5es, combinados de entrega\u2026",
|
|
1354
|
-
hint: "Vis\xEDvel s\xF3 no admin. O cliente nunca v\xEA.",
|
|
1355
|
-
showInTable: false
|
|
1356
|
-
}
|
|
1357
|
-
];
|
|
1358
|
-
return {
|
|
1359
|
-
name: "Cliente",
|
|
1360
|
-
namePlural: "Clientes",
|
|
1361
|
-
icon: "Users",
|
|
1362
|
-
displayField: "firstName",
|
|
1363
|
-
// `person` layout brings the shared archetype tabs — including Endereços,
|
|
1364
|
-
// which reads the core addresses table and therefore works in every app.
|
|
1365
|
-
layout: "person",
|
|
1366
|
-
// Without archetypeKind the shell shows every person tab, including the
|
|
1367
|
-
// staff-only Agenda (its visibleFor filter only runs when a kind is set).
|
|
1368
|
-
data: { table: "plg_shop_customers", archetypeKind: "customer" },
|
|
1369
|
-
fields,
|
|
1370
|
-
// The SAME tab and the SAME provider beauty-saas uses. Since the shop's
|
|
1371
|
-
// orders now live in public.orders with party_id set, the canonical provider
|
|
1372
|
-
// already returns them — a shop-specific adapter would be a second
|
|
1373
|
-
// implementation of "list this person's orders", which is how two apps end
|
|
1374
|
-
// up with two different answers to the same question.
|
|
1375
|
-
detailTabs: [{
|
|
1376
|
-
id: "orders",
|
|
1377
|
-
label: "Pedidos",
|
|
1378
|
-
icon: "ShoppingBag",
|
|
1379
|
-
aliases: ["appointments", "quotes"],
|
|
1380
|
-
component: ClientOrdersTab,
|
|
1381
|
-
props: {
|
|
1382
|
-
provider: createClientOrdersProvider(),
|
|
1383
|
-
currency: { code: "BRL", locale: "pt-BR" },
|
|
1384
|
-
// The one part that legitimately differs per app: where a document
|
|
1385
|
-
// leads. Beauty sends a booking to the agenda and an invoice to
|
|
1386
|
-
// receivables; a shop order goes to the order screen.
|
|
1387
|
-
navigator: {
|
|
1388
|
-
onNavigate: (doc) => adminNavigateTo(`/shop/orders/${doc.id}`)
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
}],
|
|
1392
|
-
fieldGroups: [
|
|
1393
|
-
{ id: "contact", label: "Contato", columns: 2 },
|
|
1394
|
-
{ id: "internal", label: "Notas internas", columns: 1 }
|
|
1395
|
-
]
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
1923
|
var STATUS_TONE2 = {
|
|
1399
1924
|
active: "bg-emerald-100 text-emerald-800",
|
|
1400
1925
|
draft: "bg-gray-100 text-gray-700",
|
|
@@ -1556,6 +2081,34 @@ function buildShopDiscountEntity() {
|
|
|
1556
2081
|
facets: [{ field: "status", allLabel: "Todos" }]
|
|
1557
2082
|
};
|
|
1558
2083
|
}
|
|
2084
|
+
|
|
2085
|
+
// src/views/discount-agent.ts
|
|
2086
|
+
var DISCOUNT_CREATE_INSTRUCTIONS = `
|
|
2087
|
+
Esta lista \xE9 a de Descontos da loja. Um registro aqui \xE9 um CUPOM que o comprador digita no carrinho.
|
|
2088
|
+
|
|
2089
|
+
O c\xF3digo \xE9 seu para escrever:
|
|
2090
|
+
\xB7 Se o lojista n\xE3o ditar um, invente a partir da campanha: MAI\xDASCULAS, s\xF3 letras A-Z e n\xFAmeros, de 4 a 16 caracteres, sem espa\xE7o, acento, h\xEDfen ou pontua\xE7\xE3o.
|
|
2091
|
+
\xB7 Curto e dit\xE1vel ao telefone. Evite 0/O e 1/I juntos, evite palavra que o comprador precise soletrar.
|
|
2092
|
+
\xB7 Nunca repita um c\xF3digo que j\xE1 aparece em \`taken.code\`. Se a cria\xE7\xE3o voltar \`duplicate_value\`, escolha outro e chame de novo \u2014 sem perguntar nada ao lojista.
|
|
2093
|
+
\xB7 \`title\` \xE9 o nome interno, s\xF3 o lojista v\xEA: escreva a campanha em portugu\xEAs ("Frete gr\xE1tis fim de semana"), n\xE3o repita o c\xF3digo.
|
|
2094
|
+
|
|
2095
|
+
A regra:
|
|
2096
|
+
\xB7 "frete gr\xE1tis" \u2192 \`type: free_shipping\` e SEM \`value\` \u2014 frete gr\xE1tis n\xE3o desconta o subtotal.
|
|
2097
|
+
\xB7 "10%", "10 por cento" \u2192 \`type: percentage\`, \`value: 10\`.
|
|
2098
|
+
\xB7 "R$ 50", "50 reais de desconto" \u2192 \`type: fixed_amount\`, \`value: 50\`.
|
|
2099
|
+
\xB7 "acima de R$ 200", "a partir de 200" \u2192 \`minSubtotal: 200\`.
|
|
2100
|
+
\xB7 "s\xF3 a primeira compra", "uma vez por cliente" \u2192 \`oncePerCustomer: true\`.
|
|
2101
|
+
\xB7 "os 100 primeiros" \u2192 \`usageLimit: 100\`.
|
|
2102
|
+
\xB7 Prazo vira data em \`endsAt\`: "at\xE9 o fim dessa semana" \xE9 o domingo desta semana, "at\xE9 o fim do m\xEAs" \xE9 o \xFAltimo dia do m\xEAs. Sem prazo dito, deixe vazio \u2014 cupom sem fim \xE9 uma escolha, n\xE3o um esquecimento.
|
|
2103
|
+
|
|
2104
|
+
Status:
|
|
2105
|
+
\xB7 \`active\` por padr\xE3o. O lojista est\xE1 pedindo uma promo\xE7\xE3o para divulgar hoje; um cupom que nasce dormindo \xE9 um cupom que o cliente tenta usar e n\xE3o funciona.
|
|
2106
|
+
\xB7 \`draft\` s\xF3 quando ele disser que \xE9 para deixar pronto para depois.
|
|
2107
|
+
|
|
2108
|
+
O que voc\xEA N\xC3O inventa: o tamanho do desconto. Se ele disser "desconto para clientes VIP" sem dizer quanto, essa \xE9 a \xFAnica pergunta a fazer \u2014 e fa\xE7a s\xF3 ela.
|
|
2109
|
+
|
|
2110
|
+
Depois de criar, diga em uma linha o c\xF3digo, o efeito e at\xE9 quando vale, para ele j\xE1 poder divulgar.
|
|
2111
|
+
`.trim();
|
|
1559
2112
|
function buildShopCategoryEntity() {
|
|
1560
2113
|
const fields = [
|
|
1561
2114
|
{
|
|
@@ -1744,7 +2297,7 @@ var STATUS_LABEL2 = {
|
|
|
1744
2297
|
disabled: "Desativado"
|
|
1745
2298
|
};
|
|
1746
2299
|
function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
1747
|
-
const
|
|
2300
|
+
const money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
|
|
1748
2301
|
const fields = [
|
|
1749
2302
|
{
|
|
1750
2303
|
key: "code",
|
|
@@ -1772,7 +2325,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
|
1772
2325
|
hint: "Valor de face do cart\xE3o. N\xE3o muda depois de emitido.",
|
|
1773
2326
|
showInTable: true,
|
|
1774
2327
|
sortable: true,
|
|
1775
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children:
|
|
2328
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money2(v) })
|
|
1776
2329
|
},
|
|
1777
2330
|
{
|
|
1778
2331
|
key: "balance",
|
|
@@ -1786,7 +2339,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
|
1786
2339
|
hint: "O que ainda pode ser gasto. Cai a cada resgate.",
|
|
1787
2340
|
showInTable: true,
|
|
1788
2341
|
sortable: true,
|
|
1789
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children:
|
|
2342
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money2(v) })
|
|
1790
2343
|
},
|
|
1791
2344
|
{
|
|
1792
2345
|
key: "status",
|
|
@@ -1921,20 +2474,20 @@ function buildShopCustomerGroupEntity() {
|
|
|
1921
2474
|
fieldGroups: [{ id: "basic", label: "Grupo", columns: 2 }]
|
|
1922
2475
|
};
|
|
1923
2476
|
}
|
|
1924
|
-
function describeSegment(s,
|
|
2477
|
+
function describeSegment(s, money2) {
|
|
1925
2478
|
const parts = [];
|
|
1926
2479
|
if (s.minOrders != null && s.maxOrders != null) parts.push(`${s.minOrders}\u2013${s.maxOrders} pedidos`);
|
|
1927
2480
|
else if (s.minOrders != null) parts.push(`${s.minOrders}+ pedidos`);
|
|
1928
2481
|
else if (s.maxOrders != null) parts.push(`at\xE9 ${s.maxOrders} pedidos`);
|
|
1929
|
-
if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${
|
|
1930
|
-
else if (s.minSpent != null) parts.push(`gastou ${
|
|
1931
|
-
else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${
|
|
2482
|
+
if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${money2(s.minSpent)}\u2013${money2(s.maxSpent)}`);
|
|
2483
|
+
else if (s.minSpent != null) parts.push(`gastou ${money2(s.minSpent)}+`);
|
|
2484
|
+
else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${money2(s.maxSpent)}`);
|
|
1932
2485
|
if (s.joinedAfter) parts.push(`entrou ap\xF3s ${new Date(s.joinedAfter).toLocaleDateString("pt-BR")}`);
|
|
1933
2486
|
if (s.joinedBefore) parts.push(`entrou antes de ${new Date(s.joinedBefore).toLocaleDateString("pt-BR")}`);
|
|
1934
2487
|
return parts.length ? parts.join(" \xB7 ") : "Todos os clientes";
|
|
1935
2488
|
}
|
|
1936
2489
|
function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
|
|
1937
|
-
const
|
|
2490
|
+
const money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
|
|
1938
2491
|
const fields = [
|
|
1939
2492
|
{
|
|
1940
2493
|
key: "name",
|
|
@@ -2011,7 +2564,7 @@ function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
|
|
|
2011
2564
|
showInForm: false,
|
|
2012
2565
|
showInTable: true,
|
|
2013
2566
|
sortable: false,
|
|
2014
|
-
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row,
|
|
2567
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money2) })
|
|
2015
2568
|
},
|
|
2016
2569
|
{
|
|
2017
2570
|
key: "matchCount",
|
|
@@ -2341,17 +2894,17 @@ function createShopCustomerSegmentDataProvider(provider) {
|
|
|
2341
2894
|
}
|
|
2342
2895
|
var DEBOUNCE_MS = 400;
|
|
2343
2896
|
function useStoreSettings(provider, defaults = {}) {
|
|
2344
|
-
const shop =
|
|
2897
|
+
const shop = React9.useMemo(
|
|
2345
2898
|
() => provider ? resolve(provider) : getShopProvider(),
|
|
2346
2899
|
[provider]
|
|
2347
2900
|
);
|
|
2348
2901
|
const supported = typeof shop.getStoreSettings === "function" && typeof shop.updateStoreSettings === "function";
|
|
2349
|
-
const [stored, setStored] =
|
|
2350
|
-
const [loading, setLoading] =
|
|
2351
|
-
const [saving, setSaving] =
|
|
2352
|
-
const [error, setError] =
|
|
2353
|
-
const [loadError, setLoadError] =
|
|
2354
|
-
|
|
2902
|
+
const [stored, setStored] = React9.useState(null);
|
|
2903
|
+
const [loading, setLoading] = React9.useState(supported);
|
|
2904
|
+
const [saving, setSaving] = React9.useState(false);
|
|
2905
|
+
const [error, setError] = React9.useState(null);
|
|
2906
|
+
const [loadError, setLoadError] = React9.useState(null);
|
|
2907
|
+
React9.useEffect(() => {
|
|
2355
2908
|
if (!supported) return;
|
|
2356
2909
|
let cancelled = false;
|
|
2357
2910
|
setLoading(true);
|
|
@@ -2367,12 +2920,12 @@ function useStoreSettings(provider, defaults = {}) {
|
|
|
2367
2920
|
cancelled = true;
|
|
2368
2921
|
};
|
|
2369
2922
|
}, [shop, supported]);
|
|
2370
|
-
const pendingRef =
|
|
2371
|
-
const timerRef =
|
|
2372
|
-
|
|
2923
|
+
const pendingRef = React9.useRef({});
|
|
2924
|
+
const timerRef = React9.useRef(null);
|
|
2925
|
+
React9.useEffect(() => () => {
|
|
2373
2926
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2374
2927
|
}, []);
|
|
2375
|
-
const set =
|
|
2928
|
+
const set = React9.useCallback(
|
|
2376
2929
|
(patch) => {
|
|
2377
2930
|
setStored((current) => ({ ...current ?? {}, ...patch }));
|
|
2378
2931
|
if (!supported) return;
|
|
@@ -2388,7 +2941,7 @@ function useStoreSettings(provider, defaults = {}) {
|
|
|
2388
2941
|
},
|
|
2389
2942
|
[shop, supported]
|
|
2390
2943
|
);
|
|
2391
|
-
const value =
|
|
2944
|
+
const value = React9.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
|
|
2392
2945
|
return { value, stored, set, loading, saving, error, loadError, supported };
|
|
2393
2946
|
}
|
|
2394
2947
|
function SettingsState({
|
|
@@ -2617,25 +3170,25 @@ function PaymentsSettings({ provider }) {
|
|
|
2617
3170
|
const settings = useStoreSettings(provider);
|
|
2618
3171
|
const { value, set } = settings;
|
|
2619
3172
|
const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
|
|
2620
|
-
const [adding, setAdding] =
|
|
2621
|
-
const [selected, setSelected] =
|
|
3173
|
+
const [adding, setAdding] = React9.useState(false);
|
|
3174
|
+
const [selected, setSelected] = React9.useState(null);
|
|
2622
3175
|
const panelAvailable = useConnectorsPanelAvailable();
|
|
2623
|
-
const openJourney =
|
|
3176
|
+
const openJourney = React9.useCallback(
|
|
2624
3177
|
(connector) => {
|
|
2625
3178
|
if (panelAvailable) openConnectorsPanel(connector);
|
|
2626
3179
|
else setSelected(connector);
|
|
2627
3180
|
},
|
|
2628
3181
|
[panelAvailable]
|
|
2629
3182
|
);
|
|
2630
|
-
const payment =
|
|
3183
|
+
const payment = React9.useMemo(
|
|
2631
3184
|
() => (connectors ?? []).filter((c) => c.category && PAYMENT_CATEGORIES.includes(c.category)),
|
|
2632
3185
|
[connectors]
|
|
2633
3186
|
);
|
|
2634
|
-
const live =
|
|
3187
|
+
const live = React9.useMemo(() => payment.filter((c) => c.state === "connected"), [payment]);
|
|
2635
3188
|
const chosen = value.paymentMethods ?? [];
|
|
2636
3189
|
const staleProvider = !!value.paymentProvider && !live.some((c) => c.slug === value.paymentProvider) && live.length === 0;
|
|
2637
|
-
const movedFrom =
|
|
2638
|
-
|
|
3190
|
+
const movedFrom = React9.useRef(null);
|
|
3191
|
+
React9.useEffect(() => {
|
|
2639
3192
|
if (!settings.supported || settings.loading) return;
|
|
2640
3193
|
const next = resolvePaymentProvider(value.paymentProvider, live.map((c) => c.slug));
|
|
2641
3194
|
if (!next) return;
|
|
@@ -2676,7 +3229,7 @@ function PaymentsSettings({ provider }) {
|
|
|
2676
3229
|
] }),
|
|
2677
3230
|
/* @__PURE__ */ jsx(AddConnectorButton, { onClick: () => setAdding(true) })
|
|
2678
3231
|
] }),
|
|
2679
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-
|
|
3232
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card shadow-sm", children: payment.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Nenhum provedor de pagamento dispon\xEDvel para este app ainda." }) : /* @__PURE__ */ jsxs("ul", { className: "divide-y", role: "radiogroup", "aria-label": "Quem cobra", children: [
|
|
2680
3233
|
payment.map((connector) => /* @__PURE__ */ jsx(
|
|
2681
3234
|
PaymentProviderRow,
|
|
2682
3235
|
{
|
|
@@ -2841,17 +3394,17 @@ function CheckoutSettings({ provider }) {
|
|
|
2841
3394
|
{
|
|
2842
3395
|
title: "Campos do cliente",
|
|
2843
3396
|
description: "O que o comprador v\xEA e o que ele precisa preencher. Campos travados s\xE3o os que a entrega n\xE3o dispensa.",
|
|
2844
|
-
children: FIELDS.map((
|
|
3397
|
+
children: FIELDS.map((field2) => /* @__PURE__ */ jsx(
|
|
2845
3398
|
SelectRow,
|
|
2846
3399
|
{
|
|
2847
|
-
label:
|
|
2848
|
-
description:
|
|
2849
|
-
value: toRequirement(fields[
|
|
2850
|
-
options:
|
|
2851
|
-
onChange: (choice) => setField(
|
|
2852
|
-
disabled:
|
|
3400
|
+
label: field2.label,
|
|
3401
|
+
description: field2.hint,
|
|
3402
|
+
value: toRequirement(fields[field2.key], !!field2.locked),
|
|
3403
|
+
options: field2.locked ? [{ value: "required", label: "Pedir (obrigat\xF3rio)" }] : REQUIREMENT,
|
|
3404
|
+
onChange: (choice) => setField(field2.key, choice),
|
|
3405
|
+
disabled: field2.locked
|
|
2853
3406
|
},
|
|
2854
|
-
|
|
3407
|
+
field2.key
|
|
2855
3408
|
))
|
|
2856
3409
|
}
|
|
2857
3410
|
)
|
|
@@ -2867,11 +3420,11 @@ function PackagesSettings({ basePath = shippingRegistryPath(PACKAGES_REGISTRY) }
|
|
|
2867
3420
|
] });
|
|
2868
3421
|
}
|
|
2869
3422
|
function HandlingSettings({ settings, provider }) {
|
|
2870
|
-
const shop =
|
|
3423
|
+
const shop = React9.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
|
|
2871
3424
|
const { value, set, loading, supported } = settings;
|
|
2872
|
-
const [noteDraft, setNoteDraft] =
|
|
2873
|
-
const [profiles, setProfiles] =
|
|
2874
|
-
|
|
3425
|
+
const [noteDraft, setNoteDraft] = React9.useState(null);
|
|
3426
|
+
const [profiles, setProfiles] = React9.useState(null);
|
|
3427
|
+
React9.useEffect(() => {
|
|
2875
3428
|
let cancelled = false;
|
|
2876
3429
|
void shop.listShippingProfiles?.().then((list) => {
|
|
2877
3430
|
if (!cancelled) setProfiles(list);
|
|
@@ -2894,7 +3447,7 @@ function HandlingSettings({ settings, provider }) {
|
|
|
2894
3447
|
] }),
|
|
2895
3448
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Um perfil \xE9 um grupo de produtos com zonas pr\xF3prias \u2014 refrigerados, fr\xE1geis, volumosos, sob encomenda. Um perfil com s\xF3 as suas zonas e o gateway desligado \xE9 como se diz \xABisto n\xE3o vai de transportadora\xBB. Produto sem perfil usa o geral, que \xE9 o de todos hoje." })
|
|
2896
3449
|
] }),
|
|
2897
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-
|
|
3450
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
|
|
2898
3451
|
"button",
|
|
2899
3452
|
{
|
|
2900
3453
|
type: "button",
|
|
@@ -3001,46 +3554,46 @@ var FREIGHT_SOURCE = [
|
|
|
3001
3554
|
}
|
|
3002
3555
|
];
|
|
3003
3556
|
function ShippingSettings({ provider }) {
|
|
3004
|
-
const [registryId, setRegistryId] =
|
|
3005
|
-
|
|
3557
|
+
const [registryId, setRegistryId] = React9.useState(shippingRegistryIdFromHash);
|
|
3558
|
+
React9.useEffect(() => {
|
|
3006
3559
|
const handler = () => setRegistryId(shippingRegistryIdFromHash());
|
|
3007
3560
|
window.addEventListener("hashchange", handler);
|
|
3008
3561
|
return () => window.removeEventListener("hashchange", handler);
|
|
3009
3562
|
}, []);
|
|
3010
|
-
const shop =
|
|
3011
|
-
const registries =
|
|
3563
|
+
const shop = React9.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
|
|
3564
|
+
const registries = React9.useMemo(
|
|
3012
3565
|
() => buildShopRegistries(provider ?? getShopProvider),
|
|
3013
3566
|
[provider]
|
|
3014
3567
|
);
|
|
3015
3568
|
const storeSettings = useStoreSettings(provider);
|
|
3016
3569
|
const supported = typeof shop.getShippingSettings === "function" && typeof shop.updateShippingSettings === "function";
|
|
3017
|
-
const [row, setRow] =
|
|
3018
|
-
const [zones, setZones] =
|
|
3019
|
-
const [locations, setLocations] =
|
|
3020
|
-
const [loading, setLoading] =
|
|
3021
|
-
const [saving, setSaving] =
|
|
3022
|
-
const [error, setError] =
|
|
3023
|
-
const [loadError, setLoadError] =
|
|
3024
|
-
const [flatDraft, setFlatDraft] =
|
|
3025
|
-
const [freeDraft, setFreeDraft] =
|
|
3026
|
-
const [flatBad, setFlatBad] =
|
|
3027
|
-
const [freeBad, setFreeBad] =
|
|
3570
|
+
const [row, setRow] = React9.useState(null);
|
|
3571
|
+
const [zones, setZones] = React9.useState(null);
|
|
3572
|
+
const [locations, setLocations] = React9.useState(null);
|
|
3573
|
+
const [loading, setLoading] = React9.useState(supported);
|
|
3574
|
+
const [saving, setSaving] = React9.useState(false);
|
|
3575
|
+
const [error, setError] = React9.useState(null);
|
|
3576
|
+
const [loadError, setLoadError] = React9.useState(null);
|
|
3577
|
+
const [flatDraft, setFlatDraft] = React9.useState("");
|
|
3578
|
+
const [freeDraft, setFreeDraft] = React9.useState("");
|
|
3579
|
+
const [flatBad, setFlatBad] = React9.useState(false);
|
|
3580
|
+
const [freeBad, setFreeBad] = React9.useState(false);
|
|
3028
3581
|
const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
|
|
3029
|
-
const [selected, setSelected] =
|
|
3582
|
+
const [selected, setSelected] = React9.useState(null);
|
|
3030
3583
|
const panelAvailable = useConnectorsPanelAvailable();
|
|
3031
|
-
const openJourney =
|
|
3584
|
+
const openJourney = React9.useCallback(
|
|
3032
3585
|
(connector) => {
|
|
3033
3586
|
if (panelAvailable) openConnectorsPanel(connector);
|
|
3034
3587
|
else setSelected(connector);
|
|
3035
3588
|
},
|
|
3036
3589
|
[panelAvailable]
|
|
3037
3590
|
);
|
|
3038
|
-
const carriers =
|
|
3591
|
+
const carriers = React9.useMemo(
|
|
3039
3592
|
() => (connectors ?? []).filter((c) => c.category === "logistics" || c.category === "shipping"),
|
|
3040
3593
|
[connectors]
|
|
3041
3594
|
);
|
|
3042
3595
|
const carriersConnected = carriers.some((c) => c.state !== "ready");
|
|
3043
|
-
|
|
3596
|
+
React9.useEffect(() => {
|
|
3044
3597
|
let cancelled = false;
|
|
3045
3598
|
if (supported) {
|
|
3046
3599
|
setLoading(true);
|
|
@@ -3069,7 +3622,7 @@ function ShippingSettings({ provider }) {
|
|
|
3069
3622
|
cancelled = true;
|
|
3070
3623
|
};
|
|
3071
3624
|
}, [shop, supported]);
|
|
3072
|
-
const save =
|
|
3625
|
+
const save = React9.useCallback(
|
|
3073
3626
|
(patch) => {
|
|
3074
3627
|
if (!supported) return;
|
|
3075
3628
|
setSaving(true);
|
|
@@ -3180,7 +3733,7 @@ function ShippingSettings({ provider }) {
|
|
|
3180
3733
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "De onde voc\xEA despacha" }),
|
|
3181
3734
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Os locais que guardam estoque e despacham pedidos. O CEP do local padr\xE3o \xE9 a origem que a transportadora usa para cotar o frete \u2014 \xE9 aqui que ele se cadastra, uma vez. Cada local pode ter o seu pr\xF3prio despachante ou seguir a decis\xE3o da loja." })
|
|
3182
3735
|
] }),
|
|
3183
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-
|
|
3736
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
|
|
3184
3737
|
"button",
|
|
3185
3738
|
{
|
|
3186
3739
|
type: "button",
|
|
@@ -3208,7 +3761,7 @@ function ShippingSettings({ provider }) {
|
|
|
3208
3761
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Zonas de entrega" }),
|
|
3209
3762
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Faixas de CEP com pre\xE7o, prazo e frete gr\xE1tis pr\xF3prios. Um CEP fora de todas as zonas cai no pre\xE7o base acima; se a loja n\xE3o tiver pre\xE7o base nem zona que o cubra, o checkout diz que n\xE3o entrega ali. Uma faixa tamb\xE9m pode ser uma recusa \u2014 \xABn\xE3o entrego aqui\xBB \u2014 e ela vence qualquer zona e qualquer gateway." })
|
|
3210
3763
|
] }),
|
|
3211
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-
|
|
3764
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
|
|
3212
3765
|
"button",
|
|
3213
3766
|
{
|
|
3214
3767
|
type: "button",
|
|
@@ -3262,7 +3815,7 @@ function ShippingSettings({ provider }) {
|
|
|
3262
3815
|
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Conectado, ele cota o frete para o CEP do comprador em vez de a loja adivinhar. O pre\xE7o cotado \xE9 gravado no servidor e \xE9 ele que o pedido cobra \u2014 o navegador escolhe entre op\xE7\xF5es, nunca informa um valor. Quais transportadoras aparecem vem da conta do gateway, n\xE3o desta lista." }),
|
|
3263
3816
|
!carriersConnected && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "Nenhum conectado \u2014 a loja entrega por conta pr\xF3pria, pelo pre\xE7o base e pelas zonas acima. Conectar \xE9 opcional e pode ficar para depois." })
|
|
3264
3817
|
] }),
|
|
3265
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-
|
|
3818
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card shadow-sm", children: /* @__PURE__ */ jsx("ul", { className: "divide-y", children: carriers.map((connector) => /* @__PURE__ */ jsx(ConnectorRow, { connector, onOpen: openJourney }, connector.id)) }) })
|
|
3266
3819
|
] }),
|
|
3267
3820
|
/* @__PURE__ */ jsx(HandlingSettings, { settings: storeSettings, provider }),
|
|
3268
3821
|
/* @__PURE__ */ jsx("div", { className: "flex min-h-5 items-center gap-1.5 px-1 text-xs text-muted-foreground", children: error ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-destructive", children: [
|
|
@@ -3630,6 +4183,39 @@ var appmaxConnectorDef = {
|
|
|
3630
4183
|
disconnect: () => store2.disconnect(),
|
|
3631
4184
|
ExtraPanel: AppmaxExtraPanel
|
|
3632
4185
|
};
|
|
4186
|
+
var ABACATEPAY_FUNCTION = "abacatepay-sync";
|
|
4187
|
+
function client() {
|
|
4188
|
+
const supabase = getSupabaseClientOptional();
|
|
4189
|
+
if (!supabase) throw new Error("Sem conex\xE3o com o banco para falar com a AbacatePay.");
|
|
4190
|
+
return supabase;
|
|
4191
|
+
}
|
|
4192
|
+
async function invoke(body) {
|
|
4193
|
+
const tenantId = getShopTenantId();
|
|
4194
|
+
if (!tenantId) throw new Error("Sem loja selecionada para sincronizar com a AbacatePay.");
|
|
4195
|
+
const runtimeToken = await connectorRuntimeToken({ tenantId });
|
|
4196
|
+
const { data, error } = await client().functions.invoke(ABACATEPAY_FUNCTION, {
|
|
4197
|
+
body: { ...body, tenantId },
|
|
4198
|
+
headers: { [CONNECTOR_RUNTIME_TOKEN_HEADER]: runtimeToken }
|
|
4199
|
+
});
|
|
4200
|
+
if (!error) return data;
|
|
4201
|
+
const status = Number(error?.context?.status ?? 0);
|
|
4202
|
+
let message2 = "";
|
|
4203
|
+
try {
|
|
4204
|
+
const payload = await error?.context?.json?.();
|
|
4205
|
+
message2 = String(payload?.message ?? payload?.error ?? "").trim();
|
|
4206
|
+
} catch {
|
|
4207
|
+
}
|
|
4208
|
+
if (!message2 && status === 404) {
|
|
4209
|
+
message2 = "Esta loja n\xE3o tem uma conta AbacatePay conectada.";
|
|
4210
|
+
}
|
|
4211
|
+
throw new Error(message2 || error.message);
|
|
4212
|
+
}
|
|
4213
|
+
function pushCatalogue() {
|
|
4214
|
+
return invoke({ action: "push_products" });
|
|
4215
|
+
}
|
|
4216
|
+
function reconcileCatalogue() {
|
|
4217
|
+
return invoke({ action: "reconcile" });
|
|
4218
|
+
}
|
|
3633
4219
|
var ABACATEPAY_CONNECTOR_ID = "abacatepay";
|
|
3634
4220
|
var ABACATEPAY_LATENCY_BUDGET_MS = 6e3;
|
|
3635
4221
|
var ABACATEPAY_FIELDS = [
|
|
@@ -3649,8 +4235,13 @@ var ABACATEPAY_SETTINGS = [
|
|
|
3649
4235
|
type: "multi-select",
|
|
3650
4236
|
options: [
|
|
3651
4237
|
{ value: "PIX", label: "Pix" },
|
|
3652
|
-
{ value: "BOLETO", label: "Boleto" }
|
|
3653
|
-
|
|
4238
|
+
{ value: "BOLETO", label: "Boleto" }
|
|
4239
|
+
// Credit card does NOT belong here. AbacatePay discontinued it for new
|
|
4240
|
+
// clients and new integrations, so offering the checkbox would let a
|
|
4241
|
+
// merchant switch on a method the acquirer refuses — and the first to find
|
|
4242
|
+
// out would be a buyer. `CARD` stays in METHOD_LABELS because an older
|
|
4243
|
+
// connection may have the option stored and its row still has to describe
|
|
4244
|
+
// itself.
|
|
3654
4245
|
],
|
|
3655
4246
|
requiredToOperate: true
|
|
3656
4247
|
}
|
|
@@ -3665,33 +4256,127 @@ function methodsDetail2(value) {
|
|
|
3665
4256
|
const labels = list.map((v) => METHOD_LABELS2[String(v).trim()] ?? String(v).trim()).filter(Boolean);
|
|
3666
4257
|
return labels.length > 0 ? labels.join(", ") : void 0;
|
|
3667
4258
|
}
|
|
3668
|
-
function
|
|
3669
|
-
const [
|
|
3670
|
-
const [
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
4259
|
+
function CatalogueSync() {
|
|
4260
|
+
const [busy, setBusy] = useState(null);
|
|
4261
|
+
const [result, setResult] = useState(null);
|
|
4262
|
+
const [error, setError] = useState(null);
|
|
4263
|
+
const run = async (kind) => {
|
|
4264
|
+
setBusy(kind);
|
|
4265
|
+
setError(null);
|
|
4266
|
+
try {
|
|
4267
|
+
setResult(await (kind === "push" ? pushCatalogue() : reconcileCatalogue()));
|
|
4268
|
+
} catch (caught) {
|
|
4269
|
+
setError(caught instanceof Error ? caught.message : "N\xE3o conseguimos sincronizar agora.");
|
|
4270
|
+
} finally {
|
|
4271
|
+
setBusy(null);
|
|
4272
|
+
}
|
|
4273
|
+
};
|
|
4274
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
4275
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4276
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "Cat\xE1logo" }),
|
|
4277
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: "O link de pagamento \xE9 aberto na p\xE1gina da AbacatePay, e ela cobra por produtos que precisam existir l\xE1. Envie os seus antes de mandar um link para um cliente." })
|
|
4278
|
+
] }),
|
|
4279
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
|
|
4280
|
+
/* @__PURE__ */ jsx(
|
|
4281
|
+
"button",
|
|
4282
|
+
{
|
|
4283
|
+
type: "button",
|
|
4284
|
+
onClick: () => {
|
|
4285
|
+
void run("push");
|
|
4286
|
+
},
|
|
4287
|
+
disabled: busy !== null,
|
|
4288
|
+
className: "inline-flex min-h-9 items-center rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
|
|
4289
|
+
children: busy === "push" ? "Enviando\u2026" : "Enviar produtos"
|
|
4290
|
+
}
|
|
4291
|
+
),
|
|
4292
|
+
/* @__PURE__ */ jsx(
|
|
4293
|
+
"button",
|
|
4294
|
+
{
|
|
4295
|
+
type: "button",
|
|
4296
|
+
onClick: () => {
|
|
4297
|
+
void run("reconcile");
|
|
4298
|
+
},
|
|
4299
|
+
disabled: busy !== null,
|
|
4300
|
+
className: "inline-flex min-h-9 items-center rounded-md border px-3 text-xs font-medium transition hover:bg-muted/50 disabled:opacity-60",
|
|
4301
|
+
children: busy === "reconcile" ? "Conferindo\u2026" : "Conferir pre\xE7os"
|
|
4302
|
+
}
|
|
4303
|
+
)
|
|
4304
|
+
] }),
|
|
4305
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }),
|
|
4306
|
+
result && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded-md border bg-muted/30 p-3 text-xs", children: [
|
|
4307
|
+
/* @__PURE__ */ jsxs("p", { className: "text-foreground", children: [
|
|
4308
|
+
result.pushed > 0 ? `${result.pushed} produto${result.pushed > 1 ? "s" : ""} enviado${result.pushed > 1 ? "s" : ""}.` : "Nenhum produto novo para enviar.",
|
|
4309
|
+
" ",
|
|
4310
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
4311
|
+
result.unchanged,
|
|
4312
|
+
" j\xE1 ",
|
|
4313
|
+
result.unchanged === 1 ? "estava" : "estavam",
|
|
4314
|
+
" l\xE1, de ",
|
|
4315
|
+
result.total,
|
|
4316
|
+
"."
|
|
4317
|
+
] })
|
|
4318
|
+
] }),
|
|
4319
|
+
result.hasMore && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Ainda h\xE1 produtos na fila \u2014 clique novamente para continuar." }),
|
|
4320
|
+
result.withoutImage?.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
|
|
4321
|
+
result.withoutImage.length,
|
|
4322
|
+
" produto",
|
|
4323
|
+
result.withoutImage.length > 1 ? "s foram enviados" : " foi enviado",
|
|
4324
|
+
" sem foto \u2014 a AbacatePay recusou a imagem. O produto vende normalmente; s\xF3 aparece sem imagem na p\xE1gina dela."
|
|
4325
|
+
] }),
|
|
4326
|
+
result.skipped?.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
|
|
4327
|
+
result.skipped.length,
|
|
4328
|
+
" ignorado",
|
|
4329
|
+
result.skipped.length > 1 ? "s" : "",
|
|
4330
|
+
" por estar sem nome ou com pre\xE7o zerado."
|
|
4331
|
+
] }),
|
|
4332
|
+
result.failed?.length > 0 && /* @__PURE__ */ jsxs("div", { className: "text-destructive", children: [
|
|
4333
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
4334
|
+
result.failed.length,
|
|
4335
|
+
" recusado",
|
|
4336
|
+
result.failed.length > 1 ? "s" : "",
|
|
4337
|
+
" pela AbacatePay:"
|
|
4338
|
+
] }),
|
|
4339
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-0.5 list-disc pl-4", children: result.failed.slice(0, 3).map((f) => /* @__PURE__ */ jsx("li", { children: f.error }, f.productId)) })
|
|
4340
|
+
] }),
|
|
4341
|
+
result.drifted && result.drifted.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-amber-600 dark:text-amber-500", children: [
|
|
4342
|
+
result.drifted.length,
|
|
4343
|
+
" produto",
|
|
4344
|
+
result.drifted.length > 1 ? "s mudaram" : " mudou",
|
|
4345
|
+
" de pre\xE7o aqui depois de enviado. A AbacatePay n\xE3o permite alterar um produto j\xE1 criado, ent\xE3o o link ainda cobra o pre\xE7o antigo \u2014 refa\xE7a o produto l\xE1 se o novo valor precisa valer."
|
|
4346
|
+
] })
|
|
4347
|
+
] })
|
|
4348
|
+
] });
|
|
4349
|
+
}
|
|
4350
|
+
function AbacatePayExtraPanel() {
|
|
4351
|
+
const [connected, setConnected] = useState(false);
|
|
4352
|
+
const [loaded, setLoaded] = useState(false);
|
|
4353
|
+
useEffect(() => {
|
|
4354
|
+
let cancelled = false;
|
|
4355
|
+
void store3.get().then((c) => {
|
|
4356
|
+
if (!cancelled) setConnected(!!c?.active);
|
|
4357
|
+
}).catch(() => {
|
|
4358
|
+
}).finally(() => {
|
|
4359
|
+
if (!cancelled) setLoaded(true);
|
|
4360
|
+
});
|
|
4361
|
+
return () => {
|
|
4362
|
+
cancelled = true;
|
|
4363
|
+
};
|
|
4364
|
+
}, []);
|
|
4365
|
+
if (!loaded || !connected) return null;
|
|
4366
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 border-t pt-4", children: [
|
|
4367
|
+
/* @__PURE__ */ jsx(CatalogueSync, {}),
|
|
4368
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2 text-xs text-muted-foreground", children: [
|
|
4369
|
+
/* @__PURE__ */ jsx("p", { children: "Pix e boleto acontecem dentro da sua loja: o cliente v\xEA o QR Code ou a linha digit\xE1vel sem sair da p\xE1gina. Cart\xE3o de cr\xE9dito a AbacatePay encerrou para contas novas \u2014 para vender no cart\xE3o, conecte outro provedor em Pagamentos." }),
|
|
4370
|
+
/* @__PURE__ */ jsx("p", { children: "Quem decide se a cobran\xE7a \xE9 de verdade \xE9 a chave. Uma chave de teste simula o pagamento e n\xE3o movimenta dinheiro; a de produ\xE7\xE3o cobra o cliente. \xC9 a mesma tela para as duas, ent\xE3o confira no painel da AbacatePay qual chave voc\xEA colou aqui." }),
|
|
4371
|
+
/* @__PURE__ */ jsx("p", { children: 'O valor cobrado \xE9 sempre recalculado aqui no servidor a partir do pedido. Pre\xE7o alterado no navegador n\xE3o \xE9 aceito, e clicar duas vezes em "pagar" gera uma cobran\xE7a s\xF3.' })
|
|
4372
|
+
] })
|
|
4373
|
+
] });
|
|
4374
|
+
}
|
|
3690
4375
|
var abacatePayConnectorDef = {
|
|
3691
4376
|
id: ABACATEPAY_CONNECTOR_ID,
|
|
3692
4377
|
hostPluginId: "shop",
|
|
3693
4378
|
name: "AbacatePay",
|
|
3694
|
-
description: "Pix
|
|
4379
|
+
description: "Pix e boleto com uma chave s\xF3, sem contrato de adquirente e sem tirar o cliente da loja.",
|
|
3695
4380
|
icon: "CreditCard",
|
|
3696
4381
|
category: "payments",
|
|
3697
4382
|
// One pasted key, no consent screen: AbacatePay has no OAuth and no app to
|
|
@@ -3740,7 +4425,7 @@ var abacatePayConnectorDef = {
|
|
|
3740
4425
|
invokedAt: [
|
|
3741
4426
|
{
|
|
3742
4427
|
key: "shop.checkout.session_open",
|
|
3743
|
-
label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na AbacatePay (Pix
|
|
4428
|
+
label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na AbacatePay (Pix ou boleto)",
|
|
3744
4429
|
touches: ["order", "payment"]
|
|
3745
4430
|
},
|
|
3746
4431
|
{
|
|
@@ -3908,6 +4593,10 @@ var stripeConnectConnectorDef = {
|
|
|
3908
4593
|
};
|
|
3909
4594
|
var VINDI_CONNECTOR_ID = "vindi";
|
|
3910
4595
|
var VINDI_LATENCY_BUDGET_MS = 6e3;
|
|
4596
|
+
var VINDI_AUTHENTICATION_URLS = {
|
|
4597
|
+
sandbox: "https://tc-intermediador-sandbox.yapay.com.br/authentication?consumer_key=",
|
|
4598
|
+
production: "https://tc.intermediador.yapay.com.br/authentication?consumer_key="
|
|
4599
|
+
};
|
|
3911
4600
|
var VINDI_FIELDS = [
|
|
3912
4601
|
{
|
|
3913
4602
|
key: "tokenAccount",
|
|
@@ -3915,6 +4604,27 @@ var VINDI_FIELDS = [
|
|
|
3915
4604
|
type: "password",
|
|
3916
4605
|
secret: true,
|
|
3917
4606
|
placeholder: "Vindi Pagamentos \u2192 Minha Conta \u2192 Token"
|
|
4607
|
+
},
|
|
4608
|
+
{
|
|
4609
|
+
key: "consumerKey",
|
|
4610
|
+
label: "Chave de consumidor (para links de pagamento)",
|
|
4611
|
+
type: "password",
|
|
4612
|
+
secret: true,
|
|
4613
|
+
placeholder: "consumer_key"
|
|
4614
|
+
},
|
|
4615
|
+
{
|
|
4616
|
+
key: "consumerSecret",
|
|
4617
|
+
label: "Segredo de consumidor",
|
|
4618
|
+
type: "password",
|
|
4619
|
+
secret: true,
|
|
4620
|
+
placeholder: "consumer_secret"
|
|
4621
|
+
},
|
|
4622
|
+
{
|
|
4623
|
+
key: "authorizationCode",
|
|
4624
|
+
label: "CODE de autoriza\xE7\xE3o",
|
|
4625
|
+
type: "password",
|
|
4626
|
+
secret: true,
|
|
4627
|
+
placeholder: "copiado da barra de endere\xE7o, uma vez por ambiente"
|
|
3918
4628
|
}
|
|
3919
4629
|
];
|
|
3920
4630
|
var VINDI_ENVIRONMENT_SETTING = {
|
|
@@ -3928,26 +4638,80 @@ var VINDI_ENVIRONMENT_SETTING = {
|
|
|
3928
4638
|
],
|
|
3929
4639
|
requiredToOperate: true
|
|
3930
4640
|
};
|
|
4641
|
+
var VINDI_CARD_METHOD = "3";
|
|
3931
4642
|
var VINDI_SETTINGS = [
|
|
3932
4643
|
VINDI_ENVIRONMENT_SETTING,
|
|
3933
4644
|
{
|
|
3934
4645
|
key: "methods",
|
|
3935
4646
|
label: "Formas de pagamento",
|
|
3936
|
-
description: "O que o cliente pode escolher
|
|
4647
|
+
description: "O que o cliente pode escolher. O cart\xE3o \xE9 pago na p\xE1gina segura da Vindi.",
|
|
3937
4648
|
type: "multi-select",
|
|
3938
4649
|
options: [
|
|
3939
4650
|
{ value: "27", label: "Pix" },
|
|
3940
4651
|
{ value: "28", label: "Bolepix (boleto com QR)" },
|
|
3941
|
-
{ value: "6", label: "Boleto" }
|
|
4652
|
+
{ value: "6", label: "Boleto" },
|
|
4653
|
+
{ value: VINDI_CARD_METHOD, label: "Cart\xE3o de cr\xE9dito (p\xE1gina da Vindi)" }
|
|
3942
4654
|
],
|
|
3943
4655
|
requiredToOperate: true
|
|
4656
|
+
},
|
|
4657
|
+
/**
|
|
4658
|
+
* The seller's postcode, which Vindi's cart endpoint refuses to open without.
|
|
4659
|
+
*
|
|
4660
|
+
* NOT `requiredToOperate` in the static list — a store selling Pix has never
|
|
4661
|
+
* needed it, and turning it into a requirement would put every working
|
|
4662
|
+
* connection into "falta configurar" the day this version ships. It becomes
|
|
4663
|
+
* required the moment card is enabled: see `requiredSettingsFor`.
|
|
4664
|
+
*/
|
|
4665
|
+
{
|
|
4666
|
+
key: "sellerPostalCode",
|
|
4667
|
+
label: "CEP da loja",
|
|
4668
|
+
description: "De onde a loja vende. A Vindi exige esse dado para abrir a p\xE1gina de cart\xE3o.",
|
|
4669
|
+
type: "text",
|
|
4670
|
+
placeholder: "01310-100",
|
|
4671
|
+
pattern: "\\d{5}-?\\d{3}",
|
|
4672
|
+
patternHint: "Oito d\xEDgitos, com ou sem h\xEDfen."
|
|
4673
|
+
},
|
|
4674
|
+
/**
|
|
4675
|
+
* How many instalments Vindi's page may offer.
|
|
4676
|
+
*
|
|
4677
|
+
* A `select` rather than free text because it is a number with a ceiling the
|
|
4678
|
+
* merchant does not know: Vindi refuses a split above what the account was
|
|
4679
|
+
* accredited for, and a typed "24" would be a checkout that fails at the last
|
|
4680
|
+
* click. The default is 1 — a store that never thought about instalments
|
|
4681
|
+
* should not discover it is financing purchases.
|
|
4682
|
+
*/
|
|
4683
|
+
{
|
|
4684
|
+
key: "maxSplit",
|
|
4685
|
+
label: "Parcelas no cart\xE3o",
|
|
4686
|
+
description: "Em quantas vezes o cliente pode dividir. Depende do que a Vindi liberou para a conta.",
|
|
4687
|
+
type: "select",
|
|
4688
|
+
options: [
|
|
4689
|
+
{ value: "1", label: "\xC0 vista" },
|
|
4690
|
+
{ value: "2", label: "At\xE9 2x" },
|
|
4691
|
+
{ value: "3", label: "At\xE9 3x" },
|
|
4692
|
+
{ value: "6", label: "At\xE9 6x" },
|
|
4693
|
+
{ value: "10", label: "At\xE9 10x" },
|
|
4694
|
+
{ value: "12", label: "At\xE9 12x" }
|
|
4695
|
+
],
|
|
4696
|
+
defaultValue: "1"
|
|
3944
4697
|
}
|
|
3945
4698
|
];
|
|
4699
|
+
function requiredSettingsFor(settings) {
|
|
4700
|
+
const raw = settings?.methods;
|
|
4701
|
+
const list = Array.isArray(raw) ? raw : String(raw ?? "").split(",");
|
|
4702
|
+
const hasCard = list.map((v) => String(v).trim()).includes(VINDI_CARD_METHOD);
|
|
4703
|
+
return VINDI_SETTINGS.map((setting) => setting.key === "sellerPostalCode" && hasCard ? { ...setting, requiredToOperate: true } : setting);
|
|
4704
|
+
}
|
|
3946
4705
|
var store5 = createConnectionStore(VINDI_CONNECTOR_ID, {
|
|
3947
4706
|
fields: VINDI_FIELDS,
|
|
3948
4707
|
settings: VINDI_SETTINGS
|
|
3949
4708
|
});
|
|
3950
|
-
var METHOD_LABELS4 = {
|
|
4709
|
+
var METHOD_LABELS4 = {
|
|
4710
|
+
"27": "Pix",
|
|
4711
|
+
"28": "Bolepix",
|
|
4712
|
+
"6": "Boleto",
|
|
4713
|
+
[VINDI_CARD_METHOD]: "Cart\xE3o"
|
|
4714
|
+
};
|
|
3951
4715
|
var ENVIRONMENT_LABELS = { sandbox: "Sandbox", production: "Produ\xE7\xE3o" };
|
|
3952
4716
|
function connectionDetail(settings) {
|
|
3953
4717
|
const environment = ENVIRONMENT_LABELS[String(settings?.environment ?? "")];
|
|
@@ -3959,11 +4723,14 @@ function connectionDetail(settings) {
|
|
|
3959
4723
|
}
|
|
3960
4724
|
function VindiExtraPanel() {
|
|
3961
4725
|
const [connected, setConnected] = useState(false);
|
|
4726
|
+
const [environment, setEnvironment] = useState("");
|
|
3962
4727
|
const [loaded, setLoaded] = useState(false);
|
|
3963
4728
|
useEffect(() => {
|
|
3964
4729
|
let cancelled = false;
|
|
3965
4730
|
void store5.get().then((c) => {
|
|
3966
|
-
if (
|
|
4731
|
+
if (cancelled) return;
|
|
4732
|
+
setConnected(!!c?.active);
|
|
4733
|
+
setEnvironment(String(c?.settings?.environment ?? ""));
|
|
3967
4734
|
}).catch(() => {
|
|
3968
4735
|
}).finally(() => {
|
|
3969
4736
|
if (!cancelled) setLoaded(true);
|
|
@@ -3973,17 +4740,59 @@ function VindiExtraPanel() {
|
|
|
3973
4740
|
};
|
|
3974
4741
|
}, []);
|
|
3975
4742
|
if (!loaded) return null;
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
/* @__PURE__ */
|
|
3979
|
-
|
|
3980
|
-
|
|
4743
|
+
const authenticationUrl = VINDI_AUTHENTICATION_URLS[environment];
|
|
4744
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
|
|
4745
|
+
connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4746
|
+
/* @__PURE__ */ jsx("p", { children: "Pix e boleto acontecem dentro da sua loja: o cliente v\xEA o QR Code ou a linha digit\xE1vel sem sair da p\xE1gina. O cart\xE3o \xE9 diferente \u2014 ele \xE9 digitado na p\xE1gina segura da Vindi, e o cliente volta para a loja em seguida. \xC9 assim de prop\xF3sito: nenhum n\xFAmero de cart\xE3o passa pelo seu site." }),
|
|
4747
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
4748
|
+
"Voc\xEA tamb\xE9m pode gerar um ",
|
|
4749
|
+
/* @__PURE__ */ jsx("strong", { children: "link de pagamento" }),
|
|
4750
|
+
" em Pedidos \u2192 Novo e mandar no WhatsApp. O cliente escolhe como pagar na p\xE1gina da Vindi, e o pedido aparece aqui sozinho quando o dinheiro cair."
|
|
4751
|
+
] }),
|
|
4752
|
+
/* @__PURE__ */ jsx("p", { children: "Sandbox e produ\xE7\xE3o s\xE3o contas diferentes, com tokens diferentes. Se voc\xEA trocar o ambiente aqui, troque tamb\xE9m as credenciais \u2014 as de teste n\xE3o cobram em produ\xE7\xE3o e as de produ\xE7\xE3o n\xE3o funcionam no sandbox." }),
|
|
4753
|
+
/* @__PURE__ */ jsx("p", { children: 'O valor cobrado \xE9 sempre recalculado aqui no servidor a partir do pedido. Pre\xE7o alterado no navegador n\xE3o \xE9 aceito, e clicar duas vezes em "pagar" gera uma cobran\xE7a s\xF3.' })
|
|
4754
|
+
] }) : /* @__PURE__ */ jsx("p", { children: "O token da conta voc\xEA pega no painel da Vindi Pagamentos, em Minha Conta. Confira se ele \xE9 o do mesmo ambiente que voc\xEA vai escolher aqui \u2014 o token de sandbox n\xE3o cobra em produ\xE7\xE3o." }),
|
|
4755
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed p-3", children: [
|
|
4756
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Para gerar links de pagamento" }),
|
|
4757
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
|
|
4758
|
+
"O token da conta n\xE3o emite link \u2014 a Vindi exige uma autoriza\xE7\xE3o \xE0 parte. Pe\xE7a a",
|
|
4759
|
+
" ",
|
|
4760
|
+
/* @__PURE__ */ jsx("strong", { children: "chave" }),
|
|
4761
|
+
" e o ",
|
|
4762
|
+
/* @__PURE__ */ jsx("strong", { children: "segredo de consumidor" }),
|
|
4763
|
+
" no painel da Vindi e preencha os campos acima. Falta ainda o ",
|
|
4764
|
+
/* @__PURE__ */ jsx("strong", { children: "CODE" }),
|
|
4765
|
+
":"
|
|
4766
|
+
] }),
|
|
4767
|
+
/* @__PURE__ */ jsxs("ol", { className: "mt-2 list-decimal space-y-1 pl-4", children: [
|
|
4768
|
+
/* @__PURE__ */ jsx("li", { children: authenticationUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4769
|
+
"Abra no navegador",
|
|
4770
|
+
" ",
|
|
4771
|
+
/* @__PURE__ */ jsxs("code", { className: "break-all rounded bg-muted px-1 py-0.5", children: [
|
|
4772
|
+
authenticationUrl,
|
|
4773
|
+
"SUA_CHAVE"
|
|
4774
|
+
] }),
|
|
4775
|
+
" ",
|
|
4776
|
+
"trocando ",
|
|
4777
|
+
/* @__PURE__ */ jsx("code", { children: "SUA_CHAVE" }),
|
|
4778
|
+
" pela sua chave de consumidor."
|
|
4779
|
+
] }) : /* @__PURE__ */ jsx(Fragment, { children: "Escolha o ambiente acima e o endere\xE7o para buscar o CODE aparece aqui." }) }),
|
|
4780
|
+
/* @__PURE__ */ jsx("li", { children: "A Vindi redireciona. O CODE fica na barra de endere\xE7o \u2014 copie de l\xE1." }),
|
|
4781
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
4782
|
+
"Cole no campo ",
|
|
4783
|
+
/* @__PURE__ */ jsx("strong", { children: "CODE de autoriza\xE7\xE3o" }),
|
|
4784
|
+
" e salve."
|
|
4785
|
+
] })
|
|
4786
|
+
] }),
|
|
4787
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2", children: "\xC9 uma vez por ambiente. A partir da\xED a renova\xE7\xE3o di\xE1ria acontece sozinha." })
|
|
4788
|
+
] })
|
|
4789
|
+
] });
|
|
3981
4790
|
}
|
|
3982
4791
|
var vindiConnectorDef = {
|
|
3983
4792
|
id: VINDI_CONNECTOR_ID,
|
|
3984
4793
|
hostPluginId: "shop",
|
|
3985
4794
|
name: "Vindi",
|
|
3986
|
-
description: "Pix e
|
|
4795
|
+
description: "Pix, boleto e cart\xE3o pela Vindi. A loja recebe na conta dela; o cart\xE3o \xE9 digitado na p\xE1gina segura da Vindi.",
|
|
3987
4796
|
icon: "CreditCard",
|
|
3988
4797
|
// Declared rather than borrowed from the catalogue, because the catalogue row
|
|
3989
4798
|
// is a change in another repo that has to be seeded before it exists anywhere:
|
|
@@ -4021,7 +4830,7 @@ var vindiConnectorDef = {
|
|
|
4021
4830
|
kind: "data",
|
|
4022
4831
|
resource: "payment",
|
|
4023
4832
|
actions: ["write"],
|
|
4024
|
-
reason: "Registrar aqui a cobran\xE7a aberta e a confirma\xE7\xE3o do pagamento. Dados do cart\xE3o n\xE3o passam por este app.",
|
|
4833
|
+
reason: "Registrar aqui a cobran\xE7a aberta e a confirma\xE7\xE3o do pagamento. Dados do cart\xE3o n\xE3o passam por este app \u2014 s\xE3o digitados na p\xE1gina da Vindi.",
|
|
4025
4834
|
sensitive: true
|
|
4026
4835
|
}
|
|
4027
4836
|
],
|
|
@@ -4035,7 +4844,7 @@ var vindiConnectorDef = {
|
|
|
4035
4844
|
invokedAt: [
|
|
4036
4845
|
{
|
|
4037
4846
|
key: "shop.checkout.session_open",
|
|
4038
|
-
label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na Vindi (Pix ou
|
|
4847
|
+
label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na Vindi (Pix, boleto ou a p\xE1gina de cart\xE3o)",
|
|
4039
4848
|
touches: ["order", "payment"]
|
|
4040
4849
|
},
|
|
4041
4850
|
{
|
|
@@ -4054,7 +4863,10 @@ var vindiConnectorDef = {
|
|
|
4054
4863
|
connected: !!connection?.active,
|
|
4055
4864
|
detail: connectionDetail(connection?.settings),
|
|
4056
4865
|
plane: "request",
|
|
4057
|
-
|
|
4866
|
+
// Judged against what THIS connection needs, not against a constant: a
|
|
4867
|
+
// store that ticked card owes a postcode, and one that did not owes
|
|
4868
|
+
// nothing. See `requiredSettingsFor`.
|
|
4869
|
+
...settingsStatus(requiredSettingsFor(connection?.settings), connection)
|
|
4058
4870
|
}),
|
|
4059
4871
|
{ store: () => store5 }
|
|
4060
4872
|
),
|
|
@@ -4073,6 +4885,188 @@ var vindiConnectorDef = {
|
|
|
4073
4885
|
disconnect: () => store5.disconnect(),
|
|
4074
4886
|
ExtraPanel: VindiExtraPanel
|
|
4075
4887
|
};
|
|
4888
|
+
var MERCADOPAGO_CONNECTOR_ID = "mercadopago";
|
|
4889
|
+
var MERCADOPAGO_LATENCY_BUDGET_MS = 6e3;
|
|
4890
|
+
var MERCADOPAGO_FIELDS = [
|
|
4891
|
+
{
|
|
4892
|
+
key: "accessToken",
|
|
4893
|
+
label: "Access token",
|
|
4894
|
+
type: "password",
|
|
4895
|
+
secret: true,
|
|
4896
|
+
placeholder: "APP_USR-\u2026 (Suas integra\xE7\xF5es \u2192 sua aplica\xE7\xE3o \u2192 Credenciais)"
|
|
4897
|
+
},
|
|
4898
|
+
{
|
|
4899
|
+
key: "publicKey",
|
|
4900
|
+
label: "Public key",
|
|
4901
|
+
// Not masked: it travels to the browser anyway, and a merchant who cannot
|
|
4902
|
+
// see it cannot tell the test key from the production one at a glance.
|
|
4903
|
+
type: "text",
|
|
4904
|
+
secret: true,
|
|
4905
|
+
placeholder: "APP_USR-\u2026"
|
|
4906
|
+
}
|
|
4907
|
+
];
|
|
4908
|
+
var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID, {
|
|
4909
|
+
fields: MERCADOPAGO_FIELDS
|
|
4910
|
+
});
|
|
4911
|
+
function connectionDetail2(settings) {
|
|
4912
|
+
const key = String(settings?.publicKey ?? "").trim();
|
|
4913
|
+
if (!key) return "Pix e cart\xE3o";
|
|
4914
|
+
return /^TEST-/i.test(key) ? "Sandbox \xB7 Pix e cart\xE3o" : "Produ\xE7\xE3o \xB7 Pix e cart\xE3o";
|
|
4915
|
+
}
|
|
4916
|
+
function MercadoPagoExtraPanel() {
|
|
4917
|
+
const [connected, setConnected] = useState(false);
|
|
4918
|
+
const [loaded, setLoaded] = useState(false);
|
|
4919
|
+
useEffect(() => {
|
|
4920
|
+
let cancelled = false;
|
|
4921
|
+
void store6.get().then((c) => {
|
|
4922
|
+
if (!cancelled) setConnected(!!c?.active);
|
|
4923
|
+
}).catch(() => {
|
|
4924
|
+
}).finally(() => {
|
|
4925
|
+
if (!cancelled) setLoaded(true);
|
|
4926
|
+
});
|
|
4927
|
+
return () => {
|
|
4928
|
+
cancelled = true;
|
|
4929
|
+
};
|
|
4930
|
+
}, []);
|
|
4931
|
+
if (!loaded) return null;
|
|
4932
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
|
|
4933
|
+
connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4934
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
4935
|
+
"Pix e cart\xE3o acontecem ",
|
|
4936
|
+
/* @__PURE__ */ jsx("strong", { children: "dentro da sua loja" }),
|
|
4937
|
+
". O cliente v\xEA o QR Code ou digita o cart\xE3o sem sair da p\xE1gina, e volta para a confirma\xE7\xE3o no mesmo lugar. Diferente das outras adquirentes, aqui o cart\xE3o n\xE3o redireciona."
|
|
4938
|
+
] }),
|
|
4939
|
+
/* @__PURE__ */ jsx("p", { children: "O n\xFAmero do cart\xE3o n\xE3o passa pelo seu site: ele \xE9 digitado dentro de um formul\xE1rio do pr\xF3prio Mercado Pago e vira um c\xF3digo de uso \xFAnico antes de chegar at\xE9 n\xF3s. \xC9 por isso que d\xE1 para receber cart\xE3o na sua p\xE1gina sem a auditoria que isso normalmente exige." }),
|
|
4940
|
+
/* @__PURE__ */ jsx("p", { children: 'O valor cobrado \xE9 sempre recalculado aqui no servidor a partir do pedido. Pre\xE7o alterado no navegador n\xE3o \xE9 aceito, e clicar duas vezes em "pagar" gera uma cobran\xE7a s\xF3.' }),
|
|
4941
|
+
/* @__PURE__ */ jsx("p", { children: "O Pix vale por 30 minutos. Se o cliente fechar a aba depois de pagar, o pedido aparece aqui do mesmo jeito \u2014 o Mercado Pago avisa a loja assim que o dinheiro cai." })
|
|
4942
|
+
] }) : /* @__PURE__ */ jsxs("p", { children: [
|
|
4943
|
+
"As duas chaves ficam no painel do Mercado Pago, em ",
|
|
4944
|
+
/* @__PURE__ */ jsx("strong", { children: "Suas integra\xE7\xF5es" }),
|
|
4945
|
+
" \u2192 sua aplica\xE7\xE3o \u2192 ",
|
|
4946
|
+
/* @__PURE__ */ jsx("strong", { children: "Credenciais de produ\xE7\xE3o" }),
|
|
4947
|
+
". Copie o ",
|
|
4948
|
+
/* @__PURE__ */ jsx("em", { children: "access token" }),
|
|
4949
|
+
" e a",
|
|
4950
|
+
" ",
|
|
4951
|
+
/* @__PURE__ */ jsx("em", { children: "public key" }),
|
|
4952
|
+
" da ",
|
|
4953
|
+
/* @__PURE__ */ jsx("strong", { children: "mesma" }),
|
|
4954
|
+
" aplica\xE7\xE3o: elas s\xE3o um par, e uma sem a outra n\xE3o fecha uma venda."
|
|
4955
|
+
] }),
|
|
4956
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed p-3", children: [
|
|
4957
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Para testar sem cobrar de verdade" }),
|
|
4958
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
|
|
4959
|
+
"As chaves que come\xE7am com ",
|
|
4960
|
+
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: "TEST-" }),
|
|
4961
|
+
" ",
|
|
4962
|
+
/* @__PURE__ */ jsx("strong", { children: "n\xE3o funcionam" }),
|
|
4963
|
+
" aqui \u2014 o Mercado Pago as recusa nesta API. O teste \xE9 feito com ",
|
|
4964
|
+
/* @__PURE__ */ jsx("strong", { children: "usu\xE1rios de teste" }),
|
|
4965
|
+
": crie um vendedor e um comprador em Suas integra\xE7\xF5es \u2192 Contas de teste, e use as chaves de ",
|
|
4966
|
+
/* @__PURE__ */ jsx("em", { children: "produ\xE7\xE3o" }),
|
|
4967
|
+
" do vendedor de teste. N\xE3o \xE9 dinheiro real, e \xE9 assim que o pr\xF3prio Mercado Pago recomenda."
|
|
4968
|
+
] })
|
|
4969
|
+
] })
|
|
4970
|
+
] });
|
|
4971
|
+
}
|
|
4972
|
+
var mercadoPagoConnectorDef = {
|
|
4973
|
+
id: MERCADOPAGO_CONNECTOR_ID,
|
|
4974
|
+
hostPluginId: "shop",
|
|
4975
|
+
name: "Mercado Pago",
|
|
4976
|
+
description: "Pix e cart\xE3o pelo Mercado Pago, os dois pagos dentro da loja \u2014 o cart\xE3o \xE9 digitado sem o cliente sair da p\xE1gina.",
|
|
4977
|
+
icon: "CreditCard",
|
|
4978
|
+
// Declared rather than borrowed from the catalogue, for the same reason Vindi
|
|
4979
|
+
// declares its own: the catalogue row is a change in another repo that has to
|
|
4980
|
+
// be seeded before it exists anywhere, and until then the shelf renders a grey
|
|
4981
|
+
// plug. The absolute URL answers from any origin — a tenant app is served from
|
|
4982
|
+
// its own domain, so a path would not.
|
|
4983
|
+
logoUrl: "https://assets.fayz.ai/connectors/mercadopago.png",
|
|
4984
|
+
category: "payments",
|
|
4985
|
+
// Pasted keys today. Unlike Vindi — whose consent screen only LOOKS like OAuth
|
|
4986
|
+
// — Mercado Pago has a real merchant OAuth, and it is the right end state: one
|
|
4987
|
+
// platform application, each seller authorizing it, `marketplace_fee` taken on
|
|
4988
|
+
// the charge instead of invoiced afterwards. What it needs is the redirect URI
|
|
4989
|
+
// registered against the platform broker, which is a change in ymaia and not
|
|
4990
|
+
// here. The same slug is already declared by plugin-financial with
|
|
4991
|
+
// `authKind: 'oauth'`, and because a credential is keyed by slug + tenant, a
|
|
4992
|
+
// store that authorizes there is redeemable from here the day that lands.
|
|
4993
|
+
authKind: "api-key",
|
|
4994
|
+
// The charge is opened in the buyer's request path; the notification and the
|
|
4995
|
+
// refund are outbound sync work.
|
|
4996
|
+
planes: ["request", "sync"],
|
|
4997
|
+
direction: "outbound",
|
|
4998
|
+
docsUrl: "https://www.mercadopago.com.br/developers/pt/docs/checkout-api/landing",
|
|
4999
|
+
providerInstall: { url: "https://www.mercadopago.com.br/developers/panel/app" },
|
|
5000
|
+
// Grounded in what the request plane does, and padded with nothing: the total
|
|
5001
|
+
// is recomputed server-side from the order before the charge is opened, which
|
|
5002
|
+
// is a read of the order and never a write of it; the charge and its
|
|
5003
|
+
// confirmation are the write.
|
|
5004
|
+
permissions: [
|
|
5005
|
+
{
|
|
5006
|
+
kind: "data",
|
|
5007
|
+
resource: "order",
|
|
5008
|
+
actions: ["read"],
|
|
5009
|
+
reason: "Ler o pedido para recalcular o valor no servidor antes de abrir a cobran\xE7a \u2014 o total nunca vem do navegador do cliente.",
|
|
5010
|
+
sensitive: true
|
|
5011
|
+
},
|
|
5012
|
+
{
|
|
5013
|
+
kind: "data",
|
|
5014
|
+
resource: "payment",
|
|
5015
|
+
actions: ["write"],
|
|
5016
|
+
reason: "Registrar aqui a cobran\xE7a aberta e a confirma\xE7\xE3o do pagamento. O n\xFAmero do cart\xE3o n\xE3o passa por este app \u2014 ele \xE9 digitado num formul\xE1rio do pr\xF3prio Mercado Pago.",
|
|
5017
|
+
sensitive: true
|
|
5018
|
+
}
|
|
5019
|
+
],
|
|
5020
|
+
latencyBudgetMs: MERCADOPAGO_LATENCY_BUDGET_MS,
|
|
5021
|
+
// A gateway that does not answer must stop the sale: an order that reads as
|
|
5022
|
+
// confirmed without a payment is worse than a refused checkout. Same verdict
|
|
5023
|
+
// as the other four gateways, opposite to the carrier next door.
|
|
5024
|
+
onFailure: "block",
|
|
5025
|
+
fields: MERCADOPAGO_FIELDS,
|
|
5026
|
+
invokedAt: [
|
|
5027
|
+
{
|
|
5028
|
+
key: "shop.checkout.session_open",
|
|
5029
|
+
label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta no Mercado Pago (QR do Pix ou cart\xE3o digitado na pr\xF3pria loja)",
|
|
5030
|
+
touches: ["order", "payment"]
|
|
5031
|
+
},
|
|
5032
|
+
{
|
|
5033
|
+
key: "shop.payment.capture",
|
|
5034
|
+
label: "O Mercado Pago avisa que o pagamento caiu, a cobran\xE7a \xE9 reconsultada e o pedido \xE9 liberado",
|
|
5035
|
+
plane: "sync",
|
|
5036
|
+
trigger: "webhook",
|
|
5037
|
+
touches: ["payment"]
|
|
5038
|
+
}
|
|
5039
|
+
],
|
|
5040
|
+
// Read from the row the list already batched, so a screen with N connectors
|
|
5041
|
+
// costs one plg_connections request rather than N.
|
|
5042
|
+
//
|
|
5043
|
+
// No `settingsStatus`: this connector has nothing to configure, so a
|
|
5044
|
+
// connection either has its credential pair or does not. "Conectado, falta
|
|
5045
|
+
// configurar" would be a state that can never be true here.
|
|
5046
|
+
...connectionStatus(
|
|
5047
|
+
MERCADOPAGO_CONNECTOR_ID,
|
|
5048
|
+
(connection) => ({
|
|
5049
|
+
connected: !!connection?.active,
|
|
5050
|
+
detail: connectionDetail2(connection?.settings),
|
|
5051
|
+
plane: "request"
|
|
5052
|
+
}),
|
|
5053
|
+
{ store: () => store6 }
|
|
5054
|
+
),
|
|
5055
|
+
...connectionSettings(store6),
|
|
5056
|
+
// Reached only AFTER the credential sink accepted the pair — that ordering is
|
|
5057
|
+
// what makes `active = true` mean "credentials were placed for this store"
|
|
5058
|
+
// rather than "somebody pressed a button". The secret never arrives here: the
|
|
5059
|
+
// hub split it out before calling this.
|
|
5060
|
+
//
|
|
5061
|
+
// No `testConnection`: the probe is handed the non-secret half, and a public
|
|
5062
|
+
// key alone cannot open an order. Testing belongs to the edge function that
|
|
5063
|
+
// holds the credential.
|
|
5064
|
+
saveConnection: async () => {
|
|
5065
|
+
await store6.save({ active: true });
|
|
5066
|
+
},
|
|
5067
|
+
disconnect: () => store6.disconnect(),
|
|
5068
|
+
ExtraPanel: MercadoPagoExtraPanel
|
|
5069
|
+
};
|
|
4076
5070
|
var KIND_META = {
|
|
4077
5071
|
online_store: { label: "Loja Online", icon: Globe, hint: "Seu storefront fayz \u2014 a vitrine p\xFAblica da loja.", placeholder: "https://minhaloja.com.br/" },
|
|
4078
5072
|
whatsapp: { label: "WhatsApp", icon: MessageCircle, hint: "Link direto para o WhatsApp Business.", placeholder: "https://wa.me/5521999999999" },
|
|
@@ -4090,12 +5084,12 @@ function notifyChannelsChanged() {
|
|
|
4090
5084
|
if (typeof window !== "undefined") window.dispatchEvent(new Event("fayz:channels-changed"));
|
|
4091
5085
|
}
|
|
4092
5086
|
function ChannelsPage({ resolveProvider }) {
|
|
4093
|
-
const provider =
|
|
5087
|
+
const provider = React9.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
|
|
4094
5088
|
const supported = typeof provider.listSalesChannels === "function";
|
|
4095
|
-
const [channels, setChannels] =
|
|
4096
|
-
const [loading, setLoading] =
|
|
4097
|
-
const [adding, setAdding] =
|
|
4098
|
-
const reload =
|
|
5089
|
+
const [channels, setChannels] = React9.useState([]);
|
|
5090
|
+
const [loading, setLoading] = React9.useState(true);
|
|
5091
|
+
const [adding, setAdding] = React9.useState(null);
|
|
5092
|
+
const reload = React9.useCallback(async () => {
|
|
4099
5093
|
if (!provider.listSalesChannels) {
|
|
4100
5094
|
setLoading(false);
|
|
4101
5095
|
return;
|
|
@@ -4113,7 +5107,7 @@ function ChannelsPage({ resolveProvider }) {
|
|
|
4113
5107
|
setLoading(false);
|
|
4114
5108
|
}
|
|
4115
5109
|
}, [provider]);
|
|
4116
|
-
|
|
5110
|
+
React9.useEffect(() => {
|
|
4117
5111
|
void reload();
|
|
4118
5112
|
}, [reload]);
|
|
4119
5113
|
const patch = async (id, input) => {
|
|
@@ -4169,7 +5163,7 @@ function ChannelsPage({ resolveProvider }) {
|
|
|
4169
5163
|
] });
|
|
4170
5164
|
}
|
|
4171
5165
|
function OnlineStoreCard({ channel, onSave, onToggle }) {
|
|
4172
|
-
const [url, setUrl] =
|
|
5166
|
+
const [url, setUrl] = React9.useState(channel.url ?? "");
|
|
4173
5167
|
const dirty = (channel.url ?? "") !== url;
|
|
4174
5168
|
const meta = KIND_META.online_store;
|
|
4175
5169
|
return /* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
@@ -4195,8 +5189,8 @@ function OnlineStoreCard({ channel, onSave, onToggle }) {
|
|
|
4195
5189
|
] }) });
|
|
4196
5190
|
}
|
|
4197
5191
|
function ChannelRow({ channel, onSave, onToggle, onRemove }) {
|
|
4198
|
-
const [url, setUrl] =
|
|
4199
|
-
const [editing, setEditing] =
|
|
5192
|
+
const [url, setUrl] = React9.useState(channel.url ?? "");
|
|
5193
|
+
const [editing, setEditing] = React9.useState(false);
|
|
4200
5194
|
const meta = KIND_META[channel.kind] ?? KIND_META.custom;
|
|
4201
5195
|
const Icon = meta.icon;
|
|
4202
5196
|
return /* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -4224,9 +5218,9 @@ function ChannelRow({ channel, onSave, onToggle, onRemove }) {
|
|
|
4224
5218
|
}
|
|
4225
5219
|
function AddChannelForm({ kind, onKind, onCancel, onAdd }) {
|
|
4226
5220
|
const meta = KIND_META[kind];
|
|
4227
|
-
const [label, setLabel] =
|
|
4228
|
-
const [url, setUrl] =
|
|
4229
|
-
|
|
5221
|
+
const [label, setLabel] = React9.useState(meta.label);
|
|
5222
|
+
const [url, setUrl] = React9.useState("");
|
|
5223
|
+
React9.useEffect(() => {
|
|
4230
5224
|
setLabel(KIND_META[kind].label);
|
|
4231
5225
|
}, [kind]);
|
|
4232
5226
|
return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-card border border-border bg-card p-4", children: [
|
|
@@ -4278,17 +5272,17 @@ function formatSize(bytes) {
|
|
|
4278
5272
|
return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
4279
5273
|
}
|
|
4280
5274
|
function ContentPage({ resolveProvider }) {
|
|
4281
|
-
const provider =
|
|
5275
|
+
const provider = React9.useMemo(() => resolve3(resolveProvider), [resolveProvider]);
|
|
4282
5276
|
const supported = typeof provider.listMediaFiles === "function";
|
|
4283
|
-
const [files, setFiles] =
|
|
4284
|
-
const [tab, setTab] =
|
|
4285
|
-
const [uploading, setUploading] =
|
|
4286
|
-
const [pendingDelete, setPendingDelete] =
|
|
4287
|
-
const [deleting, setDeleting] =
|
|
4288
|
-
const [replacing, setReplacing] =
|
|
4289
|
-
const [openId, setOpenId] =
|
|
4290
|
-
const [dimensions, setDimensions] =
|
|
4291
|
-
const reload =
|
|
5277
|
+
const [files, setFiles] = React9.useState(null);
|
|
5278
|
+
const [tab, setTab] = React9.useState("all");
|
|
5279
|
+
const [uploading, setUploading] = React9.useState(false);
|
|
5280
|
+
const [pendingDelete, setPendingDelete] = React9.useState(null);
|
|
5281
|
+
const [deleting, setDeleting] = React9.useState(false);
|
|
5282
|
+
const [replacing, setReplacing] = React9.useState(false);
|
|
5283
|
+
const [openId, setOpenId] = React9.useState(null);
|
|
5284
|
+
const [dimensions, setDimensions] = React9.useState({});
|
|
5285
|
+
const reload = React9.useCallback(async () => {
|
|
4292
5286
|
if (!provider.listMediaFiles) {
|
|
4293
5287
|
setFiles([]);
|
|
4294
5288
|
return;
|
|
@@ -4300,10 +5294,10 @@ function ContentPage({ resolveProvider }) {
|
|
|
4300
5294
|
setFiles([]);
|
|
4301
5295
|
}
|
|
4302
5296
|
}, [provider]);
|
|
4303
|
-
|
|
5297
|
+
React9.useEffect(() => {
|
|
4304
5298
|
void reload();
|
|
4305
5299
|
}, [reload]);
|
|
4306
|
-
const measure =
|
|
5300
|
+
const measure = React9.useCallback((id, d) => {
|
|
4307
5301
|
setDimensions((prev) => prev[id] ? prev : { ...prev, [id]: d });
|
|
4308
5302
|
}, []);
|
|
4309
5303
|
const handleUpload = async (picked) => {
|
|
@@ -4493,7 +5487,7 @@ function SectionTitle({ title, count }) {
|
|
|
4493
5487
|
] });
|
|
4494
5488
|
}
|
|
4495
5489
|
function UploadButton({ uploading, onPick }) {
|
|
4496
|
-
const inputRef =
|
|
5490
|
+
const inputRef = React9.useRef(null);
|
|
4497
5491
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4498
5492
|
/* @__PURE__ */ jsx(
|
|
4499
5493
|
"input",
|
|
@@ -4573,8 +5567,8 @@ function MediaDetail({
|
|
|
4573
5567
|
onDelete,
|
|
4574
5568
|
replacing
|
|
4575
5569
|
}) {
|
|
4576
|
-
const replaceRef =
|
|
4577
|
-
|
|
5570
|
+
const replaceRef = React9.useRef(null);
|
|
5571
|
+
React9.useEffect(() => {
|
|
4578
5572
|
function onKey(e) {
|
|
4579
5573
|
if (e.key === "ArrowLeft") onNavigate(-1);
|
|
4580
5574
|
if (e.key === "ArrowRight") onNavigate(1);
|
|
@@ -4582,116 +5576,138 @@ function MediaDetail({
|
|
|
4582
5576
|
document.addEventListener("keydown", onKey);
|
|
4583
5577
|
return () => document.removeEventListener("keydown", onKey);
|
|
4584
5578
|
}, [onNavigate]);
|
|
4585
|
-
return
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
" de ",
|
|
4602
|
-
position.total
|
|
4603
|
-
] }),
|
|
4604
|
-
/* @__PURE__ */ jsx(
|
|
4605
|
-
"button",
|
|
4606
|
-
{
|
|
4607
|
-
type: "button",
|
|
4608
|
-
"aria-label": "Pr\xF3ximo arquivo",
|
|
4609
|
-
disabled: position.index === position.total - 1,
|
|
4610
|
-
onClick: () => onNavigate(1),
|
|
4611
|
-
className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
|
|
4612
|
-
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
4613
|
-
}
|
|
4614
|
-
)
|
|
4615
|
-
] }) }),
|
|
4616
|
-
/* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
|
|
4617
|
-
/* @__PURE__ */ jsx(Card, { className: "flex min-h-[320px] items-center justify-center overflow-hidden bg-muted p-2", children: file.kind === "image" ? /* @__PURE__ */ jsx(
|
|
4618
|
-
"img",
|
|
4619
|
-
{
|
|
4620
|
-
src: file.url,
|
|
4621
|
-
alt: file.name,
|
|
4622
|
-
className: "max-h-[70vh] w-auto max-w-full object-contain",
|
|
4623
|
-
onLoad: (e) => {
|
|
4624
|
-
const img = e.currentTarget;
|
|
4625
|
-
if (img.naturalWidth) onMeasured({ width: img.naturalWidth, height: img.naturalHeight });
|
|
5579
|
+
return (
|
|
5580
|
+
// `fz-container` é o ponto de MEDIÇÃO: sem ele a container query de
|
|
5581
|
+
// `fz-split` nunca casa e as duas colunas ficam empilhadas para sempre,
|
|
5582
|
+
// por mais larga que a janela esteja.
|
|
5583
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
|
|
5584
|
+
/* @__PURE__ */ jsx(PageHeader, { title: file.name, parentLabel: "Conte\xFAdo", onBack }),
|
|
5585
|
+
position.total > 1 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
5586
|
+
/* @__PURE__ */ jsx(
|
|
5587
|
+
"button",
|
|
5588
|
+
{
|
|
5589
|
+
type: "button",
|
|
5590
|
+
"aria-label": "Arquivo anterior",
|
|
5591
|
+
disabled: position.index === 0,
|
|
5592
|
+
onClick: () => onNavigate(-1),
|
|
5593
|
+
className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
|
|
5594
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
4626
5595
|
}
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
] }) }),
|
|
4633
|
-
/* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
|
|
4634
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
4635
|
-
/* @__PURE__ */ jsx("p", { className: "break-words font-medium", title: file.name, children: file.name }),
|
|
4636
|
-
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: /* @__PURE__ */ jsx(FileMeta, { file, dimensions }) }),
|
|
4637
|
-
isLowResolution(file, dimensions) && /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded-md bg-warning/10 px-2 py-1.5 text-xs font-medium text-warning", children: [
|
|
4638
|
-
"Baixa resolu\xE7\xE3o \u2014 no zoom da loja essa foto fica borrada. Troque por uma de pelo menos ",
|
|
4639
|
-
LOW_RESOLUTION_PX,
|
|
4640
|
-
"px no lado maior."
|
|
4641
|
-
] })
|
|
4642
|
-
] }),
|
|
4643
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
4644
|
-
/* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Onde \xE9 usada" }),
|
|
4645
|
-
file.reference ? /* @__PURE__ */ jsx(RecordLink, { reference: file.reference, className: "mt-1 text-sm" }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Biblioteca \u2014 este arquivo n\xE3o est\xE1 preso a nenhum registro." }),
|
|
4646
|
-
file.reference?.isCover && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\xC9 a capa: \xE9 ela que aparece no cat\xE1logo, no carrinho e no pedido." })
|
|
5596
|
+
),
|
|
5597
|
+
/* @__PURE__ */ jsxs("span", { className: "px-1 tabular-nums", children: [
|
|
5598
|
+
position.index + 1,
|
|
5599
|
+
" de ",
|
|
5600
|
+
position.total
|
|
4647
5601
|
] }),
|
|
4648
|
-
/* @__PURE__ */
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
5602
|
+
/* @__PURE__ */ jsx(
|
|
5603
|
+
"button",
|
|
5604
|
+
{
|
|
5605
|
+
type: "button",
|
|
5606
|
+
"aria-label": "Pr\xF3ximo arquivo",
|
|
5607
|
+
disabled: position.index === position.total - 1,
|
|
5608
|
+
onClick: () => onNavigate(1),
|
|
5609
|
+
className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
|
|
5610
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
|
|
5611
|
+
}
|
|
5612
|
+
)
|
|
5613
|
+
] }) }),
|
|
5614
|
+
/* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
|
|
5615
|
+
/* @__PURE__ */ jsx(Card, { className: "flex min-h-[320px] items-center justify-center overflow-hidden bg-muted p-2", children: file.kind === "image" ? /* @__PURE__ */ jsx(
|
|
5616
|
+
"img",
|
|
5617
|
+
{
|
|
5618
|
+
src: file.url,
|
|
5619
|
+
alt: file.name,
|
|
5620
|
+
className: "max-h-[70vh] w-auto max-w-full object-contain",
|
|
5621
|
+
onLoad: (e) => {
|
|
5622
|
+
const img = e.currentTarget;
|
|
5623
|
+
if (img.naturalWidth) onMeasured({ width: img.naturalWidth, height: img.naturalHeight });
|
|
5624
|
+
}
|
|
5625
|
+
},
|
|
5626
|
+
file.url
|
|
5627
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 p-12 text-muted-foreground", children: [
|
|
5628
|
+
/* @__PURE__ */ jsx(KindIcon, { kind: file.kind, className: "h-10 w-10" }),
|
|
5629
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: "Pr\xE9-visualiza\xE7\xE3o n\xE3o dispon\xEDvel" })
|
|
5630
|
+
] }) }),
|
|
5631
|
+
/* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
|
|
5632
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5633
|
+
/* @__PURE__ */ jsx("p", { className: "break-words font-medium", title: file.name, children: file.name }),
|
|
5634
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: /* @__PURE__ */ jsx(FileMeta, { file, dimensions }) }),
|
|
5635
|
+
isLowResolution(file, dimensions) && /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded-md bg-warning/10 px-2 py-1.5 text-xs font-medium text-warning", children: [
|
|
5636
|
+
"Baixa resolu\xE7\xE3o \u2014 no zoom da loja essa foto fica borrada. Troque por uma de pelo menos ",
|
|
5637
|
+
LOW_RESOLUTION_PX,
|
|
5638
|
+
"px no lado maior."
|
|
5639
|
+
] })
|
|
4652
5640
|
] }),
|
|
4653
|
-
/* @__PURE__ */
|
|
4654
|
-
/* @__PURE__ */ jsx(
|
|
4655
|
-
"
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
ref: replaceRef,
|
|
4662
|
-
type: "file",
|
|
4663
|
-
accept: file.kind === "image" ? "image/*" : void 0,
|
|
4664
|
-
className: "hidden",
|
|
4665
|
-
onChange: (e) => {
|
|
4666
|
-
const picked = e.target.files?.[0];
|
|
4667
|
-
e.target.value = "";
|
|
4668
|
-
if (picked) onReplace(picked);
|
|
4669
|
-
}
|
|
4670
|
-
}
|
|
4671
|
-
),
|
|
4672
|
-
/* @__PURE__ */ jsxs(
|
|
4673
|
-
Button,
|
|
5641
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
5642
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Onde \xE9 usada" }),
|
|
5643
|
+
file.reference ? /* @__PURE__ */ jsx(RecordLink, { reference: file.reference, className: "mt-1 text-sm" }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Biblioteca \u2014 este arquivo n\xE3o est\xE1 preso a nenhum registro." }),
|
|
5644
|
+
file.reference?.isCover && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\xC9 a capa: \xE9 ela que aparece no cat\xE1logo, no carrinho e no pedido." })
|
|
5645
|
+
] }),
|
|
5646
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
5647
|
+
file.kind === "image" && /* @__PURE__ */ jsx(
|
|
5648
|
+
AskAIButton,
|
|
4674
5649
|
{
|
|
4675
|
-
variant: "outline",
|
|
4676
5650
|
className: "w-full",
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
5651
|
+
size: "lg",
|
|
5652
|
+
label: "Melhorar com IA",
|
|
5653
|
+
media: { url: file.url, name: file.name },
|
|
5654
|
+
draft: "Melhora essa imagem",
|
|
5655
|
+
hint: "o que voc\xEA quer mudar nessa foto",
|
|
5656
|
+
intro: {
|
|
5657
|
+
persona: "Est\xFAdio de imagem",
|
|
5658
|
+
pitch: `Vamos melhorar ${file.name}.`,
|
|
5659
|
+
description: "Posso limpar o fundo, corrigir luz e cor, e gerar uma vers\xE3o em resolu\xE7\xE3o maior para o cat\xE1logo.",
|
|
5660
|
+
suggestions: [
|
|
5661
|
+
{ label: "Fundo branco", prompt: "Remove o fundo e deixa branco" },
|
|
5662
|
+
{ label: "Mais n\xEDtida", prompt: "Aumenta a resolu\xE7\xE3o e a nitidez" },
|
|
5663
|
+
{ label: "Corrigir a luz", prompt: "Corrige a ilumina\xE7\xE3o e as cores" }
|
|
5664
|
+
]
|
|
5665
|
+
}
|
|
4683
5666
|
}
|
|
4684
5667
|
),
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
5668
|
+
/* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", className: "w-full", children: /* @__PURE__ */ jsxs("a", { href: downloadUrlOf(file), download: file.name, children: [
|
|
5669
|
+
/* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }),
|
|
5670
|
+
"Baixar"
|
|
5671
|
+
] }) }),
|
|
5672
|
+
onReplace && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5673
|
+
/* @__PURE__ */ jsx(
|
|
5674
|
+
"input",
|
|
5675
|
+
{
|
|
5676
|
+
ref: replaceRef,
|
|
5677
|
+
type: "file",
|
|
5678
|
+
accept: file.kind === "image" ? "image/*" : void 0,
|
|
5679
|
+
className: "hidden",
|
|
5680
|
+
onChange: (e) => {
|
|
5681
|
+
const picked = e.target.files?.[0];
|
|
5682
|
+
e.target.value = "";
|
|
5683
|
+
if (picked) onReplace(picked);
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
),
|
|
5687
|
+
/* @__PURE__ */ jsxs(
|
|
5688
|
+
Button,
|
|
5689
|
+
{
|
|
5690
|
+
variant: "outline",
|
|
5691
|
+
className: "w-full",
|
|
5692
|
+
disabled: replacing,
|
|
5693
|
+
onClick: () => replaceRef.current?.click(),
|
|
5694
|
+
children: [
|
|
5695
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${replacing ? "animate-spin" : ""}` }),
|
|
5696
|
+
replacing ? "Trocando\u2026" : "Trocar arquivo"
|
|
5697
|
+
]
|
|
5698
|
+
}
|
|
5699
|
+
),
|
|
5700
|
+
file.reference && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "A troca mant\xE9m a posi\xE7\xE3o da foto na galeria do produto." })
|
|
5701
|
+
] }),
|
|
5702
|
+
onDelete && /* @__PURE__ */ jsxs(Button, { variant: "ghost", className: "w-full text-destructive hover:text-destructive", onClick: onDelete, children: [
|
|
5703
|
+
/* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
|
|
5704
|
+
"Excluir"
|
|
5705
|
+
] })
|
|
4690
5706
|
] })
|
|
4691
5707
|
] })
|
|
4692
5708
|
] })
|
|
4693
5709
|
] })
|
|
4694
|
-
|
|
5710
|
+
);
|
|
4695
5711
|
}
|
|
4696
5712
|
function RecordLink({ reference, className = "" }) {
|
|
4697
5713
|
return /* @__PURE__ */ jsxs(
|
|
@@ -4730,16 +5746,16 @@ var BLING_SAMPLE_SIZE = 100;
|
|
|
4730
5746
|
var BLING_SIGNATURE_HEADER = "x-bling-signature-256";
|
|
4731
5747
|
var BLING_WEBHOOK_DEADLINE_MS = 5e3;
|
|
4732
5748
|
var BLING_FUNCTION = "bling-sync";
|
|
4733
|
-
function
|
|
5749
|
+
function client2() {
|
|
4734
5750
|
const supabase = getSupabaseClientOptional();
|
|
4735
5751
|
if (!supabase) throw new Error("Sem conex\xE3o com o banco para falar com o Bling.");
|
|
4736
5752
|
return supabase;
|
|
4737
5753
|
}
|
|
4738
|
-
async function
|
|
5754
|
+
async function invoke2(body) {
|
|
4739
5755
|
const tenantId = getShopTenantId();
|
|
4740
5756
|
if (!tenantId) throw new Error("Sem loja selecionada para sincronizar com o Bling.");
|
|
4741
5757
|
const runtimeToken = await connectorRuntimeToken({ tenantId });
|
|
4742
|
-
const { data, error } = await
|
|
5758
|
+
const { data, error } = await client2().functions.invoke(BLING_FUNCTION, {
|
|
4743
5759
|
body: { ...body, tenantId },
|
|
4744
5760
|
headers: { [CONNECTOR_RUNTIME_TOKEN_HEADER]: runtimeToken }
|
|
4745
5761
|
});
|
|
@@ -4747,19 +5763,19 @@ async function invoke(body) {
|
|
|
4747
5763
|
return data;
|
|
4748
5764
|
}
|
|
4749
5765
|
function pullCatalogue(mode) {
|
|
4750
|
-
return
|
|
5766
|
+
return invoke2({ action: "pull_products", ...{ mode } });
|
|
4751
5767
|
}
|
|
4752
|
-
function
|
|
4753
|
-
return
|
|
5768
|
+
function pushCatalogue2() {
|
|
5769
|
+
return invoke2({ action: "push_products" });
|
|
4754
5770
|
}
|
|
4755
5771
|
function pushCustomers() {
|
|
4756
|
-
return
|
|
5772
|
+
return invoke2({ action: "push_customers" });
|
|
4757
5773
|
}
|
|
4758
5774
|
function pushOrders() {
|
|
4759
|
-
return
|
|
5775
|
+
return invoke2({ action: "push_orders" });
|
|
4760
5776
|
}
|
|
4761
5777
|
function pushStock() {
|
|
4762
|
-
return
|
|
5778
|
+
return invoke2({ action: "push_stock" });
|
|
4763
5779
|
}
|
|
4764
5780
|
async function countRows(table, apply) {
|
|
4765
5781
|
const tenantId = getShopTenantId();
|
|
@@ -4779,7 +5795,7 @@ async function pendingCustomers() {
|
|
|
4779
5795
|
if (!tenantId || !supabase) return null;
|
|
4780
5796
|
const { data: linked } = await supabase.from("plg_shop_customer_links").select("customer_id").eq("tenant_id", tenantId).eq("source", "bling");
|
|
4781
5797
|
const already = new Set((linked ?? []).map((row) => row.customer_id));
|
|
4782
|
-
const { data, error } = await supabase.from("
|
|
5798
|
+
const { data, error } = await supabase.from("v_shop_customers").select("id").eq("tenant_id", tenantId).gt("orders_count", 0);
|
|
4783
5799
|
if (error) return null;
|
|
4784
5800
|
return (data ?? []).filter((row) => !already.has(row.id)).length;
|
|
4785
5801
|
}
|
|
@@ -4789,7 +5805,7 @@ async function pendingOrders() {
|
|
|
4789
5805
|
if (!tenantId || !supabase) return null;
|
|
4790
5806
|
const { data: linked } = await supabase.from("plg_shop_order_links").select("order_id").eq("tenant_id", tenantId).eq("source", "bling").eq("kind", "sales_order");
|
|
4791
5807
|
const already = new Set((linked ?? []).map((row) => row.order_id));
|
|
4792
|
-
const { data, error } = await supabase.from("
|
|
5808
|
+
const { data, error } = await supabase.from("v_shop_orders").select("id").eq("tenant_id", tenantId).neq("status", "cancelled");
|
|
4793
5809
|
if (error) return null;
|
|
4794
5810
|
return (data ?? []).filter((row) => !already.has(row.id)).length;
|
|
4795
5811
|
}
|
|
@@ -4797,7 +5813,7 @@ async function listOrderDeliveries(limit = 10) {
|
|
|
4797
5813
|
const tenantId = getShopTenantId();
|
|
4798
5814
|
const supabase = getSupabaseClientOptional();
|
|
4799
5815
|
if (!tenantId || !supabase) return [];
|
|
4800
|
-
const { data: orders } = await supabase.from("
|
|
5816
|
+
const { data: orders } = await supabase.from("v_shop_orders").select("id,order_number,customer_name,customer_document,paid_at,created_at").eq("tenant_id", tenantId).neq("status", "cancelled").order("created_at", { ascending: false }).limit(limit);
|
|
4801
5817
|
const rows = orders ?? [];
|
|
4802
5818
|
if (rows.length === 0) return [];
|
|
4803
5819
|
const { data: filed } = await supabase.from("plg_shop_order_links").select("order_id,external_id,external_number,synced_at").eq("tenant_id", tenantId).eq("source", "bling").eq("kind", "sales_order").in("order_id", rows.map((row) => row.id));
|
|
@@ -4836,7 +5852,7 @@ async function listOrderDeliveries(limit = 10) {
|
|
|
4836
5852
|
});
|
|
4837
5853
|
}
|
|
4838
5854
|
function reconcile() {
|
|
4839
|
-
return
|
|
5855
|
+
return invoke2({ action: "reconcile" });
|
|
4840
5856
|
}
|
|
4841
5857
|
|
|
4842
5858
|
// src/integrations/bling/connectorDef.tsx
|
|
@@ -5062,7 +6078,7 @@ var blingConnectorDef = {
|
|
|
5062
6078
|
},
|
|
5063
6079
|
pending: pendingProducts,
|
|
5064
6080
|
run: async () => {
|
|
5065
|
-
const result = await
|
|
6081
|
+
const result = await pushCatalogue2();
|
|
5066
6082
|
return { message: result.message, status: result.status };
|
|
5067
6083
|
}
|
|
5068
6084
|
},
|
|
@@ -5275,6 +6291,311 @@ async function acceptWebhook(request, options) {
|
|
|
5275
6291
|
}
|
|
5276
6292
|
return { status: 200, queued: true };
|
|
5277
6293
|
}
|
|
6294
|
+
|
|
6295
|
+
// src/integrations/neochef/limits.ts
|
|
6296
|
+
var NEOCHEF_CONNECTOR_ID = "neochef";
|
|
6297
|
+
var NEOCHEF_RATE_LIMIT = { perSecond: 5, perDay: 5e4 };
|
|
6298
|
+
var NEOCHEF_PAGE_SIZE = 100;
|
|
6299
|
+
var NEOCHEF_SAMPLE_SIZE = 100;
|
|
6300
|
+
var NEOCHEF_CURSOR_OVERLAP_MS = 6e4;
|
|
6301
|
+
var NEOCHEF_TOKEN_LEAD_MS = 10 * 6e4;
|
|
6302
|
+
|
|
6303
|
+
// src/integrations/neochef/mark.ts
|
|
6304
|
+
var NEOCHEF_MARK = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAATp0lEQVR42u1da2xcx3WeMzP3sU/uksunKIl6y7JkyZYsSzYtuUlrGE1ctClao3kVadMkRVPYTQIXbdKkSIPWBYqkSZ00qYOmllGgRpu0aOEisBPXEuVHJFmmLFsv6kFJlCi+ltz3fcyd0x+zd3dJy7YSU+Ry9w5ACAtSd++955vvnPnmnDNAbuIYflpHEoz3PPoecuBmXRsCgzc3ICAwfHMDAQLDNzcQIDB8cwOBBsZvnPGL2IYGxm9uEEBg+OZ2CTQwfnOzAQ2M39wgoMErau5Bg9nf3CxAA+M3NwhoYPzmBkEQAwQxQDD7m5kFAgYIGCCY/c3MAgEDBAwQzP5mZoGAAQIGCEYAgGA07YDA/zf34I0JayoJIYSgpIQQQvXWaSO166yZ6i9qia1RLdrXBlpLtJwQg+hmC6JwccqZOZazJ1807clX1nr2RFv5YkiASoIeCxig/h9nlrGM1O7j0b6PzphdD6zh4d6eG72KZ10bK117dqgw/FTUGj+wTV2aeQpQCAEA6nXWl2e80bbzzZZNXyyGuh+4s/J7lBJRCEIIAaCUEEoJABBEJERKRCnV7xgjwCqz3Rp7/mjmxF8za2Jg69zvCQBQN0/BPIIeA6o7ic1/+XJ8wyP9yoiIKF0XgFIC/MbdHQqBKCUA5wQoJYSQ3NB3Dkwf/9J2FIWI/30BAOrI+Dy84kpq1760kdq9RRleCDWblQE9a2zcnnzpvJ0+bLvZ0yFpT4ZRWhyoIajRXtRi60pG6w7dSN29ioV6uqqs4Xn+dZzpwVOTr3zUdHNDfY0CgqUNgLIR9MSWM+39/xXl4d4eNeMB/BlvTQwcy5/7p0Lp2nObpDOdeNd1sdaSNTvffyK25pO62fn+O6qMgAhU0zx7YnLi4IfG7KlDtzYCCJYuAMovX4ttON/5vp/GmdGeIigEIQAEGHNzZy7MvP7F8eKV/75r1v8hgNVADkF9LgeQBKHWoKHuBw4ntnytRU9sWU/Q8whBJMC5dGcyYy88MOpMv7ZxqYNgiQIAkAAg1RKZ7l8+mOXRNStrjZ+/8OTB6cEvbJVuNqaWhIA3HsGXVxIEgaCkwEwrufVvD8XWfmZPLQi80pXR0efupp411kHAvz4JlMCFjPhTO78/xKNrVqJ0Xd/4M8e/8sLU4U/1K+NzQVBSNUNvdPlWZgGUlADz0LPM9NGH90wPfmG/CiwBULouCy3rTu3aN0oAULEHYACAmznj/R/ggqDHoqs+MRDq+cDOqs9Xxs+cfOw+AlyoWS/em9CFHvO/M3vmH/amX/v8fgKMAVCK0nXNjr3b4uv+eICgx6ruZWmBgdbtDAcuZvvs8g8KTo1UOnnb124lBNWLBs7zF548mDn52H1ANffnm/E3wgiCA9Xc3NDje3ND3y6DAICglC2b/2I7Cy0bVWCruU8/5pj1HAEA3sX3clGRcFFw35BADZvyWJ5q8RwhhMTXP/w6NVKtKF0XKOcif3Y4ffSROwgwD+fV+DUwkIITYF568NF7nJljpwlwjigE5bFoyy2PDhFCCOWxPOWxPDDTqjBIzXNUmCkIAq8f0fsf9eQdJ82OPeNG606dR1cnqZ6MADN1gp6UTqbAo6t7gRkGQSkJMDZ+4MEjpWvP7rjpEXn5+kbq7uNd73t+s/p+5Q5E/txl4LEQUM7Qs13pzhRE/vy0nT5i2xMHUvbU4U2zmKFOVg6LDAAVzROUFHi0EF35kVejq363XW/dfssN+GePAGPW+P7BsRfu37ZgL7X8PR39PzwU6vngTv8+3u2/OTPHTheGn7qWv7Dvdulm4iqQrXEXTecCapZakZW/82LP/T+baN3+rT166/ZbCEqJnmWp6P5t7F9ekmXPfNNZ+MALMHvmmzohiP59XN9tuC56lkXQ8/TE1g3JbX+3t/v+w9no6t8bqCxL/Z3LpmKA8iyiWiLTuuPxE5Hlv7VbvTDHIQQAqKb5fyrdbE7ak9NS5C2gOqd6a4wZqTYClEpnKj3yP2tC6FlmVdhZKOZi3rIPnBrn4eU9hCB69uSUdNJZ9CyX8liIGm0JqrXE37K/QHWdEEJKV585NHX406s9eyK1mC5h4QHga/eRlSMd/T8qaS2b19VKrYQQ4mZPnS+O/Ocla+z5pJs71ePZ6aS/pKNaS5aFeibMjr1XCDDMDX1772x1b+F0iOjqTw4AM6Q1vr/HK450SHemxX9GprdO89j6q2bHfenI8g8t01o2r/NZwZeqRf7s8PjAh6SbO716sUCwsAAovzgeXn61875nBY+uXlHdrWPMzZ46nzn52Gjx8g/vROnopFEGcBFe9sEjLZu+2Konbltf2WSimuaVRsfGXri/6ObOrFqMreYFBIAK+ICFS13ve+GKntiyXhlf7bTlzv7jgenXv7QdRT7ivzQ/RphN7+XAkfgxxCJG00Clug9JZwd05XikIkMr9gJmWonNX/lZfMPn9lZ2LKmmify5i6M/uTcm3fJm1QKCgC707G/b/q3BqvEpJUBp+rU/OZA++sgeFPnILBWvsqavpfcyKCq/X8RRuY+5+wy+aFXWAcpuAT3LnD72Z3unDn/qoJ98gtJ1eXTNyrY7vzekrrOwOgFdSL8f6vngocjKj9xDUAhfvk2/9rn9uaHv7AGqudXZgg2VdlUrKwPV3PyFJ/unDn3qRQKU+rJyeNmDd0VWfvjFqqzcSABASYEadnLrYx1q6aR21PLnvz+QG/r2XqCai7JBDT9HVkbpakA1Nz+8rz97+hv7qxoCYvK2r60GHik0oAtACPf++qtabF2f8nuci8LFkfTgo0q+bQrjv1VWnjn+5d1u9uQ5oJz7O4zRlR8+upDvYsGQFl39+2ElmCASAjDz5lcvoihEFnwJVydMQAggSkefOf7lSUIA1A9idNUnUgspDi0IALTYumGj7a6NKhbUdVG4OFK89O87lM9vzHz7G4oJgMri1Wd2uJkTZ4FqGkFEPbltvZ7YdrqhAGB2vv8SMNNUSh8hxZEfnUNpGyrYabbZX8sCqoahcOnpERUFuC4BxkJd9481FACM1O5yYobK0C1dey7WnNR/PRAQYo0/366WhSqR1UjtjjSYC9jYquhf09ArldzsyZ7ZIk+z2l89v5sbWiad9LS/ItBi6zoaCgDM7EhUy67Gp6Q9lSTBqDCAdGZaPGssXTGKkUo21iqgZncPpe02lM5P5iPfEUG6mZKvBwDVeAPnAwAERn8rCwBwuhjvZ0EAgKJQrJJBLAQ8UgwMX7tpxAXVk5Hq+ypaDQUArzgy7dMbM1JtzOyaKIM9aE5BCGFm+xQLdbf7n0VpdLKhAOBk3pipZPwA50brHVcV+hc3Hao+GlkA6omtl4CFwyoFDtHNvDHRUACwJg5otTpAqOdB8tZt3uYNAEM9v1oJAAkBsCYGGksKtsb3b5D2ZFqlfCGGuh/YwsyuMVV+1awsoGRwqiUykd7f3FzRSUS+YF17dk1DAUDak62l0R+f9N0A1eKx+IZHTlXk0Ka0v5LBY2s/85oqcrFtQggpjT3/hihe7mm4ZWDu3PcSFbkTpYyt/cO7tJZNZwmqrdGm8/0oOI+sHIlv/Px2glJCufA0d/Y7ekNmBNlTh24tXX3mEAHGEIUAZpqpnT8QwEyrqVyB/5zAvNTOf05TLR5DFIIA59bEgWPW2P/d3nDbwf7+d3rw0Q70SiUAxggKoSe3bUzd9S+DFV3crw1sZNpHSQlK2nbnd1822vtvU+lx6n1Mv/YFc5Y+0EgpYQSoFPmzfdODf3pYsQAiQSHCvb+xq6P/Pw5TrSVbcQeN5hL8amf0GLBwMbVr30vRvo/3+/UQBBibPv6VF52ZYxsWuj5ggesCuCAoeOv2xwdia/7g3toiCTd78lz66MM5a3z/tll/X5tlW2njUoe7iBXQzmk7U1OtbKTuPt56+zc0PbltY20xTOHS0y9NvvLxuxejOGSBK4P8Ro6Spnbtezmy4rfvJuXefX5Tp8Klp1/Knf1uzJ58eXOj6AR6644TsbWfTkf7PnYPIQC1z1y6+syhiZceuh3RY4tRLLoItYHViuDW27++P7buj/b6y0OlE6jNEGd68JQ1MTDmTB/longphm7WBBZyCEqw04dvrSchB3ikYLbvOSGdqTB6NgcetXl4WV5P3uGa7fe26607NvlCj+ppoOoD8xd+MDB15LO7q7N+4QG/SOXhVRBE+z52MLH1b25hRntbebvYVg0ar19yLQoXLl/5303LqkUUi8gSfpu65LZT3b/ys43vEANJRNcFahiq4HUmM/P6l17PnXvi3qq7WJznoIu2BVpuwpQffqr/2nO77Ny5JwbQKxaBGgYBxghKidK2Udp2pTsXQaRaPEK1RKaeKJ6FerMEpaxQOwpRvXfVRAKoYaC07fzwUwdHn92VzZ174t7qcm/xQMwXPzOWeaI40pN+9bM92dN/Pxzt+/BwqOfXevSWW9f6M6ZWJ6daMsHMjmHppJOKRRYzTlCzl0dWlghQSsr9hglwDpXWtIhu5sTZ4ugzI4Xhf13uZk/211OXEF4vJVP+MnHmja/2zbzxV6jFN57XE1vHeHSNUymvLgsmzOzKuNlTpF767fBwr+/hEYCQ0uiPDzvpIwU3f15zZgY73eypVQS9tbUScL2kw/O6yYop58mrVGnB3ezJNW72pNoUQfeFxJbN61AKAYxzFuouLrRg8s77+d2z3uP063+edDNv3vmWJTDxexaS4MCIt8+SLQd3AAhUd1AKLp2ZWbEKM9rrRDFUegQzO0OVVvPoeUS6jAAXAICV5d177VnYXCeGIBBUhZQEPebZE3o5gwhU1mx7fWQSleMPqreGy4oflW4mK53pOEHBEeu/9mFJ5OVLJ23WJkxSPcnqRQMgwAXV4uHa/EfpFU0SdAqdRwCIvFE96YMQqif4Yi+fKi+QhSzgYaMa0xatatOqAADzkjKNoqj7y0DVkTNaNwAgVHeAmWYVrAV7MZW9hmQA9CwdpRD+6R/Awhqpmz0g3QVq6NV7Lbn1tEJZ2gAoB1koLZ1guSW8yp1jtb9fHMsrAwM1HP98ojJY3aWU8r40ijPnNuKkGq03N1XT/UMG5wXc9PwKU6uje3FUnqNCKcDSKn1bouXZWE9oRBWbYHBiyAIqhrJ+AtSSriqeaHBm0MK99KJbx680cAHzHmkz0yGgqorq8CZnf+IhbdFXKI3GAEAN1VMY6xAAKJhySeUlKisDgAQAmMdk4oitfKzy/VIUnUVfa1c0CltTGUu+MBTSq5k+9a8F0KXQQIHyqOMnXNTdKmDO/VAeMYGaFiFBDDCPcnvCqlWE0LNEvcwwlK7mF3aW2SoMPBwAYF4ZwEg5syRBdLGeklgQJVYZIBqhPFZYKnIwXRotVDo9P+muzABeHTEAJ+UOqCoDmHNqtOWCGGBe06676eyX7tQRAwiO0hXVk8wIYWZHIXAB87jRwkO9Zm1KGHql+lECpauhdESti+Kh3tJSYQC+FFqpstCy+Bz5FW+s/drc8wRvxCD++r38L875PKfknRAE9OxZyiSLrFgyO4K83hsoUR7Ls1BPW22TKRRFJAQQgEoktQcz+5XDc403fx29Zpdy+cmr1iwAaJFVRgCA+ZCBEYGFuiaZ0bacEESCnkcQQIoCqONXrt9yFphpUR7LUz2Zo1pLEbS4TbWYAzzqAdUlMBOB6khQEgKUoCgBogsoigy9ApNuxpBu1pROJiLdTFS62dg7pXWjKDgEhVCKICKPrEpUi14CALynJSALr0gTAisIAfBz71DkKAHm8ciqyzyyIq1F1+V5bB3yyEqTh3pi1OxMUK0lRnkkeSPn+r69B3IcFIWidNJXPOtaRhQv50Vh2BG5s9zNn20RhYvtXulqF0ohCXAOTJWDsXBvG/BoQR2BV9+p4XUPAKNtV5YApZ41PuHmTl910odnjPY9Wmz9w5d5eHkXMLPvXdOJ0D+qpnxg1XUJx0/kKB/fAgBAdR10Xad6MsGja8hcXpduJisKw6ftiRcLucL5A0brzg4tvn4FMzs7tPiGk0761VtUP4T6ZYI6BoAKAO2pV+LjBx48YqePrJFOeishhHT+0k+OabF1fcq4UhIiJVaMXGtS36jqfMJ3z9fxhSYpVUm3f73rXBcYo1o8pie2xvMX9l3LDT2+lwCVPLTsmpG6ZxDdXPdS2BTi9b4CsMZ+esfckzopC2l+W1XVVILznycRq9KaZvaOE6906i67jXe8JkqpEkEYY6Eu9NvZiOLlHnHp33rmPkcAgPccfasIHyjzWKg7CbPPILClPTXtla6mPetaXhRHLM8ak541xqST1qSb0VHkDSkKBpEuQ+nwuXv6wAwXqC6AR23KozbVEw7V2wQLdUlmdjIW6g3zUHecmp2tTG9LEqDUj0mY0cFVgysuqgWu1d5AAQDmgQn8mAB4LO+VRqftqUMXnfSrlpN9MyLy59u84pUOKXJdNxuMVG+d4eHeM1psfVprudUxWnfGCfH3AlQLON99LYWxSC1igkECKTgYdQGAvoec4AiXJh19DzkQMEDgAoIRACAYzQ2AIA5oTv8fMEAwqgAIWKD5Zn/AAMGYDYCABZpr9gcMEIy3AiBggeaZ/W/LAAEImsP47+gCAhA0vvGDGCAY7wyAgAUae/bfEAMEIGhc4xMyt8HNu4wge6hxDP8LxQABGzSW8X+hIDAAQeMY/+d2AYFLaBzDzwsAAiAsXcPPKwACICw9w98UAASAqH+Dzx3/Dzwey48k2VW1AAAAAElFTkSuQmCC";
|
|
6305
|
+
var NEOCHEF_FUNCTION = "neochef-sync";
|
|
6306
|
+
function client3() {
|
|
6307
|
+
const supabase = getSupabaseClientOptional();
|
|
6308
|
+
if (!supabase) throw new Error("Sem conex\xE3o com o banco para falar com o NeoChef.");
|
|
6309
|
+
return supabase;
|
|
6310
|
+
}
|
|
6311
|
+
async function invoke3(body) {
|
|
6312
|
+
const tenantId = getShopTenantId();
|
|
6313
|
+
if (!tenantId) throw new Error("Sem loja selecionada para sincronizar com o NeoChef.");
|
|
6314
|
+
const runtimeToken = await connectorRuntimeToken({ tenantId });
|
|
6315
|
+
const { data, error } = await client3().functions.invoke(NEOCHEF_FUNCTION, {
|
|
6316
|
+
body: { ...body, tenantId },
|
|
6317
|
+
headers: { [CONNECTOR_RUNTIME_TOKEN_HEADER]: runtimeToken }
|
|
6318
|
+
});
|
|
6319
|
+
if (error) throw new Error(error.message);
|
|
6320
|
+
return data;
|
|
6321
|
+
}
|
|
6322
|
+
function testNeoChef() {
|
|
6323
|
+
return invoke3({ action: "test" });
|
|
6324
|
+
}
|
|
6325
|
+
function pullCatalogue2(mode) {
|
|
6326
|
+
return invoke3({ action: "pull_products", ...mode ? { mode } : {} });
|
|
6327
|
+
}
|
|
6328
|
+
function pushOrders2() {
|
|
6329
|
+
return invoke3({ action: "push_orders" });
|
|
6330
|
+
}
|
|
6331
|
+
function reconcile2() {
|
|
6332
|
+
return invoke3({ action: "reconcile" });
|
|
6333
|
+
}
|
|
6334
|
+
async function pendingOrders2() {
|
|
6335
|
+
const tenantId = getShopTenantId();
|
|
6336
|
+
const supabase = getSupabaseClientOptional();
|
|
6337
|
+
if (!tenantId || !supabase) return null;
|
|
6338
|
+
const { data: linked } = await supabase.from("plg_shop_order_links").select("order_id").eq("tenant_id", tenantId).eq("source", "neochef").eq("kind", "kitchen_order");
|
|
6339
|
+
const already = new Set((linked ?? []).map((row) => row.order_id));
|
|
6340
|
+
const { data, error } = await supabase.from("v_shop_orders").select("id").eq("tenant_id", tenantId).neq("status", "cancelled");
|
|
6341
|
+
if (error) return null;
|
|
6342
|
+
return (data ?? []).filter((row) => !already.has(row.id)).length;
|
|
6343
|
+
}
|
|
6344
|
+
async function listOrderDeliveries2(limit = 10) {
|
|
6345
|
+
const tenantId = getShopTenantId();
|
|
6346
|
+
const supabase = getSupabaseClientOptional();
|
|
6347
|
+
if (!tenantId || !supabase) return [];
|
|
6348
|
+
const { data: orders } = await supabase.from("v_shop_orders").select("id,order_number,paid_at,created_at,customer_name,status").eq("tenant_id", tenantId).order("created_at", { ascending: false }).limit(limit);
|
|
6349
|
+
const rows = orders ?? [];
|
|
6350
|
+
if (rows.length === 0) return [];
|
|
6351
|
+
const { data: filed } = await supabase.from("plg_shop_order_links").select("order_id,external_id,external_number,synced_at").eq("tenant_id", tenantId).eq("source", "neochef").eq("kind", "kitchen_order").in("order_id", rows.map((row) => row.id));
|
|
6352
|
+
const byOrder = new Map(
|
|
6353
|
+
(filed ?? []).map((row) => [row.order_id, row])
|
|
6354
|
+
);
|
|
6355
|
+
return rows.map((order) => {
|
|
6356
|
+
const subject = order.order_number ? `Pedido ${order.order_number}` : "Pedido";
|
|
6357
|
+
const link = byOrder.get(order.id);
|
|
6358
|
+
if (link) {
|
|
6359
|
+
return {
|
|
6360
|
+
subject,
|
|
6361
|
+
subjectId: order.id,
|
|
6362
|
+
status: "delivered",
|
|
6363
|
+
externalId: link.external_number ?? link.external_id,
|
|
6364
|
+
at: link.synced_at ?? order.paid_at,
|
|
6365
|
+
...order.customer_name ? { detail: String(order.customer_name) } : {}
|
|
6366
|
+
};
|
|
6367
|
+
}
|
|
6368
|
+
if (String(order.status ?? "") === "cancelled") {
|
|
6369
|
+
return {
|
|
6370
|
+
subject,
|
|
6371
|
+
subjectId: order.id,
|
|
6372
|
+
status: "failed",
|
|
6373
|
+
detail: "Cancelado \u2014 n\xE3o sobe para a cozinha.",
|
|
6374
|
+
at: order.created_at
|
|
6375
|
+
};
|
|
6376
|
+
}
|
|
6377
|
+
return {
|
|
6378
|
+
subject,
|
|
6379
|
+
subjectId: order.id,
|
|
6380
|
+
status: "pending",
|
|
6381
|
+
detail: "Ainda n\xE3o subiu \u2014 vai na pr\xF3xima sincroniza\xE7\xE3o.",
|
|
6382
|
+
at: order.paid_at ?? order.created_at
|
|
6383
|
+
};
|
|
6384
|
+
});
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6387
|
+
// src/integrations/neochef/connectorDef.tsx
|
|
6388
|
+
var neoChefConnectionStore = () => createConnectionStore(NEOCHEF_CONNECTOR_ID, {
|
|
6389
|
+
settingsKeys: ["kitchenId", "backfillFrom", "stockOwner"]
|
|
6390
|
+
});
|
|
6391
|
+
var neoChefConnectorDef = {
|
|
6392
|
+
id: NEOCHEF_CONNECTOR_ID,
|
|
6393
|
+
hostPluginId: "shop",
|
|
6394
|
+
name: "NeoChef",
|
|
6395
|
+
description: "Traz o card\xE1pio, o pre\xE7o e o estoque do NeoChef para a loja e devolve cada pedido pago como pedido na cozinha.",
|
|
6396
|
+
// The first question anyone connecting asks, answered before they connect.
|
|
6397
|
+
howItSyncs: [
|
|
6398
|
+
"De tempos em tempos, sozinho: desce do NeoChef o que mudou l\xE1 e sobe o que estiver pendente daqui.",
|
|
6399
|
+
"A qualquer momento, no bot\xE3o Sincronizar agora.",
|
|
6400
|
+
"Nada sobe duas vezes: cada pedido guarda o n\xFAmero que recebeu na cozinha.",
|
|
6401
|
+
"Produto desativado no NeoChef sai de venda na loja \u2014 nenhum produto \xE9 apagado."
|
|
6402
|
+
],
|
|
6403
|
+
icon: "ChefHat",
|
|
6404
|
+
// The app's own mark, so the row wears the same face the consent screen does.
|
|
6405
|
+
// `icon` stays as the fallback the mark resolver advances to — see mark.ts.
|
|
6406
|
+
logoUrl: NEOCHEF_MARK,
|
|
6407
|
+
authKind: "oauth",
|
|
6408
|
+
// `erp` and not `ecommerce`: NeoChef is the back office, not the storefront.
|
|
6409
|
+
// Same reading of §2 that moved Bling off the word 'commerce'.
|
|
6410
|
+
category: "erp",
|
|
6411
|
+
// Both ways, and the connector says so before anyone connects: the catalogue
|
|
6412
|
+
// comes in, the paid order goes out.
|
|
6413
|
+
direction: "inbound",
|
|
6414
|
+
planes: ["sync"],
|
|
6415
|
+
docsUrl: "https://fayz.ai/docs/integrations/neochef",
|
|
6416
|
+
/**
|
|
6417
|
+
* The consent names these, entity by entity, on NeoChef's own screen — this
|
|
6418
|
+
* list is what the STORE says it will do with them, which is the other half
|
|
6419
|
+
* of the same sentence.
|
|
6420
|
+
*
|
|
6421
|
+
* `shop_product` is read as well as written because the catalogue is a
|
|
6422
|
+
* MIRROR: `reconcileMirror` has to see what is here before it can decide what
|
|
6423
|
+
* a product missing from a page means.
|
|
6424
|
+
*/
|
|
6425
|
+
permissions: [
|
|
6426
|
+
{
|
|
6427
|
+
kind: "data",
|
|
6428
|
+
resource: "shop_product",
|
|
6429
|
+
actions: ["read", "write"],
|
|
6430
|
+
reason: "Trazer do NeoChef o card\xE1pio, o pre\xE7o e o saldo do estoque, comparando com o que j\xE1 est\xE1 na loja para n\xE3o duplicar nem tirar de venda por engano."
|
|
6431
|
+
},
|
|
6432
|
+
{
|
|
6433
|
+
kind: "data",
|
|
6434
|
+
resource: "shop_order",
|
|
6435
|
+
actions: ["read", "write"],
|
|
6436
|
+
// `write` is the LINK, not the order: without it every sync would file the
|
|
6437
|
+
// same sale again.
|
|
6438
|
+
reason: "Ler o pedido pago para lan\xE7\xE1-lo na cozinha, e guardar aqui o n\xFAmero que ele recebeu l\xE1. O conte\xFAdo do pedido n\xE3o \xE9 alterado.",
|
|
6439
|
+
sensitive: true
|
|
6440
|
+
},
|
|
6441
|
+
{
|
|
6442
|
+
kind: "event",
|
|
6443
|
+
resource: "shop.order_paid",
|
|
6444
|
+
actions: ["listen"],
|
|
6445
|
+
reason: "Saber que um pedido foi pago, para mand\xE1-lo para a cozinha."
|
|
6446
|
+
},
|
|
6447
|
+
{
|
|
6448
|
+
kind: "event",
|
|
6449
|
+
resource: "shop.order_cancelled",
|
|
6450
|
+
actions: ["listen"],
|
|
6451
|
+
reason: "Saber que um pedido foi cancelado, para n\xE3o mand\xE1-lo para a cozinha."
|
|
6452
|
+
}
|
|
6453
|
+
],
|
|
6454
|
+
// Declared, so the scheduler owns the clock. The budget is ours and not the
|
|
6455
|
+
// provider's — see limits.ts.
|
|
6456
|
+
rateLimit: NEOCHEF_RATE_LIMIT,
|
|
6457
|
+
// A connect screen that waits for a whole catalogue is a connect screen nobody
|
|
6458
|
+
// finishes, so the first load answers with a page and the backfill follows.
|
|
6459
|
+
firstLoad: { sampleSize: NEOCHEF_SAMPLE_SIZE, samplePages: 1, stream: "products" },
|
|
6460
|
+
// With underscores. `shop.order.paid` is not a key anything publishes — the
|
|
6461
|
+
// shop builds its facts as `shop.${def.event}` over SHOP_EMAIL_EVENTS.
|
|
6462
|
+
subscribesTo: ["shop.order_paid", "shop.order_cancelled"],
|
|
6463
|
+
managedAutomations: [
|
|
6464
|
+
{ eventKey: "shop.order_paid", label: "Mandar o pedido pago para a cozinha do NeoChef" },
|
|
6465
|
+
{ eventKey: "shop.order_cancelled", label: "Parar de mandar para a cozinha um pedido cancelado" }
|
|
6466
|
+
],
|
|
6467
|
+
// What HAPPENS, not what is declared. Nothing here names a moment that has no
|
|
6468
|
+
// code behind it.
|
|
6469
|
+
invokedAt: [
|
|
6470
|
+
{
|
|
6471
|
+
key: "shop.order.push",
|
|
6472
|
+
label: "O pedido pago sobe como pedido na cozinha",
|
|
6473
|
+
plane: "sync",
|
|
6474
|
+
trigger: "scheduled",
|
|
6475
|
+
touches: ["shop_order"]
|
|
6476
|
+
},
|
|
6477
|
+
{
|
|
6478
|
+
key: "shop.catalogue.pull",
|
|
6479
|
+
label: "O card\xE1pio, o pre\xE7o e o estoque do NeoChef descem para a loja",
|
|
6480
|
+
plane: "sync",
|
|
6481
|
+
trigger: "scheduled",
|
|
6482
|
+
touches: ["shop_product"]
|
|
6483
|
+
}
|
|
6484
|
+
],
|
|
6485
|
+
// Read from the row the list already batched — one request for N connectors.
|
|
6486
|
+
...connectionStatus(
|
|
6487
|
+
NEOCHEF_CONNECTOR_ID,
|
|
6488
|
+
(connection) => {
|
|
6489
|
+
if (!connection?.active) return { connected: false };
|
|
6490
|
+
const kitchen = connection.settings?.kitchenId;
|
|
6491
|
+
return {
|
|
6492
|
+
connected: connection.status === "connected",
|
|
6493
|
+
detail: kitchen ? `Cozinha ${kitchen}` : void 0,
|
|
6494
|
+
plane: "sync"
|
|
6495
|
+
};
|
|
6496
|
+
},
|
|
6497
|
+
{ store: neoChefConnectionStore }
|
|
6498
|
+
),
|
|
6499
|
+
// The whole connect step. Declaring `oauth` and no `startOAuth` is what renders
|
|
6500
|
+
// the Conectar button disabled with nothing on screen saying why.
|
|
6501
|
+
startOAuth: fayzConnectorOAuth(NEOCHEF_CONNECTOR_ID),
|
|
6502
|
+
// The other half of the consent: the platform stores the token and the pool
|
|
6503
|
+
// stores "is it on?". With nobody writing the second, the connector answers
|
|
6504
|
+
// "não conectado" to a merchant who has just authorized.
|
|
6505
|
+
saveConnection: async () => {
|
|
6506
|
+
await neoChefConnectionStore().save({ active: true });
|
|
6507
|
+
},
|
|
6508
|
+
// Disconnect FREEZES the mirror. A merchant who unplugs the kitchen still has
|
|
6509
|
+
// a store, and a connector that empties the catalogue on disconnect is one
|
|
6510
|
+
// nobody dares to disconnect.
|
|
6511
|
+
disconnect: async () => {
|
|
6512
|
+
await neoChefConnectionStore().disconnect();
|
|
6513
|
+
},
|
|
6514
|
+
disconnectNote: "Desconectar congela o espelho: nenhum produto \xE9 apagado e o estoque para de ser atualizado.",
|
|
6515
|
+
...connectionRuns(neoChefConnectionStore),
|
|
6516
|
+
// The managed automation says a paid order goes to the kitchen; this says
|
|
6517
|
+
// whether it arrived.
|
|
6518
|
+
listDeliveries: (options) => listOrderDeliveries2(options?.limit ?? 10),
|
|
6519
|
+
syncActions: [
|
|
6520
|
+
{
|
|
6521
|
+
key: "reconcile",
|
|
6522
|
+
label: "Sincronizar agora",
|
|
6523
|
+
direction: "inbound",
|
|
6524
|
+
// The one that is right on an ordinary day; the rest fold away.
|
|
6525
|
+
primary: true,
|
|
6526
|
+
description: "Traz o que mudou no NeoChef e envia o que est\xE1 pendente daqui \u2014 card\xE1pio, pre\xE7o, estoque e pedidos pagos.",
|
|
6527
|
+
run: async () => {
|
|
6528
|
+
const result = await reconcile2();
|
|
6529
|
+
const legs = Object.values(result.push ?? {}).map((leg) => leg?.message ? String(leg.message) : null).filter(Boolean);
|
|
6530
|
+
return [`${result.pull?.fetched ?? 0} produto(s) lidos do NeoChef`, ...legs].join(" \xB7 ");
|
|
6531
|
+
}
|
|
6532
|
+
},
|
|
6533
|
+
{
|
|
6534
|
+
key: "pull_products",
|
|
6535
|
+
label: "Trazer card\xE1pio do NeoChef",
|
|
6536
|
+
direction: "inbound",
|
|
6537
|
+
description: "Pre\xE7o e estoque do NeoChef passam a valer aqui. Nada \xE9 apagado da loja.",
|
|
6538
|
+
run: async () => {
|
|
6539
|
+
const result = await pullCatalogue2("full");
|
|
6540
|
+
return `${result.fetched} produto(s) lidos, ${result.inserted} criado(s), ${result.updated} atualizado(s)`;
|
|
6541
|
+
}
|
|
6542
|
+
},
|
|
6543
|
+
{
|
|
6544
|
+
key: "push_orders",
|
|
6545
|
+
label: "Enviar pedidos para a cozinha",
|
|
6546
|
+
direction: "outbound",
|
|
6547
|
+
description: "Cada pedido pago vira um pedido na cozinha. Pedido cancelado n\xE3o sobe, e um j\xE1 enviado n\xE3o sobe duas vezes.",
|
|
6548
|
+
pending: pendingOrders2,
|
|
6549
|
+
run: async () => {
|
|
6550
|
+
const result = await pushOrders2();
|
|
6551
|
+
return { message: result.message, status: result.status };
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
]
|
|
6555
|
+
};
|
|
6556
|
+
|
|
6557
|
+
// src/integrations/neochef/mapping.ts
|
|
6558
|
+
function toNumber2(value) {
|
|
6559
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
6560
|
+
const n = typeof value === "number" ? value : Number(String(value).replace(",", "."));
|
|
6561
|
+
return Number.isFinite(n) ? n : null;
|
|
6562
|
+
}
|
|
6563
|
+
function toMirroredNeoChefProduct(raw) {
|
|
6564
|
+
if (!raw || raw.id === null || raw.id === void 0 || raw.id === "") return null;
|
|
6565
|
+
const inactive = raw.is_active === false || String(raw.status ?? "active").toLowerCase() === "archived";
|
|
6566
|
+
return {
|
|
6567
|
+
externalId: String(raw.id),
|
|
6568
|
+
sku: raw.sku ? String(raw.sku) : null,
|
|
6569
|
+
name: String(raw.name ?? "").trim() || `Produto ${raw.id}`,
|
|
6570
|
+
price: toNumber2(raw.price),
|
|
6571
|
+
// The kitchen owns this number. The store mirrors it; it does not decide it.
|
|
6572
|
+
inventoryCount: toNumber2(raw.stock),
|
|
6573
|
+
status: inactive ? "archived" : "active",
|
|
6574
|
+
imageUrl: raw.image_url ? String(raw.image_url) : null,
|
|
6575
|
+
// Unlike Bling, the far side DOES return its change stamp, so the cursor can
|
|
6576
|
+
// advance from the data instead of from the window we asked for.
|
|
6577
|
+
updatedAt: raw.updated_at ? String(raw.updated_at) : null
|
|
6578
|
+
};
|
|
6579
|
+
}
|
|
6580
|
+
function toNeoChefOrder(order) {
|
|
6581
|
+
return {
|
|
6582
|
+
kind: order.pickup === true ? "takeout" : "delivery",
|
|
6583
|
+
// The store's own number, so a human can match the two screens. Falls back
|
|
6584
|
+
// to the id rather than to empty: an order with no reference is unmatchable.
|
|
6585
|
+
reference_number: String(order.referenceNumber ?? order.id),
|
|
6586
|
+
// It arrives paid and confirmed — the store already took the money, and a
|
|
6587
|
+
// kitchen that has to re-confirm it is a kitchen doing the work twice.
|
|
6588
|
+
status: "confirmed",
|
|
6589
|
+
total: toNumber2(order.total),
|
|
6590
|
+
subtotal: toNumber2(order.subtotal),
|
|
6591
|
+
notes: order.notes ? String(order.notes) : null
|
|
6592
|
+
};
|
|
6593
|
+
}
|
|
6594
|
+
function advanceNeoChefCursor(products, overlapMs) {
|
|
6595
|
+
const stamps = products.map((product) => product.updatedAt ? Date.parse(product.updatedAt) : NaN).filter((value) => Number.isFinite(value));
|
|
6596
|
+
if (stamps.length === 0) return null;
|
|
6597
|
+
return new Date(Math.max(...stamps) - overlapMs).toISOString();
|
|
6598
|
+
}
|
|
5278
6599
|
var ORDERS = "plg_shop_rep_orders";
|
|
5279
6600
|
var ITEMS = "plg_shop_rep_order_items";
|
|
5280
6601
|
var CUSTOMERS = "plg_shop_rep_customers";
|
|
@@ -6102,7 +7423,7 @@ function buildShopAnalyticsCards() {
|
|
|
6102
7423
|
}
|
|
6103
7424
|
|
|
6104
7425
|
// src/index.ts
|
|
6105
|
-
var ShopPage =
|
|
7426
|
+
var ShopPage = React9.lazy(() => import('./ShopPage-QFIM2HDU.js').then((m) => ({ default: m.ShopPage })));
|
|
6106
7427
|
function createShopPlugin(options) {
|
|
6107
7428
|
const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
|
|
6108
7429
|
setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
|
|
@@ -6111,10 +7432,10 @@ function createShopPlugin(options) {
|
|
|
6111
7432
|
registerEntityPath("shop_customer", "/orders/customers");
|
|
6112
7433
|
registerPersonLookup("shop_customer", shopCustomerLookup(() => resolveProvider()));
|
|
6113
7434
|
const pageFor = (section) => {
|
|
6114
|
-
const Wrapped = () =>
|
|
6115
|
-
|
|
7435
|
+
const Wrapped = () => React9.createElement(
|
|
7436
|
+
React9.Suspense,
|
|
6116
7437
|
{ fallback: null },
|
|
6117
|
-
|
|
7438
|
+
React9.createElement(
|
|
6118
7439
|
ShopPage,
|
|
6119
7440
|
{ provider: options?.provider, section }
|
|
6120
7441
|
)
|
|
@@ -6133,6 +7454,24 @@ function createShopPlugin(options) {
|
|
|
6133
7454
|
const ProductsCrudPage = createCrudPage(buildShopProductEntity(curr.code, resolveProvider), {
|
|
6134
7455
|
dataProvider: createShopProductDataProvider(resolveProvider),
|
|
6135
7456
|
feature: "shop",
|
|
7457
|
+
// O produto da loja não tem tabela própria — vive atrás do provider de
|
|
7458
|
+
// commerce —, então as primitivas de dado do agente não o alcançam. A porta
|
|
7459
|
+
// funciona porque a lista empresta o próprio store: ver CrudAgentSurface.
|
|
7460
|
+
aiCreate: {
|
|
7461
|
+
hint: "Descreva o produto\u2026",
|
|
7462
|
+
description: "Descreva o produto e eu cadastro.",
|
|
7463
|
+
intro: {
|
|
7464
|
+
role: "operations",
|
|
7465
|
+
persona: "Cadastro de produtos",
|
|
7466
|
+
pitch: "Diga o produto que voc\xEA quer cadastrar.",
|
|
7467
|
+
description: "Nome, pre\xE7o, descri\xE7\xE3o, estoque \u2014 diga o que souber, do jeito que falar, e eu preencho o resto com o que faz sentido. Nasce como rascunho e voc\xEA confere antes de publicar.",
|
|
7468
|
+
suggestions: [
|
|
7469
|
+
{ label: "Camiseta preta, R$ 79, estoque 20" },
|
|
7470
|
+
{ label: "Cadastre 5 produtos de exemplo para eu testar a loja" },
|
|
7471
|
+
{ label: "Um combo com os dois produtos mais vendidos" }
|
|
7472
|
+
]
|
|
7473
|
+
}
|
|
7474
|
+
},
|
|
6136
7475
|
stats: (items, total) => [
|
|
6137
7476
|
{ id: "total", label: "Produtos", value: total, icon: Package },
|
|
6138
7477
|
{ id: "active", label: "Ativos", value: items?.filter((p) => p.status === "active").length ?? 0, icon: Eye },
|
|
@@ -6222,6 +7561,29 @@ function createShopPlugin(options) {
|
|
|
6222
7561
|
const DiscountsCrudPage = createCrudPage(buildShopDiscountEntity(), {
|
|
6223
7562
|
dataProvider: createShopDiscountDataProvider(resolveProvider),
|
|
6224
7563
|
feature: "shop",
|
|
7564
|
+
// O cupom vive atrás do provider de commerce, sem tabela que as primitivas
|
|
7565
|
+
// de dado do agente alcancem — então a lista empresta o próprio store, como
|
|
7566
|
+
// a de produtos faz (ver CrudAgentSurface).
|
|
7567
|
+
aiCreate: {
|
|
7568
|
+
hint: "Descreva o cupom\u2026",
|
|
7569
|
+
description: "Descreva a promo\xE7\xE3o e eu crio o cupom.",
|
|
7570
|
+
// O código não se pergunta: ver DISCOUNT_CREATE_INSTRUCTIONS. `code` é
|
|
7571
|
+
// único no banco (0001:208), então a lista publica os que já existem e a
|
|
7572
|
+
// recusa do índice volta legível se dois turnos escolherem o mesmo.
|
|
7573
|
+
instructions: DISCOUNT_CREATE_INSTRUCTIONS,
|
|
7574
|
+
uniqueFields: ["code"],
|
|
7575
|
+
intro: {
|
|
7576
|
+
role: "operations",
|
|
7577
|
+
persona: "Cupons e promo\xE7\xF5es",
|
|
7578
|
+
pitch: "Diga a promo\xE7\xE3o que voc\xEA quer fazer.",
|
|
7579
|
+
description: "Frete gr\xE1tis, percentual ou valor fixo \u2014 diga a regra do jeito que voc\xEA falaria com um cliente, e eu escrevo o c\xF3digo, o tipo, o m\xEDnimo e a validade. S\xF3 pergunto o tamanho do desconto quando voc\xEA n\xE3o disser.",
|
|
7580
|
+
suggestions: [
|
|
7581
|
+
{ label: "Frete gr\xE1tis at\xE9 o fim da semana" },
|
|
7582
|
+
{ label: "10% na primeira compra, uma vez por cliente" },
|
|
7583
|
+
{ label: "R$ 50 off acima de R$ 300, limite de 100 usos" }
|
|
7584
|
+
]
|
|
7585
|
+
}
|
|
7586
|
+
},
|
|
6225
7587
|
stats: (items, total) => [
|
|
6226
7588
|
{ id: "total", label: "Total de cupons", value: total, icon: Tag },
|
|
6227
7589
|
{ id: "active", label: "Ativos agora", value: items?.filter((d) => d.status === "active").length ?? 0, icon: Eye },
|
|
@@ -6252,7 +7614,7 @@ function createShopPlugin(options) {
|
|
|
6252
7614
|
dataProvider: createShopGiftCardDataProvider(resolveProvider),
|
|
6253
7615
|
feature: "shop",
|
|
6254
7616
|
stats: (items, total) => {
|
|
6255
|
-
const
|
|
7617
|
+
const money2 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
|
|
6256
7618
|
const active = items?.filter((g) => g.status === "active") ?? [];
|
|
6257
7619
|
return [
|
|
6258
7620
|
{ id: "total", label: "Cart\xF5es emitidos", value: total, icon: CreditCard },
|
|
@@ -6263,20 +7625,20 @@ function createShopPlugin(options) {
|
|
|
6263
7625
|
id: "outstanding",
|
|
6264
7626
|
label: "Saldo em aberto",
|
|
6265
7627
|
icon: CircleDollarSign,
|
|
6266
|
-
value:
|
|
7628
|
+
value: money2(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
|
|
6267
7629
|
},
|
|
6268
7630
|
{
|
|
6269
7631
|
id: "issued",
|
|
6270
7632
|
label: "Valor emitido",
|
|
6271
7633
|
icon: TicketCheck,
|
|
6272
|
-
value:
|
|
7634
|
+
value: money2(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
|
|
6273
7635
|
}
|
|
6274
7636
|
];
|
|
6275
7637
|
}
|
|
6276
7638
|
});
|
|
6277
|
-
const ContentPageWrapped = () =>
|
|
7639
|
+
const ContentPageWrapped = () => React9.createElement(ContentPage, { resolveProvider: options?.provider });
|
|
6278
7640
|
ContentPageWrapped.displayName = "ContentPage";
|
|
6279
|
-
const ChannelsPageWrapped = () =>
|
|
7641
|
+
const ChannelsPageWrapped = () => React9.createElement(ChannelsPage, { resolveProvider: options?.provider });
|
|
6280
7642
|
ChannelsPageWrapped.displayName = "ChannelsPage";
|
|
6281
7643
|
const navSection = options?.navSection ?? "main";
|
|
6282
7644
|
const navPosition = options?.navPosition ?? 6;
|
|
@@ -6366,7 +7728,7 @@ function createShopPlugin(options) {
|
|
|
6366
7728
|
];
|
|
6367
7729
|
const redirectTo = (target) => {
|
|
6368
7730
|
const Redirect = () => {
|
|
6369
|
-
|
|
7731
|
+
React9.useEffect(() => {
|
|
6370
7732
|
adminNavigateTo(target);
|
|
6371
7733
|
}, []);
|
|
6372
7734
|
return null;
|
|
@@ -6397,6 +7759,7 @@ function createShopPlugin(options) {
|
|
|
6397
7759
|
const READ = { feature: "shop", action: "read" };
|
|
6398
7760
|
return {
|
|
6399
7761
|
id: "shop",
|
|
7762
|
+
defaultAgentRole: "operations",
|
|
6400
7763
|
name: options?.navLabel ?? "Shop",
|
|
6401
7764
|
icon: "ShoppingBag",
|
|
6402
7765
|
version: "1.0.0",
|
|
@@ -6489,7 +7852,7 @@ function createShopPlugin(options) {
|
|
|
6489
7852
|
label: label("store", "Loja"),
|
|
6490
7853
|
icon: "ShoppingBag",
|
|
6491
7854
|
group: COMMERCE_GROUP,
|
|
6492
|
-
component: settingsTab("StoreGeneralSettings", () =>
|
|
7855
|
+
component: settingsTab("StoreGeneralSettings", () => React9.createElement(StoreGeneralSettings, { currency: curr, provider: options?.provider })),
|
|
6493
7856
|
order: 30,
|
|
6494
7857
|
permission: READ
|
|
6495
7858
|
},
|
|
@@ -6498,7 +7861,7 @@ function createShopPlugin(options) {
|
|
|
6498
7861
|
label: "Pagamentos",
|
|
6499
7862
|
icon: "CreditCard",
|
|
6500
7863
|
group: COMMERCE_GROUP,
|
|
6501
|
-
component: settingsTab("PaymentsSettings", () =>
|
|
7864
|
+
component: settingsTab("PaymentsSettings", () => React9.createElement(PaymentsSettings, { provider: options?.provider })),
|
|
6502
7865
|
order: 31,
|
|
6503
7866
|
permission: READ
|
|
6504
7867
|
},
|
|
@@ -6507,7 +7870,7 @@ function createShopPlugin(options) {
|
|
|
6507
7870
|
label: "Checkout",
|
|
6508
7871
|
icon: "ShoppingCart",
|
|
6509
7872
|
group: COMMERCE_GROUP,
|
|
6510
|
-
component: settingsTab("CheckoutSettings", () =>
|
|
7873
|
+
component: settingsTab("CheckoutSettings", () => React9.createElement(CheckoutSettings, { provider: options?.provider })),
|
|
6511
7874
|
order: 32,
|
|
6512
7875
|
permission: READ
|
|
6513
7876
|
},
|
|
@@ -6516,7 +7879,7 @@ function createShopPlugin(options) {
|
|
|
6516
7879
|
label: label("shipping", "Entrega"),
|
|
6517
7880
|
icon: "Truck",
|
|
6518
7881
|
group: COMMERCE_GROUP,
|
|
6519
|
-
component: settingsTab("ShippingSettings", () =>
|
|
7882
|
+
component: settingsTab("ShippingSettings", () => React9.createElement(ShippingSettings, { provider: options?.provider })),
|
|
6520
7883
|
order: 33,
|
|
6521
7884
|
permission: READ
|
|
6522
7885
|
},
|
|
@@ -6525,7 +7888,7 @@ function createShopPlugin(options) {
|
|
|
6525
7888
|
label: "Fulfillment",
|
|
6526
7889
|
icon: "PackageCheck",
|
|
6527
7890
|
group: COMMERCE_GROUP,
|
|
6528
|
-
component: settingsTab("FulfillmentSettings", () =>
|
|
7891
|
+
component: settingsTab("FulfillmentSettings", () => React9.createElement(FulfillmentSettings, { provider: options?.provider })),
|
|
6529
7892
|
order: 34,
|
|
6530
7893
|
permission: READ
|
|
6531
7894
|
}
|
|
@@ -6534,28 +7897,37 @@ function createShopPlugin(options) {
|
|
|
6534
7897
|
// screens (commerce-stack.md §0). Melhor Envio, AppMax and AbacatePay are
|
|
6535
7898
|
// REQUEST-plane connectors: the buyer is waiting, so each declares a latency
|
|
6536
7899
|
// budget and, because the carrier and the gateways want opposite answers to
|
|
6537
|
-
// the same failure, its own `onFailure`. Bling
|
|
6538
|
-
// nobody is waiting on
|
|
6539
|
-
// a
|
|
7900
|
+
// the same failure, its own `onFailure`. Bling and NeoChef are the SYNC-plane
|
|
7901
|
+
// ones — nobody is waiting on them, and what they declare instead is a call
|
|
7902
|
+
// budget and a cursor.
|
|
6540
7903
|
//
|
|
6541
|
-
//
|
|
6542
|
-
//
|
|
7904
|
+
// Four gateways on the shelf is the point: the store picks one, and each
|
|
7905
|
+
// one arriving changed no contract — only how it authorizes. Mercado Pago
|
|
7906
|
+
// pushes that furthest, because it is the first whose CARD is paid inside
|
|
7907
|
+
// the store: it tokenises in the browser, so there is no PAN for an edge
|
|
7908
|
+
// function to hold and no redirect to send the buyer through. The shelf
|
|
7909
|
+
// absorbed that too, as a capability the checkout reads rather than a branch
|
|
7910
|
+
// it hard-codes. NeoChef makes the same point about the back office, and one
|
|
7911
|
+
// better: its provider is another Fayz app, and it still fits without a
|
|
7912
|
+
// special case.
|
|
6543
7913
|
connectors: [
|
|
6544
7914
|
melhorEnvioConnectorDef,
|
|
6545
7915
|
appmaxConnectorDef,
|
|
6546
7916
|
abacatePayConnectorDef,
|
|
6547
7917
|
stripeConnectConnectorDef,
|
|
6548
7918
|
vindiConnectorDef,
|
|
6549
|
-
|
|
7919
|
+
mercadoPagoConnectorDef,
|
|
7920
|
+
blingConnectorDef,
|
|
7921
|
+
neoChefConnectorDef
|
|
6550
7922
|
],
|
|
6551
|
-
// What having a store makes plugable — the
|
|
7923
|
+
// What having a store makes plugable — the ones above plus the ones still
|
|
6552
7924
|
// to be built. Carried over from the Fayz plugin page's "Integra com" row,
|
|
6553
7925
|
// minus Nuvemshop: the platform catalogue has no row for it, and a slug the
|
|
6554
7926
|
// directory cannot back is refused rather than shown.
|
|
6555
|
-
integrations: ["shopify", "stripe"
|
|
7927
|
+
integrations: ["shopify", "stripe"]
|
|
6556
7928
|
};
|
|
6557
7929
|
}
|
|
6558
7930
|
|
|
6559
|
-
export { ABACATEPAY_CONNECTOR_ID, ABACATEPAY_LATENCY_BUDGET_MS, APPMAX_CONNECTOR_ID, APPMAX_LATENCY_BUDGET_MS, BLING_CONNECTOR_ID, BLING_MARKETPLACE_SOURCE, BLING_MAX_WINDOW_MS, BLING_PAGE_SIZE, BLING_RATE_LIMIT, BLING_SAMPLE_SIZE, BLING_SIGNATURE_HEADER, BLING_TOKEN_LEAD_MS, BLING_WEBHOOK_DEADLINE_MS, BLING_WINDOW_OVERLAP_MS, MELHOR_ENVIO_CONNECTOR_ID, MELHOR_ENVIO_LATENCY_BUDGET_MS, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, melhorEnvioConnectorDef, orphanConnection, pullProducts, routeCallback, sampleProducts, stripeConnectConnectorDef, toMirroredProduct, vindiConnectorDef, windowFor };
|
|
7931
|
+
export { ABACATEPAY_CONNECTOR_ID, ABACATEPAY_LATENCY_BUDGET_MS, APPMAX_CONNECTOR_ID, APPMAX_LATENCY_BUDGET_MS, BLING_CONNECTOR_ID, BLING_MARKETPLACE_SOURCE, BLING_MAX_WINDOW_MS, BLING_PAGE_SIZE, BLING_RATE_LIMIT, BLING_SAMPLE_SIZE, BLING_SIGNATURE_HEADER, BLING_TOKEN_LEAD_MS, BLING_WEBHOOK_DEADLINE_MS, BLING_WINDOW_OVERLAP_MS, MELHOR_ENVIO_CONNECTOR_ID, MELHOR_ENVIO_LATENCY_BUDGET_MS, MERCADOPAGO_CONNECTOR_ID, MERCADOPAGO_LATENCY_BUDGET_MS, NEOCHEF_CONNECTOR_ID, NEOCHEF_CURSOR_OVERLAP_MS, NEOCHEF_FUNCTION, NEOCHEF_MARK, NEOCHEF_PAGE_SIZE, NEOCHEF_RATE_LIMIT, NEOCHEF_SAMPLE_SIZE, NEOCHEF_TOKEN_LEAD_MS, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, advanceNeoChefCursor, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, listOrderDeliveries2 as listOrderDeliveries, melhorEnvioConnectorDef, mercadoPagoConnectorDef, neoChefConnectionStore, neoChefConnectorDef, orphanConnection, pendingOrders2 as pendingOrders, pullCatalogue2 as pullCatalogue, pullProducts, pushOrders2 as pushOrders, reconcile2 as reconcile, routeCallback, sampleProducts, stripeConnectConnectorDef, testNeoChef, toMirroredNeoChefProduct, toMirroredProduct, toNeoChefOrder, vindiConnectorDef, windowFor };
|
|
6560
7932
|
//# sourceMappingURL=index.js.map
|
|
6561
7933
|
//# sourceMappingURL=index.js.map
|