UNPKG

@devx-commerce/plugin-loyalty

Version:

A starter for Medusa plugins.

397 lines 34.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.config = void 0; exports.default = loyaltyBulkImportHandler; const utils_1 = require("@medusajs/framework/utils"); const core_flows_1 = require("@medusajs/medusa/core-flows"); const sync_1 = require("csv-parse/sync"); const phone_formater_1 = require("../lib/phone-formater"); const import_feed_notification_1 = require("../lib/import-feed-notification"); const manage_points_1 = require("../workflows/points/manage-points"); // Canonical import columns, echoed back (with original values) in the error CSV, plus a // trailing Error column — matching the edd/review/reship import convention so Ops can fix // the failed rows in place and re-upload. const ERROR_CSV_COLUMNS = [ "order_id", "phone", "email", "points", "l1_tag", "l2_tag", "reason", "expiry_days", ]; // `points`, `l1_tag` and `l2_tag` are always required; at least one identifier column must exist too. const REQUIRED_HEADERS = ["points", "l1_tag", "l2_tag"]; const IDENTIFIER_HEADERS = ["phone", "order_id", "email"]; // Allowed L1/L2 tags are read from this host-app (twt-backend) config module + key. const GLOBAL_CONFIG_MODULE = "global_config"; const INTERNAL_NOTES_CONFIG_KEY = "internal_notes_for_manual_order"; // Each row runs a workflow (with a per-customer lock inside), so cap concurrency. const WRITE_BATCH_SIZE = 8; // ---------------------------- cell parsers ---------------------------- // Trim a cell; treat empty/whitespace as absent. function cell(v) { if (v == null) return undefined; const t = v.trim(); return t === "" ? undefined : t; } function parsePositiveInt(v, field) { const t = cell(v); if (t === undefined) throw new Error(`Missing required value "${field}"`); const n = Number(t); if (!Number.isInteger(n) || n <= 0) throw new Error(`${field} must be a positive integer (got "${t}")`); return n; } // expiry_days: blank -> 365 (default validity); otherwise a positive integer number of days. const DEFAULT_EXPIRY_DAYS = 365; function parseExpiryDays(v) { const t = cell(v); if (t === undefined) return DEFAULT_EXPIRY_DAYS; const n = Number(t); if (!Number.isInteger(n) || n <= 0) throw new Error(`expiry_days must be a positive integer or blank (got "${t}")`); return n; } function dropUndefined(obj) { const out = {}; for (const [k, v] of Object.entries(obj)) { if (v !== undefined) out[k] = v; } return out; } // ---------------------------- customer resolution ---------------------------- // Resolve a customer_id from a row using the priority order_id -> phone -> email. // A higher-priority identifier that is present but doesn't resolve falls through to the // next present one; if none resolve, throws with every reason collected so the error CSV // tells Ops exactly why. Matches guest and account-holding customers alike; credits only on // exactly one match, otherwise reports the row as ambiguous (never guesses). async function resolveCustomer(query, row) { const phone = cell(row.phone); const orderId = cell(row.order_id); const email = cell(row.email); if (!phone && !orderId && !email) { throw new Error("No identifier provided (need one of phone, order_id, email)"); } const reasons = []; if (orderId) { // Accept either the internal Medusa id (order_01…) or the human order number // (display_id, e.g. "21295350"): order_-prefixed -> id; all-digits -> display_id; // otherwise fall back to id. const filters = orderId.startsWith("order_") ? { id: orderId } : /^\d+$/.test(orderId) ? { display_id: Number(orderId) } : { id: orderId }; const { data } = await query.graph({ entity: "order", fields: ["id", "customer_id"], filters, }); const customerId = data[0]?.customer_id; if (customerId) return { customer_id: customerId, matched_by: "order_id", matched_value: orderId }; reasons.push(data.length === 0 ? `no order found for order_id ${orderId}` : `order ${orderId} has no customer`); } if (phone) { const normalizedPhone = (0, phone_formater_1.formatPhoneNumber)(phone); if (normalizedPhone === "Invalid phone number") { reasons.push(`phone "${phone}" is not a valid number`); } else { const { data } = await query.graph({ entity: "customer", fields: ["id"], filters: { phone: normalizedPhone }, }); if (data.length === 1) return { customer_id: data[0].id, matched_by: "phone", matched_value: normalizedPhone }; reasons.push(data.length === 0 ? `no customer found for phone ${normalizedPhone}` : `phone ${normalizedPhone} matches ${data.length} customers`); } } if (email) { const { data } = await query.graph({ entity: "customer", fields: ["id"], filters: { email }, }); if (data.length === 1) return { customer_id: data[0].id, matched_by: "email", matched_value: email }; reasons.push(data.length === 0 ? `no customer found for email ${email}` : `email ${email} matches ${data.length} customers`); } throw new Error(`Could not resolve customer — ${reasons.join("; ")}`); } // l1_tag / l2_tag -> transaction notes, in the same shape the admin manual-credit form // produces: { [l1_category]: [l2_tag] }. Both are mandatory (validated in creditRow). function buildNotes(l1, l2) { return { [l1]: [l2] }; } // Match a row's l1/l2 against the allowed set, case-insensitively, and return the // canonically-cased tags to persist (so "general query" is stored as the defined // "General query"). "Custom" (any casing) is an escape-hatch L1 (any L2); every other L1 // must be defined and its L2 must belong to that L1. Only casing is forgiven — a genuine // wrong/misspelled tag still fails. function resolveTags(l1, l2, allowed) { if (l1.toLowerCase() === "custom") return { l1: "Custom", l2 }; const resolvedL1 = allowed.l1.find((v) => v.toLowerCase() === l1.toLowerCase()); if (!resolvedL1) throw new Error(`l1_tag "${l1}" is not an allowed tag`); const resolvedL2 = allowed.l2[resolvedL1]?.find((v) => v.toLowerCase() === l2.toLowerCase()); if (!resolvedL2) throw new Error(`l2_tag "${l2}" is not allowed under l1_tag "${resolvedL1}"`); return { l1: resolvedL1, l2: resolvedL2 }; } // Credit a single row. reference_type is always "manual" and the order_id is NOT stamped on // the transaction (it lives in metadata only) so imported points are permanent and never // auto-reversed by the order-return flow (which matches on the transaction's order_id). async function creditRow(container, query, row, allowedTags) { const points = parsePositiveInt(row.points, "points"); const expiry_days = parseExpiryDays(row.expiry_days); // L1/L2 tags are mandatory, and must be from the defined internal-notes set. const l1 = cell(row.l1_tag); const l2 = cell(row.l2_tag); if (!l1) throw new Error('Missing required value "l1_tag"'); if (!l2) throw new Error('Missing required value "l2_tag"'); const { l1: resolvedL1, l2: resolvedL2 } = resolveTags(l1, l2, allowedTags); const { customer_id, matched_by, matched_value } = await resolveCustomer(query, row); // reason -> customer-visible description. Blank -> undefined so the service applies its // "Points added" default. const description = cell(row.reason); const metadata = dropUndefined({ import: true, matched_by, matched_value, order_id: cell(row.order_id), phone: cell(row.phone), email: cell(row.email), }); await (0, manage_points_1.managePointsWorkflow)(container).run({ input: { customer_id, points, type: "credit", reference_type: "manual", description, expiry_days, metadata, notes: buildNotes(resolvedL1, resolvedL2), }, }); } // ---------------------------- handler helpers ---------------------------- function validateCsvHeaders(headers) { const missing = REQUIRED_HEADERS.filter((h) => !headers.includes(h)); if (missing.length > 0) return { isValid: false, error: `Missing required columns: ${missing.join(", ")}` }; if (!IDENTIFIER_HEADERS.some((h) => headers.includes(h))) return { isValid: false, error: `At least one identifier column required: ${IDENTIFIER_HEADERS.join(", ")}`, }; return { isValid: true }; } // The identifiers as supplied in the row, for the error CSV. function rowIdentifierLabel(row) { return ([cell(row.phone), cell(row.order_id), cell(row.email)] .filter(Boolean) .join(" / ") || "(none)"); } function buildErrorCsv(errors) { const header = ["Row", ...ERROR_CSV_COLUMNS, "Error"]; const lines = errors.map((e) => [e.row || "", ...ERROR_CSV_COLUMNS.map((c) => e.data[c] ?? ""), e.error] .map((v) => `"${String(v).replace(/"/g, '""')}"`) .join(",")); return [header.map((h) => `"${h}"`).join(","), ...lines].join("\n"); } async function sendImportNotification(container, opts) { await (0, import_feed_notification_1.sendImportFeedNotification)(container, { template: "loyalty-import", resourceType: "bulk_job", triggerType: opts.status === "completed" ? "job_completed" : "job_failed", title: opts.status === "completed" ? "Loyalty Import Completed" : "Loyalty Import Failed", ...(opts.errorCsvUrl && { file: { url: opts.errorCsvUrl } }), metadata: { ...opts.counts, requested_by: opts.requestedBy, ...(opts.errorMessage && { error: opts.errorMessage }), }, }); } const ZERO_COUNTS = { total: 0, successful: 0, failed: 0 }; // ---------------------------- handler ---------------------------- async function loyaltyBulkImportHandler({ event, container, }) { const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER); const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY); const { s3_url, requested_by_name } = event.data; const requestedBy = requested_by_name || "Ops"; logger.info(`Loyalty bulk import started from: ${s3_url}`); // Download CSV from S3. let csvContent; try { const response = await fetch(s3_url); if (!response.ok) throw new Error(`${response.status} ${response.statusText}`); csvContent = await response.text(); } catch (error) { logger.error("Loyalty bulk import: failed to fetch CSV", error); await sendImportNotification(container, { status: "failed", requestedBy, counts: ZERO_COUNTS, errorMessage: "Could not download the uploaded file.", }); return; } // Parse CSV. Headers normalized to snake_case so "Order ID"/"Phone"/etc. map to the // documented names; relax_column_count so a malformed row fails only itself; bom strips // a leading byte-order mark. let rows; let headers; try { rows = (0, sync_1.parse)(csvContent, { columns: (hdrs) => hdrs.map((h) => h.trim().toLowerCase().replace(/[\s-]+/g, "_")), skip_empty_lines: true, trim: true, relax_column_count: true, bom: true, }); headers = Object.keys(rows[0] ?? {}); } catch (error) { logger.error("Loyalty bulk import: failed to parse CSV", error); await sendImportNotification(container, { status: "failed", requestedBy, counts: ZERO_COUNTS, errorMessage: "The file could not be read as a CSV.", }); return; } if (rows.length === 0) { logger.error("Loyalty bulk import: CSV file is empty or contains no data rows"); await sendImportNotification(container, { status: "failed", requestedBy, counts: ZERO_COUNTS, errorMessage: "The file has no data rows.", }); return; } const headerValidation = validateCsvHeaders(headers); if (!headerValidation.isValid) { logger.error(`Loyalty bulk import: ${headerValidation.error}`); await sendImportNotification(container, { status: "failed", requestedBy, counts: ZERO_COUNTS, errorMessage: headerValidation.error, }); return; } // Load the allowed L1/L2 tags once (from the app's internal-notes config). Fail-closed: // if they can't be read/parsed, abort the whole import — not a single row is credited. let allowedTags; try { const globalConfig = container.resolve(GLOBAL_CONFIG_MODULE); const [notesConfigRow] = await globalConfig.listGlobalConfigs({ key: INTERNAL_NOTES_CONFIG_KEY, }); const parsed = notesConfigRow?.value ? JSON.parse(notesConfigRow.value) : null; if (!parsed || !Array.isArray(parsed.l1) || typeof parsed.l2 !== "object" || parsed.l2 === null) { throw new Error("internal_notes_for_manual_order config missing or malformed"); } allowedTags = parsed; } catch (error) { logger.error("Loyalty bulk import: failed to load allowed L1/L2 tags", error); await sendImportNotification(container, { status: "failed", requestedBy, counts: ZERO_COUNTS, errorMessage: "Failed to load the allowed L1/L2 tags. Import aborted — no points were credited.", }); return; } const errors = []; let successful = 0; logger.info(`Loyalty bulk import: processing ${rows.length} row(s)`); for (let i = 0; i < rows.length; i += WRITE_BATCH_SIZE) { const batch = rows.slice(i, i + WRITE_BATCH_SIZE); await Promise.all(batch.map(async (row, j) => { const rowNumber = i + j + 1; // 1-based data-row number (CSV header not counted) try { await creditRow(container, query, row, allowedTags); successful++; } catch (error) { const message = error.message || "Unknown error"; errors.push({ row: rowNumber, data: row, error: message, }); logger.error(`Loyalty bulk import: row ${rowNumber} (${rowIdentifierLabel(row)}) failed — ${message}`); } })); logger.info(`Loyalty bulk import: ${Math.min(i + WRITE_BATCH_SIZE, rows.length)}/${rows.length} processed`); } // Upload failed rows CSV to S3. let errorCsvUrl; if (errors.length > 0) { try { const { result: uploaded } = await (0, core_flows_1.uploadFilesWorkflow)(container).run({ input: { files: [ { filename: `loyalty-imports/errors/loyalty-errors-${Date.now()}.csv`, mimeType: "text/csv", content: buildErrorCsv(errors), access: "private", }, ], }, }); const fileService = container.resolve(utils_1.Modules.FILE); errorCsvUrl = await fileService .getProvider() .getPresignedDownloadUrl({ fileKey: uploaded[0].id }); } catch (csvError) { logger.error("Loyalty bulk import: failed to upload error CSV", csvError); } } const failed = errors.length; await sendImportNotification(container, { status: "completed", requestedBy, counts: { total: successful + failed, successful, failed }, errorCsvUrl, }); logger.info(`Loyalty bulk import completed. Rows: ${rows.length}, Credited: ${successful}, Failed: ${failed}`); } exports.config = { event: "loyalty.bulk_import", }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG95YWx0eS1idWxrLWltcG9ydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zdWJzY3JpYmVycy9sb3lhbHR5LWJ1bGstaW1wb3J0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQWtVQSwyQ0F1TEM7QUFwZkQscURBQStFO0FBQy9FLDREQUFrRTtBQUNsRSx5Q0FBdUM7QUFFdkMsMERBQTBEO0FBQzFELDhFQUE2RTtBQUM3RSxxRUFBeUU7QUFlekUsd0ZBQXdGO0FBQ3hGLDBGQUEwRjtBQUMxRiwwQ0FBMEM7QUFDMUMsTUFBTSxpQkFBaUIsR0FBRztJQUN4QixVQUFVO0lBQ1YsT0FBTztJQUNQLE9BQU87SUFDUCxRQUFRO0lBQ1IsUUFBUTtJQUNSLFFBQVE7SUFDUixRQUFRO0lBQ1IsYUFBYTtDQUNMLENBQUM7QUFLWCxzR0FBc0c7QUFDdEcsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFFBQVEsRUFBRSxRQUFRLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDeEQsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDMUQsb0ZBQW9GO0FBQ3BGLE1BQU0sb0JBQW9CLEdBQUcsZUFBZSxDQUFDO0FBQzdDLE1BQU0seUJBQXlCLEdBQUcsaUNBQWlDLENBQUM7QUFDcEUsa0ZBQWtGO0FBQ2xGLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxDQUFDO0FBRTNCLHlFQUF5RTtBQUV6RSxpREFBaUQ7QUFDakQsU0FBUyxJQUFJLENBQUMsQ0FBcUI7SUFDakMsSUFBSSxDQUFDLElBQUksSUFBSTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2hDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNuQixPQUFPLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2xDLENBQUM7QUFFRCxTQUFTLGdC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