@legenki/print2medusa 0.8.2 → 0.9.2
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/.medusa/server/src/admin/index.js +158 -2
- package/.medusa/server/src/admin/index.mjs +159 -3
- package/.medusa/server/src/api/admin/printful/bundles/route.js +79 -0
- package/.medusa/server/src/api/admin/printful/design/route.js +38 -0
- package/.medusa/server/src/api/admin/printful/prompts/route.js +55 -0
- package/.medusa/server/src/utils/bundle.js +243 -0
- package/.medusa/server/src/utils/design.js +438 -0
- package/.medusa/server/src/utils/printful-client.js +23 -1
- package/.medusa/server/src/utils/prompt.js +423 -0
- package/.medusa/server/src/workflows/apply-order-status.js +16 -5
- package/.medusa/server/src/workflows/create-printful-order.js +20 -3
- package/.medusa/server/src/workflows/sync-products.js +68 -1
- package/README.md +91 -0
- package/package.json +1 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Merch bundles: one Medusa product that stands for several Printful ones.
|
|
4
|
+
*
|
|
5
|
+
* Printful has no notion of a bundle — it fulfils individual items. So a
|
|
6
|
+
* bundle is a Medusa product whose variant records which member variants it
|
|
7
|
+
* contains, and order creation expands it back into those members.
|
|
8
|
+
*
|
|
9
|
+
* **Why expansion is mandatory rather than a nicety.** `create-printful-order`
|
|
10
|
+
* resolves one order line to one Printful item via `item.variant_id`. A bundle
|
|
11
|
+
* left unexpanded therefore ships a single thing where the customer bought
|
|
12
|
+
* three — or, if the bundle variant has no Printful link at all, the order is
|
|
13
|
+
* skipped as `no_printful_items` and nothing ships.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.BUNDLE_MEMBERS_KEY = void 0;
|
|
17
|
+
exports.bundleMembersOf = bundleMembersOf;
|
|
18
|
+
exports.isBundleLine = isBundleLine;
|
|
19
|
+
exports.expandOrderLines = expandOrderLines;
|
|
20
|
+
exports.medusaLineIdFor = medusaLineIdFor;
|
|
21
|
+
exports.planBundlePass = planBundlePass;
|
|
22
|
+
exports.planBundlePublication = planBundlePublication;
|
|
23
|
+
exports.planBundleAvailability = planBundleAvailability;
|
|
24
|
+
const stock_1 = require("./stock");
|
|
25
|
+
/** Statuses meaning a variant cannot be ordered. Mirrors `stock.ts`. */
|
|
26
|
+
const UNAVAILABLE = new Set([
|
|
27
|
+
"out_of_stock",
|
|
28
|
+
"temporary_out_of_stock",
|
|
29
|
+
"discontinued",
|
|
30
|
+
]);
|
|
31
|
+
/** Where a bundle's composition lives, on the variant and on the order line. */
|
|
32
|
+
exports.BUNDLE_MEMBERS_KEY = "printful_bundle_members";
|
|
33
|
+
/**
|
|
34
|
+
* The members recorded on a line, or nothing.
|
|
35
|
+
*
|
|
36
|
+
* Metadata is free-form JSON, so this validates rather than trusts: something
|
|
37
|
+
* else writing a string to this key must not crash order creation.
|
|
38
|
+
*/
|
|
39
|
+
function bundleMembersOf(line) {
|
|
40
|
+
const raw = (line?.metadata ?? {})[exports.BUNDLE_MEMBERS_KEY];
|
|
41
|
+
if (!Array.isArray(raw)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const members = [];
|
|
45
|
+
for (const entry of raw) {
|
|
46
|
+
if (!entry || typeof entry !== "object") {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const variantId = entry.variant_id;
|
|
50
|
+
if (typeof variantId !== "string" || !variantId.trim()) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const quantity = Number(entry.quantity ?? 1);
|
|
54
|
+
members.push({
|
|
55
|
+
variant_id: variantId,
|
|
56
|
+
quantity: Number.isFinite(quantity) && quantity > 0 ? quantity : 1,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return members;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether a line stands for several products.
|
|
63
|
+
*
|
|
64
|
+
* A line whose member list is empty is *not* a bundle. Treating it as one
|
|
65
|
+
* would expand it to nothing, and an order of nothing looks identical to an
|
|
66
|
+
* order Printful was never asked about. Left alone, it flows through the
|
|
67
|
+
* ordinary unresolved-variant path, which reports it.
|
|
68
|
+
*/
|
|
69
|
+
function isBundleLine(line) {
|
|
70
|
+
return bundleMembersOf(line).length > 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Replace every bundle line with the lines it stands for.
|
|
74
|
+
*
|
|
75
|
+
* Composition is read from the line's own metadata, captured when the customer
|
|
76
|
+
* bought — never looked up live. A merchant who edits a bundle after a sale
|
|
77
|
+
* must not change what an already-placed order ships.
|
|
78
|
+
*
|
|
79
|
+
* Ordinary lines pass through untouched and keep their position, so a cart
|
|
80
|
+
* mixing bundles and singles reaches Printful in the order it was built.
|
|
81
|
+
*/
|
|
82
|
+
function expandOrderLines(lines) {
|
|
83
|
+
const out = [];
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
const members = bundleMembersOf(line);
|
|
86
|
+
if (members.length === 0) {
|
|
87
|
+
out.push(line);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
for (const member of members) {
|
|
91
|
+
out.push({
|
|
92
|
+
// Synthesised, but stable and traceable: two bundles in one cart
|
|
93
|
+
// cannot collide, and the id says where the line came from.
|
|
94
|
+
id: `${line.id}::${member.variant_id}`,
|
|
95
|
+
variant_id: member.variant_id,
|
|
96
|
+
title: line.title,
|
|
97
|
+
// Two bundles each holding two stickers is four stickers. Dropping
|
|
98
|
+
// this multiplication ships a customer half their order.
|
|
99
|
+
quantity: (member.quantity ?? 1) * Number(line.quantity || 1),
|
|
100
|
+
metadata: {},
|
|
101
|
+
bundle_line_id: line.id,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/** Separates the bundle line id from the member variant id in a synthetic id. */
|
|
108
|
+
const SYNTHETIC_SEPARATOR = "::";
|
|
109
|
+
/**
|
|
110
|
+
* The Medusa line an `external_id` from Printful refers to.
|
|
111
|
+
*
|
|
112
|
+
* Expansion sends synthetic ids like `ordli_abc::variant_tee` as `external_id`,
|
|
113
|
+
* because Printful needs each item distinguishable and two members of one
|
|
114
|
+
* bundle would otherwise share the bundle line's id.
|
|
115
|
+
*
|
|
116
|
+
* Fulfillment reads those ids back to decide which Medusa line a parcel filled.
|
|
117
|
+
* Without this, a bundle's items resolve to an id no Medusa line has, every
|
|
118
|
+
* parcel clamps to zero open quantity, and the order is never marked shipped.
|
|
119
|
+
*
|
|
120
|
+
* A member id maps back to the *bundle* line, since that is the line the
|
|
121
|
+
* customer bought and the only one Medusa can fulfil.
|
|
122
|
+
*/
|
|
123
|
+
function medusaLineIdFor(externalId) {
|
|
124
|
+
const at = externalId.indexOf(SYNTHETIC_SEPARATOR);
|
|
125
|
+
return at === -1 ? externalId : externalId.slice(0, at);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Which bundle products the sync should rewrite, and to what.
|
|
129
|
+
*
|
|
130
|
+
* Bundles are invisible to the ordinary sync: they have no Printful product of
|
|
131
|
+
* their own, so no `sync_product` ever names them and the per-product loop
|
|
132
|
+
* never reaches them. Without this pass a bundle stays on sale after a member
|
|
133
|
+
* sells out, and every such order fails at Printful.
|
|
134
|
+
*
|
|
135
|
+
* Availability is read from the member variants passed in, not from the bundle
|
|
136
|
+
* — the bundle variant has no Printful status of its own to read.
|
|
137
|
+
*
|
|
138
|
+
* Returns only products that need writing, so a sync over a catalogue with no
|
|
139
|
+
* bundle changes issues no updates at all.
|
|
140
|
+
*/
|
|
141
|
+
function planBundlePass(input) {
|
|
142
|
+
const writes = [];
|
|
143
|
+
for (const bundle of input.bundles) {
|
|
144
|
+
const memberIds = (bundle.variants ?? []).flatMap((v) => bundleMembersOf({ id: v.id, quantity: 1, metadata: v.metadata }).map((m) => m.variant_id));
|
|
145
|
+
if (memberIds.length === 0) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// A member variant the sync could not load is left out of the decision
|
|
149
|
+
// rather than counted as sold out: a failed lookup is not evidence that
|
|
150
|
+
// Printful ran out of anything, and drafting on it would hide a live
|
|
151
|
+
// bundle on a transient error.
|
|
152
|
+
const known = memberIds
|
|
153
|
+
.map((id) => input.variantsById.get(id))
|
|
154
|
+
.filter((v) => !!v);
|
|
155
|
+
// Knowing nothing about any member is not the same as knowing they are all
|
|
156
|
+
// in stock. Left to fall through, a bundle drafted for a sellout would be
|
|
157
|
+
// republished on the strength of a lookup that simply failed.
|
|
158
|
+
if (known.length === 0) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const plan = planBundlePublication({
|
|
162
|
+
members: known,
|
|
163
|
+
currentStatus: bundle.status === "published" ? "published" : "draft",
|
|
164
|
+
currentMetadata: (bundle.metadata ?? {}),
|
|
165
|
+
});
|
|
166
|
+
if (!plan.changed) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
writes.push({
|
|
170
|
+
product_id: bundle.id,
|
|
171
|
+
status: plan.status,
|
|
172
|
+
metadata: plan.metadata,
|
|
173
|
+
missing: memberIds.filter((id) => {
|
|
174
|
+
const status = input.variantsById.get(id)?.metadata?.printful_availability_status;
|
|
175
|
+
return typeof status === "string" && UNAVAILABLE.has(status);
|
|
176
|
+
}),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return writes;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Whether a bundle should be published, from the stock of its members.
|
|
183
|
+
*
|
|
184
|
+
* Reuses `resolvePublication` so a bundle obeys the same ownership rule as
|
|
185
|
+
* every other product: the plugin undoes only unpublishes it performed, and a
|
|
186
|
+
* draft the merchant made themselves is left alone however available Printful
|
|
187
|
+
* says the members are.
|
|
188
|
+
*
|
|
189
|
+
* What differs is the threshold. `planStockActions` drafts a product only when
|
|
190
|
+
* *every* variant is gone, because any remaining variant is still sellable. A
|
|
191
|
+
* bundle is a promise to ship all of it, so one missing member already breaks
|
|
192
|
+
* the promise.
|
|
193
|
+
*
|
|
194
|
+
* A bundle with no members recorded is left untouched — that is a bundle the
|
|
195
|
+
* merchant has not finished configuring, not one that sold out.
|
|
196
|
+
*/
|
|
197
|
+
function planBundlePublication(input) {
|
|
198
|
+
const availability = planBundleAvailability(input.members.map((m) => m?.metadata ?? {}));
|
|
199
|
+
// One missing member is enough. A bundle with no members recorded has no
|
|
200
|
+
// unavailable ones either, so it falls through as available — an
|
|
201
|
+
// unconfigured bundle is not a sold-out one.
|
|
202
|
+
const allUnavailable = availability.unavailableCount > 0;
|
|
203
|
+
return (0, stock_1.resolvePublication)({
|
|
204
|
+
plan: {
|
|
205
|
+
allUnavailable,
|
|
206
|
+
status: allUnavailable ? "draft" : "published",
|
|
207
|
+
hasDiscontinued: false,
|
|
208
|
+
variantAvailability: {},
|
|
209
|
+
},
|
|
210
|
+
currentStatus: input.currentStatus,
|
|
211
|
+
currentMetadata: input.currentMetadata,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Whether a bundle can be sold, from the availability of its members.
|
|
216
|
+
*
|
|
217
|
+
* A bundle is a promise to ship all of it, so one sold-out member makes the
|
|
218
|
+
* promise unkeepable however available the rest are.
|
|
219
|
+
*
|
|
220
|
+
* Reads `printful_availability_status`, which the sync already writes — no new
|
|
221
|
+
* source of truth, and no second place to keep in step with Printful.
|
|
222
|
+
*
|
|
223
|
+
* An unknown status counts as available, matching `planStockActions`: the
|
|
224
|
+
* unavailable set is a closed list, and a bundle must not be stricter about a
|
|
225
|
+
* status than the member product itself is.
|
|
226
|
+
*/
|
|
227
|
+
function planBundleAvailability(members) {
|
|
228
|
+
let unavailable = 0;
|
|
229
|
+
for (const member of members) {
|
|
230
|
+
const status = member?.printful_availability_status;
|
|
231
|
+
if (typeof status === "string" && UNAVAILABLE.has(status)) {
|
|
232
|
+
unavailable += 1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
// An empty bundle is unavailable rather than vacuously available: there is
|
|
237
|
+
// nothing to ship, which is not the same as everything being in stock.
|
|
238
|
+
available: members.length > 0 && unavailable === 0,
|
|
239
|
+
memberCount: members.length,
|
|
240
|
+
unavailableCount: unavailable,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL2J1bmRsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7OztHQVlHOzs7QUE4Q0gsMENBeUJDO0FBVUQsb0NBSUM7QUFZRCw0Q0E2QkM7QUFtQkQsMENBR0M7QUFtQ0Qsd0NBdURDO0FBd0JELHNEQXdCQztBQWVELHdEQW1CQztBQTlURCxtQ0FBb0U7QUFFcEUsd0VBQXdFO0FBQ3hFLE1BQU0sV0FBVyxHQUFHLElBQUksR0FBRyxDQUFDO0lBQzFCLGNBQWM7SUFDZCx3QkFBd0I7SUFDeEIsY0FBYztDQUNmLENBQUMsQ0FBQTtBQUVGLGdGQUFnRjtBQUNuRSxRQUFBLGtCQUFrQixHQUFHLHlCQUF5QixDQUFBO0FBNEIzRDs7Ozs7R0FLRztBQUNILFNBQWdCLGVBQWUsQ0FDN0IsSUFBNEM7SUFFNUMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxJQUFJLEVBQUUsUUFBUSxJQUFJLEVBQUUsQ0FBQyxDQUFDLDBCQUFrQixDQUFDLENBQUE7SUFDdEQsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN4QixPQUFPLEVBQUUsQ0FBQTtJQUNYLENBQUM7SUFFRCxNQUFNLE9BQU8sR0FBbUIsRUFBRSxDQUFBO0lBQ2xDLEtBQUssTUFBTSxLQUFLLElBQUksR0FBRyxFQUFFLENBQUM7UUFDeEIsSUFBSSxDQUFDLEtBQUssSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUN4QyxTQUFRO1FBQ1YsQ0FBQztRQUNELE1BQU0sU0FBUyxHQUFJLEtBQXNCLENBQUMsVUFBVSxDQUFBO1FBQ3BELElBQUksT0FBTyxTQUFTLEtBQUssUUFBUSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7WUFDdkQsU0FBUTtRQUNWLENBQUM7UUFDRCxNQUFNLFFBQVEsR0FBRyxNQUFN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
|