@fayz-ai/shop 0.10.0 → 0.11.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/{chunk-IL6GA4ER.js → chunk-2WACO2TG.js} +3 -3
- package/dist/{chunk-IL6GA4ER.js.map → chunk-2WACO2TG.js.map} +1 -1
- package/dist/{chunk-RGTIJQJL.js → chunk-6BO2TMZ5.js} +3 -3
- package/dist/{chunk-RGTIJQJL.js.map → chunk-6BO2TMZ5.js.map} +1 -1
- package/dist/{chunk-XOP6SHPB.js → chunk-NWW3WJ7Y.js} +4 -10
- package/dist/chunk-NWW3WJ7Y.js.map +1 -0
- package/dist/{chunk-XERLK5LD.js → chunk-STRNMFC4.js} +28 -5
- package/dist/chunk-STRNMFC4.js.map +1 -0
- package/dist/{chunk-U3O2I3RM.js → chunk-U6HV3LVP.js} +385 -23
- package/dist/chunk-U6HV3LVP.js.map +1 -0
- package/dist/data/customer-search.d.ts +3 -0
- package/dist/data/customer-search.d.ts.map +1 -0
- package/dist/data/functions.d.ts +9 -0
- package/dist/data/functions.d.ts.map +1 -0
- package/dist/data/media.d.ts +37 -0
- package/dist/data/media.d.ts.map +1 -0
- package/dist/data/mock-provider.d.ts +40 -8
- package/dist/data/mock-provider.d.ts.map +1 -1
- package/dist/data/provider.d.ts +200 -7
- package/dist/data/provider.d.ts.map +1 -1
- package/dist/data/supabase-provider.d.ts +113 -5
- package/dist/data/supabase-provider.d.ts.map +1 -1
- package/dist/data/tables.d.ts +3 -0
- package/dist/data/tables.d.ts.map +1 -1
- package/dist/entities/index.d.ts +0 -1
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities.js +2 -2
- package/dist/index.d.ts +14 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +891 -60
- package/dist/index.js.map +1 -1
- package/dist/inventory/index.d.ts +2 -2
- package/dist/inventory/index.d.ts.map +1 -1
- package/dist/inventory/stock-types.d.ts +60 -1
- package/dist/inventory/stock-types.d.ts.map +1 -1
- package/dist/inventory/supabase-stock-store.d.ts +1 -0
- package/dist/inventory/supabase-stock-store.d.ts.map +1 -1
- package/dist/inventory.js +1 -1
- package/dist/lib/commerce.d.ts.map +1 -1
- package/dist/lib/document.d.ts +13 -0
- package/dist/lib/document.d.ts.map +1 -0
- package/dist/lib/variant-matrix.d.ts +120 -0
- package/dist/lib/variant-matrix.d.ts.map +1 -0
- package/dist/mock.js +2 -2
- package/dist/providers/checkout.d.ts +129 -0
- package/dist/providers/checkout.d.ts.map +1 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/shipping.d.ts +43 -0
- package/dist/providers/shipping.d.ts.map +1 -0
- package/dist/providers.js +70 -0
- package/dist/providers.js.map +1 -0
- package/dist/runtime.js +3 -3
- package/dist/shipping/label-provider.d.ts +43 -0
- package/dist/shipping/label-provider.d.ts.map +1 -0
- package/dist/types.d.ts +457 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -4
- package/dist/chunk-U3O2I3RM.js.map +0 -1
- package/dist/chunk-XERLK5LD.js.map +0 -1
- package/dist/chunk-XOP6SHPB.js.map +0 -1
- package/dist/entities/sidecar.d.ts +0 -9
- package/dist/entities/sidecar.d.ts.map +0 -1
|
@@ -1,8 +1,192 @@
|
|
|
1
1
|
import { MOCK_PRODUCTS, MOCK_CATEGORIES, MOCK_DISCOUNTS, MOCK_VARIANTS } from './chunk-IKNGCRUM.js';
|
|
2
|
-
import { resolveEntityStore, createMockEntityStore
|
|
2
|
+
import { resolveEntityStore, createMockEntityStore } from './chunk-NWW3WJ7Y.js';
|
|
3
3
|
import { buildRuleContext, computePromotionActions, discountToPromotion } from './chunk-3GERSY5F.js';
|
|
4
4
|
import { resolveEntityDeclaration } from '@fayz-ai/core';
|
|
5
5
|
|
|
6
|
+
// src/data/media.ts
|
|
7
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "avif", "bmp"]);
|
|
8
|
+
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set(["mp4", "mov", "webm", "m4v", "avi", "mkv"]);
|
|
9
|
+
function mediaKindOf(name, mime) {
|
|
10
|
+
if (mime?.startsWith("image/")) return "image";
|
|
11
|
+
if (mime?.startsWith("video/")) return "video";
|
|
12
|
+
const ext = (name.split(".").pop() ?? "").toLowerCase();
|
|
13
|
+
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
14
|
+
if (VIDEO_EXTENSIONS.has(ext)) return "video";
|
|
15
|
+
return "document";
|
|
16
|
+
}
|
|
17
|
+
function storagePathOf(url) {
|
|
18
|
+
const match = /\/object\/(?:public|sign|authenticated)\/[^/]+\/([^?]+)/.exec(url);
|
|
19
|
+
if (!match) return null;
|
|
20
|
+
try {
|
|
21
|
+
return decodeURIComponent(match[1]);
|
|
22
|
+
} catch {
|
|
23
|
+
return match[1];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function displayNameOf(path) {
|
|
27
|
+
const base = path.split("/").pop() ?? path;
|
|
28
|
+
return base.replace(/^\d{10,}-/, "");
|
|
29
|
+
}
|
|
30
|
+
function productImageToMediaFile(image, productName) {
|
|
31
|
+
const path = storagePathOf(image.url);
|
|
32
|
+
const name = displayNameOf(path ?? image.url);
|
|
33
|
+
return {
|
|
34
|
+
// Fall back to the URL as identity: an image row that points outside our
|
|
35
|
+
// bucket still belongs in the library, it just cannot be deleted by path.
|
|
36
|
+
id: path ?? image.url,
|
|
37
|
+
name,
|
|
38
|
+
url: image.url,
|
|
39
|
+
kind: mediaKindOf(name),
|
|
40
|
+
size: 0,
|
|
41
|
+
extension: (name.split(".").pop() ?? "").toUpperCase(),
|
|
42
|
+
createdAt: image.createdAt ?? "",
|
|
43
|
+
reference: {
|
|
44
|
+
type: "product",
|
|
45
|
+
id: image.productId,
|
|
46
|
+
label: productName?.trim() || "Produto sem nome",
|
|
47
|
+
attachmentId: image.id,
|
|
48
|
+
isCover: image.isPrimary
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function mergeMediaFiles(attached, library) {
|
|
53
|
+
const seen = new Set(attached.map((f) => f.id));
|
|
54
|
+
return [...attached, ...library.filter((f) => !seen.has(f.id))].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/lib/variant-matrix.ts
|
|
58
|
+
var MAX_VARIANT_AXES = 3;
|
|
59
|
+
var MAX_VARIANT_COMBINATIONS = 100;
|
|
60
|
+
function variantOptionsKey(options) {
|
|
61
|
+
return JSON.stringify(
|
|
62
|
+
Object.keys(options).sort().map((key) => [key, options[key]])
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
function normalizeVariantAxes(axes) {
|
|
66
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
67
|
+
const result = [];
|
|
68
|
+
for (const axis of axes) {
|
|
69
|
+
const name = axis.name.trim();
|
|
70
|
+
if (!name || seenNames.has(name)) continue;
|
|
71
|
+
const values = [];
|
|
72
|
+
const seenValues = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const raw of axis.values) {
|
|
74
|
+
const value = raw.trim();
|
|
75
|
+
if (!value || seenValues.has(value)) continue;
|
|
76
|
+
seenValues.add(value);
|
|
77
|
+
values.push(value);
|
|
78
|
+
}
|
|
79
|
+
if (values.length === 0) continue;
|
|
80
|
+
seenNames.add(name);
|
|
81
|
+
result.push({ name, values });
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
function variantCombinations(axes) {
|
|
86
|
+
const clean = normalizeVariantAxes(axes);
|
|
87
|
+
if (clean.length === 0) return [];
|
|
88
|
+
let rows = [{}];
|
|
89
|
+
for (const axis of clean) {
|
|
90
|
+
const next = [];
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
for (const value of axis.values) next.push({ ...row, [axis.name]: value });
|
|
93
|
+
}
|
|
94
|
+
rows = next;
|
|
95
|
+
}
|
|
96
|
+
return rows;
|
|
97
|
+
}
|
|
98
|
+
function validateVariantAxes(axes) {
|
|
99
|
+
const clean = normalizeVariantAxes(axes);
|
|
100
|
+
if (clean.length > MAX_VARIANT_AXES) {
|
|
101
|
+
return `Um produto aceita no m\xE1ximo ${MAX_VARIANT_AXES} op\xE7\xF5es (ex.: Tamanho, Cor, Material).`;
|
|
102
|
+
}
|
|
103
|
+
const total = clean.reduce((acc, axis) => acc * axis.values.length, 1);
|
|
104
|
+
if (clean.length > 0 && total > MAX_VARIANT_COMBINATIONS) {
|
|
105
|
+
return `Essas op\xE7\xF5es gerariam ${total} variantes, e o limite \xE9 ${MAX_VARIANT_COMBINATIONS}. Reduza os valores ou separe em dois produtos.`;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function planVariantMatrix(axes, existing) {
|
|
110
|
+
const wanted = variantCombinations(axes);
|
|
111
|
+
const wantedKeys = new Set(wanted.map(variantOptionsKey));
|
|
112
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
113
|
+
let defaultVariant = null;
|
|
114
|
+
for (const variant of existing) {
|
|
115
|
+
if (variant.isDefaultVariant) {
|
|
116
|
+
defaultVariant = variant;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
byKey.set(variantOptionsKey(variant.options), variant);
|
|
120
|
+
}
|
|
121
|
+
const plan = {
|
|
122
|
+
rows: [],
|
|
123
|
+
create: [],
|
|
124
|
+
reactivate: [],
|
|
125
|
+
keep: [],
|
|
126
|
+
archive: [],
|
|
127
|
+
defaultVariant
|
|
128
|
+
};
|
|
129
|
+
for (const [position, options] of wanted.entries()) {
|
|
130
|
+
const variant = byKey.get(variantOptionsKey(options)) ?? null;
|
|
131
|
+
const row = { options, position, variant };
|
|
132
|
+
plan.rows.push(row);
|
|
133
|
+
if (!variant) plan.create.push(row);
|
|
134
|
+
else if (variant.status === "archived") plan.reactivate.push(row);
|
|
135
|
+
else plan.keep.push(row);
|
|
136
|
+
}
|
|
137
|
+
for (const [key, variant] of byKey) {
|
|
138
|
+
if (!wantedKeys.has(key) && variant.status === "active") plan.archive.push(variant);
|
|
139
|
+
}
|
|
140
|
+
return plan;
|
|
141
|
+
}
|
|
142
|
+
function hasOnlyDefaultVariant(variants) {
|
|
143
|
+
const active = variants.filter((variant) => variant.status === "active");
|
|
144
|
+
return active.length === 1 && Boolean(active[0].isDefaultVariant);
|
|
145
|
+
}
|
|
146
|
+
function leavesNothingSellable(plan) {
|
|
147
|
+
const willHaveReal = plan.keep.length + plan.reactivate.length + plan.create.length;
|
|
148
|
+
return willHaveReal === 0 && plan.defaultVariant === null;
|
|
149
|
+
}
|
|
150
|
+
function axesFromOptions(options) {
|
|
151
|
+
return [...options].sort((a, b) => a.position - b.position).map((option) => ({ name: option.name, values: [...option.values] }));
|
|
152
|
+
}
|
|
153
|
+
async function applyVariantMatrix(provider, productId, axes, existing) {
|
|
154
|
+
const refusal = validateVariantAxes(axes);
|
|
155
|
+
if (refusal) throw new Error(refusal);
|
|
156
|
+
if (!provider.saveProductOptions || !provider.createProductVariant || !provider.updateProductVariant) {
|
|
157
|
+
throw new Error("Este provider n\xE3o administra variantes.");
|
|
158
|
+
}
|
|
159
|
+
const clean = normalizeVariantAxes(axes);
|
|
160
|
+
const plan = planVariantMatrix(clean, existing);
|
|
161
|
+
await provider.saveProductOptions(productId, clean);
|
|
162
|
+
for (const row of plan.create) {
|
|
163
|
+
await provider.createProductVariant(productId, { options: row.options, position: row.position });
|
|
164
|
+
}
|
|
165
|
+
for (const row of plan.reactivate) {
|
|
166
|
+
await provider.updateProductVariant(row.variant.id, { status: "active", position: row.position });
|
|
167
|
+
}
|
|
168
|
+
for (const row of plan.keep) {
|
|
169
|
+
if (row.variant.position !== row.position) {
|
|
170
|
+
await provider.updateProductVariant(row.variant.id, { position: row.position });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const variant of plan.archive) {
|
|
174
|
+
await (provider.archiveProductVariant?.(variant.id) ?? provider.updateProductVariant(variant.id, { status: "archived" }));
|
|
175
|
+
}
|
|
176
|
+
const real = plan.create.length + plan.reactivate.length + plan.keep.length;
|
|
177
|
+
if (plan.defaultVariant) {
|
|
178
|
+
if (real > 0 && plan.defaultVariant.status === "active") {
|
|
179
|
+
await provider.updateProductVariant(plan.defaultVariant.id, { status: "archived" });
|
|
180
|
+
} else if (real === 0 && plan.defaultVariant.status === "archived") {
|
|
181
|
+
await provider.updateProductVariant(plan.defaultVariant.id, { status: "active" });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return plan;
|
|
185
|
+
}
|
|
186
|
+
function formatVariantCombination(options, axes) {
|
|
187
|
+
const order = axes?.map((axis) => axis.name) ?? Object.keys(options).sort();
|
|
188
|
+
return order.filter((name) => options[name] != null).map((name) => `${name}: ${options[name]}`).join(" \xB7 ");
|
|
189
|
+
}
|
|
6
190
|
function uid() {
|
|
7
191
|
return Math.random().toString(36).slice(2);
|
|
8
192
|
}
|
|
@@ -13,6 +197,8 @@ var ORDERS_KEY = "fayz.shop.mock.orders.v1";
|
|
|
13
197
|
var CUSTOMERS_KEY = "fayz.shop.mock.customers.v1";
|
|
14
198
|
var ENQUIRIES_KEY = "fayz.shop.mock.enquiries.v1";
|
|
15
199
|
var CHANNELS_KEY = "fayz.shop.mock.channels.v1";
|
|
200
|
+
var SETTINGS_KEY = "fayz.shop.mock.settings.v1";
|
|
201
|
+
var SHIPPING_SETTINGS_KEY = "fayz.shop.mock.shipping-settings.v1";
|
|
16
202
|
var DEFAULT_CHANNELS = [
|
|
17
203
|
{ id: "online-store", tenantId: "mock", kind: "online_store", label: "Loja Online", url: null, enabled: true, position: 0, createdAt: now(), updatedAt: now() }
|
|
18
204
|
];
|
|
@@ -61,7 +247,15 @@ var MockShopProvider = class {
|
|
|
61
247
|
return stored.length ? stored : DEFAULT_CHANNELS.map((c) => ({ ...c }));
|
|
62
248
|
})();
|
|
63
249
|
this.entityStores = /* @__PURE__ */ new Map();
|
|
64
|
-
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Checkout sessions
|
|
252
|
+
//
|
|
253
|
+
// Mirrors the live inversion: the session holds the cart, and the ORDER is
|
|
254
|
+
// only written when the settle says the money is in. Here the "provider" is a
|
|
255
|
+
// clock — the second poll pays — which is what lets the four-step checkout be
|
|
256
|
+
// walked end to end without an acquirer, in demo stores and in the e2e suite.
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
this.checkoutSessions = {};
|
|
65
259
|
this.products = (seed?.products ?? MOCK_PRODUCTS).map((p) => ({ ...p }));
|
|
66
260
|
this.categories = (seed?.categories ?? MOCK_CATEGORIES).map((c) => ({ ...c }));
|
|
67
261
|
this.discounts = (seed?.discounts ?? MOCK_DISCOUNTS).map((d) => ({ ...d }));
|
|
@@ -73,9 +267,6 @@ var MockShopProvider = class {
|
|
|
73
267
|
);
|
|
74
268
|
this.promotions = (seed?.promotions ?? []).map((p) => ({ ...p }));
|
|
75
269
|
this.entitySeed = seed?.entities ?? {};
|
|
76
|
-
for (const [key, fields] of Object.entries(seed?.entityFields ?? {})) {
|
|
77
|
-
this.sidecars.set(key, { ...fields });
|
|
78
|
-
}
|
|
79
270
|
}
|
|
80
271
|
/** The I5 indirection: a promoted (registered) store wins; otherwise the
|
|
81
272
|
* entity gets a lazily-built in-memory store seeded from the mock seed.
|
|
@@ -162,16 +353,107 @@ var MockShopProvider = class {
|
|
|
162
353
|
}
|
|
163
354
|
/** Option axes + variants (spine M5) — active variants only, like the 0028
|
|
164
355
|
* storefront RLS. Answers empty sets for a variantless product. */
|
|
165
|
-
async getProductVariants(productId) {
|
|
356
|
+
async getProductVariants(productId, opts) {
|
|
166
357
|
const set = this.variantSets[productId];
|
|
167
358
|
if (!set) return { options: [], variants: [] };
|
|
168
359
|
return {
|
|
169
360
|
options: set.options.map((o) => ({ ...o, values: [...o.values] })).sort((a, b) => a.position - b.position),
|
|
170
|
-
variants: set.variants.filter((v) => v.status === "active").map((v) => ({ ...v, options: { ...v.options } })).sort((a, b) => a.position - b.position)
|
|
361
|
+
variants: set.variants.filter((v) => opts?.includeArchived || v.status === "active").map((v) => ({ ...v, options: { ...v.options } })).sort((a, b) => a.position - b.position)
|
|
171
362
|
};
|
|
172
363
|
}
|
|
364
|
+
// --- The variant admin ------------------------------------------------------
|
|
365
|
+
// Modelled on the SQL rather than on convenience: 0028's UNIQUE
|
|
366
|
+
// (product_id, options), 0037's default-variant triggers and 0036's mirror all
|
|
367
|
+
// have a counterpart here, so a test that passes against this mock is
|
|
368
|
+
// asserting the real semantics and not a friendlier imitation of them.
|
|
369
|
+
variantSet(productId) {
|
|
370
|
+
var _a;
|
|
371
|
+
(_a = this.variantSets)[productId] ?? (_a[productId] = { options: [], variants: [] });
|
|
372
|
+
return this.variantSets[productId];
|
|
373
|
+
}
|
|
374
|
+
async saveProductOptions(productId, input) {
|
|
375
|
+
const set = this.variantSet(productId);
|
|
376
|
+
const existing = new Map(set.options.map((o) => [o.name, o]));
|
|
377
|
+
set.options = input.map((axis, position) => ({
|
|
378
|
+
id: existing.get(axis.name)?.id ?? uid(),
|
|
379
|
+
name: axis.name,
|
|
380
|
+
values: [...axis.values],
|
|
381
|
+
position
|
|
382
|
+
}));
|
|
383
|
+
return set.options.map((o) => ({ ...o, values: [...o.values] }));
|
|
384
|
+
}
|
|
385
|
+
async createProductVariant(productId, input) {
|
|
386
|
+
const set = this.variantSet(productId);
|
|
387
|
+
const key = variantOptionsKey(input.options);
|
|
388
|
+
const existing = set.variants.find((v) => variantOptionsKey(v.options) === key);
|
|
389
|
+
if (existing) return this.updateProductVariant(existing.id, { ...input, status: input.status ?? "active" });
|
|
390
|
+
const variant = {
|
|
391
|
+
id: uid(),
|
|
392
|
+
productId,
|
|
393
|
+
options: { ...input.options },
|
|
394
|
+
sku: input.sku ?? null,
|
|
395
|
+
price: input.price ?? null,
|
|
396
|
+
compareAtPrice: input.compareAtPrice ?? null,
|
|
397
|
+
inventoryCount: 0,
|
|
398
|
+
position: input.position ?? set.variants.length,
|
|
399
|
+
status: input.status ?? "active",
|
|
400
|
+
imageId: input.imageId ?? null,
|
|
401
|
+
isDefaultVariant: false,
|
|
402
|
+
weight: input.weight ?? null,
|
|
403
|
+
weightUnit: input.weightUnit ?? null,
|
|
404
|
+
heightCm: input.heightCm ?? null,
|
|
405
|
+
widthCm: input.widthCm ?? null,
|
|
406
|
+
lengthCm: input.lengthCm ?? null
|
|
407
|
+
};
|
|
408
|
+
set.variants.push(variant);
|
|
409
|
+
for (const other of set.variants) {
|
|
410
|
+
if (other.isDefaultVariant && other.status === "active") other.status = "archived";
|
|
411
|
+
}
|
|
412
|
+
this.syncProductFromVariants(productId);
|
|
413
|
+
return { ...variant, options: { ...variant.options } };
|
|
414
|
+
}
|
|
415
|
+
async updateProductVariant(variantId, input) {
|
|
416
|
+
for (const [productId, set] of Object.entries(this.variantSets)) {
|
|
417
|
+
const variant = set.variants.find((v) => v.id === variantId);
|
|
418
|
+
if (!variant) continue;
|
|
419
|
+
for (const [key, value] of Object.entries(input)) {
|
|
420
|
+
if (value !== void 0) variant[key] = value;
|
|
421
|
+
}
|
|
422
|
+
this.syncProductFromVariants(productId);
|
|
423
|
+
return { ...variant, options: { ...variant.options } };
|
|
424
|
+
}
|
|
425
|
+
throw new Error("Variant not found");
|
|
426
|
+
}
|
|
427
|
+
async archiveProductVariant(variantId) {
|
|
428
|
+
await this.updateProductVariant(variantId, { status: "archived" });
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* The 0034→0036 path, compressed: the merchant states a count, the difference
|
|
432
|
+
* is what gets recorded, and inventory_count is the mirror that follows. The
|
|
433
|
+
* ledger itself is MockStockStore's business — what matters here is that no
|
|
434
|
+
* caller ever assigns to inventoryCount.
|
|
435
|
+
*/
|
|
436
|
+
async setVariantStock(variantId, count) {
|
|
437
|
+
const target = Math.max(0, Math.trunc(count));
|
|
438
|
+
for (const [productId, set] of Object.entries(this.variantSets)) {
|
|
439
|
+
const variant = set.variants.find((v) => v.id === variantId);
|
|
440
|
+
if (!variant) continue;
|
|
441
|
+
variant.inventoryCount = target;
|
|
442
|
+
this.syncProductFromVariants(productId);
|
|
443
|
+
return target;
|
|
444
|
+
}
|
|
445
|
+
throw new Error("Variant not found");
|
|
446
|
+
}
|
|
447
|
+
/** 0028's sync trigger: the product row is the SUM of active variant stock. */
|
|
448
|
+
syncProductFromVariants(productId) {
|
|
449
|
+
const product = this.products.find((p) => p.id === productId);
|
|
450
|
+
const set = this.variantSets[productId];
|
|
451
|
+
if (!product || !set) return;
|
|
452
|
+
const active = set.variants.filter((v) => v.status === "active");
|
|
453
|
+
product.inventoryCount = active.reduce((sum, v) => sum + v.inventoryCount, 0);
|
|
454
|
+
}
|
|
173
455
|
async createProduct(input) {
|
|
174
|
-
const p = { id: uid(), tenantId: "mock", name: input.name, slug: input.slug ?? input.name.toLowerCase().replace(/\s+/g, "-"), description: input.description ?? null, price: input.price, compareAtPrice: input.compareAtPrice ?? null, currency: input.currency ?? "BRL", status: input.status ?? "active", inventoryCount: input.inventoryCount ?? 0, sku: input.sku ?? null, sortOrder: input.sortOrder ?? 0, metadata: input.metadata ?? {}, images: [], categoryId: input.categoryId ?? null, categoryName: null, isPhysical: input.isPhysical ?? true, weight: input.weight ?? null, weightUnit: input.weightUnit ?? "kg", createdAt: now(), updatedAt: now() };
|
|
456
|
+
const p = { id: uid(), tenantId: "mock", name: input.name, slug: input.slug ?? input.name.toLowerCase().replace(/\s+/g, "-"), description: input.description ?? null, price: input.price, compareAtPrice: input.compareAtPrice ?? null, currency: input.currency ?? "BRL", status: input.status ?? "active", inventoryCount: input.inventoryCount ?? 0, sku: input.sku ?? null, sortOrder: input.sortOrder ?? 0, metadata: input.metadata ?? {}, images: [], categoryId: input.categoryId ?? null, categoryName: null, isPhysical: input.isPhysical ?? true, weight: input.weight ?? null, weightUnit: input.weightUnit ?? "kg", handling: input.handling ?? [], createdAt: now(), updatedAt: now() };
|
|
175
457
|
this.products.push(p);
|
|
176
458
|
return p;
|
|
177
459
|
}
|
|
@@ -187,7 +469,15 @@ var MockShopProvider = class {
|
|
|
187
469
|
async uploadProductImage(_productId, _file) {
|
|
188
470
|
throw new Error("Image upload not available in mock mode");
|
|
189
471
|
}
|
|
190
|
-
async deleteProductImage(
|
|
472
|
+
async deleteProductImage(imageId) {
|
|
473
|
+
this.products = this.products.map((p) => p.images.some((i) => i.id === imageId) ? { ...p, images: p.images.filter((i) => i.id !== imageId) } : p);
|
|
474
|
+
}
|
|
475
|
+
/** The media library, mock-side: no bucket to walk, so the catalogue's own
|
|
476
|
+
* photos ARE the library. Uploading has nowhere to go, which is why
|
|
477
|
+
* uploadMediaFile stays unimplemented and the screen hides its button. */
|
|
478
|
+
async listMediaFiles() {
|
|
479
|
+
const attached = this.products.flatMap((p) => p.images.map((image) => productImageToMediaFile(image, p.name)));
|
|
480
|
+
return mergeMediaFiles(attached, []);
|
|
191
481
|
}
|
|
192
482
|
async createProductEnquiry(input) {
|
|
193
483
|
this.syncEnquiries();
|
|
@@ -458,6 +748,45 @@ var MockShopProvider = class {
|
|
|
458
748
|
}
|
|
459
749
|
return order;
|
|
460
750
|
}
|
|
751
|
+
async openCheckoutSession(input) {
|
|
752
|
+
const sessionId = uid();
|
|
753
|
+
this.checkoutSessions[sessionId] = { input, polls: 0 };
|
|
754
|
+
let total = 0;
|
|
755
|
+
for (const line of input.items) {
|
|
756
|
+
const product = this.products.find((p) => p.id === line.productId);
|
|
757
|
+
if (!product) continue;
|
|
758
|
+
const variant = line.variantId ? this.variantSets[line.productId]?.variants.find((v) => v.id === line.variantId) : void 0;
|
|
759
|
+
total += (variant?.price ?? product.price) * line.quantity;
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
sessionId,
|
|
763
|
+
total: Math.round(total * 100) / 100,
|
|
764
|
+
state: "open",
|
|
765
|
+
charge: {
|
|
766
|
+
// Shaped like a real EMV payload so the copy button and the layout are
|
|
767
|
+
// exercised, not a placeholder that fits in one line.
|
|
768
|
+
emv: `00020126580014BR.GOV.BCB.PIX0136${sessionId}520400005303986540${total.toFixed(2)}5802BR6304MOCK`,
|
|
769
|
+
qrcodeBase64: null,
|
|
770
|
+
expiresAt: new Date(Date.now() + 30 * 6e4).toISOString()
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
async settleCheckoutSession(sessionId) {
|
|
775
|
+
const session = this.checkoutSessions[sessionId];
|
|
776
|
+
if (!session) return { state: "expired", reason: "Sess\xE3o de checkout n\xE3o encontrada." };
|
|
777
|
+
if (session.orderId) return { state: "paid", orderId: session.orderId };
|
|
778
|
+
session.polls += 1;
|
|
779
|
+
if (session.polls < 2) return { state: "open" };
|
|
780
|
+
const order = await this.placeOrder({
|
|
781
|
+
items: session.input.items,
|
|
782
|
+
customer: session.input.customer,
|
|
783
|
+
discountCode: session.input.discountCode ?? void 0,
|
|
784
|
+
shippingAddress: session.input.shippingAddress
|
|
785
|
+
});
|
|
786
|
+
await this.updateOrder(order.id, { financialStatus: "paid" });
|
|
787
|
+
session.orderId = order.id;
|
|
788
|
+
return { state: "paid", orderId: order.id };
|
|
789
|
+
}
|
|
461
790
|
// ---------------------------------------------------------------------------
|
|
462
791
|
// Customers
|
|
463
792
|
// ---------------------------------------------------------------------------
|
|
@@ -474,7 +803,7 @@ var MockShopProvider = class {
|
|
|
474
803
|
}
|
|
475
804
|
async createCustomer(input) {
|
|
476
805
|
this.syncCustomers();
|
|
477
|
-
const c = { id: uid(), tenantId: "mock", firstName: input.firstName, lastName: input.lastName ?? "", email: input.email ?? null, phone: input.phone ?? null, notes: input.notes ?? null, ordersCount: 0, totalSpent: 0, createdAt: now(), updatedAt: now() };
|
|
806
|
+
const c = { id: uid(), tenantId: "mock", firstName: input.firstName, lastName: input.lastName ?? "", email: input.email ?? null, phone: input.phone ?? null, document: input.document ?? null, notes: input.notes ?? null, ordersCount: 0, totalSpent: 0, createdAt: now(), updatedAt: now() };
|
|
478
807
|
this.customers.push(c);
|
|
479
808
|
this.persistCustomers();
|
|
480
809
|
return c;
|
|
@@ -570,6 +899,49 @@ var MockShopProvider = class {
|
|
|
570
899
|
this.persistChannels();
|
|
571
900
|
}
|
|
572
901
|
// ---------------------------------------------------------------------------
|
|
902
|
+
// Store policy + delivery fallback
|
|
903
|
+
//
|
|
904
|
+
// A single object behind a list-shaped store helper, so the same loadStored /
|
|
905
|
+
// store pair serves it. Absent = null, NOT an object of defaults: "nothing
|
|
906
|
+
// decided" is a state the callers are written to handle, and manufacturing
|
|
907
|
+
// defaults here would hide the one case the live provider really has.
|
|
908
|
+
// ---------------------------------------------------------------------------
|
|
909
|
+
async getStoreSettings() {
|
|
910
|
+
return loadStored(SETTINGS_KEY)[0] ?? null;
|
|
911
|
+
}
|
|
912
|
+
async updateStoreSettings(input) {
|
|
913
|
+
const next = { ...await this.getStoreSettings() ?? {}, ...input };
|
|
914
|
+
store(SETTINGS_KEY, [next]);
|
|
915
|
+
return next;
|
|
916
|
+
}
|
|
917
|
+
/** In mock mode there is no publication gate to fail, so the shopper's read
|
|
918
|
+
* and the merchant's are the same row. */
|
|
919
|
+
async getPublicStoreSettings() {
|
|
920
|
+
return this.getStoreSettings();
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Mock mode has no delivery zones at all, so nothing is refused and no zone is
|
|
924
|
+
* the store's own. Answered rather than omitted so a screen developed against
|
|
925
|
+
* the mock sees the same shape it will see live — an absent method would make
|
|
926
|
+
* the caller take its "provider cannot check" branch and never exercise this.
|
|
927
|
+
*/
|
|
928
|
+
async checkDelivery() {
|
|
929
|
+
return { refused: false, ownZone: false };
|
|
930
|
+
}
|
|
931
|
+
async getShippingSettings() {
|
|
932
|
+
return loadStored(SHIPPING_SETTINGS_KEY)[0] ?? null;
|
|
933
|
+
}
|
|
934
|
+
async updateShippingSettings(input) {
|
|
935
|
+
const current = await this.getShippingSettings();
|
|
936
|
+
const next = {
|
|
937
|
+
flatRate: input.flatRate ?? current?.flatRate ?? 0,
|
|
938
|
+
freeAbove: input.freeAbove !== void 0 ? input.freeAbove : current?.freeAbove ?? null,
|
|
939
|
+
currency: input.currency ?? current?.currency ?? "BRL"
|
|
940
|
+
};
|
|
941
|
+
store(SHIPPING_SETTINGS_KEY, [next]);
|
|
942
|
+
return next;
|
|
943
|
+
}
|
|
944
|
+
// ---------------------------------------------------------------------------
|
|
573
945
|
// Custom entities (spine M4) — same EntityStore port as the live provider.
|
|
574
946
|
// ---------------------------------------------------------------------------
|
|
575
947
|
async listEntityRecords(entity, options) {
|
|
@@ -590,16 +962,6 @@ var MockShopProvider = class {
|
|
|
590
962
|
async deleteEntityRecord(entity, id) {
|
|
591
963
|
return this.entityStore(entity).delete(id);
|
|
592
964
|
}
|
|
593
|
-
// Sidecar fields on core records — validated against defineSidecarFields.
|
|
594
|
-
async getEntityFields(reference, referenceId) {
|
|
595
|
-
return this.sidecars.get(`${reference}:${referenceId}`) ?? null;
|
|
596
|
-
}
|
|
597
|
-
async setEntityFields(reference, referenceId, fields) {
|
|
598
|
-
validateSidecarWrite(reference, fields);
|
|
599
|
-
const stored = { ...fields };
|
|
600
|
-
this.sidecars.set(`${reference}:${referenceId}`, stored);
|
|
601
|
-
return stored;
|
|
602
|
-
}
|
|
603
965
|
// Coleções, grupos/segmentos, vale-presente e e-mails — paridade com o
|
|
604
966
|
// provider Supabase. Mídia não entra: é storage, não tem mock honesto.
|
|
605
967
|
async listCollections() {
|
|
@@ -803,6 +1165,6 @@ function createMockShopProvider(seed) {
|
|
|
803
1165
|
return new MockShopProvider(seed);
|
|
804
1166
|
}
|
|
805
1167
|
|
|
806
|
-
export { MockShopProvider, createMockShopProvider };
|
|
807
|
-
//# sourceMappingURL=chunk-
|
|
808
|
-
//# sourceMappingURL=chunk-
|
|
1168
|
+
export { MAX_VARIANT_AXES, MAX_VARIANT_COMBINATIONS, MockShopProvider, applyVariantMatrix, axesFromOptions, createMockShopProvider, displayNameOf, formatVariantCombination, hasOnlyDefaultVariant, leavesNothingSellable, mediaKindOf, mergeMediaFiles, normalizeVariantAxes, planVariantMatrix, productImageToMediaFile, storagePathOf, validateVariantAxes, variantCombinations, variantOptionsKey };
|
|
1169
|
+
//# sourceMappingURL=chunk-U6HV3LVP.js.map
|
|
1170
|
+
//# sourceMappingURL=chunk-U6HV3LVP.js.map
|