@fayz-ai/shop 0.8.3 → 0.8.4

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { Product, ProductImage, Category, ProductEnquiry, ProductEnquiryStatus, Order, OrderItem, OrderStatus, FinancialStatus, FulfillmentStatus, ShopCustomer, Discount, DiscountType, DiscountMethod, DiscountStatus, ProductStatus, CreateProductInput, UpdateProductInput, ListProductsOptions, CreateProductEnquiryInput, ListProductEnquiriesOptions, CreateCategoryInput, UpdateCategoryInput, CreateOrderInput, UpdateOrderInput, ListOrdersOptions, CreateCustomerInput, UpdateCustomerInput, ListCustomersOptions, ResolveCustomerInput, CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions, PlaceOrderInput, PlaceOrderLine, DiscountValidation, InventoryLine, InventoryIssue, InventoryCheck, CartTotalLine, CartTotals, } from './types';
1
+ export type { Product, ProductImage, Category, ProductEnquiry, ProductEnquiryStatus, Order, OrderItem, OrderStatus, FinancialStatus, FulfillmentStatus, ShopCustomer, Discount, DiscountType, DiscountMethod, DiscountStatus, ProductStatus, CreateProductInput, UpdateProductInput, ListProductsOptions, CreateProductEnquiryInput, ListProductEnquiriesOptions, CreateCategoryInput, UpdateCategoryInput, CreateOrderInput, UpdateOrderInput, ListOrdersOptions, CreateCustomerInput, UpdateCustomerInput, ListCustomersOptions, ResolveCustomerInput, CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions, SalesChannel, SalesChannelKind, CreateSalesChannelInput, UpdateSalesChannelInput, PlaceOrderInput, PlaceOrderLine, DiscountValidation, InventoryLine, InventoryIssue, InventoryCheck, CartTotalLine, CartTotals, } from './types';
2
2
  export type { ShippingZone, CreateShippingZoneInput, UpdateShippingZoneInput, ShippingQuoteOption, CustomerAddress, } from './types';
3
3
  export type { ShopProvider } from './provider';
4
4
  export { validateDiscount, applyDiscount, checkInventory, computeCartTotals, getCustomerOrders, } from './commerce';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,oBAAoB,EACrE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,iBAAiB,EACjE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EACpE,aAAa,EACb,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAC3D,yBAAyB,EAAE,2BAA2B,EACtD,mBAAmB,EAAE,mBAAmB,EACxC,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,EACrD,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,oBAAoB,EACpF,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAC9D,eAAe,EAAE,cAAc,EAC/B,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,GAC7F,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,YAAY,EAAE,uBAAuB,EAAE,uBAAuB,EAC9D,mBAAmB,EAAE,eAAe,GACrC,MAAM,SAAS,CAAA;AAChB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EACL,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,GACtF,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AACtF,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACjE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAG/E,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,oBAAoB,EACrE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,iBAAiB,EACjE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EACpE,aAAa,EACb,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAC3D,yBAAyB,EAAE,2BAA2B,EACtD,mBAAmB,EAAE,mBAAmB,EACxC,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,EACrD,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,oBAAoB,EACpF,mBAAmB,EAAE,mBAAmB,EAAE,oBAAoB,EAC9D,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,uBAAuB,EAChF,eAAe,EAAE,cAAc,EAC/B,kBAAkB,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,GAC7F,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,YAAY,EAAE,uBAAuB,EAAE,uBAAuB,EAC9D,mBAAmB,EAAE,eAAe,GACrC,MAAM,SAAS,CAAA;AAChB,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EACL,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,iBAAiB,GACtF,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AACtF,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACjE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAG/E,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA"}
package/dist/index.js CHANGED
@@ -84,6 +84,10 @@ var MOCK_DISCOUNTS = [
84
84
  value: 10,
85
85
  usageLimit: null,
86
86
  oncePerCustomer: false,
87
+ minSubtotal: null,
88
+ appliesTo: "all",
89
+ productIds: [],
90
+ categoryIds: [],
87
91
  startsAt: T0,
88
92
  endsAt: null,
89
93
  status: "active",
@@ -157,6 +161,10 @@ function buildMockCatalog(input) {
157
161
  value: d.percent,
158
162
  usageLimit: null,
159
163
  oncePerCustomer: false,
164
+ minSubtotal: null,
165
+ appliesTo: "all",
166
+ productIds: [],
167
+ categoryIds: [],
160
168
  startsAt: T0,
161
169
  endsAt: null,
162
170
  status: "active",
@@ -177,6 +185,10 @@ function now() {
177
185
  var ORDERS_KEY = "fayz.shop.mock.orders.v1";
178
186
  var CUSTOMERS_KEY = "fayz.shop.mock.customers.v1";
179
187
  var ENQUIRIES_KEY = "fayz.shop.mock.enquiries.v1";
188
+ var CHANNELS_KEY = "fayz.shop.mock.channels.v1";
189
+ var DEFAULT_CHANNELS = [
190
+ { id: "online-store", tenantId: "mock", kind: "online_store", label: "Loja Online", url: null, enabled: true, position: 0, createdAt: now(), updatedAt: now() }
191
+ ];
180
192
  function loadStored(key) {
181
193
  try {
182
194
  if (typeof localStorage === "undefined") return [];
@@ -198,6 +210,10 @@ var MockShopProvider = class {
198
210
  this.orders = loadStored(ORDERS_KEY);
199
211
  this.customers = loadStored(CUSTOMERS_KEY);
200
212
  this.enquiries = loadStored(ENQUIRIES_KEY);
213
+ this.channels = (() => {
214
+ const stored = loadStored(CHANNELS_KEY);
215
+ return stored.length ? stored : DEFAULT_CHANNELS.map((c) => ({ ...c }));
216
+ })();
201
217
  this.products = (seed?.products ?? MOCK_PRODUCTS).map((p) => ({ ...p }));
202
218
  this.categories = (seed?.categories ?? MOCK_CATEGORIES).map((c) => ({ ...c }));
203
219
  this.discounts = (seed?.discounts ?? MOCK_DISCOUNTS).map((d) => ({ ...d }));
@@ -213,6 +229,10 @@ var MockShopProvider = class {
213
229
  syncEnquiries() {
214
230
  this.enquiries = loadStored(ENQUIRIES_KEY);
215
231
  }
232
+ syncChannels() {
233
+ const stored = loadStored(CHANNELS_KEY);
234
+ if (stored.length) this.channels = stored;
235
+ }
216
236
  persistOrders() {
217
237
  store(ORDERS_KEY, this.orders);
218
238
  }
@@ -222,6 +242,9 @@ var MockShopProvider = class {
222
242
  persistEnquiries() {
223
243
  store(ENQUIRIES_KEY, this.enquiries);
224
244
  }
245
+ persistChannels() {
246
+ store(CHANNELS_KEY, this.channels);
247
+ }
225
248
  // ---------------------------------------------------------------------------
226
249
  // Products
227
250
  // ---------------------------------------------------------------------------
@@ -430,13 +453,16 @@ var MockShopProvider = class {
430
453
  }
431
454
  }
432
455
  const items = [];
456
+ const lineDetails = [];
433
457
  let subtotal = 0;
434
458
  for (const line of input.items) {
435
459
  const product = this.products.find((p) => p.id === line.productId);
436
460
  if (!product || product.status !== "active") throw new Error(`Product unavailable: ${line.productId}`);
437
461
  if (product.inventoryCount < line.quantity) throw new Error(`Insufficient stock for ${product.name}`);
438
462
  const name = line.optionsLabel ? `${product.name} (${line.optionsLabel})` : product.name;
439
- subtotal += product.price * line.quantity;
463
+ const lineTotal = product.price * line.quantity;
464
+ subtotal += lineTotal;
465
+ lineDetails.push({ productId: product.id, categoryId: product.categoryId, lineTotal });
440
466
  items.push({ productId: product.id, name, sku: product.sku ?? void 0, quantity: line.quantity, unitPrice: product.price, imageUrl: product.images[0]?.url });
441
467
  }
442
468
  subtotal = Math.round(subtotal * 100) / 100;
@@ -447,11 +473,19 @@ var MockShopProvider = class {
447
473
  const code = input.discountCode.trim().toLowerCase();
448
474
  const d = this.discounts.find((x) => (x.code ?? "").toLowerCase() === code);
449
475
  const ts = now();
450
- const valid = !!d && d.status === "active" && d.startsAt <= ts && (!d.endsAt || d.endsAt >= ts) && (d.usageLimit == null || d.timesUsed < d.usageLimit);
476
+ const meetsMin = !d?.minSubtotal || subtotal >= d.minSubtotal;
477
+ const valid = !!d && d.status === "active" && d.startsAt <= ts && (!d.endsAt || d.endsAt >= ts) && (d.usageLimit == null || d.timesUsed < d.usageLimit) && meetsMin;
451
478
  if (valid && d) {
452
479
  appliedCode = d.code;
453
- if (d.type === "percentage") discountTotal = Math.round(subtotal * d.value) / 100;
454
- else if (d.type === "fixed_amount") discountTotal = Math.min(d.value, subtotal);
480
+ let eligible = subtotal;
481
+ if (d.appliesTo === "products") {
482
+ eligible = lineDetails.filter((l) => d.productIds.includes(l.productId)).reduce((s, l) => s + l.lineTotal, 0);
483
+ } else if (d.appliesTo === "categories") {
484
+ eligible = lineDetails.filter((l) => l.categoryId && d.categoryIds.includes(l.categoryId)).reduce((s, l) => s + l.lineTotal, 0);
485
+ }
486
+ eligible = Math.round(eligible * 100) / 100;
487
+ if (d.type === "percentage") discountTotal = Math.round(eligible * d.value) / 100;
488
+ else if (d.type === "fixed_amount") discountTotal = Math.min(d.value, eligible);
455
489
  else if (d.type === "free_shipping") shippingTotal = 0;
456
490
  d.timesUsed += 1;
457
491
  }
@@ -531,7 +565,7 @@ var MockShopProvider = class {
531
565
  return this.discounts.find((d) => d.id === id) ?? null;
532
566
  }
533
567
  async createDiscount(input) {
534
- const d = { id: uid(), tenantId: "mock", title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? "code", value: input.value, usageLimit: input.usageLimit ?? null, oncePerCustomer: input.oncePerCustomer ?? false, startsAt: input.startsAt ?? now(), endsAt: input.endsAt ?? null, status: input.status ?? "active", timesUsed: 0, createdAt: now(), updatedAt: now() };
568
+ const d = { id: uid(), tenantId: "mock", title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? "code", value: input.value, usageLimit: input.usageLimit ?? null, oncePerCustomer: input.oncePerCustomer ?? false, minSubtotal: input.minSubtotal ?? null, appliesTo: input.appliesTo ?? "all", productIds: input.productIds ?? [], categoryIds: input.categoryIds ?? [], startsAt: input.startsAt ?? now(), endsAt: input.endsAt ?? null, status: input.status ?? "active", timesUsed: 0, createdAt: now(), updatedAt: now() };
535
569
  this.discounts.push(d);
536
570
  return d;
537
571
  }
@@ -544,6 +578,33 @@ var MockShopProvider = class {
544
578
  async deleteDiscount(id) {
545
579
  this.discounts = this.discounts.filter((d) => d.id !== id);
546
580
  }
581
+ // ---------------------------------------------------------------------------
582
+ // Sales channels
583
+ // ---------------------------------------------------------------------------
584
+ async listSalesChannels() {
585
+ this.syncChannels();
586
+ return [...this.channels].sort((a, b) => a.position - b.position);
587
+ }
588
+ async createSalesChannel(input) {
589
+ this.syncChannels();
590
+ const c = { id: uid(), tenantId: "mock", kind: input.kind, label: input.label, url: input.url ?? null, enabled: input.enabled ?? true, position: input.position ?? this.channels.length, createdAt: now(), updatedAt: now() };
591
+ this.channels.push(c);
592
+ this.persistChannels();
593
+ return c;
594
+ }
595
+ async updateSalesChannel(id, input) {
596
+ this.syncChannels();
597
+ const i = this.channels.findIndex((c) => c.id === id);
598
+ if (i < 0) throw new Error("Sales channel not found");
599
+ this.channels[i] = { ...this.channels[i], ...input, updatedAt: now() };
600
+ this.persistChannels();
601
+ return this.channels[i];
602
+ }
603
+ async deleteSalesChannel(id) {
604
+ this.syncChannels();
605
+ this.channels = this.channels.filter((c) => c.id !== id);
606
+ this.persistChannels();
607
+ }
547
608
  };
548
609
  function createMockShopProvider(seed) {
549
610
  return new MockShopProvider(seed);
@@ -663,6 +724,7 @@ var T = {
663
724
  orderItems: "plg_shop_order_items",
664
725
  customers: "plg_shop_customers",
665
726
  discounts: "plg_shop_discounts",
727
+ salesChannels: "plg_shop_channels",
666
728
  // Core, not plg_shop_*: a delivery zone is a fact about the business, and
667
729
  // fulfilment and financial read it too. Same reasoning as addresses and
668
730
  // orders — see migration 0021.
@@ -831,6 +893,10 @@ function rowToDiscount(row) {
831
893
  value: row.value ?? 0,
832
894
  usageLimit: row.usage_limit,
833
895
  oncePerCustomer: row.once_per_customer ?? false,
896
+ minSubtotal: row.min_subtotal ?? null,
897
+ appliesTo: row.applies_to ?? "all",
898
+ productIds: row.product_ids ?? [],
899
+ categoryIds: row.category_ids ?? [],
834
900
  startsAt: row.starts_at,
835
901
  endsAt: row.ends_at,
836
902
  status: row.status ?? "active",
@@ -839,6 +905,19 @@ function rowToDiscount(row) {
839
905
  updatedAt: row.updated_at
840
906
  };
841
907
  }
908
+ function rowToSalesChannel(row) {
909
+ return {
910
+ id: row.id,
911
+ tenantId: row.tenant_id,
912
+ kind: row.kind,
913
+ label: row.label,
914
+ url: row.url,
915
+ enabled: row.enabled ?? true,
916
+ position: row.position ?? 0,
917
+ createdAt: row.created_at,
918
+ updatedAt: row.updated_at
919
+ };
920
+ }
842
921
  var SupabaseShopProvider = class {
843
922
  // ---------------------------------------------------------------------------
844
923
  // Products
@@ -1199,7 +1278,7 @@ var SupabaseShopProvider = class {
1199
1278
  return data ? rowToDiscount(data) : null;
1200
1279
  }
1201
1280
  async createDiscount(input) {
1202
- const { data, error } = await getDb().from(T.discounts).insert({ tenant_id: getTenantId(), title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? "code", value: input.value, usage_limit: input.usageLimit ?? null, once_per_customer: input.oncePerCustomer ?? false, starts_at: input.startsAt ?? (/* @__PURE__ */ new Date()).toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? "active", times_used: 0 }).select().single();
1281
+ const { data, error } = await getDb().from(T.discounts).insert({ tenant_id: getTenantId(), title: input.title, code: input.code ?? null, type: input.type, method: input.method ?? "code", value: input.value, usage_limit: input.usageLimit ?? null, once_per_customer: input.oncePerCustomer ?? false, min_subtotal: input.minSubtotal ?? null, applies_to: input.appliesTo ?? "all", product_ids: input.productIds ?? [], category_ids: input.categoryIds ?? [], starts_at: input.startsAt ?? (/* @__PURE__ */ new Date()).toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? "active", times_used: 0 }).select().single();
1203
1282
  if (error) throw error;
1204
1283
  return rowToDiscount(data);
1205
1284
  }
@@ -1210,6 +1289,10 @@ var SupabaseShopProvider = class {
1210
1289
  if (input.type !== void 0) updates.type = input.type;
1211
1290
  if (input.value !== void 0) updates.value = input.value;
1212
1291
  if (input.usageLimit !== void 0) updates.usage_limit = input.usageLimit;
1292
+ if (input.minSubtotal !== void 0) updates.min_subtotal = input.minSubtotal;
1293
+ if (input.appliesTo !== void 0) updates.applies_to = input.appliesTo;
1294
+ if (input.productIds !== void 0) updates.product_ids = input.productIds;
1295
+ if (input.categoryIds !== void 0) updates.category_ids = input.categoryIds;
1213
1296
  if (input.status !== void 0) updates.status = input.status;
1214
1297
  if (input.endsAt !== void 0) updates.ends_at = input.endsAt;
1215
1298
  const { data, error } = await getDb().from(T.discounts).update(updates).eq("id", id).select().single();
@@ -1220,6 +1303,38 @@ var SupabaseShopProvider = class {
1220
1303
  const { error } = await getDb().from(T.discounts).delete().eq("id", id);
1221
1304
  if (error) throw error;
1222
1305
  }
1306
+ // ---------------------------------------------------------------------------
1307
+ // Sales channels
1308
+ // ---------------------------------------------------------------------------
1309
+ async listSalesChannels() {
1310
+ const db = getDb();
1311
+ const tenantId = getTenantId();
1312
+ let q = db.from(T.salesChannels).select("*").order("position", { ascending: true });
1313
+ if (tenantId) q = q.eq("tenant_id", tenantId);
1314
+ const { data, error } = await q;
1315
+ if (error) throw error;
1316
+ return (data ?? []).map(rowToSalesChannel);
1317
+ }
1318
+ async createSalesChannel(input) {
1319
+ const { data, error } = await getDb().from(T.salesChannels).insert({ tenant_id: getTenantId(), kind: input.kind, label: input.label, url: input.url ?? null, enabled: input.enabled ?? true, position: input.position ?? 0 }).select().single();
1320
+ if (error) throw error;
1321
+ return rowToSalesChannel(data);
1322
+ }
1323
+ async updateSalesChannel(id, input) {
1324
+ const updates = {};
1325
+ if (input.kind !== void 0) updates.kind = input.kind;
1326
+ if (input.label !== void 0) updates.label = input.label;
1327
+ if (input.url !== void 0) updates.url = input.url;
1328
+ if (input.enabled !== void 0) updates.enabled = input.enabled;
1329
+ if (input.position !== void 0) updates.position = input.position;
1330
+ const { data, error } = await getDb().from(T.salesChannels).update(updates).eq("id", id).select().single();
1331
+ if (error) throw error;
1332
+ return rowToSalesChannel(data);
1333
+ }
1334
+ async deleteSalesChannel(id) {
1335
+ const { error } = await getDb().from(T.salesChannels).delete().eq("id", id);
1336
+ if (error) throw error;
1337
+ }
1223
1338
  };
1224
1339
  function createSupabaseShopProvider() {
1225
1340
  return new SupabaseShopProvider();