@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/catalog.cjs +8 -0
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.js +8 -0
- package/dist/catalog.js.map +1 -1
- package/dist/index.cjs +121 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -6
- package/dist/index.js.map +1 -1
- package/dist/mock-catalog.d.ts.map +1 -1
- package/dist/mock-provider.d.ts +8 -1
- package/dist/mock-provider.d.ts.map +1 -1
- package/dist/mock.cjs +62 -5
- package/dist/mock.cjs.map +1 -1
- package/dist/mock.js +62 -5
- package/dist/mock.js.map +1 -1
- package/dist/provider.d.ts +5 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/runtime.cjs +62 -5
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +62 -5
- package/dist/runtime.js.map +1 -1
- package/dist/supabase-provider.d.ts +5 -1
- package/dist/supabase-provider.d.ts.map +1 -1
- package/dist/tables.d.ts +1 -0
- package/dist/tables.d.ts.map +1 -1
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +1 -0
- package/src/mock-catalog.ts +8 -0
- package/src/mock-provider.ts +63 -5
- package/src/provider.ts +8 -0
- package/src/supabase-provider.ts +64 -1
- package/src/tables.ts +1 -0
- package/src/types.ts +67 -0
package/src/mock-provider.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
CreateOrderInput, UpdateOrderInput, ListOrdersOptions, PlaceOrderInput,
|
|
8
8
|
CreateCustomerInput, UpdateCustomerInput, ListCustomersOptions, ResolveCustomerInput,
|
|
9
9
|
CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions,
|
|
10
|
+
SalesChannel, CreateSalesChannelInput, UpdateSalesChannelInput,
|
|
10
11
|
} from './types'
|
|
11
12
|
import { MOCK_PRODUCTS, MOCK_CATEGORIES, MOCK_DISCOUNTS } from './mock-catalog'
|
|
12
13
|
|
|
@@ -19,6 +20,11 @@ function now() { return new Date().toISOString() }
|
|
|
19
20
|
const ORDERS_KEY = 'fayz.shop.mock.orders.v1'
|
|
20
21
|
const CUSTOMERS_KEY = 'fayz.shop.mock.customers.v1'
|
|
21
22
|
const ENQUIRIES_KEY = 'fayz.shop.mock.enquiries.v1'
|
|
23
|
+
const CHANNELS_KEY = 'fayz.shop.mock.channels.v1'
|
|
24
|
+
|
|
25
|
+
const DEFAULT_CHANNELS: SalesChannel[] = [
|
|
26
|
+
{ id: 'online-store', tenantId: 'mock', kind: 'online_store', label: 'Loja Online', url: null, enabled: true, position: 0, createdAt: now(), updatedAt: now() },
|
|
27
|
+
]
|
|
22
28
|
|
|
23
29
|
function loadStored<T>(key: string): T[] {
|
|
24
30
|
try {
|
|
@@ -52,6 +58,10 @@ export class MockShopProvider implements ShopProvider {
|
|
|
52
58
|
private orders: Order[] = loadStored<Order>(ORDERS_KEY)
|
|
53
59
|
private customers: ShopCustomer[] = loadStored<ShopCustomer>(CUSTOMERS_KEY)
|
|
54
60
|
private enquiries: ProductEnquiry[] = loadStored<ProductEnquiry>(ENQUIRIES_KEY)
|
|
61
|
+
private channels: SalesChannel[] = (() => {
|
|
62
|
+
const stored = loadStored<SalesChannel>(CHANNELS_KEY)
|
|
63
|
+
return stored.length ? stored : DEFAULT_CHANNELS.map(c => ({ ...c }))
|
|
64
|
+
})()
|
|
55
65
|
|
|
56
66
|
constructor(seed?: MockShopSeed) {
|
|
57
67
|
this.products = (seed?.products ?? MOCK_PRODUCTS).map(p => ({ ...p }))
|
|
@@ -64,9 +74,14 @@ export class MockShopProvider implements ShopProvider {
|
|
|
64
74
|
private syncOrders() { this.orders = loadStored<Order>(ORDERS_KEY) }
|
|
65
75
|
private syncCustomers() { this.customers = loadStored<ShopCustomer>(CUSTOMERS_KEY) }
|
|
66
76
|
private syncEnquiries() { this.enquiries = loadStored<ProductEnquiry>(ENQUIRIES_KEY) }
|
|
77
|
+
private syncChannels() {
|
|
78
|
+
const stored = loadStored<SalesChannel>(CHANNELS_KEY)
|
|
79
|
+
if (stored.length) this.channels = stored
|
|
80
|
+
}
|
|
67
81
|
private persistOrders() { store(ORDERS_KEY, this.orders) }
|
|
68
82
|
private persistCustomers() { store(CUSTOMERS_KEY, this.customers) }
|
|
69
83
|
private persistEnquiries() { store(ENQUIRIES_KEY, this.enquiries) }
|
|
84
|
+
private persistChannels() { store(CHANNELS_KEY, this.channels) }
|
|
70
85
|
|
|
71
86
|
// ---------------------------------------------------------------------------
|
|
72
87
|
// Products
|
|
@@ -285,13 +300,17 @@ export class MockShopProvider implements ShopProvider {
|
|
|
285
300
|
|
|
286
301
|
// Re-read price + guard stock from the catalog (never trust the client).
|
|
287
302
|
const items: CreateOrderInput['items'] = []
|
|
303
|
+
// Per-line facts for coupon scoping (productId, categoryId, line total).
|
|
304
|
+
const lineDetails: Array<{ productId: string; categoryId: string | null; lineTotal: number }> = []
|
|
288
305
|
let subtotal = 0
|
|
289
306
|
for (const line of input.items) {
|
|
290
307
|
const product = this.products.find(p => p.id === line.productId)
|
|
291
308
|
if (!product || product.status !== 'active') throw new Error(`Product unavailable: ${line.productId}`)
|
|
292
309
|
if (product.inventoryCount < line.quantity) throw new Error(`Insufficient stock for ${product.name}`)
|
|
293
310
|
const name = line.optionsLabel ? `${product.name} (${line.optionsLabel})` : product.name
|
|
294
|
-
|
|
311
|
+
const lineTotal = product.price * line.quantity
|
|
312
|
+
subtotal += lineTotal
|
|
313
|
+
lineDetails.push({ productId: product.id, categoryId: product.categoryId, lineTotal })
|
|
295
314
|
items.push({ productId: product.id, name, sku: product.sku ?? undefined, quantity: line.quantity, unitPrice: product.price, imageUrl: product.images[0]?.url })
|
|
296
315
|
}
|
|
297
316
|
subtotal = Math.round(subtotal * 100) / 100
|
|
@@ -304,11 +323,21 @@ export class MockShopProvider implements ShopProvider {
|
|
|
304
323
|
const code = input.discountCode.trim().toLowerCase()
|
|
305
324
|
const d = this.discounts.find(x => (x.code ?? '').toLowerCase() === code)
|
|
306
325
|
const ts = now()
|
|
307
|
-
const
|
|
326
|
+
const meetsMin = !d?.minSubtotal || subtotal >= d.minSubtotal
|
|
327
|
+
const valid = !!d && d.status === 'active' && d.startsAt <= ts && (!d.endsAt || d.endsAt >= ts) && (d.usageLimit == null || d.timesUsed < d.usageLimit) && meetsMin
|
|
308
328
|
if (valid && d) {
|
|
309
329
|
appliedCode = d.code
|
|
310
|
-
|
|
311
|
-
|
|
330
|
+
// Eligible subtotal = the part of the cart the coupon is scoped to.
|
|
331
|
+
// applies_to='all' → whole subtotal (unchanged for legacy coupons).
|
|
332
|
+
let eligible = subtotal
|
|
333
|
+
if (d.appliesTo === 'products') {
|
|
334
|
+
eligible = lineDetails.filter(l => d.productIds.includes(l.productId)).reduce((s, l) => s + l.lineTotal, 0)
|
|
335
|
+
} else if (d.appliesTo === 'categories') {
|
|
336
|
+
eligible = lineDetails.filter(l => l.categoryId && d.categoryIds.includes(l.categoryId)).reduce((s, l) => s + l.lineTotal, 0)
|
|
337
|
+
}
|
|
338
|
+
eligible = Math.round(eligible * 100) / 100
|
|
339
|
+
if (d.type === 'percentage') discountTotal = Math.round(eligible * d.value) / 100
|
|
340
|
+
else if (d.type === 'fixed_amount') discountTotal = Math.min(d.value, eligible)
|
|
312
341
|
else if (d.type === 'free_shipping') shippingTotal = 0
|
|
313
342
|
// buy_x_get_y unsupported in v1: code recorded, no monetary effect
|
|
314
343
|
d.timesUsed += 1
|
|
@@ -403,7 +432,7 @@ export class MockShopProvider implements ShopProvider {
|
|
|
403
432
|
async getDiscount(id: string) { return this.discounts.find(d => d.id === id) ?? null }
|
|
404
433
|
|
|
405
434
|
async createDiscount(input: CreateDiscountInput): Promise<Discount> {
|
|
406
|
-
const d: Discount = { 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() }
|
|
435
|
+
const d: Discount = { 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() }
|
|
407
436
|
this.discounts.push(d); return d
|
|
408
437
|
}
|
|
409
438
|
|
|
@@ -415,6 +444,35 @@ export class MockShopProvider implements ShopProvider {
|
|
|
415
444
|
}
|
|
416
445
|
|
|
417
446
|
async deleteDiscount(id: string) { this.discounts = this.discounts.filter(d => d.id !== id) }
|
|
447
|
+
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
// Sales channels
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
async listSalesChannels(): Promise<SalesChannel[]> {
|
|
453
|
+
this.syncChannels()
|
|
454
|
+
return [...this.channels].sort((a, b) => a.position - b.position)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async createSalesChannel(input: CreateSalesChannelInput): Promise<SalesChannel> {
|
|
458
|
+
this.syncChannels()
|
|
459
|
+
const c: SalesChannel = { 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() }
|
|
460
|
+
this.channels.push(c); this.persistChannels(); return c
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async updateSalesChannel(id: string, input: UpdateSalesChannelInput): Promise<SalesChannel> {
|
|
464
|
+
this.syncChannels()
|
|
465
|
+
const i = this.channels.findIndex(c => c.id === id)
|
|
466
|
+
if (i < 0) throw new Error('Sales channel not found')
|
|
467
|
+
this.channels[i] = { ...this.channels[i], ...input, updatedAt: now() }
|
|
468
|
+
this.persistChannels(); return this.channels[i]
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async deleteSalesChannel(id: string): Promise<void> {
|
|
472
|
+
this.syncChannels()
|
|
473
|
+
this.channels = this.channels.filter(c => c.id !== id)
|
|
474
|
+
this.persistChannels()
|
|
475
|
+
}
|
|
418
476
|
}
|
|
419
477
|
|
|
420
478
|
export function createMockShopProvider(seed?: MockShopSeed): ShopProvider {
|
package/src/provider.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
CustomerAddress, ShippingQuoteOption,
|
|
10
10
|
ShippingZone, CreateShippingZoneInput, UpdateShippingZoneInput,
|
|
11
11
|
CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions,
|
|
12
|
+
SalesChannel, CreateSalesChannelInput, UpdateSalesChannelInput,
|
|
12
13
|
} from './types'
|
|
13
14
|
|
|
14
15
|
export interface ShopProvider {
|
|
@@ -106,4 +107,11 @@ export interface ShopProvider {
|
|
|
106
107
|
createDiscount(input: CreateDiscountInput): Promise<Discount>
|
|
107
108
|
updateDiscount(id: string, input: UpdateDiscountInput): Promise<Discount>
|
|
108
109
|
deleteDiscount(id: string): Promise<void>
|
|
110
|
+
|
|
111
|
+
// Sales channels — the store's points of sale (Loja Online, WhatsApp, …).
|
|
112
|
+
// Optional: a provider without them yields no "Canais de venda" section.
|
|
113
|
+
listSalesChannels?(): Promise<SalesChannel[]>
|
|
114
|
+
createSalesChannel?(input: CreateSalesChannelInput): Promise<SalesChannel>
|
|
115
|
+
updateSalesChannel?(id: string, input: UpdateSalesChannelInput): Promise<SalesChannel>
|
|
116
|
+
deleteSalesChannel?(id: string): Promise<void>
|
|
109
117
|
}
|
package/src/supabase-provider.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
CreateOrderInput, UpdateOrderInput, ListOrdersOptions, PlaceOrderInput,
|
|
11
11
|
CreateCustomerInput, UpdateCustomerInput, ListCustomersOptions, ResolveCustomerInput,
|
|
12
12
|
CreateDiscountInput, UpdateDiscountInput, ListDiscountsOptions, OrderItem,
|
|
13
|
+
SalesChannel, CreateSalesChannelInput, UpdateSalesChannelInput,
|
|
13
14
|
ShippingZone, CreateShippingZoneInput, UpdateShippingZoneInput } from './types'
|
|
14
15
|
|
|
15
16
|
function getDb(): any {
|
|
@@ -195,6 +196,10 @@ function rowToDiscount(row: any): Discount {
|
|
|
195
196
|
value: row.value ?? 0,
|
|
196
197
|
usageLimit: row.usage_limit,
|
|
197
198
|
oncePerCustomer: row.once_per_customer ?? false,
|
|
199
|
+
minSubtotal: row.min_subtotal ?? null,
|
|
200
|
+
appliesTo: row.applies_to ?? 'all',
|
|
201
|
+
productIds: row.product_ids ?? [],
|
|
202
|
+
categoryIds: row.category_ids ?? [],
|
|
198
203
|
startsAt: row.starts_at,
|
|
199
204
|
endsAt: row.ends_at,
|
|
200
205
|
status: row.status ?? 'active',
|
|
@@ -204,6 +209,20 @@ function rowToDiscount(row: any): Discount {
|
|
|
204
209
|
}
|
|
205
210
|
}
|
|
206
211
|
|
|
212
|
+
function rowToSalesChannel(row: any): SalesChannel {
|
|
213
|
+
return {
|
|
214
|
+
id: row.id,
|
|
215
|
+
tenantId: row.tenant_id,
|
|
216
|
+
kind: row.kind,
|
|
217
|
+
label: row.label,
|
|
218
|
+
url: row.url,
|
|
219
|
+
enabled: row.enabled ?? true,
|
|
220
|
+
position: row.position ?? 0,
|
|
221
|
+
createdAt: row.created_at,
|
|
222
|
+
updatedAt: row.updated_at,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
207
226
|
export class SupabaseShopProvider implements ShopProvider {
|
|
208
227
|
// ---------------------------------------------------------------------------
|
|
209
228
|
// Products
|
|
@@ -660,7 +679,7 @@ export class SupabaseShopProvider implements ShopProvider {
|
|
|
660
679
|
async createDiscount(input: CreateDiscountInput): Promise<Discount> {
|
|
661
680
|
const { data, error } = await getDb()
|
|
662
681
|
.from(T.discounts)
|
|
663
|
-
.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 ?? new Date().toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? 'active', times_used: 0 })
|
|
682
|
+
.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 ?? new Date().toISOString(), ends_at: input.endsAt ?? null, status: input.status ?? 'active', times_used: 0 })
|
|
664
683
|
.select().single()
|
|
665
684
|
if (error) throw error
|
|
666
685
|
return rowToDiscount(data)
|
|
@@ -673,6 +692,10 @@ export class SupabaseShopProvider implements ShopProvider {
|
|
|
673
692
|
if (input.type !== undefined) updates.type = input.type
|
|
674
693
|
if (input.value !== undefined) updates.value = input.value
|
|
675
694
|
if (input.usageLimit !== undefined) updates.usage_limit = input.usageLimit
|
|
695
|
+
if (input.minSubtotal !== undefined) updates.min_subtotal = input.minSubtotal
|
|
696
|
+
if (input.appliesTo !== undefined) updates.applies_to = input.appliesTo
|
|
697
|
+
if (input.productIds !== undefined) updates.product_ids = input.productIds
|
|
698
|
+
if (input.categoryIds !== undefined) updates.category_ids = input.categoryIds
|
|
676
699
|
if (input.status !== undefined) updates.status = input.status
|
|
677
700
|
if (input.endsAt !== undefined) updates.ends_at = input.endsAt
|
|
678
701
|
const { data, error } = await getDb().from(T.discounts).update(updates).eq('id', id).select().single()
|
|
@@ -684,6 +707,46 @@ export class SupabaseShopProvider implements ShopProvider {
|
|
|
684
707
|
const { error } = await getDb().from(T.discounts).delete().eq('id', id)
|
|
685
708
|
if (error) throw error
|
|
686
709
|
}
|
|
710
|
+
|
|
711
|
+
// ---------------------------------------------------------------------------
|
|
712
|
+
// Sales channels
|
|
713
|
+
// ---------------------------------------------------------------------------
|
|
714
|
+
|
|
715
|
+
async listSalesChannels(): Promise<SalesChannel[]> {
|
|
716
|
+
const db = getDb()
|
|
717
|
+
const tenantId = getTenantId()
|
|
718
|
+
let q = db.from(T.salesChannels).select('*').order('position', { ascending: true })
|
|
719
|
+
if (tenantId) q = q.eq('tenant_id', tenantId)
|
|
720
|
+
const { data, error } = await q
|
|
721
|
+
if (error) throw error
|
|
722
|
+
return (data ?? []).map(rowToSalesChannel)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async createSalesChannel(input: CreateSalesChannelInput): Promise<SalesChannel> {
|
|
726
|
+
const { data, error } = await getDb()
|
|
727
|
+
.from(T.salesChannels)
|
|
728
|
+
.insert({ tenant_id: getTenantId(), kind: input.kind, label: input.label, url: input.url ?? null, enabled: input.enabled ?? true, position: input.position ?? 0 })
|
|
729
|
+
.select().single()
|
|
730
|
+
if (error) throw error
|
|
731
|
+
return rowToSalesChannel(data)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async updateSalesChannel(id: string, input: UpdateSalesChannelInput): Promise<SalesChannel> {
|
|
735
|
+
const updates: Record<string, unknown> = {}
|
|
736
|
+
if (input.kind !== undefined) updates.kind = input.kind
|
|
737
|
+
if (input.label !== undefined) updates.label = input.label
|
|
738
|
+
if (input.url !== undefined) updates.url = input.url
|
|
739
|
+
if (input.enabled !== undefined) updates.enabled = input.enabled
|
|
740
|
+
if (input.position !== undefined) updates.position = input.position
|
|
741
|
+
const { data, error } = await getDb().from(T.salesChannels).update(updates).eq('id', id).select().single()
|
|
742
|
+
if (error) throw error
|
|
743
|
+
return rowToSalesChannel(data)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async deleteSalesChannel(id: string): Promise<void> {
|
|
747
|
+
const { error } = await getDb().from(T.salesChannels).delete().eq('id', id)
|
|
748
|
+
if (error) throw error
|
|
749
|
+
}
|
|
687
750
|
}
|
|
688
751
|
|
|
689
752
|
export function createSupabaseShopProvider(): ShopProvider {
|
package/src/tables.ts
CHANGED
|
@@ -9,6 +9,7 @@ export const T = {
|
|
|
9
9
|
orderItems: 'plg_shop_order_items',
|
|
10
10
|
customers: 'plg_shop_customers',
|
|
11
11
|
discounts: 'plg_shop_discounts',
|
|
12
|
+
salesChannels: 'plg_shop_channels',
|
|
12
13
|
// Core, not plg_shop_*: a delivery zone is a fact about the business, and
|
|
13
14
|
// fulfilment and financial read it too. Same reasoning as addresses and
|
|
14
15
|
// orders — see migration 0021.
|
package/src/types.ts
CHANGED
|
@@ -453,6 +453,9 @@ export type DiscountType = 'percentage' | 'fixed_amount' | 'free_shipping' | 'bu
|
|
|
453
453
|
export type DiscountMethod = 'code' | 'automatic'
|
|
454
454
|
export type DiscountStatus = 'active' | 'expired' | 'scheduled' | 'draft'
|
|
455
455
|
|
|
456
|
+
/** What subset of the cart a discount applies to. 'all' = order-wide (default). */
|
|
457
|
+
export type DiscountScope = 'all' | 'products' | 'categories'
|
|
458
|
+
|
|
456
459
|
export interface Discount {
|
|
457
460
|
id: string
|
|
458
461
|
tenantId: string
|
|
@@ -463,6 +466,15 @@ export interface Discount {
|
|
|
463
466
|
value: number
|
|
464
467
|
usageLimit: number | null
|
|
465
468
|
oncePerCustomer: boolean
|
|
469
|
+
/** Minimum order subtotal for the coupon to apply (pedido mínimo). Null = none. */
|
|
470
|
+
minSubtotal: number | null
|
|
471
|
+
/** Scope of the discount. 'products'/'categories' restrict it to the eligible
|
|
472
|
+
* line items; the %/valor then applies to that eligible subtotal only. */
|
|
473
|
+
appliesTo: DiscountScope
|
|
474
|
+
/** When appliesTo='products', the eligible product ids. */
|
|
475
|
+
productIds: string[]
|
|
476
|
+
/** When appliesTo='categories', the eligible category ids. */
|
|
477
|
+
categoryIds: string[]
|
|
466
478
|
startsAt: string
|
|
467
479
|
endsAt: string | null
|
|
468
480
|
status: DiscountStatus
|
|
@@ -479,6 +491,10 @@ export interface CreateDiscountInput {
|
|
|
479
491
|
value: number
|
|
480
492
|
usageLimit?: number
|
|
481
493
|
oncePerCustomer?: boolean
|
|
494
|
+
minSubtotal?: number | null
|
|
495
|
+
appliesTo?: DiscountScope
|
|
496
|
+
productIds?: string[]
|
|
497
|
+
categoryIds?: string[]
|
|
482
498
|
startsAt?: string
|
|
483
499
|
endsAt?: string
|
|
484
500
|
status?: DiscountStatus
|
|
@@ -492,6 +508,10 @@ export interface UpdateDiscountInput {
|
|
|
492
508
|
value?: number
|
|
493
509
|
usageLimit?: number | null
|
|
494
510
|
oncePerCustomer?: boolean
|
|
511
|
+
minSubtotal?: number | null
|
|
512
|
+
appliesTo?: DiscountScope
|
|
513
|
+
productIds?: string[]
|
|
514
|
+
categoryIds?: string[]
|
|
495
515
|
startsAt?: string
|
|
496
516
|
endsAt?: string | null
|
|
497
517
|
status?: DiscountStatus
|
|
@@ -505,6 +525,53 @@ export interface ListDiscountsOptions {
|
|
|
505
525
|
offset?: number
|
|
506
526
|
}
|
|
507
527
|
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
// Sales channels — the store's points of sale (Loja Online, WhatsApp, TikTok…).
|
|
530
|
+
// A channel is per-tenant (the store IS the tenant). The 'online_store' channel
|
|
531
|
+
// points at the merchant's fayz storefront; the rest are external links the
|
|
532
|
+
// merchant maintains. Surfaced as a "Canais de venda" group in the admin nav.
|
|
533
|
+
// ---------------------------------------------------------------------------
|
|
534
|
+
|
|
535
|
+
export type SalesChannelKind =
|
|
536
|
+
| 'online_store'
|
|
537
|
+
| 'whatsapp'
|
|
538
|
+
| 'instagram'
|
|
539
|
+
| 'tiktok_shop'
|
|
540
|
+
| 'marketplace'
|
|
541
|
+
| 'physical'
|
|
542
|
+
| 'custom'
|
|
543
|
+
|
|
544
|
+
export interface SalesChannel {
|
|
545
|
+
id: string
|
|
546
|
+
tenantId: string
|
|
547
|
+
kind: SalesChannelKind
|
|
548
|
+
label: string
|
|
549
|
+
/** External URL opened from the sidebar / channels page. Null while unconfigured
|
|
550
|
+
* (e.g. the Loja Online channel before the storefront address is set). */
|
|
551
|
+
url: string | null
|
|
552
|
+
enabled: boolean
|
|
553
|
+
/** Sidebar/list ordering (lower first). */
|
|
554
|
+
position: number
|
|
555
|
+
createdAt: string
|
|
556
|
+
updatedAt: string
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export interface CreateSalesChannelInput {
|
|
560
|
+
kind: SalesChannelKind
|
|
561
|
+
label: string
|
|
562
|
+
url?: string | null
|
|
563
|
+
enabled?: boolean
|
|
564
|
+
position?: number
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export interface UpdateSalesChannelInput {
|
|
568
|
+
kind?: SalesChannelKind
|
|
569
|
+
label?: string
|
|
570
|
+
url?: string | null
|
|
571
|
+
enabled?: boolean
|
|
572
|
+
position?: number
|
|
573
|
+
}
|
|
574
|
+
|
|
508
575
|
// ---------------------------------------------------------------------------
|
|
509
576
|
// Commerce primitives (FAY-1193) — provider-agnostic helpers for custom workflows
|
|
510
577
|
// ---------------------------------------------------------------------------
|