1'use strict';23// Bundles: one sellable product made of other products. Wix Stores has no bundle type, so the4// bundle imports as a normal product and its links to its parts become an operator task: install a5// bundle app and link the parts. This module is the one producer of that task, for every source.6// It never installs, buys or configures an app, and it never holds or defers a product.7//8// Input: the canonical `product.bundle` of each source product that carries one (see9// wix-target-spec.js validateCanonicalBundle), whether the bundle product was written, and a10// crosswalk lookup for the parts. Output, in the two durable shapes cutover reads today:11// completion-report `operatorActions` (one row per written bundle, code bundle-app-setup) and12// migration-review/1 tasks in group Manual, plus the ledger execution/stores/bundle-setup.json13// that keeps every bundle's contents, written or not. Ids only; names come from the store.
55 if (!entry || typeof entry !== 'object' || Array.isArray(entry)) throw new Error(`${label} must be an object`);
56 if (typeof entry.sourceProductId !== 'string' || !entry.sourceProductId) throw new Error(`${label}.sourceProductId must be a non-empty string`);
57 if (typeof entry.written !== 'boolean') throw new Error(`${label}.written must be a boolean`);
58 if (entry.written && (typeof entry.wixProductId !== 'string' || !entry.wixProductId)) throw new Error(`${label}: a written bundle needs its wixProductId`);
59 if (!entry.written && (typeof entry.reason !== 'string' || !entry.reason)) throw new Error(`${label}: an unwritten bundle needs a reason (for example price-missing)`);
60 // A record whose source carried bundle fields either emitted the canonical bundle or says why
61 // its contents could not be read. A silent null would drop the contents.
62 if (entry.bundle === null || entry.bundle === undefined) {
63 if (typeof entry.unreadableReason !== 'string' || !entry.unreadableReason) throw new Error(`${label}: a bundle record needs its canonical bundle or an unreadableReason`);
104 if (row.kind === 'configurable') return 'Until it is set up, the kit sells at its price as a plain product: the buyer cannot pick its parts, and the parts\' stock does not drop.';
105 return 'Until it is set up, the bundle sells at its price, but its parts\' stock does not drop.';
106}
107
108function priceNote(row) {
109 if (row.priceRule !== 'derived') return '';
110 return ' Its source price was worked out from its parts; it imported with the source price at extraction, frozen (0 if the source showed 0). Check the price in the app.';
111}
112
113// Whether to write a bundle product. Its parts never decide it (a bundle is never held because its
114// parts cannot be linked). Its price does: a bundle whose source reports no price (empty, null or
115// blank: an auto-priced bundle or a kit priced at checkout) is not written with an invented 0; it
116// is reported as a missing-price gap and kept in the ledger as written:false / price-missing. A
117// reported 0 is a price. The importer calls this before building a bundle product's create body.
128 description: `Bundle product ${sourceProductId} has no source price, so it was not written: Wix needs a price, and an invented 0 would sell it for free.`,
129 recommendedHandling: 'Set a price for this bundle in the source (or decide its price), then re-import it; its bundle-app task follows on the next run.',
130 },
131 };
132}
133
134// One operator action per WRITTEN bundle. An unwritten bundle (for example no source price) has no
135// product to link; its own gap is its cutover item, and the ledger keeps its contents.
141 ? `The import could not read this bundle's contents (${row.unreadableReason}); take the parts and quantities from the source store.`
142 : `Parts: ${partsText(row)}.`;
143 const action = `Install a bundle app and link the components: choose one of ${apps.join(' or ')}, install it from the App Market, and link the listed parts and quantities to product \`${row.wixProductId}\`. ${contents}${priceNote(row)} Or dismiss and accept the consequence.`;
152 deadlineReason: 'After the bundle and its parts are imported; before the DNS switch.',
153 affectedCount: 1,
154 affectedIds: [row.wixProductId],
155 artifactRefs: [LEDGER],
156 prerequisites: missing
157 ? [`Requires Import Operator: Re-import the missing parts listed in ${LEDGER} for bundle ${row.wixProductId}, then rerun the bundle cutover producer.`]
158 : ['The bundle and its parts are imported.'],
159 resolutionEvidence: 'The operator resolves it after linking the parts in the app and checking the bundle, or dismisses it accepting the consequence.',