@endora-commerce/mod-import-export 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/admin/index.d.ts +30 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +36 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/ImportExportPage.d.ts +3 -0
- package/dist/admin/pages/ImportExportPage.d.ts.map +1 -0
- package/dist/admin/pages/ImportExportPage.js +71 -0
- package/dist/admin/pages/ImportExportPage.js.map +1 -0
- package/dist/backend/index.d.ts +43 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +54 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.d.ts +9 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +44 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/adapter.d.ts +59 -0
- package/dist/backend/services/adapter.d.ts.map +1 -0
- package/dist/backend/services/adapter.js +2 -0
- package/dist/backend/services/adapter.js.map +1 -0
- package/dist/backend/services/adapters/categories.adapter.d.ts +19 -0
- package/dist/backend/services/adapters/categories.adapter.d.ts.map +1 -0
- package/dist/backend/services/adapters/categories.adapter.js +93 -0
- package/dist/backend/services/adapters/categories.adapter.js.map +1 -0
- package/dist/backend/services/adapters/customers.adapter.d.ts +16 -0
- package/dist/backend/services/adapters/customers.adapter.d.ts.map +1 -0
- package/dist/backend/services/adapters/customers.adapter.js +48 -0
- package/dist/backend/services/adapters/customers.adapter.js.map +1 -0
- package/dist/backend/services/adapters/orders.adapter.d.ts +13 -0
- package/dist/backend/services/adapters/orders.adapter.d.ts.map +1 -0
- package/dist/backend/services/adapters/orders.adapter.js +48 -0
- package/dist/backend/services/adapters/orders.adapter.js.map +1 -0
- package/dist/backend/services/adapters/products.adapter.d.ts +16 -0
- package/dist/backend/services/adapters/products.adapter.d.ts.map +1 -0
- package/dist/backend/services/adapters/products.adapter.js +88 -0
- package/dist/backend/services/adapters/products.adapter.js.map +1 -0
- package/dist/backend/services/adapters/stock.adapter.d.ts +15 -0
- package/dist/backend/services/adapters/stock.adapter.d.ts.map +1 -0
- package/dist/backend/services/adapters/stock.adapter.js +62 -0
- package/dist/backend/services/adapters/stock.adapter.js.map +1 -0
- package/dist/backend/services/csv-codec.d.ts +17 -0
- package/dist/backend/services/csv-codec.d.ts.map +1 -0
- package/dist/backend/services/csv-codec.js +95 -0
- package/dist/backend/services/csv-codec.js.map +1 -0
- package/dist/backend/services/import-export-service.d.ts +52 -0
- package/dist/backend/services/import-export-service.d.ts.map +1 -0
- package/dist/backend/services/import-export-service.js +100 -0
- package/dist/backend/services/import-export-service.js.map +1 -0
- package/dist/manifest.d.ts +169 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +138 -0
- package/dist/manifest.js.map +1 -0
- package/docs/import_export.md +79 -0
- package/i18n/en.json +27 -0
- package/i18n/pl.json +27 -0
- package/package.json +82 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import / Export module — bulk product import/export wizards.
|
|
3
|
+
*
|
|
4
|
+
* Manifest backfilled alongside feature 020 so the module can declare
|
|
5
|
+
* command-palette actions. Same legacy-module note as catalog: the
|
|
6
|
+
* underlying behaviour is foundational; hard-uninstall is intentionally
|
|
7
|
+
* unsupported in this iteration.
|
|
8
|
+
*/
|
|
9
|
+
export declare const manifest: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
dependencies: string[];
|
|
14
|
+
description?: string | undefined;
|
|
15
|
+
acknowledgedDependencies?: {
|
|
16
|
+
moduleId: string;
|
|
17
|
+
port: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}[] | undefined;
|
|
20
|
+
nonBindingDependencies?: {
|
|
21
|
+
moduleId: string;
|
|
22
|
+
name: string;
|
|
23
|
+
kind: "contributes-to" | "degrades-without" | "refuses-without";
|
|
24
|
+
reason: string;
|
|
25
|
+
whenAbsent?: string | undefined;
|
|
26
|
+
}[] | undefined;
|
|
27
|
+
activation?: {
|
|
28
|
+
settingCode: string;
|
|
29
|
+
default: boolean;
|
|
30
|
+
} | {
|
|
31
|
+
nonDeactivatable: true;
|
|
32
|
+
reason: string;
|
|
33
|
+
} | undefined;
|
|
34
|
+
settings?: {
|
|
35
|
+
moduleCode: string;
|
|
36
|
+
groups: {
|
|
37
|
+
code: string;
|
|
38
|
+
name: string;
|
|
39
|
+
salesChannelCodes?: string[] | undefined;
|
|
40
|
+
isSystemProtected?: boolean | undefined;
|
|
41
|
+
}[];
|
|
42
|
+
settings: {
|
|
43
|
+
code: string;
|
|
44
|
+
name: string;
|
|
45
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
46
|
+
defaultValue: unknown;
|
|
47
|
+
description?: string | undefined;
|
|
48
|
+
groupCode?: string | undefined;
|
|
49
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
50
|
+
salesChannelCodes?: string[] | undefined;
|
|
51
|
+
enumOptions?: string[] | undefined;
|
|
52
|
+
configurationType?: string | undefined;
|
|
53
|
+
hidden?: boolean | undefined;
|
|
54
|
+
}[];
|
|
55
|
+
} | undefined;
|
|
56
|
+
i18n?: {
|
|
57
|
+
bundlesDir: string;
|
|
58
|
+
} | undefined;
|
|
59
|
+
docs?: false | {
|
|
60
|
+
dir: string;
|
|
61
|
+
} | undefined;
|
|
62
|
+
demo?: false | {
|
|
63
|
+
summary: string;
|
|
64
|
+
seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
|
|
65
|
+
reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
|
|
66
|
+
after?: readonly string[] | undefined;
|
|
67
|
+
package?: string | undefined;
|
|
68
|
+
} | undefined;
|
|
69
|
+
actions?: {
|
|
70
|
+
id: string;
|
|
71
|
+
labelKey: string;
|
|
72
|
+
icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
|
|
73
|
+
targetRoute: string;
|
|
74
|
+
keywords: string[];
|
|
75
|
+
weight: number;
|
|
76
|
+
descriptionKey?: string | undefined;
|
|
77
|
+
requiredPermission?: string | undefined;
|
|
78
|
+
}[] | undefined;
|
|
79
|
+
permissions?: {
|
|
80
|
+
code: string;
|
|
81
|
+
label: string;
|
|
82
|
+
module?: string | undefined;
|
|
83
|
+
description?: string | undefined;
|
|
84
|
+
requires?: string[] | undefined;
|
|
85
|
+
}[] | undefined;
|
|
86
|
+
transactionalEmails?: {
|
|
87
|
+
code: string;
|
|
88
|
+
name: string;
|
|
89
|
+
variables: {
|
|
90
|
+
key: string;
|
|
91
|
+
label: string;
|
|
92
|
+
sampleValue?: string | undefined;
|
|
93
|
+
description?: string | undefined;
|
|
94
|
+
}[];
|
|
95
|
+
description?: string | undefined;
|
|
96
|
+
group?: string | undefined;
|
|
97
|
+
}[] | undefined;
|
|
98
|
+
capabilities?: string[] | undefined;
|
|
99
|
+
exclusiveCapabilities?: {
|
|
100
|
+
key: string;
|
|
101
|
+
errorCode: string;
|
|
102
|
+
}[] | undefined;
|
|
103
|
+
errorCodes?: {
|
|
104
|
+
code: string;
|
|
105
|
+
tokens?: string[] | undefined;
|
|
106
|
+
}[] | undefined;
|
|
107
|
+
blocks?: {
|
|
108
|
+
name: string;
|
|
109
|
+
labelKey: string;
|
|
110
|
+
category: string;
|
|
111
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
112
|
+
fields: Record<string, {
|
|
113
|
+
type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
|
|
114
|
+
label?: string | undefined;
|
|
115
|
+
required?: boolean | undefined;
|
|
116
|
+
options?: {
|
|
117
|
+
label: string;
|
|
118
|
+
value: string | number;
|
|
119
|
+
}[] | undefined;
|
|
120
|
+
refKind?: string | undefined;
|
|
121
|
+
}>;
|
|
122
|
+
descriptionKey?: string | undefined;
|
|
123
|
+
defaultProps?: Record<string, unknown> | undefined;
|
|
124
|
+
responsiveFields?: string[] | undefined;
|
|
125
|
+
previewIcon?: string | undefined;
|
|
126
|
+
weight?: number | undefined;
|
|
127
|
+
}[] | undefined;
|
|
128
|
+
blockCategories?: {
|
|
129
|
+
key: string;
|
|
130
|
+
titleKey: string;
|
|
131
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
132
|
+
weight?: number | undefined;
|
|
133
|
+
visible?: boolean | undefined;
|
|
134
|
+
}[] | undefined;
|
|
135
|
+
env?: {
|
|
136
|
+
name: string;
|
|
137
|
+
describes: {
|
|
138
|
+
en: string;
|
|
139
|
+
pl: string;
|
|
140
|
+
};
|
|
141
|
+
requirement: {
|
|
142
|
+
kind: "required";
|
|
143
|
+
} | {
|
|
144
|
+
kind: "requiredWhen";
|
|
145
|
+
input: string;
|
|
146
|
+
equals: string;
|
|
147
|
+
} | {
|
|
148
|
+
kind: "optional";
|
|
149
|
+
without: {
|
|
150
|
+
en: string;
|
|
151
|
+
pl: string;
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
secret: boolean;
|
|
155
|
+
generable: boolean;
|
|
156
|
+
owner: {
|
|
157
|
+
kind: "platform";
|
|
158
|
+
} | {
|
|
159
|
+
kind: "application";
|
|
160
|
+
application: "admin" | "backend" | "storefront";
|
|
161
|
+
} | {
|
|
162
|
+
kind: "module";
|
|
163
|
+
moduleId: string;
|
|
164
|
+
};
|
|
165
|
+
consumers: ("admin" | "backend" | "storefront")[];
|
|
166
|
+
addressOf: "admin" | "backend" | "storefront" | null;
|
|
167
|
+
}[] | undefined;
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgJnB,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { defineModuleManifest } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Import / Export module — bulk product import/export wizards.
|
|
4
|
+
*
|
|
5
|
+
* Manifest backfilled alongside feature 020 so the module can declare
|
|
6
|
+
* command-palette actions. Same legacy-module note as catalog: the
|
|
7
|
+
* underlying behaviour is foundational; hard-uninstall is intentionally
|
|
8
|
+
* unsupported in this iteration.
|
|
9
|
+
*/
|
|
10
|
+
export const manifest = defineModuleManifest({
|
|
11
|
+
id: 'import_export',
|
|
12
|
+
name: 'Import / Export',
|
|
13
|
+
description: 'Bulk product import and export wizard.',
|
|
14
|
+
version: '1.0.0',
|
|
15
|
+
// `auth` owns the `requireAdmin` port the routes are gated by; feature 072
|
|
16
|
+
// made it a container resolution rather than a constructor argument. It is
|
|
17
|
+
// the only **binding** dependency: every route is admin-gated, so an
|
|
18
|
+
// import/export centre without `auth` is a centre with no door.
|
|
19
|
+
//
|
|
20
|
+
// `catalog` left this list in D-74, and the direction is the point. It used
|
|
21
|
+
// to be here while `inventory` — whose `stock_levels` this module wrote —
|
|
22
|
+
// was not, an edge no check in the tree could see. Both are declared now,
|
|
23
|
+
// and neither binds: an operator must be able to switch `orders` off without
|
|
24
|
+
// being told an import module forbids it.
|
|
25
|
+
dependencies: ['auth'],
|
|
26
|
+
/**
|
|
27
|
+
* D-74 — the five owners whose rows this module reads and writes, declared as
|
|
28
|
+
* real container edges that bind no operator.
|
|
29
|
+
*
|
|
30
|
+
* `degrades-without` obliges the module to **check presence before it reads**,
|
|
31
|
+
* and it does: the adapter table carries the owning module ids per entity and
|
|
32
|
+
* the supported-entity list is filtered by `effectiveState.isPresent`, so a
|
|
33
|
+
* switched-off owner removes one entity from
|
|
34
|
+
* `GET /api/v1/admin/import-export/entities` and makes a POST naming it the
|
|
35
|
+
* same 404 an unknown slug gets. That is what makes these declarations true
|
|
36
|
+
* rather than decorative — before it, the five slugs were a literal array in
|
|
37
|
+
* the admin SPA.
|
|
38
|
+
*/
|
|
39
|
+
nonBindingDependencies: [
|
|
40
|
+
{
|
|
41
|
+
moduleId: 'catalog',
|
|
42
|
+
name: 'catalogProductReadPort',
|
|
43
|
+
kind: 'degrades-without',
|
|
44
|
+
whenAbsent: 'The import/export centre stops offering the Products and Stock entities; every other entity is unaffected.',
|
|
45
|
+
reason: 'The products export reads the catalogue through `catalog`\'s published read port. An operator switching the catalogue off should lose the products sheet, not the orders and customers sheets beside it.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
moduleId: 'catalog',
|
|
49
|
+
name: 'catalogCategoryReadPort',
|
|
50
|
+
kind: 'degrades-without',
|
|
51
|
+
whenAbsent: 'The import/export centre stops offering the Categories entity; every other entity is unaffected.',
|
|
52
|
+
reason: 'The categories export reads the tree through `catalog`\'s published read port; the entity is withdrawn from the offered list rather than answering 503.',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
moduleId: 'catalog',
|
|
56
|
+
name: 'catalogBulkImportPort',
|
|
57
|
+
kind: 'degrades-without',
|
|
58
|
+
whenAbsent: 'The import/export centre stops offering the Products and Categories imports; every other entity is unaffected.',
|
|
59
|
+
reason: 'A catalogue import is `catalog`\'s transaction and `catalog`\'s audit row (D-74). With the module off the entity is not offered, so the operator never reaches the gate.',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
moduleId: 'inventory',
|
|
63
|
+
name: 'inventoryStockReadPort',
|
|
64
|
+
kind: 'degrades-without',
|
|
65
|
+
whenAbsent: 'The import/export centre stops offering the Stock entity; every other entity is unaffected.',
|
|
66
|
+
reason: 'The stock export joins `inventory`\'s levels onto `catalog`\'s SKUs. This module wrote `stock_levels` for a year without declaring the edge at all; declaring it non-bindingly is what keeps the centre usable while stock management is off.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
moduleId: 'inventory',
|
|
70
|
+
name: 'inventoryStockImportPort',
|
|
71
|
+
kind: 'degrades-without',
|
|
72
|
+
whenAbsent: 'The import/export centre stops offering the Stock import; every other entity is unaffected.',
|
|
73
|
+
reason: 'A stock import is `inventory`\'s transaction and `inventory`\'s audit row (D-74), including the seeded warehouse this module used to name by a copied UUID.',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
moduleId: 'orders',
|
|
77
|
+
name: 'orderReadPort',
|
|
78
|
+
kind: 'degrades-without',
|
|
79
|
+
whenAbsent: 'The import/export centre stops offering the Orders export; every other entity is unaffected.',
|
|
80
|
+
reason: 'Export only — orders are produced by the checkout flow. `OrderReadPort.listAll` was published by Phase P with "for the bulk export adapter" in its own doc comment; this is that adapter.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
moduleId: 'customer_accounts',
|
|
84
|
+
name: 'customerAccountReadPort',
|
|
85
|
+
kind: 'degrades-without',
|
|
86
|
+
whenAbsent: 'The import/export centre stops offering the Customers export; every other entity is unaffected.',
|
|
87
|
+
reason: 'Export only — provisioning accounts needs a password story a CSV upload has no place for. `CustomerAccountReadPort.listAll` was published for this adapter by name.',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
moduleId: 'organizations',
|
|
91
|
+
name: 'organizationDetailsPort',
|
|
92
|
+
kind: 'degrades-without',
|
|
93
|
+
whenAbsent: 'The import/export centre stops offering the Customers export; every other entity is unaffected.',
|
|
94
|
+
reason: 'The customers sheet names each account\'s organisation, so that one entity needs both owners present. No other entity reads this port.',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
settings: {
|
|
98
|
+
moduleCode: 'import_export',
|
|
99
|
+
groups: [{ code: 'import_export', name: 'Import / export' }],
|
|
100
|
+
settings: [
|
|
101
|
+
{
|
|
102
|
+
// Feature 073 — the operator's activation control. Platform-wide.
|
|
103
|
+
code: 'import_export.enabled',
|
|
104
|
+
name: 'Import / export enabled',
|
|
105
|
+
description: 'Switches the CSV import and export endpoints on or off. The module holds no data of its own, so nothing is dropped — the catalog it reads and writes is untouched either way.',
|
|
106
|
+
groupCode: 'import_export',
|
|
107
|
+
valueType: 'boolean',
|
|
108
|
+
defaultValue: true,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
i18n: { bundlesDir: 'i18n' },
|
|
113
|
+
docs: { dir: 'docs' },
|
|
114
|
+
actions: [
|
|
115
|
+
{
|
|
116
|
+
id: 'import-products',
|
|
117
|
+
labelKey: 'actions.importProducts.label',
|
|
118
|
+
descriptionKey: 'actions.importProducts.description',
|
|
119
|
+
icon: 'Upload',
|
|
120
|
+
targetRoute: '/import-export',
|
|
121
|
+
requiredPermission: 'catalog:write',
|
|
122
|
+
keywords: ['import', 'csv', 'excel', 'upload', 'wgraj'],
|
|
123
|
+
weight: 110,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'open-import-export-center',
|
|
127
|
+
labelKey: 'actions.openCenter.label',
|
|
128
|
+
descriptionKey: 'actions.openCenter.description',
|
|
129
|
+
icon: 'FileUp',
|
|
130
|
+
targetRoute: '/import-export',
|
|
131
|
+
requiredPermission: 'catalog:write',
|
|
132
|
+
keywords: ['import', 'export', 'center', 'centrum'],
|
|
133
|
+
weight: 220,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
activation: { settingCode: 'import_export.enabled', default: true },
|
|
137
|
+
});
|
|
138
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: import_export
|
|
3
|
+
description: CSV import / export for bulk-edit entities
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `import_export`
|
|
7
|
+
|
|
8
|
+
CSV import and export for the bulk-edit cases. Owns a small RFC-4180 codec
|
|
9
|
+
(no new dependency) and a per-entity adapter registry.
|
|
10
|
+
|
|
11
|
+
## Public surface
|
|
12
|
+
|
|
13
|
+
| Verb + Path | Audience | Purpose |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `GET /api/v1/admin/export/:entity.csv` | admin (`catalog:write`) | Streaming CSV download with attachment headers |
|
|
16
|
+
| `POST /api/v1/admin/import/:entity` | admin (`catalog:write`) | Apply a `text/csv` body inside a single transaction |
|
|
17
|
+
|
|
18
|
+
## Supported entities
|
|
19
|
+
|
|
20
|
+
| Entity | Export | Import | Notes |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| `products` | ✓ | ✓ | Multilingual `name` / `description` collapse to a single locale (`en-US`); product `type` is immutable post-create |
|
|
23
|
+
| `categories` | ✓ | ✓ | Parents resolved by `parent_slug`; rows must list parents before children |
|
|
24
|
+
| `stock` | ✓ | ✓ | Operators only edit `on_hand`; reservations are read-only |
|
|
25
|
+
| `customers` | ✓ | — | Import is intentionally unsupported — bulk account provisioning needs a password story that doesn't fit a CSV upload |
|
|
26
|
+
| `orders` | ✓ | — | Orders are produced by the checkout flow; importing historical orders would bypass stock, payment, and credit-limit lifecycles |
|
|
27
|
+
|
|
28
|
+
## Import semantics
|
|
29
|
+
|
|
30
|
+
Every import runs inside one MikroORM transaction. If any single row
|
|
31
|
+
fails validation, the entire batch rolls back and the response surfaces a
|
|
32
|
+
row-numbered error report (1-based, excluding the header line, matching
|
|
33
|
+
what spreadsheet apps display). This makes re-uploads after a fix
|
|
34
|
+
deterministic.
|
|
35
|
+
|
|
36
|
+
```http
|
|
37
|
+
POST /api/v1/admin/import/products
|
|
38
|
+
Content-Type: text/csv
|
|
39
|
+
|
|
40
|
+
sku,status,visibility,name_en,description_en
|
|
41
|
+
SKU-001,active,public,Updated name,Updated description.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Response:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"data": {
|
|
49
|
+
"imported": 1,
|
|
50
|
+
"errors": []
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If a row fails (e.g. unknown SKU):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"data": {
|
|
60
|
+
"imported": 0,
|
|
61
|
+
"errors": [
|
|
62
|
+
{ "rowNumber": 3, "reason": "unknown sku: SKU-XYZ" }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Extension points
|
|
69
|
+
|
|
70
|
+
- **New entity** — implement `ImportExportAdapter` (`name`,
|
|
71
|
+
`exportHeader`, `exportRows`, optional `importHeader` + `importRow`)
|
|
72
|
+
and register in `import-export-service.ts`.
|
|
73
|
+
- **Different delimiter / Excel quirks** — extend `csv-codec.ts`. RFC
|
|
74
|
+
4180 covers the common cases; if a real-world file fails, the codec is
|
|
75
|
+
the single place to evolve.
|
|
76
|
+
- **Streaming export** — the in-memory loader fits hundreds of thousands
|
|
77
|
+
of rows comfortably; for sustained million-row exports, swap
|
|
78
|
+
`serializeCsv` for an async iterator that pipes into Fastify's
|
|
79
|
+
`reply.raw`.
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"actions.importProducts.description": "Upload products from a CSV or Excel file",
|
|
3
|
+
"actions.importProducts.label": "Import products",
|
|
4
|
+
"actions.openCenter.description": "Browse all import and export operations",
|
|
5
|
+
"actions.openCenter.label": "Open import/export center",
|
|
6
|
+
"column.reason": "Reason",
|
|
7
|
+
"column.rowNumber": "Row #",
|
|
8
|
+
"empty": "No entity is available right now — every module that owns one is switched off.",
|
|
9
|
+
"entity.categories": "Categories",
|
|
10
|
+
"entity.customers": "Customers",
|
|
11
|
+
"entity.orders": "Orders",
|
|
12
|
+
"entity.products": "Products",
|
|
13
|
+
"entity.stock": "Stock levels",
|
|
14
|
+
"error.import": "Import failed.",
|
|
15
|
+
"error.load": "Could not load the available entities.",
|
|
16
|
+
"exportCsv": "Export CSV",
|
|
17
|
+
"importCsv": "Import CSV",
|
|
18
|
+
"importNotSupported": "Import not supported — see the docs site for the rationale.",
|
|
19
|
+
"imported": "Imported {count} row(s).",
|
|
20
|
+
"loading": "Loading available entities…",
|
|
21
|
+
"nav.importExport.label": "Import / Export",
|
|
22
|
+
"page.description": "CSV-only round-trips for the bulk-edit cases. Per-row errors roll the entire upload back, so re-uploads are deterministic.",
|
|
23
|
+
"page.title": "Import / Export",
|
|
24
|
+
"requiredHeader": "Required header:",
|
|
25
|
+
"rolledBack": "Rolled back: {count} row(s) failed. No changes applied.",
|
|
26
|
+
"uploading": "Uploading…"
|
|
27
|
+
}
|
package/i18n/pl.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"actions.importProducts.description": "Wgraj produkty z pliku CSV lub Excel",
|
|
3
|
+
"actions.importProducts.label": "Importuj produkty",
|
|
4
|
+
"actions.openCenter.description": "Przeglądaj wszystkie operacje importu i eksportu",
|
|
5
|
+
"actions.openCenter.label": "Otwórz centrum importu/eksportu",
|
|
6
|
+
"column.reason": "Powód",
|
|
7
|
+
"column.rowNumber": "Nr wiersza",
|
|
8
|
+
"empty": "Brak dostępnych encji — każdy moduł, który jakąś posiada, jest wyłączony.",
|
|
9
|
+
"entity.categories": "Kategorie",
|
|
10
|
+
"entity.customers": "Klienci",
|
|
11
|
+
"entity.orders": "Zamówienia",
|
|
12
|
+
"entity.products": "Produkty",
|
|
13
|
+
"entity.stock": "Stany magazynowe",
|
|
14
|
+
"error.import": "Import nie powiódł się.",
|
|
15
|
+
"error.load": "Nie udało się wczytać dostępnych encji.",
|
|
16
|
+
"exportCsv": "Eksportuj CSV",
|
|
17
|
+
"importCsv": "Importuj CSV",
|
|
18
|
+
"importNotSupported": "Import nieobsługiwany — rationale znajdziesz w dokumentacji.",
|
|
19
|
+
"imported": "Zaimportowano {count} wierszy.",
|
|
20
|
+
"loading": "Wczytywanie dostępnych encji…",
|
|
21
|
+
"nav.importExport.label": "Import / Eksport",
|
|
22
|
+
"page.description": "Wymiana danych w obie strony tylko przez CSV dla operacji masowej edycji. Błędy per-wiersz wycofują cały upload, więc ponowne wgrania są deterministyczne.",
|
|
23
|
+
"page.title": "Import / Eksport",
|
|
24
|
+
"requiredHeader": "Wymagany nagłówek:",
|
|
25
|
+
"rolledBack": "Wycofano: {count} wierszy nie powiodło się. Brak zmian.",
|
|
26
|
+
"uploading": "Wgrywanie…"
|
|
27
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endora-commerce/mod-import-export",
|
|
3
|
+
"version": "0.100.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Bulk product import and export wizard.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"endora": {
|
|
9
|
+
"type": "module",
|
|
10
|
+
"id": "import_export"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/endora-commerce/endora-commerce.git",
|
|
15
|
+
"directory": "packages/modules/import_export"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/manifest.d.ts",
|
|
23
|
+
"default": "./dist/manifest.js"
|
|
24
|
+
},
|
|
25
|
+
"./backend": {
|
|
26
|
+
"types": "./dist/backend/index.d.ts",
|
|
27
|
+
"default": "./dist/backend/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./admin": {
|
|
30
|
+
"types": "./dist/admin/index.d.ts",
|
|
31
|
+
"default": "./dist/admin/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./tailwind.css": "./tailwind.css",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"i18n",
|
|
39
|
+
"docs",
|
|
40
|
+
"tailwind.css"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22.18.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"fastify": "^5",
|
|
47
|
+
"lucide-react": "^1",
|
|
48
|
+
"react": "^19",
|
|
49
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
50
|
+
"@endora-commerce/platform": "0.100.0",
|
|
51
|
+
"@endora-commerce/contracts": "0.100.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@endora-commerce/admin-kit": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"lucide-react": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"react": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^22.9.0",
|
|
66
|
+
"@types/react": "^19.2.14",
|
|
67
|
+
"fastify": "^5.12.5",
|
|
68
|
+
"lucide-react": "^1.11.0",
|
|
69
|
+
"react": "^19.2.5",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"vitest": "^4.1.11",
|
|
72
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
73
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
74
|
+
"@endora-commerce/platform": "0.100.0"
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
|
|
78
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
|
|
79
|
+
"lint": "eslint src",
|
|
80
|
+
"test": "vitest run"
|
|
81
|
+
}
|
|
82
|
+
}
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @endora-commerce/mod-import-export — AUTO-GENERATED by `pnpm --filter backend run manifests:generate`.
|
|
2
|
+
*
|
|
3
|
+
* The `@source` directives this package asks its host to scan
|
|
4
|
+
* (`specs/110-instance-repository/contracts/admin-stylesheet-composition.md` R1).
|
|
5
|
+
* They resolve relative to **this file**, so they hold wherever the package is
|
|
6
|
+
* installed — a workspace link here, `node_modules` in a client's instance.
|
|
7
|
+
*
|
|
8
|
+
* The `dist` line is what a published tarball ships and is what an instance
|
|
9
|
+
* scans; the `src` line is inert there and is what keeps `pnpm --filter admin
|
|
10
|
+
* run dev` reading source in this repository. Do not edit: run
|
|
11
|
+
* `pnpm --filter backend run manifests:generate`.
|
|
12
|
+
*/
|
|
13
|
+
@source "./dist/admin";
|
|
14
|
+
@source "./src/admin";
|