@open-mercato/core 0.4.5-develop-3ce83a8b24 → 0.4.5-develop-4849712ccb
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/generated/entities/catalog_product/index.js +16 -0
- package/dist/generated/entities/catalog_product/index.js.map +2 -2
- package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
- package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
- package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
- package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
- package/dist/generated/entities/sales_invoice_line/index.js +7 -1
- package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +6 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entities/sales_quote_line/index.js +6 -0
- package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/api/prices/route.js +123 -8
- package/dist/modules/catalog/api/prices/route.js.map +2 -2
- package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
- package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +351 -201
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/index.js +1 -0
- package/dist/modules/catalog/commands/index.js.map +2 -2
- package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
- package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +355 -73
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/shared.js +18 -4
- package/dist/modules/catalog/commands/shared.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
- package/dist/modules/catalog/components/products/productForm.js +66 -5
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
- package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
- package/dist/modules/catalog/data/entities.js +86 -0
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +65 -3
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/events.js +3 -0
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/lib/unitCodes.js +7 -0
- package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
- package/dist/modules/catalog/lib/unitResolution.js +53 -0
- package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
- package/dist/modules/catalog/search.js +69 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/catalog/seed/examples.js +91 -42
- package/dist/modules/catalog/seed/examples.js.map +2 -2
- package/dist/modules/dashboards/seed/analytics.js +3 -0
- package/dist/modules/dashboards/seed/analytics.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +98 -15
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +101 -14
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
- package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +1424 -260
- package/dist/modules/sales/commands/documents.js.map +3 -3
- package/dist/modules/sales/commands/shared.js +6 -2
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +59 -3
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +35 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
- package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
- package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
- package/dist/modules/sales/search.js +28 -0
- package/dist/modules/sales/search.js.map +2 -2
- package/dist/modules/sales/seed/examples.js +14 -1
- package/dist/modules/sales/seed/examples.js.map +2 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
- package/generated/entities/catalog_product/index.ts +8 -0
- package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
- package/generated/entities/sales_credit_memo_line/index.ts +3 -0
- package/generated/entities/sales_invoice_line/index.ts +3 -0
- package/generated/entities/sales_order_line/index.ts +3 -0
- package/generated/entities/sales_quote_line/index.ts +3 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/auth/i18n/de.json +1 -1
- package/src/modules/auth/i18n/en.json +1 -1
- package/src/modules/auth/i18n/es.json +1 -1
- package/src/modules/auth/i18n/pl.json +1 -1
- package/src/modules/catalog/api/prices/route.ts +213 -81
- package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
- package/src/modules/catalog/api/products/route.ts +638 -402
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
- package/src/modules/catalog/commands/index.ts +1 -0
- package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
- package/src/modules/catalog/commands/products.ts +1151 -693
- package/src/modules/catalog/commands/shared.ts +19 -5
- package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
- package/src/modules/catalog/components/products/productForm.ts +369 -256
- package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
- package/src/modules/catalog/data/entities.ts +82 -1
- package/src/modules/catalog/data/validators.ts +118 -34
- package/src/modules/catalog/events.ts +3 -0
- package/src/modules/catalog/i18n/de.json +56 -0
- package/src/modules/catalog/i18n/en.json +56 -0
- package/src/modules/catalog/i18n/es.json +56 -0
- package/src/modules/catalog/i18n/pl.json +56 -0
- package/src/modules/catalog/lib/unitCodes.ts +1 -0
- package/src/modules/catalog/lib/unitResolution.ts +62 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
- package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
- package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
- package/src/modules/catalog/search.ts +73 -1
- package/src/modules/catalog/seed/examples.ts +552 -479
- package/src/modules/dashboards/i18n/de.json +1 -1
- package/src/modules/dashboards/i18n/en.json +1 -1
- package/src/modules/dashboards/i18n/es.json +1 -1
- package/src/modules/dashboards/i18n/pl.json +1 -1
- package/src/modules/dashboards/seed/analytics.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +158 -68
- package/src/modules/sales/api/quote-lines/route.ts +161 -67
- package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
- package/src/modules/sales/commands/documents.ts +4250 -2424
- package/src/modules/sales/commands/shared.ts +7 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
- package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
- package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
- package/src/modules/sales/data/entities.ts +53 -0
- package/src/modules/sales/data/validators.ts +36 -0
- package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
- package/src/modules/sales/i18n/de.json +23 -3
- package/src/modules/sales/i18n/en.json +23 -3
- package/src/modules/sales/i18n/es.json +23 -3
- package/src/modules/sales/i18n/pl.json +23 -3
- package/src/modules/sales/lib/types.ts +30 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
- package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
- package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
- package/src/modules/sales/search.ts +28 -0
- package/src/modules/sales/seed/examples.ts +20 -1
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
- package/src/modules/workflows/i18n/de.json +4 -4
- package/src/modules/workflows/i18n/en.json +4 -4
- package/src/modules/workflows/i18n/es.json +4 -4
- package/src/modules/workflows/i18n/pl.json +4 -4
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { randomUUID } from
|
|
2
|
-
import path from
|
|
3
|
-
import { promises as fs } from
|
|
4
|
-
import type { EntityManager } from
|
|
5
|
-
import type { AwilixContainer } from
|
|
6
|
-
import { SalesChannel } from
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import type { EntityManager } from "@mikro-orm/postgresql";
|
|
5
|
+
import type { AwilixContainer } from "awilix";
|
|
6
|
+
import { SalesChannel } from "@open-mercato/core/modules/sales/data/entities";
|
|
7
7
|
import {
|
|
8
8
|
CatalogOffer,
|
|
9
9
|
CatalogPriceKind,
|
|
@@ -12,45 +12,59 @@ import {
|
|
|
12
12
|
CatalogProductCategoryAssignment,
|
|
13
13
|
CatalogProductPrice,
|
|
14
14
|
CatalogProductVariant,
|
|
15
|
-
} from
|
|
16
|
-
import { DefaultDataEngine } from
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
} from "../data/entities";
|
|
16
|
+
import { DefaultDataEngine } from "@open-mercato/shared/lib/data/engine";
|
|
17
|
+
import {
|
|
18
|
+
ensureCustomFieldDefinitions,
|
|
19
|
+
type FieldSetInput,
|
|
20
|
+
} from "@open-mercato/core/modules/entities/lib/field-definitions";
|
|
21
|
+
import { CustomFieldEntityConfig } from "@open-mercato/core/modules/entities/data/entities";
|
|
22
|
+
import { rebuildCategoryHierarchyForOrganization } from "../lib/categoryHierarchy";
|
|
23
|
+
import { defineFields, cf } from "@open-mercato/shared/modules/dsl";
|
|
24
|
+
import { E } from "#generated/entities.ids.generated";
|
|
25
|
+
import { SalesTaxRate } from "@open-mercato/core/modules/sales/data/entities";
|
|
26
|
+
import {
|
|
27
|
+
Attachment,
|
|
28
|
+
AttachmentPartition,
|
|
29
|
+
} from "@open-mercato/core/modules/attachments/data/entities";
|
|
30
|
+
import {
|
|
31
|
+
ensureDefaultPartitions,
|
|
32
|
+
resolveDefaultPartitionCode,
|
|
33
|
+
} from "@open-mercato/core/modules/attachments/lib/partitions";
|
|
34
|
+
import { storePartitionFile } from "@open-mercato/core/modules/attachments/lib/storage";
|
|
35
|
+
import { mergeAttachmentMetadata } from "@open-mercato/core/modules/attachments/lib/metadata";
|
|
36
|
+
import {
|
|
37
|
+
buildAttachmentFileUrl,
|
|
38
|
+
buildAttachmentImageUrl,
|
|
39
|
+
slugifyAttachmentFileName,
|
|
40
|
+
} from "@open-mercato/core/modules/attachments/lib/imageUrls";
|
|
41
|
+
import { canonicalizeUnitCode } from "../lib/unitCodes";
|
|
42
|
+
|
|
43
|
+
type SeedScope = { tenantId: string; organizationId: string };
|
|
44
|
+
|
|
45
|
+
const EXAMPLES_MEDIA_ROOT = path.join(process.cwd(), "public", "examples");
|
|
32
46
|
|
|
33
47
|
function detectMimeType(fileName: string): string {
|
|
34
|
-
const ext = fileName.toLowerCase().split(
|
|
35
|
-
if (ext ===
|
|
36
|
-
if (ext ===
|
|
37
|
-
if (ext ===
|
|
38
|
-
return
|
|
48
|
+
const ext = fileName.toLowerCase().split(".").pop() || "";
|
|
49
|
+
if (ext === "png") return "image/png";
|
|
50
|
+
if (ext === "jpg" || ext === "jpeg") return "image/jpeg";
|
|
51
|
+
if (ext === "webp") return "image/webp";
|
|
52
|
+
return "application/octet-stream";
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
async function ensureAttachmentPartition(
|
|
42
56
|
em: EntityManager,
|
|
43
|
-
code: string
|
|
57
|
+
code: string,
|
|
44
58
|
): Promise<AttachmentPartition> {
|
|
45
|
-
let partition = await em.findOne(AttachmentPartition, { code })
|
|
59
|
+
let partition = await em.findOne(AttachmentPartition, { code });
|
|
46
60
|
if (!partition) {
|
|
47
|
-
await ensureDefaultPartitions(em)
|
|
48
|
-
partition = await em.findOne(AttachmentPartition, { code })
|
|
61
|
+
await ensureDefaultPartitions(em);
|
|
62
|
+
partition = await em.findOne(AttachmentPartition, { code });
|
|
49
63
|
}
|
|
50
64
|
if (!partition) {
|
|
51
|
-
throw new Error(`Attachment partition "${code}" is not configured.`)
|
|
65
|
+
throw new Error(`Attachment partition "${code}" is not configured.`);
|
|
52
66
|
}
|
|
53
|
-
return partition
|
|
67
|
+
return partition;
|
|
54
68
|
}
|
|
55
69
|
|
|
56
70
|
async function attachMediaFromExamples(
|
|
@@ -58,20 +72,20 @@ async function attachMediaFromExamples(
|
|
|
58
72
|
scope: SeedScope,
|
|
59
73
|
entityId: string,
|
|
60
74
|
recordId: string,
|
|
61
|
-
mediaSeeds?: MediaSeed[]
|
|
75
|
+
mediaSeeds?: MediaSeed[],
|
|
62
76
|
): Promise<Array<{ id: string; imageUrl: string }>> {
|
|
63
|
-
if (!mediaSeeds?.length) return []
|
|
64
|
-
const partitionCode = resolveDefaultPartitionCode(entityId)
|
|
65
|
-
const partition = await ensureAttachmentPartition(em, partitionCode)
|
|
66
|
-
const results: Array<{ id: string; imageUrl: string }> = []
|
|
77
|
+
if (!mediaSeeds?.length) return [];
|
|
78
|
+
const partitionCode = resolveDefaultPartitionCode(entityId);
|
|
79
|
+
const partition = await ensureAttachmentPartition(em, partitionCode);
|
|
80
|
+
const results: Array<{ id: string; imageUrl: string }> = [];
|
|
67
81
|
for (const media of mediaSeeds) {
|
|
68
|
-
const sourcePath = path.join(EXAMPLES_MEDIA_ROOT, media.file)
|
|
69
|
-
let buffer: Buffer
|
|
82
|
+
const sourcePath = path.join(EXAMPLES_MEDIA_ROOT, media.file);
|
|
83
|
+
let buffer: Buffer;
|
|
70
84
|
try {
|
|
71
|
-
buffer = await fs.readFile(sourcePath)
|
|
85
|
+
buffer = await fs.readFile(sourcePath);
|
|
72
86
|
} catch (error) {
|
|
73
|
-
console.warn(`[catalog.seed] Example media missing: ${sourcePath}`)
|
|
74
|
-
continue
|
|
87
|
+
console.warn(`[catalog.seed] Example media missing: ${sourcePath}`);
|
|
88
|
+
continue;
|
|
75
89
|
}
|
|
76
90
|
const stored = await storePartitionFile({
|
|
77
91
|
partitionCode: partition.code,
|
|
@@ -79,12 +93,12 @@ async function attachMediaFromExamples(
|
|
|
79
93
|
tenantId: scope.tenantId,
|
|
80
94
|
fileName: media.file,
|
|
81
95
|
buffer,
|
|
82
|
-
})
|
|
83
|
-
const attachmentId = randomUUID()
|
|
84
|
-
const slug = slugifyAttachmentFileName(media.file,
|
|
96
|
+
});
|
|
97
|
+
const attachmentId = randomUUID();
|
|
98
|
+
const slug = slugifyAttachmentFileName(media.file, "media");
|
|
85
99
|
const metadata = mergeAttachmentMetadata(null, {
|
|
86
100
|
assignments: [{ type: entityId, id: recordId }],
|
|
87
|
-
})
|
|
101
|
+
});
|
|
88
102
|
const attachment = em.create(Attachment, {
|
|
89
103
|
id: attachmentId,
|
|
90
104
|
entityId,
|
|
@@ -95,502 +109,527 @@ async function attachMediaFromExamples(
|
|
|
95
109
|
fileName: media.file,
|
|
96
110
|
mimeType: detectMimeType(media.file),
|
|
97
111
|
fileSize: buffer.length,
|
|
98
|
-
storageDriver: partition.storageDriver ||
|
|
112
|
+
storageDriver: partition.storageDriver || "local",
|
|
99
113
|
storagePath: stored.storagePath,
|
|
100
114
|
storageMetadata: metadata,
|
|
101
115
|
url: buildAttachmentFileUrl(attachmentId),
|
|
102
|
-
})
|
|
103
|
-
em.persist(attachment)
|
|
116
|
+
});
|
|
117
|
+
em.persist(attachment);
|
|
104
118
|
results.push({
|
|
105
119
|
id: attachmentId,
|
|
106
120
|
imageUrl: buildAttachmentImageUrl(attachmentId, { slug }),
|
|
107
|
-
})
|
|
121
|
+
});
|
|
108
122
|
}
|
|
109
|
-
return results
|
|
123
|
+
return results;
|
|
110
124
|
}
|
|
111
125
|
|
|
112
126
|
const PRODUCT_FIELDSETS = [
|
|
113
127
|
{
|
|
114
|
-
code:
|
|
115
|
-
label:
|
|
116
|
-
icon:
|
|
117
|
-
description:
|
|
128
|
+
code: "fashion_mens_footwear",
|
|
129
|
+
label: "Fashion · Men · Footwear",
|
|
130
|
+
icon: "carbon:sneaker",
|
|
131
|
+
description:
|
|
132
|
+
"Material, construction, and care metadata for men’s performance footwear.",
|
|
118
133
|
groups: [
|
|
119
|
-
{ code:
|
|
120
|
-
{ code:
|
|
121
|
-
{ code:
|
|
134
|
+
{ code: "identity", title: "Identity" },
|
|
135
|
+
{ code: "materials", title: "Materials & Build" },
|
|
136
|
+
{ code: "care", title: "Care instructions" },
|
|
122
137
|
],
|
|
123
138
|
},
|
|
124
139
|
{
|
|
125
|
-
code:
|
|
126
|
-
label:
|
|
127
|
-
icon:
|
|
128
|
-
description:
|
|
140
|
+
code: "fashion_womens_dresses",
|
|
141
|
+
label: "Fashion · Women · Dresses & Jumpsuits",
|
|
142
|
+
icon: "solar:dress-linear",
|
|
143
|
+
description: "Silhouette, fabric, and care metadata for womenswear.",
|
|
129
144
|
groups: [
|
|
130
|
-
{ code:
|
|
131
|
-
{ code:
|
|
132
|
-
{ code:
|
|
133
|
-
{ code:
|
|
145
|
+
{ code: "identity", title: "Identity" },
|
|
146
|
+
{ code: "materials", title: "Materials" },
|
|
147
|
+
{ code: "fit", title: "Fit & Length" },
|
|
148
|
+
{ code: "care", title: "Care instructions" },
|
|
134
149
|
],
|
|
135
150
|
},
|
|
136
151
|
{
|
|
137
|
-
code:
|
|
138
|
-
label:
|
|
139
|
-
icon:
|
|
140
|
-
description:
|
|
152
|
+
code: "service_schedule",
|
|
153
|
+
label: "Services · Scheduling",
|
|
154
|
+
icon: "solar:calendar-linear",
|
|
155
|
+
description:
|
|
156
|
+
"Scheduling, preparation, and delivery metadata for service offerings.",
|
|
141
157
|
groups: [
|
|
142
|
-
{ code:
|
|
143
|
-
{ code:
|
|
144
|
-
{ code:
|
|
158
|
+
{ code: "identity", title: "Identity" },
|
|
159
|
+
{ code: "timing", title: "Timing rules" },
|
|
160
|
+
{ code: "resources", title: "Resources & Delivery" },
|
|
145
161
|
],
|
|
146
162
|
},
|
|
147
|
-
] as const
|
|
163
|
+
] as const;
|
|
148
164
|
|
|
149
165
|
const VARIANT_FIELDSETS = [
|
|
150
166
|
{
|
|
151
|
-
code:
|
|
152
|
-
label:
|
|
153
|
-
icon:
|
|
154
|
-
description:
|
|
167
|
+
code: "fashion_mens_footwear",
|
|
168
|
+
label: "Fashion · Men · Footwear",
|
|
169
|
+
icon: "carbon:sneaker",
|
|
170
|
+
description: "Variant-level sizing metadata for men’s footwear.",
|
|
155
171
|
groups: [
|
|
156
|
-
{ code:
|
|
157
|
-
{ code:
|
|
172
|
+
{ code: "fit", title: "Fit" },
|
|
173
|
+
{ code: "finish", title: "Finish" },
|
|
158
174
|
],
|
|
159
175
|
},
|
|
160
176
|
{
|
|
161
|
-
code:
|
|
162
|
-
label:
|
|
163
|
-
icon:
|
|
164
|
-
description:
|
|
177
|
+
code: "fashion_womens_dresses",
|
|
178
|
+
label: "Fashion · Women · Dresses & Jumpsuits",
|
|
179
|
+
icon: "solar:dress-linear",
|
|
180
|
+
description: "Variant-level sizing metadata for womenswear.",
|
|
165
181
|
groups: [
|
|
166
|
-
{ code:
|
|
167
|
-
{ code:
|
|
182
|
+
{ code: "fit", title: "Fit" },
|
|
183
|
+
{ code: "finish", title: "Finish" },
|
|
168
184
|
],
|
|
169
185
|
},
|
|
170
186
|
{
|
|
171
|
-
code:
|
|
172
|
-
label:
|
|
173
|
-
icon:
|
|
174
|
-
description:
|
|
187
|
+
code: "service_schedule",
|
|
188
|
+
label: "Services · Scheduling",
|
|
189
|
+
icon: "solar:calendar-linear",
|
|
190
|
+
description:
|
|
191
|
+
"Provider, duration, and environment metadata for service slots.",
|
|
175
192
|
groups: [
|
|
176
|
-
{ code:
|
|
177
|
-
{ code:
|
|
193
|
+
{ code: "provider", title: "Provider" },
|
|
194
|
+
{ code: "environment", title: "Environment" },
|
|
178
195
|
],
|
|
179
196
|
},
|
|
180
|
-
] as const
|
|
197
|
+
] as const;
|
|
181
198
|
|
|
182
199
|
const CUSTOM_FIELD_SETS: FieldSetInput[] = [
|
|
183
200
|
defineFields(E.catalog.catalog_product, [
|
|
184
|
-
cf.text(
|
|
185
|
-
label:
|
|
186
|
-
description:
|
|
187
|
-
filterable: true,
|
|
188
|
-
fieldset: 'fashion_mens_footwear',
|
|
189
|
-
group: { code: 'identity' },
|
|
190
|
-
}),
|
|
191
|
-
cf.select('upper_material', ['engineered_knit', 'full_grain_leather', 'recycled_mesh'], {
|
|
192
|
-
label: 'Upper material',
|
|
193
|
-
fieldset: 'fashion_mens_footwear',
|
|
194
|
-
group: { code: 'materials' },
|
|
201
|
+
cf.text("style_code", {
|
|
202
|
+
label: "Style code",
|
|
203
|
+
description: "Reference code shared with merchandising teams.",
|
|
195
204
|
filterable: true,
|
|
205
|
+
fieldset: "fashion_mens_footwear",
|
|
206
|
+
group: { code: "identity" },
|
|
196
207
|
}),
|
|
197
|
-
cf.select(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
cf.select(
|
|
209
|
+
"upper_material",
|
|
210
|
+
["engineered_knit", "full_grain_leather", "recycled_mesh"],
|
|
211
|
+
{
|
|
212
|
+
label: "Upper material",
|
|
213
|
+
fieldset: "fashion_mens_footwear",
|
|
214
|
+
group: { code: "materials" },
|
|
215
|
+
filterable: true,
|
|
216
|
+
},
|
|
217
|
+
),
|
|
218
|
+
cf.select("cushioning_profile", ["responsive", "plush", "stability"], {
|
|
219
|
+
label: "Cushioning profile",
|
|
220
|
+
fieldset: "fashion_mens_footwear",
|
|
221
|
+
group: { code: "materials" },
|
|
201
222
|
}),
|
|
202
|
-
cf.multiline(
|
|
203
|
-
label:
|
|
204
|
-
editor:
|
|
205
|
-
fieldset:
|
|
206
|
-
group: { code:
|
|
223
|
+
cf.multiline("care_notes", {
|
|
224
|
+
label: "Care notes",
|
|
225
|
+
editor: "markdown",
|
|
226
|
+
fieldset: "fashion_mens_footwear",
|
|
227
|
+
group: { code: "care" },
|
|
207
228
|
}),
|
|
208
229
|
]),
|
|
209
230
|
defineFields(E.catalog.catalog_product, [
|
|
210
|
-
cf.select(
|
|
211
|
-
label:
|
|
212
|
-
fieldset:
|
|
213
|
-
group: { code:
|
|
231
|
+
cf.select("silhouette", ["wrap", "column", "fit_and_flare", "jumpsuit"], {
|
|
232
|
+
label: "Silhouette",
|
|
233
|
+
fieldset: "fashion_womens_dresses",
|
|
234
|
+
group: { code: "identity" },
|
|
214
235
|
filterable: true,
|
|
215
236
|
}),
|
|
216
|
-
cf.select(
|
|
217
|
-
label:
|
|
218
|
-
fieldset:
|
|
219
|
-
group: { code:
|
|
237
|
+
cf.select("fabric_mix", ["silk_blend", "recycled_poly", "linen", "cupro"], {
|
|
238
|
+
label: "Fabric mix",
|
|
239
|
+
fieldset: "fashion_womens_dresses",
|
|
240
|
+
group: { code: "materials" },
|
|
220
241
|
}),
|
|
221
|
-
cf.select(
|
|
222
|
-
label:
|
|
223
|
-
fieldset:
|
|
224
|
-
group: { code:
|
|
242
|
+
cf.select("occasion_ready", ["daytime", "evening", "resort"], {
|
|
243
|
+
label: "Occasion",
|
|
244
|
+
fieldset: "fashion_womens_dresses",
|
|
245
|
+
group: { code: "fit" },
|
|
225
246
|
}),
|
|
226
|
-
cf.multiline(
|
|
227
|
-
label:
|
|
228
|
-
editor:
|
|
229
|
-
fieldset:
|
|
230
|
-
group: { code:
|
|
247
|
+
cf.multiline("finishing_details", {
|
|
248
|
+
label: "Finishing details",
|
|
249
|
+
editor: "markdown",
|
|
250
|
+
fieldset: "fashion_womens_dresses",
|
|
251
|
+
group: { code: "care" },
|
|
231
252
|
}),
|
|
232
253
|
]),
|
|
233
254
|
defineFields(E.catalog.catalog_product_variant, [
|
|
234
|
-
cf.integer(
|
|
235
|
-
label:
|
|
236
|
-
fieldset:
|
|
237
|
-
group: { code:
|
|
255
|
+
cf.integer("shoe_size", {
|
|
256
|
+
label: "US size",
|
|
257
|
+
fieldset: "fashion_mens_footwear",
|
|
258
|
+
group: { code: "fit" },
|
|
238
259
|
filterable: true,
|
|
239
260
|
}),
|
|
240
|
-
cf.select(
|
|
241
|
-
label:
|
|
242
|
-
fieldset:
|
|
243
|
-
group: { code:
|
|
261
|
+
cf.select("shoe_width", ["B", "D", "EE"], {
|
|
262
|
+
label: "Width",
|
|
263
|
+
fieldset: "fashion_mens_footwear",
|
|
264
|
+
group: { code: "fit" },
|
|
244
265
|
}),
|
|
245
|
-
cf.text(
|
|
246
|
-
label:
|
|
247
|
-
fieldset:
|
|
248
|
-
group: { code:
|
|
266
|
+
cf.text("colorway", {
|
|
267
|
+
label: "Colorway",
|
|
268
|
+
fieldset: "fashion_mens_footwear",
|
|
269
|
+
group: { code: "finish" },
|
|
249
270
|
}),
|
|
250
271
|
]),
|
|
251
272
|
defineFields(E.catalog.catalog_product_variant, [
|
|
252
|
-
cf.integer(
|
|
253
|
-
label:
|
|
254
|
-
fieldset:
|
|
255
|
-
group: { code:
|
|
273
|
+
cf.integer("numeric_size", {
|
|
274
|
+
label: "Numeric size",
|
|
275
|
+
fieldset: "fashion_womens_dresses",
|
|
276
|
+
group: { code: "fit" },
|
|
256
277
|
}),
|
|
257
|
-
cf.select(
|
|
258
|
-
label:
|
|
259
|
-
fieldset:
|
|
260
|
-
group: { code:
|
|
278
|
+
cf.select("length_profile", ["mini", "midi", "maxi"], {
|
|
279
|
+
label: "Length",
|
|
280
|
+
fieldset: "fashion_womens_dresses",
|
|
281
|
+
group: { code: "fit" },
|
|
261
282
|
}),
|
|
262
|
-
cf.text(
|
|
263
|
-
label:
|
|
264
|
-
fieldset:
|
|
265
|
-
group: { code:
|
|
283
|
+
cf.text("color_story", {
|
|
284
|
+
label: "Color story",
|
|
285
|
+
fieldset: "fashion_womens_dresses",
|
|
286
|
+
group: { code: "finish" },
|
|
266
287
|
}),
|
|
267
288
|
]),
|
|
268
289
|
defineFields(E.catalog.catalog_product, [
|
|
269
|
-
cf.integer(
|
|
270
|
-
label:
|
|
271
|
-
description:
|
|
272
|
-
fieldset:
|
|
273
|
-
group: { code:
|
|
290
|
+
cf.integer("service_duration_minutes", {
|
|
291
|
+
label: "Duration (minutes)",
|
|
292
|
+
description: "Length of a single service slot.",
|
|
293
|
+
fieldset: "service_schedule",
|
|
294
|
+
group: { code: "timing" },
|
|
274
295
|
filterable: true,
|
|
275
296
|
required: true,
|
|
276
297
|
}),
|
|
277
|
-
cf.integer(
|
|
278
|
-
label:
|
|
279
|
-
description:
|
|
280
|
-
|
|
281
|
-
|
|
298
|
+
cf.integer("service_buffer_minutes", {
|
|
299
|
+
label: "Buffer between appointments",
|
|
300
|
+
description:
|
|
301
|
+
"Minimum downtime between consecutive service slots (minutes).",
|
|
302
|
+
fieldset: "service_schedule",
|
|
303
|
+
group: { code: "timing" },
|
|
282
304
|
}),
|
|
283
|
-
cf.text(
|
|
284
|
-
label:
|
|
285
|
-
description:
|
|
286
|
-
fieldset:
|
|
287
|
-
group: { code:
|
|
305
|
+
cf.text("service_location", {
|
|
306
|
+
label: "Location / Room",
|
|
307
|
+
description: "Where the service is delivered.",
|
|
308
|
+
fieldset: "service_schedule",
|
|
309
|
+
group: { code: "identity" },
|
|
288
310
|
}),
|
|
289
|
-
cf.select(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
311
|
+
cf.select(
|
|
312
|
+
"service_resources",
|
|
313
|
+
["stylist", "therapist", "treatment_room", "wash_station", "steam_room"],
|
|
314
|
+
{
|
|
315
|
+
label: "Required resources",
|
|
316
|
+
description: "Staff or rooms required to perform the service.",
|
|
317
|
+
fieldset: "service_schedule",
|
|
318
|
+
group: { code: "resources" },
|
|
319
|
+
multi: true,
|
|
320
|
+
},
|
|
321
|
+
),
|
|
322
|
+
cf.boolean("service_remote_available", {
|
|
323
|
+
label: "Remote session available",
|
|
324
|
+
description:
|
|
325
|
+
"Indicates whether the service can be performed remotely or virtually.",
|
|
326
|
+
fieldset: "service_schedule",
|
|
327
|
+
group: { code: "resources" },
|
|
301
328
|
defaultValue: false,
|
|
302
329
|
}),
|
|
303
330
|
]),
|
|
304
331
|
defineFields(E.catalog.catalog_product_variant, [
|
|
305
|
-
cf.select(
|
|
306
|
-
label:
|
|
307
|
-
description:
|
|
308
|
-
fieldset:
|
|
309
|
-
group: { code:
|
|
332
|
+
cf.select("provider_level", ["junior", "senior", "master"], {
|
|
333
|
+
label: "Provider level",
|
|
334
|
+
description: "Seniority of the assigned specialist.",
|
|
335
|
+
fieldset: "service_schedule",
|
|
336
|
+
group: { code: "provider" },
|
|
310
337
|
filterable: true,
|
|
311
338
|
}),
|
|
312
|
-
cf.text(
|
|
313
|
-
label:
|
|
314
|
-
description:
|
|
315
|
-
|
|
316
|
-
|
|
339
|
+
cf.text("staff_member", {
|
|
340
|
+
label: "Staff member",
|
|
341
|
+
description:
|
|
342
|
+
"Optional name of the staff member who usually delivers this variant.",
|
|
343
|
+
fieldset: "service_schedule",
|
|
344
|
+
group: { code: "provider" },
|
|
317
345
|
}),
|
|
318
|
-
cf.select(
|
|
319
|
-
label:
|
|
320
|
-
description:
|
|
321
|
-
fieldset:
|
|
322
|
-
group: { code:
|
|
346
|
+
cf.select("environment_type", ["studio", "suite", "on_site"], {
|
|
347
|
+
label: "Environment",
|
|
348
|
+
description: "Where the session is hosted.",
|
|
349
|
+
fieldset: "service_schedule",
|
|
350
|
+
group: { code: "environment" },
|
|
323
351
|
}),
|
|
324
352
|
]),
|
|
325
|
-
]
|
|
353
|
+
];
|
|
326
354
|
|
|
327
355
|
type CategorySeed = {
|
|
328
|
-
slug: string
|
|
329
|
-
name: string
|
|
330
|
-
description?: string
|
|
331
|
-
children?: CategorySeed[]
|
|
332
|
-
}
|
|
356
|
+
slug: string;
|
|
357
|
+
name: string;
|
|
358
|
+
description?: string;
|
|
359
|
+
children?: CategorySeed[];
|
|
360
|
+
};
|
|
333
361
|
|
|
334
362
|
const CATEGORY_TREE: CategorySeed[] = [
|
|
335
363
|
{
|
|
336
|
-
slug:
|
|
337
|
-
name:
|
|
338
|
-
description:
|
|
364
|
+
slug: "fashion",
|
|
365
|
+
name: "Fashion",
|
|
366
|
+
description: "Seasonal assortments and vertical-specific collections.",
|
|
339
367
|
children: [
|
|
340
368
|
{
|
|
341
|
-
slug:
|
|
342
|
-
name:
|
|
369
|
+
slug: "fashion-men",
|
|
370
|
+
name: "Men",
|
|
343
371
|
children: [
|
|
344
372
|
{
|
|
345
|
-
slug:
|
|
346
|
-
name:
|
|
347
|
-
description:
|
|
373
|
+
slug: "fashion-men-footwear",
|
|
374
|
+
name: "Footwear",
|
|
375
|
+
description: "Premium sneakers, boots, and sandals.",
|
|
348
376
|
},
|
|
349
377
|
],
|
|
350
378
|
},
|
|
351
379
|
{
|
|
352
|
-
slug:
|
|
353
|
-
name:
|
|
380
|
+
slug: "fashion-women",
|
|
381
|
+
name: "Women",
|
|
354
382
|
children: [
|
|
355
383
|
{
|
|
356
|
-
slug:
|
|
357
|
-
name:
|
|
358
|
-
description:
|
|
384
|
+
slug: "fashion-women-dresses-jumpsuits",
|
|
385
|
+
name: "Dresses & Jumpsuits",
|
|
386
|
+
description: "Occasion-ready dresses and tailored jumpsuits.",
|
|
359
387
|
},
|
|
360
388
|
],
|
|
361
389
|
},
|
|
362
390
|
],
|
|
363
391
|
},
|
|
364
392
|
{
|
|
365
|
-
slug:
|
|
366
|
-
name:
|
|
367
|
-
description:
|
|
393
|
+
slug: "services",
|
|
394
|
+
name: "Services",
|
|
395
|
+
description: "Bookable in-person and virtual experiences.",
|
|
368
396
|
children: [
|
|
369
397
|
{
|
|
370
|
-
slug:
|
|
371
|
-
name:
|
|
372
|
-
description:
|
|
398
|
+
slug: "services-hairdresser",
|
|
399
|
+
name: "Hairdresser",
|
|
400
|
+
description:
|
|
401
|
+
"Salon services ranging from quick trims to signature looks.",
|
|
373
402
|
},
|
|
374
403
|
{
|
|
375
|
-
slug:
|
|
376
|
-
name:
|
|
377
|
-
description:
|
|
404
|
+
slug: "services-massage",
|
|
405
|
+
name: "Massage",
|
|
406
|
+
description: "Wellness treatments and bodywork sessions.",
|
|
378
407
|
},
|
|
379
408
|
],
|
|
380
409
|
},
|
|
381
|
-
]
|
|
410
|
+
];
|
|
382
411
|
|
|
383
412
|
type MediaSeed = {
|
|
384
|
-
file: string
|
|
385
|
-
title?: string
|
|
386
|
-
}
|
|
413
|
+
file: string;
|
|
414
|
+
title?: string;
|
|
415
|
+
};
|
|
387
416
|
|
|
388
417
|
type VariantSeed = {
|
|
389
|
-
name: string
|
|
390
|
-
sku: string
|
|
391
|
-
isDefault?: boolean
|
|
392
|
-
optionValues?: Record<string, string
|
|
418
|
+
name: string;
|
|
419
|
+
sku: string;
|
|
420
|
+
isDefault?: boolean;
|
|
421
|
+
optionValues?: Record<string, string>;
|
|
393
422
|
prices: {
|
|
394
|
-
regular: number
|
|
395
|
-
sale?: number
|
|
396
|
-
}
|
|
397
|
-
customFields?: Record<string, string | number | boolean | null
|
|
398
|
-
media?: MediaSeed[]
|
|
399
|
-
}
|
|
423
|
+
regular: number;
|
|
424
|
+
sale?: number;
|
|
425
|
+
};
|
|
426
|
+
customFields?: Record<string, string | number | boolean | null>;
|
|
427
|
+
media?: MediaSeed[];
|
|
428
|
+
};
|
|
400
429
|
|
|
401
430
|
type ProductSeed = {
|
|
402
|
-
title: string
|
|
403
|
-
handle: string
|
|
404
|
-
sku?: string
|
|
405
|
-
description: string
|
|
406
|
-
categorySlug: string
|
|
407
|
-
customFieldsetCode: string
|
|
408
|
-
variantFieldsetCode: string
|
|
409
|
-
unit: string
|
|
410
|
-
metadata?: Record<string, unknown
|
|
411
|
-
customFields?: Record<string, string | number | boolean | null
|
|
412
|
-
media?: MediaSeed[]
|
|
413
|
-
variants: VariantSeed[]
|
|
414
|
-
}
|
|
431
|
+
title: string;
|
|
432
|
+
handle: string;
|
|
433
|
+
sku?: string;
|
|
434
|
+
description: string;
|
|
435
|
+
categorySlug: string;
|
|
436
|
+
customFieldsetCode: string;
|
|
437
|
+
variantFieldsetCode: string;
|
|
438
|
+
unit: string;
|
|
439
|
+
metadata?: Record<string, unknown>;
|
|
440
|
+
customFields?: Record<string, string | number | boolean | null>;
|
|
441
|
+
media?: MediaSeed[];
|
|
442
|
+
variants: VariantSeed[];
|
|
443
|
+
};
|
|
415
444
|
|
|
416
445
|
const PRODUCT_SEEDS: ProductSeed[] = [
|
|
417
446
|
{
|
|
418
|
-
title:
|
|
419
|
-
handle:
|
|
420
|
-
sku:
|
|
447
|
+
title: "Atlas Runner Sneaker",
|
|
448
|
+
handle: "atlas-runner-sneaker",
|
|
449
|
+
sku: "ATLAS-RUNNER",
|
|
421
450
|
description:
|
|
422
|
-
|
|
423
|
-
categorySlug:
|
|
424
|
-
customFieldsetCode:
|
|
425
|
-
variantFieldsetCode:
|
|
426
|
-
unit:
|
|
427
|
-
metadata: { division:
|
|
451
|
+
"Lightweight road sneaker engineered with a breathable knit upper, recycled TPU overlays, and a decoupled heel for smooth transitions.",
|
|
452
|
+
categorySlug: "fashion-men-footwear",
|
|
453
|
+
customFieldsetCode: "fashion_mens_footwear",
|
|
454
|
+
variantFieldsetCode: "fashion_mens_footwear",
|
|
455
|
+
unit: "pair",
|
|
456
|
+
metadata: { division: "RunLab", season: "SS25" },
|
|
428
457
|
customFields: {
|
|
429
|
-
style_code:
|
|
430
|
-
upper_material:
|
|
431
|
-
cushioning_profile:
|
|
432
|
-
care_notes:
|
|
458
|
+
style_code: "AR-2025",
|
|
459
|
+
upper_material: "engineered_knit",
|
|
460
|
+
cushioning_profile: "responsive",
|
|
461
|
+
care_notes:
|
|
462
|
+
"Spot clean after each run and air dry. Avoid machine drying.",
|
|
433
463
|
},
|
|
434
|
-
media: [
|
|
435
|
-
{ file: 'atlas-runner-midnight-1.png' },
|
|
436
|
-
],
|
|
464
|
+
media: [{ file: "atlas-runner-midnight-1.png" }],
|
|
437
465
|
variants: [
|
|
438
466
|
{
|
|
439
|
-
name:
|
|
440
|
-
sku:
|
|
467
|
+
name: "Midnight Navy · US 8",
|
|
468
|
+
sku: "ATLAS-RUN-NAVY-8",
|
|
441
469
|
isDefault: true,
|
|
442
|
-
optionValues: { color:
|
|
470
|
+
optionValues: { color: "Midnight Navy", size: "US 8" },
|
|
443
471
|
prices: { regular: 168, sale: 148 },
|
|
444
|
-
customFields: {
|
|
472
|
+
customFields: {
|
|
473
|
+
shoe_size: 8,
|
|
474
|
+
shoe_width: "D",
|
|
475
|
+
colorway: "Midnight Navy",
|
|
476
|
+
},
|
|
445
477
|
media: [
|
|
446
|
-
{ file:
|
|
447
|
-
{ file:
|
|
478
|
+
{ file: "atlas-runner-midnight-1.png" },
|
|
479
|
+
{ file: "atlas-runner-midnight-2.png" },
|
|
448
480
|
],
|
|
449
481
|
},
|
|
450
482
|
{
|
|
451
|
-
name:
|
|
452
|
-
sku:
|
|
453
|
-
optionValues: { color:
|
|
483
|
+
name: "Glacier Grey · US 10",
|
|
484
|
+
sku: "ATLAS-RUN-GLACIER-10",
|
|
485
|
+
optionValues: { color: "Glacier Grey", size: "US 10" },
|
|
454
486
|
prices: { regular: 168, sale: 138 },
|
|
455
|
-
customFields: {
|
|
487
|
+
customFields: {
|
|
488
|
+
shoe_size: 10,
|
|
489
|
+
shoe_width: "EE",
|
|
490
|
+
colorway: "Glacier Grey",
|
|
491
|
+
},
|
|
456
492
|
media: [
|
|
457
|
-
{ file:
|
|
458
|
-
{ file:
|
|
493
|
+
{ file: "atlas-runner-glacier-1.png" },
|
|
494
|
+
{ file: "atlas-runner-glacier-2.png" },
|
|
459
495
|
],
|
|
460
496
|
},
|
|
461
497
|
],
|
|
462
498
|
},
|
|
463
499
|
{
|
|
464
|
-
title:
|
|
465
|
-
handle:
|
|
466
|
-
sku:
|
|
500
|
+
title: "Aurora Wrap Dress",
|
|
501
|
+
handle: "aurora-wrap-dress",
|
|
502
|
+
sku: "AURORA-WRAP",
|
|
467
503
|
description:
|
|
468
|
-
|
|
469
|
-
categorySlug:
|
|
470
|
-
customFieldsetCode:
|
|
471
|
-
variantFieldsetCode:
|
|
472
|
-
unit:
|
|
473
|
-
metadata: { capsule:
|
|
504
|
+
"Bias-cut wrap dress with blouson sleeves, matte silk blend, and hidden interior snaps so the placket stays put at events.",
|
|
505
|
+
categorySlug: "fashion-women-dresses-jumpsuits",
|
|
506
|
+
customFieldsetCode: "fashion_womens_dresses",
|
|
507
|
+
variantFieldsetCode: "fashion_womens_dresses",
|
|
508
|
+
unit: "unit",
|
|
509
|
+
metadata: { capsule: "Evening Atelier", season: "Resort 25" },
|
|
474
510
|
customFields: {
|
|
475
|
-
silhouette:
|
|
476
|
-
fabric_mix:
|
|
477
|
-
occasion_ready:
|
|
478
|
-
finishing_details:
|
|
511
|
+
silhouette: "wrap",
|
|
512
|
+
fabric_mix: "silk_blend",
|
|
513
|
+
occasion_ready: "evening",
|
|
514
|
+
finishing_details:
|
|
515
|
+
"Hand-finished hem with subtle tonal beading along the wrap edge.",
|
|
479
516
|
},
|
|
480
|
-
media: [
|
|
481
|
-
{ file: 'aurora-wrap-rosewood.png' },
|
|
482
|
-
],
|
|
517
|
+
media: [{ file: "aurora-wrap-rosewood.png" }],
|
|
483
518
|
variants: [
|
|
484
519
|
{
|
|
485
|
-
name:
|
|
486
|
-
sku:
|
|
520
|
+
name: "Rosewood · Medium",
|
|
521
|
+
sku: "AURORA-ROSE-M",
|
|
487
522
|
isDefault: true,
|
|
488
|
-
optionValues: { color:
|
|
523
|
+
optionValues: { color: "Rosewood", size: "Medium" },
|
|
489
524
|
prices: { regular: 248, sale: 212 },
|
|
490
|
-
customFields: {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
525
|
+
customFields: {
|
|
526
|
+
numeric_size: 6,
|
|
527
|
+
length_profile: "midi",
|
|
528
|
+
color_story: "Rosewood",
|
|
529
|
+
},
|
|
530
|
+
media: [{ file: "aurora-wrap-rosewood.png" }],
|
|
494
531
|
},
|
|
495
532
|
{
|
|
496
|
-
name:
|
|
497
|
-
sku:
|
|
498
|
-
optionValues: { color:
|
|
533
|
+
name: "Celestial · Large",
|
|
534
|
+
sku: "AURORA-CELESTIAL-L",
|
|
535
|
+
optionValues: { color: "Celestial", size: "Large" },
|
|
499
536
|
prices: { regular: 248, sale: 198 },
|
|
500
|
-
customFields: {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
537
|
+
customFields: {
|
|
538
|
+
numeric_size: 8,
|
|
539
|
+
length_profile: "maxi",
|
|
540
|
+
color_story: "Celestial blue",
|
|
541
|
+
},
|
|
542
|
+
media: [{ file: "aurora-wrap-celestial.png" }],
|
|
504
543
|
},
|
|
505
544
|
],
|
|
506
545
|
},
|
|
507
546
|
{
|
|
508
|
-
title:
|
|
509
|
-
handle:
|
|
510
|
-
sku:
|
|
547
|
+
title: "Signature Haircut & Finish",
|
|
548
|
+
handle: "signature-haircut-service",
|
|
549
|
+
sku: "SERV-HAIR-60",
|
|
511
550
|
description:
|
|
512
|
-
|
|
513
|
-
categorySlug:
|
|
514
|
-
customFieldsetCode:
|
|
515
|
-
variantFieldsetCode:
|
|
516
|
-
unit:
|
|
517
|
-
metadata: { channel:
|
|
551
|
+
"Tailored haircut with relaxing wash, scalp massage, and styling finish. Designed for repeat visits in the demo portal.",
|
|
552
|
+
categorySlug: "services-hairdresser",
|
|
553
|
+
customFieldsetCode: "service_schedule",
|
|
554
|
+
variantFieldsetCode: "service_schedule",
|
|
555
|
+
unit: "hour",
|
|
556
|
+
metadata: { channel: "salon", serviceType: "hairdresser" },
|
|
518
557
|
customFields: {
|
|
519
558
|
service_duration_minutes: 60,
|
|
520
559
|
service_buffer_minutes: 15,
|
|
521
|
-
service_location:
|
|
522
|
-
service_resources:
|
|
560
|
+
service_location: "Salon Studio 3",
|
|
561
|
+
service_resources: "stylist,wash_station",
|
|
523
562
|
service_remote_available: false,
|
|
524
563
|
},
|
|
525
|
-
media: [{ file:
|
|
564
|
+
media: [{ file: "hairdresser-service.png" }],
|
|
526
565
|
variants: [
|
|
527
566
|
{
|
|
528
|
-
name:
|
|
529
|
-
sku:
|
|
567
|
+
name: "Senior Stylist · 60 min",
|
|
568
|
+
sku: "SERV-HAIR-60-SENIOR",
|
|
530
569
|
isDefault: true,
|
|
531
|
-
optionValues: { stylist:
|
|
570
|
+
optionValues: { stylist: "Senior", duration: "60" },
|
|
532
571
|
prices: { regular: 95, sale: 85 },
|
|
533
572
|
customFields: {
|
|
534
|
-
provider_level:
|
|
535
|
-
staff_member:
|
|
536
|
-
environment_type:
|
|
573
|
+
provider_level: "senior",
|
|
574
|
+
staff_member: "Amelia Hart",
|
|
575
|
+
environment_type: "studio",
|
|
537
576
|
},
|
|
538
|
-
media: [{ file:
|
|
577
|
+
media: [{ file: "hairdresser-service.png" }],
|
|
539
578
|
},
|
|
540
579
|
],
|
|
541
580
|
},
|
|
542
581
|
{
|
|
543
|
-
title:
|
|
544
|
-
handle:
|
|
545
|
-
sku:
|
|
582
|
+
title: "Restorative Massage Session",
|
|
583
|
+
handle: "restorative-massage-service",
|
|
584
|
+
sku: "SERV-MASSAGE-90",
|
|
546
585
|
description:
|
|
547
|
-
|
|
548
|
-
categorySlug:
|
|
549
|
-
customFieldsetCode:
|
|
550
|
-
variantFieldsetCode:
|
|
551
|
-
unit:
|
|
552
|
-
metadata: { channel:
|
|
586
|
+
"Full-body massage with aromatherapy oils and guided breathing. Includes complimentary refreshments and studio amenities.",
|
|
587
|
+
categorySlug: "services-massage",
|
|
588
|
+
customFieldsetCode: "service_schedule",
|
|
589
|
+
variantFieldsetCode: "service_schedule",
|
|
590
|
+
unit: "hour",
|
|
591
|
+
metadata: { channel: "wellness", serviceType: "massage" },
|
|
553
592
|
customFields: {
|
|
554
593
|
service_duration_minutes: 90,
|
|
555
594
|
service_buffer_minutes: 20,
|
|
556
|
-
service_location:
|
|
557
|
-
service_resources:
|
|
595
|
+
service_location: "Wellness Suite B",
|
|
596
|
+
service_resources: "therapist,treatment_room,steam_room",
|
|
558
597
|
service_remote_available: false,
|
|
559
598
|
},
|
|
560
|
-
media: [{ file:
|
|
599
|
+
media: [{ file: "massage-service.png" }],
|
|
561
600
|
variants: [
|
|
562
601
|
{
|
|
563
|
-
name:
|
|
564
|
-
sku:
|
|
602
|
+
name: "Master Therapist · 90 min",
|
|
603
|
+
sku: "SERV-MASSAGE-90-MASTER",
|
|
565
604
|
isDefault: true,
|
|
566
|
-
optionValues: { therapist:
|
|
605
|
+
optionValues: { therapist: "Master", duration: "90" },
|
|
567
606
|
prices: { regular: 140, sale: 120 },
|
|
568
607
|
customFields: {
|
|
569
|
-
provider_level:
|
|
570
|
-
staff_member:
|
|
571
|
-
environment_type:
|
|
608
|
+
provider_level: "master",
|
|
609
|
+
staff_member: "Noah Li",
|
|
610
|
+
environment_type: "suite",
|
|
572
611
|
},
|
|
573
|
-
media: [{ file:
|
|
612
|
+
media: [{ file: "massage-service.png" }],
|
|
574
613
|
},
|
|
575
614
|
],
|
|
576
615
|
},
|
|
577
|
-
]
|
|
616
|
+
];
|
|
578
617
|
|
|
579
618
|
const CHANNEL_DEFINITION = {
|
|
580
|
-
code:
|
|
581
|
-
name:
|
|
582
|
-
description:
|
|
583
|
-
websiteUrl:
|
|
584
|
-
contactEmail:
|
|
585
|
-
}
|
|
619
|
+
code: "fashion-online",
|
|
620
|
+
name: "Mercato Fashion Online",
|
|
621
|
+
description: "Direct-to-consumer storefront showcasing premium demos.",
|
|
622
|
+
websiteUrl: "https://demo.open-mercato.com",
|
|
623
|
+
contactEmail: "store@open-mercato.com",
|
|
624
|
+
};
|
|
586
625
|
|
|
587
626
|
function formatMoney(value: number): string {
|
|
588
|
-
return value.toFixed(2)
|
|
627
|
+
return value.toFixed(2);
|
|
589
628
|
}
|
|
590
629
|
|
|
591
630
|
async function resolveDefaultTaxRate(
|
|
592
631
|
em: EntityManager,
|
|
593
|
-
scope: SeedScope
|
|
632
|
+
scope: SeedScope,
|
|
594
633
|
): Promise<SalesTaxRate | null> {
|
|
595
634
|
const [rate] = await em.find(
|
|
596
635
|
SalesTaxRate,
|
|
@@ -602,28 +641,28 @@ async function resolveDefaultTaxRate(
|
|
|
602
641
|
{
|
|
603
642
|
limit: 1,
|
|
604
643
|
orderBy: {
|
|
605
|
-
isDefault:
|
|
606
|
-
priority:
|
|
607
|
-
rate:
|
|
608
|
-
createdAt:
|
|
644
|
+
isDefault: "DESC",
|
|
645
|
+
priority: "ASC",
|
|
646
|
+
rate: "DESC",
|
|
647
|
+
createdAt: "ASC",
|
|
609
648
|
},
|
|
610
|
-
}
|
|
611
|
-
)
|
|
612
|
-
return rate ?? null
|
|
649
|
+
},
|
|
650
|
+
);
|
|
651
|
+
return rate ?? null;
|
|
613
652
|
}
|
|
614
653
|
|
|
615
654
|
async function ensureFieldsetConfig(
|
|
616
655
|
em: EntityManager,
|
|
617
656
|
scope: SeedScope,
|
|
618
657
|
entityId: string,
|
|
619
|
-
fieldsets: typeof PRODUCT_FIELDSETS | typeof VARIANT_FIELDSETS
|
|
658
|
+
fieldsets: typeof PRODUCT_FIELDSETS | typeof VARIANT_FIELDSETS,
|
|
620
659
|
): Promise<void> {
|
|
621
|
-
const now = new Date()
|
|
660
|
+
const now = new Date();
|
|
622
661
|
let config = await em.findOne(CustomFieldEntityConfig, {
|
|
623
662
|
entityId,
|
|
624
663
|
organizationId: scope.organizationId,
|
|
625
664
|
tenantId: scope.tenantId,
|
|
626
|
-
})
|
|
665
|
+
});
|
|
627
666
|
if (!config) {
|
|
628
667
|
config = em.create(CustomFieldEntityConfig, {
|
|
629
668
|
id: randomUUID(),
|
|
@@ -633,40 +672,56 @@ async function ensureFieldsetConfig(
|
|
|
633
672
|
isActive: true,
|
|
634
673
|
createdAt: now,
|
|
635
674
|
updatedAt: now,
|
|
636
|
-
})
|
|
675
|
+
});
|
|
637
676
|
}
|
|
638
677
|
config.configJson = {
|
|
639
678
|
fieldsets,
|
|
640
679
|
singleFieldsetPerRecord: true,
|
|
641
|
-
}
|
|
642
|
-
config.isActive = true
|
|
643
|
-
config.updatedAt = now
|
|
644
|
-
em.persist(config)
|
|
680
|
+
};
|
|
681
|
+
config.isActive = true;
|
|
682
|
+
config.updatedAt = now;
|
|
683
|
+
em.persist(config);
|
|
645
684
|
}
|
|
646
685
|
|
|
647
|
-
async function ensureFieldsetsAndDefinitions(
|
|
648
|
-
|
|
649
|
-
|
|
686
|
+
async function ensureFieldsetsAndDefinitions(
|
|
687
|
+
em: EntityManager,
|
|
688
|
+
scope: SeedScope,
|
|
689
|
+
): Promise<void> {
|
|
690
|
+
await ensureFieldsetConfig(
|
|
691
|
+
em,
|
|
692
|
+
scope,
|
|
693
|
+
E.catalog.catalog_product,
|
|
694
|
+
PRODUCT_FIELDSETS,
|
|
695
|
+
);
|
|
696
|
+
await ensureFieldsetConfig(
|
|
697
|
+
em,
|
|
698
|
+
scope,
|
|
699
|
+
E.catalog.catalog_product_variant,
|
|
700
|
+
VARIANT_FIELDSETS,
|
|
701
|
+
);
|
|
650
702
|
await ensureCustomFieldDefinitions(em, CUSTOM_FIELD_SETS, {
|
|
651
703
|
organizationId: scope.organizationId,
|
|
652
704
|
tenantId: scope.tenantId,
|
|
653
|
-
})
|
|
654
|
-
await em.flush()
|
|
705
|
+
});
|
|
706
|
+
await em.flush();
|
|
655
707
|
}
|
|
656
708
|
|
|
657
709
|
async function ensureCategories(
|
|
658
710
|
em: EntityManager,
|
|
659
|
-
scope: SeedScope
|
|
711
|
+
scope: SeedScope,
|
|
660
712
|
): Promise<Map<string, CatalogProductCategory>> {
|
|
661
|
-
const map = new Map<string, CatalogProductCategory>()
|
|
662
|
-
const now = new Date()
|
|
713
|
+
const map = new Map<string, CatalogProductCategory>();
|
|
714
|
+
const now = new Date();
|
|
663
715
|
|
|
664
|
-
const upsert = async (
|
|
716
|
+
const upsert = async (
|
|
717
|
+
seed: CategorySeed,
|
|
718
|
+
parent: CatalogProductCategory | null,
|
|
719
|
+
) => {
|
|
665
720
|
let record = await em.findOne(CatalogProductCategory, {
|
|
666
721
|
tenantId: scope.tenantId,
|
|
667
722
|
organizationId: scope.organizationId,
|
|
668
723
|
slug: seed.slug,
|
|
669
|
-
})
|
|
724
|
+
});
|
|
670
725
|
if (!record) {
|
|
671
726
|
record = em.create(CatalogProductCategory, {
|
|
672
727
|
id: randomUUID(),
|
|
@@ -676,7 +731,7 @@ async function ensureCategories(
|
|
|
676
731
|
slug: seed.slug,
|
|
677
732
|
description: seed.description ?? null,
|
|
678
733
|
parentId: parent ? parent.id : null,
|
|
679
|
-
rootId: parent ? parent.rootId ?? parent.id : null,
|
|
734
|
+
rootId: parent ? (parent.rootId ?? parent.id) : null,
|
|
680
735
|
treePath: null,
|
|
681
736
|
depth: parent ? (parent.depth ?? 0) + 1 : 0,
|
|
682
737
|
ancestorIds: [],
|
|
@@ -686,41 +741,48 @@ async function ensureCategories(
|
|
|
686
741
|
isActive: true,
|
|
687
742
|
createdAt: now,
|
|
688
743
|
updatedAt: now,
|
|
689
|
-
})
|
|
690
|
-
em.persist(record)
|
|
744
|
+
});
|
|
745
|
+
em.persist(record);
|
|
691
746
|
} else {
|
|
692
|
-
record.name = seed.name
|
|
693
|
-
record.description = seed.description ?? null
|
|
694
|
-
record.parentId = parent ? parent.id : null
|
|
695
|
-
record.isActive = true
|
|
696
|
-
record.updatedAt = now
|
|
747
|
+
record.name = seed.name;
|
|
748
|
+
record.description = seed.description ?? null;
|
|
749
|
+
record.parentId = parent ? parent.id : null;
|
|
750
|
+
record.isActive = true;
|
|
751
|
+
record.updatedAt = now;
|
|
697
752
|
}
|
|
698
|
-
map.set(seed.slug, record)
|
|
753
|
+
map.set(seed.slug, record);
|
|
699
754
|
if (Array.isArray(seed.children)) {
|
|
700
755
|
for (const child of seed.children) {
|
|
701
|
-
await upsert(child, record)
|
|
756
|
+
await upsert(child, record);
|
|
702
757
|
}
|
|
703
758
|
}
|
|
704
|
-
}
|
|
759
|
+
};
|
|
705
760
|
|
|
706
761
|
for (const seed of CATEGORY_TREE) {
|
|
707
|
-
await upsert(seed, null)
|
|
762
|
+
await upsert(seed, null);
|
|
708
763
|
}
|
|
709
764
|
|
|
710
|
-
await em.flush()
|
|
711
|
-
await rebuildCategoryHierarchyForOrganization(
|
|
765
|
+
await em.flush();
|
|
766
|
+
await rebuildCategoryHierarchyForOrganization(
|
|
767
|
+
em,
|
|
768
|
+
scope.organizationId,
|
|
769
|
+
scope.tenantId,
|
|
770
|
+
);
|
|
712
771
|
|
|
713
|
-
return map
|
|
772
|
+
return map;
|
|
714
773
|
}
|
|
715
774
|
|
|
716
|
-
async function ensureChannel(
|
|
717
|
-
|
|
775
|
+
async function ensureChannel(
|
|
776
|
+
em: EntityManager,
|
|
777
|
+
scope: SeedScope,
|
|
778
|
+
): Promise<SalesChannel> {
|
|
779
|
+
const now = new Date();
|
|
718
780
|
let channel = await em.findOne(SalesChannel, {
|
|
719
781
|
tenantId: scope.tenantId,
|
|
720
782
|
organizationId: scope.organizationId,
|
|
721
783
|
code: CHANNEL_DEFINITION.code,
|
|
722
784
|
deletedAt: null,
|
|
723
|
-
})
|
|
785
|
+
});
|
|
724
786
|
if (!channel) {
|
|
725
787
|
channel = em.create(SalesChannel, {
|
|
726
788
|
id: randomUUID(),
|
|
@@ -731,72 +793,76 @@ async function ensureChannel(em: EntityManager, scope: SeedScope): Promise<Sales
|
|
|
731
793
|
description: CHANNEL_DEFINITION.description,
|
|
732
794
|
websiteUrl: CHANNEL_DEFINITION.websiteUrl,
|
|
733
795
|
contactEmail: CHANNEL_DEFINITION.contactEmail,
|
|
734
|
-
status:
|
|
796
|
+
status: "active",
|
|
735
797
|
isActive: true,
|
|
736
|
-
metadata: { locale:
|
|
798
|
+
metadata: { locale: "en-US" },
|
|
737
799
|
createdAt: now,
|
|
738
800
|
updatedAt: now,
|
|
739
|
-
})
|
|
740
|
-
em.persist(channel)
|
|
741
|
-
await em.flush()
|
|
801
|
+
});
|
|
802
|
+
em.persist(channel);
|
|
803
|
+
await em.flush();
|
|
742
804
|
}
|
|
743
|
-
return channel
|
|
805
|
+
return channel;
|
|
744
806
|
}
|
|
745
807
|
|
|
746
808
|
async function loadPriceKinds(
|
|
747
809
|
em: EntityManager,
|
|
748
|
-
scope: SeedScope
|
|
810
|
+
scope: SeedScope,
|
|
749
811
|
): Promise<Map<string, CatalogPriceKind>> {
|
|
750
812
|
const kinds = await em.find(CatalogPriceKind, {
|
|
751
813
|
tenantId: scope.tenantId,
|
|
752
|
-
code: { $in: [
|
|
814
|
+
code: { $in: ["regular", "sale"] },
|
|
753
815
|
deletedAt: null,
|
|
754
|
-
})
|
|
755
|
-
const map = new Map<string, CatalogPriceKind>()
|
|
816
|
+
});
|
|
817
|
+
const map = new Map<string, CatalogPriceKind>();
|
|
756
818
|
for (const kind of kinds) {
|
|
757
|
-
map.set(kind.code.toLowerCase(), kind)
|
|
819
|
+
map.set(kind.code.toLowerCase(), kind);
|
|
758
820
|
}
|
|
759
|
-
return map
|
|
821
|
+
return map;
|
|
760
822
|
}
|
|
761
823
|
|
|
762
824
|
export async function seedCatalogExamples(
|
|
763
825
|
em: EntityManager,
|
|
764
826
|
container: AwilixContainer,
|
|
765
|
-
scope: SeedScope
|
|
827
|
+
scope: SeedScope,
|
|
766
828
|
): Promise<boolean> {
|
|
767
|
-
await ensureFieldsetsAndDefinitions(em, scope)
|
|
768
|
-
await ensureDefaultPartitions(em)
|
|
829
|
+
await ensureFieldsetsAndDefinitions(em, scope);
|
|
830
|
+
await ensureDefaultPartitions(em);
|
|
769
831
|
|
|
770
|
-
const handles = PRODUCT_SEEDS.map((seed) => seed.handle)
|
|
832
|
+
const handles = PRODUCT_SEEDS.map((seed) => seed.handle);
|
|
771
833
|
const existingProducts = await em.find(CatalogProduct, {
|
|
772
834
|
tenantId: scope.tenantId,
|
|
773
835
|
organizationId: scope.organizationId,
|
|
774
|
-
handle: { $in: handles
|
|
775
|
-
})
|
|
776
|
-
const existingByHandle = new Map(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
const
|
|
785
|
-
const
|
|
836
|
+
handle: { $in: [...handles] },
|
|
837
|
+
});
|
|
838
|
+
const existingByHandle = new Map(
|
|
839
|
+
existingProducts.map((product) => [product.handle?.toLowerCase(), product]),
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
const categoryMap = await ensureCategories(em, scope);
|
|
843
|
+
const channel = await ensureChannel(em, scope);
|
|
844
|
+
const priceKinds = await loadPriceKinds(em, scope);
|
|
845
|
+
const regularKind = priceKinds.get("regular");
|
|
846
|
+
const saleKind = priceKinds.get("sale");
|
|
847
|
+
const defaultTaxRate = await resolveDefaultTaxRate(em, scope);
|
|
848
|
+
const defaultTaxRateId = defaultTaxRate?.id ?? null;
|
|
849
|
+
const defaultTaxRateValue = defaultTaxRate?.rate ?? null;
|
|
786
850
|
if (!regularKind || !saleKind) {
|
|
787
|
-
throw new Error(
|
|
851
|
+
throw new Error(
|
|
852
|
+
"Missing catalog price kinds; run `mercato catalog seed-price-kinds` first.",
|
|
853
|
+
);
|
|
788
854
|
}
|
|
789
855
|
|
|
790
|
-
const dataEngine = new DefaultDataEngine(em, container)
|
|
791
|
-
const customFieldAssignments: Array<() => Promise<void>> = []
|
|
792
|
-
let createdAny = false
|
|
856
|
+
const dataEngine = new DefaultDataEngine(em, container);
|
|
857
|
+
const customFieldAssignments: Array<() => Promise<void>> = [];
|
|
858
|
+
let createdAny = false;
|
|
793
859
|
|
|
794
860
|
for (const productSeed of PRODUCT_SEEDS) {
|
|
795
|
-
const existing = existingByHandle.get(productSeed.handle.toLowerCase())
|
|
861
|
+
const existing = existingByHandle.get(productSeed.handle.toLowerCase());
|
|
796
862
|
if (existing) {
|
|
797
|
-
continue
|
|
863
|
+
continue;
|
|
798
864
|
}
|
|
799
|
-
createdAny = true
|
|
865
|
+
createdAny = true;
|
|
800
866
|
const product = em.create(CatalogProduct, {
|
|
801
867
|
id: randomUUID(),
|
|
802
868
|
organizationId: scope.organizationId,
|
|
@@ -805,9 +871,9 @@ export async function seedCatalogExamples(
|
|
|
805
871
|
description: productSeed.description,
|
|
806
872
|
sku: productSeed.sku ?? null,
|
|
807
873
|
handle: productSeed.handle,
|
|
808
|
-
productType:
|
|
809
|
-
primaryCurrencyCode:
|
|
810
|
-
defaultUnit: productSeed.unit,
|
|
874
|
+
productType: "configurable",
|
|
875
|
+
primaryCurrencyCode: "USD",
|
|
876
|
+
defaultUnit: canonicalizeUnitCode(productSeed.unit) ?? productSeed.unit,
|
|
811
877
|
customFieldsetCode: productSeed.customFieldsetCode,
|
|
812
878
|
metadata: productSeed.metadata ?? null,
|
|
813
879
|
taxRateId: defaultTaxRateId,
|
|
@@ -816,10 +882,10 @@ export async function seedCatalogExamples(
|
|
|
816
882
|
isActive: true,
|
|
817
883
|
createdAt: new Date(),
|
|
818
884
|
updatedAt: new Date(),
|
|
819
|
-
})
|
|
820
|
-
em.persist(product)
|
|
885
|
+
});
|
|
886
|
+
em.persist(product);
|
|
821
887
|
|
|
822
|
-
const category = categoryMap.get(productSeed.categorySlug)
|
|
888
|
+
const category = categoryMap.get(productSeed.categorySlug);
|
|
823
889
|
if (category) {
|
|
824
890
|
const assignment = em.create(CatalogProductCategoryAssignment, {
|
|
825
891
|
id: randomUUID(),
|
|
@@ -830,8 +896,8 @@ export async function seedCatalogExamples(
|
|
|
830
896
|
position: 0,
|
|
831
897
|
createdAt: new Date(),
|
|
832
898
|
updatedAt: new Date(),
|
|
833
|
-
})
|
|
834
|
-
em.persist(assignment)
|
|
899
|
+
});
|
|
900
|
+
em.persist(assignment);
|
|
835
901
|
}
|
|
836
902
|
|
|
837
903
|
const offer = em.create(CatalogOffer, {
|
|
@@ -841,16 +907,19 @@ export async function seedCatalogExamples(
|
|
|
841
907
|
product,
|
|
842
908
|
channelId: channel.id,
|
|
843
909
|
title: `${productSeed.title} · Online`,
|
|
844
|
-
description:
|
|
910
|
+
description: "Offer curated for the demo storefront channel.",
|
|
845
911
|
metadata: { channelCode: CHANNEL_DEFINITION.code },
|
|
846
912
|
isActive: true,
|
|
847
913
|
createdAt: new Date(),
|
|
848
914
|
updatedAt: new Date(),
|
|
849
|
-
})
|
|
850
|
-
em.persist(offer)
|
|
915
|
+
});
|
|
916
|
+
em.persist(offer);
|
|
851
917
|
|
|
852
|
-
if (
|
|
853
|
-
|
|
918
|
+
if (
|
|
919
|
+
productSeed.customFields &&
|
|
920
|
+
Object.keys(productSeed.customFields).length
|
|
921
|
+
) {
|
|
922
|
+
const payload = { ...productSeed.customFields };
|
|
854
923
|
customFieldAssignments.push(() =>
|
|
855
924
|
dataEngine.setCustomFields({
|
|
856
925
|
entityId: E.catalog.catalog_product,
|
|
@@ -858,8 +927,8 @@ export async function seedCatalogExamples(
|
|
|
858
927
|
organizationId: scope.organizationId,
|
|
859
928
|
tenantId: scope.tenantId,
|
|
860
929
|
values: payload,
|
|
861
|
-
})
|
|
862
|
-
)
|
|
930
|
+
}),
|
|
931
|
+
);
|
|
863
932
|
}
|
|
864
933
|
|
|
865
934
|
const productMedia = await attachMediaFromExamples(
|
|
@@ -867,14 +936,14 @@ export async function seedCatalogExamples(
|
|
|
867
936
|
scope,
|
|
868
937
|
E.catalog.catalog_product,
|
|
869
938
|
product.id,
|
|
870
|
-
productSeed.media
|
|
871
|
-
)
|
|
939
|
+
productSeed.media,
|
|
940
|
+
);
|
|
872
941
|
if (productMedia.length) {
|
|
873
|
-
const hero = productMedia[0]
|
|
874
|
-
product.defaultMediaId = hero.id
|
|
875
|
-
product.defaultMediaUrl = hero.imageUrl
|
|
876
|
-
offer.defaultMediaId = hero.id
|
|
877
|
-
offer.defaultMediaUrl = hero.imageUrl
|
|
942
|
+
const hero = productMedia[0];
|
|
943
|
+
product.defaultMediaId = hero.id;
|
|
944
|
+
product.defaultMediaUrl = hero.imageUrl;
|
|
945
|
+
offer.defaultMediaId = hero.id;
|
|
946
|
+
offer.defaultMediaUrl = hero.imageUrl;
|
|
878
947
|
}
|
|
879
948
|
|
|
880
949
|
for (const variantSeed of productSeed.variants) {
|
|
@@ -894,18 +963,16 @@ export async function seedCatalogExamples(
|
|
|
894
963
|
isActive: true,
|
|
895
964
|
createdAt: new Date(),
|
|
896
965
|
updatedAt: new Date(),
|
|
897
|
-
})
|
|
898
|
-
em.persist(variant)
|
|
966
|
+
});
|
|
967
|
+
em.persist(variant);
|
|
899
968
|
|
|
900
969
|
const variantMedia = await attachMediaFromExamples(
|
|
901
970
|
em,
|
|
902
971
|
scope,
|
|
903
972
|
E.catalog.catalog_product_variant,
|
|
904
973
|
variant.id,
|
|
905
|
-
variantSeed.media
|
|
906
|
-
)
|
|
907
|
-
const variantCover = variantMedia[0] ?? productMedia[0]
|
|
908
|
-
|
|
974
|
+
variantSeed.media,
|
|
975
|
+
);
|
|
909
976
|
const regularPrice = em.create(CatalogProductPrice, {
|
|
910
977
|
id: randomUUID(),
|
|
911
978
|
organizationId: scope.organizationId,
|
|
@@ -914,7 +981,7 @@ export async function seedCatalogExamples(
|
|
|
914
981
|
variant,
|
|
915
982
|
offer,
|
|
916
983
|
priceKind: regularKind,
|
|
917
|
-
currencyCode:
|
|
984
|
+
currencyCode: "USD",
|
|
918
985
|
kind: regularKind.code,
|
|
919
986
|
minQuantity: 1,
|
|
920
987
|
taxRate: defaultTaxRateValue,
|
|
@@ -923,8 +990,8 @@ export async function seedCatalogExamples(
|
|
|
923
990
|
channelId: channel.id,
|
|
924
991
|
createdAt: new Date(),
|
|
925
992
|
updatedAt: new Date(),
|
|
926
|
-
})
|
|
927
|
-
em.persist(regularPrice)
|
|
993
|
+
});
|
|
994
|
+
em.persist(regularPrice);
|
|
928
995
|
|
|
929
996
|
if (variantSeed.prices.sale !== undefined) {
|
|
930
997
|
const salePrice = em.create(CatalogProductPrice, {
|
|
@@ -935,7 +1002,7 @@ export async function seedCatalogExamples(
|
|
|
935
1002
|
variant,
|
|
936
1003
|
offer,
|
|
937
1004
|
priceKind: saleKind,
|
|
938
|
-
currencyCode:
|
|
1005
|
+
currencyCode: "USD",
|
|
939
1006
|
kind: saleKind.code,
|
|
940
1007
|
minQuantity: 1,
|
|
941
1008
|
taxRate: defaultTaxRateValue,
|
|
@@ -944,12 +1011,15 @@ export async function seedCatalogExamples(
|
|
|
944
1011
|
channelId: channel.id,
|
|
945
1012
|
createdAt: new Date(),
|
|
946
1013
|
updatedAt: new Date(),
|
|
947
|
-
})
|
|
948
|
-
em.persist(salePrice)
|
|
1014
|
+
});
|
|
1015
|
+
em.persist(salePrice);
|
|
949
1016
|
}
|
|
950
1017
|
|
|
951
|
-
if (
|
|
952
|
-
|
|
1018
|
+
if (
|
|
1019
|
+
variantSeed.customFields &&
|
|
1020
|
+
Object.keys(variantSeed.customFields).length
|
|
1021
|
+
) {
|
|
1022
|
+
const payload = { ...variantSeed.customFields };
|
|
953
1023
|
customFieldAssignments.push(() =>
|
|
954
1024
|
dataEngine.setCustomFields({
|
|
955
1025
|
entityId: E.catalog.catalog_product_variant,
|
|
@@ -957,25 +1027,28 @@ export async function seedCatalogExamples(
|
|
|
957
1027
|
organizationId: scope.organizationId,
|
|
958
1028
|
tenantId: scope.tenantId,
|
|
959
1029
|
values: payload,
|
|
960
|
-
})
|
|
961
|
-
)
|
|
1030
|
+
}),
|
|
1031
|
+
);
|
|
962
1032
|
}
|
|
963
1033
|
}
|
|
964
1034
|
}
|
|
965
1035
|
|
|
966
1036
|
if (!createdAny) {
|
|
967
|
-
return false
|
|
1037
|
+
return false;
|
|
968
1038
|
}
|
|
969
1039
|
|
|
970
|
-
await em.flush()
|
|
1040
|
+
await em.flush();
|
|
971
1041
|
|
|
972
1042
|
for (const assign of customFieldAssignments) {
|
|
973
1043
|
try {
|
|
974
|
-
await assign()
|
|
1044
|
+
await assign();
|
|
975
1045
|
} catch (err) {
|
|
976
|
-
console.warn(
|
|
1046
|
+
console.warn(
|
|
1047
|
+
"[catalog.seed] Failed to set example custom field values",
|
|
1048
|
+
err,
|
|
1049
|
+
);
|
|
977
1050
|
}
|
|
978
1051
|
}
|
|
979
1052
|
|
|
980
|
-
return true
|
|
1053
|
+
return true;
|
|
981
1054
|
}
|