@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.
@@ -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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