@odooconnector/medusa-odoo-connector 0.1.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/.medusa/server/src/admin/index.js +2211 -0
- package/.medusa/server/src/admin/index.mjs +2212 -0
- package/.medusa/server/src/api/admin/odoo/billing/route.js +15 -0
- package/.medusa/server/src/api/admin/odoo/clear-orders/route.js +33 -0
- package/.medusa/server/src/api/admin/odoo/clear-products/route.js +30 -0
- package/.medusa/server/src/api/admin/odoo/config/route.js +44 -0
- package/.medusa/server/src/api/admin/odoo/customer-options/route.js +15 -0
- package/.medusa/server/src/api/admin/odoo/inventory-options/route.js +15 -0
- package/.medusa/server/src/api/admin/odoo/jobs/[id]/route.js +25 -0
- package/.medusa/server/src/api/admin/odoo/logs/route.js +27 -0
- package/.medusa/server/src/api/admin/odoo/options/route.js +15 -0
- package/.medusa/server/src/api/admin/odoo/order-options/route.js +15 -0
- package/.medusa/server/src/api/admin/odoo/product-options/route.js +15 -0
- package/.medusa/server/src/api/admin/odoo/sections/[section]/route.js +55 -0
- package/.medusa/server/src/api/admin/odoo/settings/route.js +91 -0
- package/.medusa/server/src/api/admin/odoo/stripe/change/route.js +20 -0
- package/.medusa/server/src/api/admin/odoo/stripe/checkout/route.js +20 -0
- package/.medusa/server/src/api/admin/odoo/stripe/portal/route.js +16 -0
- package/.medusa/server/src/api/admin/odoo/sync/[entity]/route.js +99 -0
- package/.medusa/server/src/api/admin/odoo/sync-order/[id]/route.js +17 -0
- package/.medusa/server/src/api/admin/odoo/verify-connection/route.js +39 -0
- package/.medusa/server/src/api/odoo/billing/sync/route.js +42 -0
- package/.medusa/server/src/index.js +3 -0
- package/.medusa/server/src/jobs/import-worker.js +61 -0
- package/.medusa/server/src/lib/export-products.js +141 -0
- package/.medusa/server/src/lib/import-products.js +304 -0
- package/.medusa/server/src/lib/odoo-api-client.js +32 -0
- package/.medusa/server/src/lib/plan.js +83 -0
- package/.medusa/server/src/lib/queue.js +79 -0
- package/.medusa/server/src/lib/sync-customers.js +183 -0
- package/.medusa/server/src/lib/sync-inventory.js +135 -0
- package/.medusa/server/src/lib/sync-order.js +134 -0
- package/.medusa/server/src/lib/sync-products.js +32 -0
- package/.medusa/server/src/modules/odoo/index.js +13 -0
- package/.medusa/server/src/modules/odoo/migrations/Migration20260619101616.js +17 -0
- package/.medusa/server/src/modules/odoo/models/odoo-setting.js +19 -0
- package/.medusa/server/src/modules/odoo/service.js +28 -0
- package/.medusa/server/src/subscribers/order-placed.js +24 -0
- package/.medusa/server/src/workflows/index.js +3 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/package.json +81 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POST = void 0;
|
|
4
|
+
const odoo_1 = require("../../../../modules/odoo");
|
|
5
|
+
const odoo_api_client_1 = require("../../../../lib/odoo-api-client");
|
|
6
|
+
const MASK = "••••••••";
|
|
7
|
+
// POST /admin/odoo/test-connection — uses Medusa's stored key when none is
|
|
8
|
+
// supplied, proxies the test to odoo-api, and records the outcome in Medusa.
|
|
9
|
+
const POST = async (req, res) => {
|
|
10
|
+
const svc = req.scope.resolve(odoo_1.ODOO_MODULE);
|
|
11
|
+
const stored = await svc.getValue("connection");
|
|
12
|
+
const b = (req.body ?? {});
|
|
13
|
+
const url = String(b.url ?? "").trim() || stored?.url;
|
|
14
|
+
const database = String(b.database ?? "").trim() || stored?.database;
|
|
15
|
+
const username = String(b.username ?? "").trim() || stored?.username;
|
|
16
|
+
let api_key = String(b.api_key ?? "").trim();
|
|
17
|
+
if (!api_key || api_key === MASK)
|
|
18
|
+
api_key = stored?.api_key;
|
|
19
|
+
let result;
|
|
20
|
+
try {
|
|
21
|
+
result = await (0, odoo_api_client_1.odooApi)("/odoo/test-connection", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: JSON.stringify({ url, database, username, api_key }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
result = { ok: false, message: e?.message ?? "Connection test failed" };
|
|
28
|
+
}
|
|
29
|
+
if (stored) {
|
|
30
|
+
await svc.setValue("connection", {
|
|
31
|
+
...stored,
|
|
32
|
+
last_tested_at: new Date().toISOString(),
|
|
33
|
+
last_test_ok: Boolean(result.ok),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
res.status(result.ok ? 200 : 400).json(result);
|
|
37
|
+
};
|
|
38
|
+
exports.POST = POST;
|
|
39
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29kb28vdmVyaWZ5LWNvbm5lY3Rpb24vcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EsbURBQXNEO0FBRXRELHFFQUF5RDtBQUV6RCxNQUFNLElBQUksR0FBRyxVQUFVLENBQUE7QUFFdkIsMkVBQTJFO0FBQzNFLDZFQUE2RTtBQUN0RSxNQUFNLElBQUksR0FBRyxLQUFLLEVBQUUsR0FBa0IsRUFBRSxHQUFtQixFQUFFLEVBQUU7SUFDcEUsTUFBTSxHQUFHLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQXFCLGtCQUFXLENBQUMsQ0FBQTtJQUM5RCxNQUFNLE1BQU0sR0FBRyxNQUFNLEdBQUcsQ0FBQyxRQUFRLENBQU0sWUFBWSxDQUFDLENBQUE7SUFDcEQsTUFBTSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBd0IsQ0FBQTtJQUVqRCxNQUFNLEdBQUcsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsSUFBSSxNQUFNLEVBQUUsR0FBRyxDQUFBO0lBQ3JELE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQyxDQUFDLElBQUksRUFBRSxJQUFJLE1BQU0sRUFBRSxRQUFRLENBQUE7SUFDcEUsTUFBTSxRQUFRLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxRQUFRLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxFQUFFLElBQUksTUFBTSxFQUFFLFFBQVEsQ0FBQTtJQUNwRSxJQUFJLE9BQU8sR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtJQUM1QyxJQUFJLENBQUMsT0FBTyxJQUFJLE9BQU8sS0FBSyxJQUFJO1FBQUUsT0FBTyxHQUFHLE1BQU0sRUFBRSxPQUFPLENBQUE7SUFFM0QsSUFBSSxNQUF1RCxDQUFBO0lBQzNELElBQUksQ0FBQztRQUNILE1BQU0sR0FBRyxNQUFNLElBQUEseUJBQU8sRUFBQyx1QkFBdUIsRUFBRTtZQUM5QyxNQUFNLEVBQUUsTUFBTTtZQUNkLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLENBQUM7U0FDM0QsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUFDLE9BQU8sQ0FBTSxFQUFFLENBQUM7UUFDaEIsTUFBTSxHQUFHLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE9BQU8sSUFBSSx3QkFBd0IsRUFBRSxDQUFBO0lBQ3pFLENBQUM7SUFFRCxJQUFJLE1BQU0sRUFBRSxDQUFDO1FBQ1gsTUFBTSxHQUFHLENBQUMsUUFBUSxDQUFDLFlBQVksRUFBRTtZQUMvQixHQUFHLE1BQU07WUFDVCxjQUFjLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUU7WUFDeEMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1NBQ2pDLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFFRCxHQUFHLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFBO0FBQ2hELENBQUMsQ0FBQTtBQTlCWSxRQUFBLElBQUksUUE4QmhCIn0=
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POST = void 0;
|
|
4
|
+
const odoo_1 = require("../../../../modules/odoo");
|
|
5
|
+
const queue_1 = require("../../../../lib/queue");
|
|
6
|
+
const plan_1 = require("../../../../lib/plan");
|
|
7
|
+
/**
|
|
8
|
+
* POST /odoo/billing/sync — PUBLIC route called server-to-server by odoo-api
|
|
9
|
+
* after a Stripe billing event. It is NOT under /admin (so no admin session is
|
|
10
|
+
* required) and is protected by a shared secret instead. It writes the active
|
|
11
|
+
* plan into `section:plans` — the source of truth that the sync engines enforce.
|
|
12
|
+
*/
|
|
13
|
+
const SECRET = process.env.BILLING_SYNC_SECRET ?? "dev-billing-sync-secret";
|
|
14
|
+
const POST = async (req, res) => {
|
|
15
|
+
if (req.headers["x-billing-secret"] !== SECRET) {
|
|
16
|
+
return res.status(401).json({ message: "Invalid billing secret." });
|
|
17
|
+
}
|
|
18
|
+
const { planId, cycle, status } = (req.body ?? {});
|
|
19
|
+
if (!planId)
|
|
20
|
+
return res.status(400).json({ message: "planId is required." });
|
|
21
|
+
const svc = req.scope.resolve(odoo_1.ODOO_MODULE);
|
|
22
|
+
const prev = ((await svc.getValue("section:plans")) ?? {});
|
|
23
|
+
await svc.setValue("section:plans", {
|
|
24
|
+
...prev,
|
|
25
|
+
planId,
|
|
26
|
+
...(cycle ? { cycle } : {}),
|
|
27
|
+
...(status ? { status } : {}),
|
|
28
|
+
});
|
|
29
|
+
// The plan changed → re-clamp the import schedule (a downgrade must drop a
|
|
30
|
+
// schedule that's now too frequent).
|
|
31
|
+
try {
|
|
32
|
+
const plan = await (0, plan_1.getPlanLimits)(req.scope);
|
|
33
|
+
const prod = (await svc.getValue("section:product")) ?? {};
|
|
34
|
+
await (0, queue_1.reconcileProductSchedule)((0, plan_1.clampSchedule)(prod.importSchedule, plan));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* non-critical */
|
|
38
|
+
}
|
|
39
|
+
res.json({ ok: true, planId });
|
|
40
|
+
};
|
|
41
|
+
exports.POST = POST;
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL29kb28vYmlsbGluZy9zeW5jL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLG1EQUFzRDtBQUV0RCxpREFBZ0U7QUFDaEUsK0NBQW1FO0FBRW5FOzs7OztHQUtHO0FBQ0gsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsSUFBSSx5QkFBeUIsQ0FBQTtBQUVwRSxNQUFNLElBQUksR0FBRyxLQUFLLEVBQUUsR0FBa0IsRUFBRSxHQUFtQixFQUFFLEVBQUU7SUFDcEUsSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDLEtBQUssTUFBTSxFQUFFLENBQUM7UUFDL0MsT0FBTyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLE9BQU8sRUFBRSx5QkFBeUIsRUFBRSxDQUFDLENBQUE7SUFDckUsQ0FBQztJQUNELE1BQU0sRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksSUFBSSxFQUFFLENBSWhELENBQUE7SUFDRCxJQUFJLENBQUMsTUFBTTtRQUFFLE9BQU8sR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsQ0FBQyxDQUFBO0lBRTVFLE1BQU0sR0FBRyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFxQixrQkFBVyxDQUFDLENBQUE7SUFDOUQsTUFBTSxJQUFJLEdBQUcsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLFFBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBNEIsQ0FBQTtJQUNyRixNQUFNLEdBQUcsQ0FBQyxRQUFRLENBQUMsZUFBZSxFQUFFO1FBQ2xDLEdBQUcsSUFBSTtRQUNQLE1BQU07UUFDTixHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDM0IsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQzlCLENBQUMsQ0FBQTtJQUVGLDJFQUEyRTtJQUMzRSxxQ0FBcUM7SUFDckMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFBLG9CQUFhLEVBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQzNDLE1BQU0sSUFBSSxHQUFHLENBQUMsTUFBTSxHQUFHLENBQUMsUUFBUSxDQUE4QixpQkFBaUIsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFBO1FBQ3ZGLE1BQU0sSUFBQSxnQ0FBd0IsRUFBQyxJQUFBLG9CQUFhLEVBQUMsSUFBSSxDQUFDLGNBQWMsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFBO0lBQzFFLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxrQkFBa0I7SUFDcEIsQ0FBQztJQUVELEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUE7QUFDaEMsQ0FBQyxDQUFBO0FBL0JZLFFBQUEsSUFBSSxRQStCaEIifQ==
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
exports.default = bootImportWorker;
|
|
5
|
+
const bullmq_1 = require("bullmq");
|
|
6
|
+
const queue_1 = require("../lib/queue");
|
|
7
|
+
const sync_products_1 = require("../lib/sync-products");
|
|
8
|
+
const sync_order_1 = require("../lib/sync-order");
|
|
9
|
+
const sync_customers_1 = require("../lib/sync-customers");
|
|
10
|
+
const sync_inventory_1 = require("../lib/sync-inventory");
|
|
11
|
+
const plan_1 = require("../lib/plan");
|
|
12
|
+
const odoo_1 = require("../modules/odoo");
|
|
13
|
+
/**
|
|
14
|
+
* Boots the BullMQ worker that processes background product imports.
|
|
15
|
+
*
|
|
16
|
+
* Implemented as a scheduled job (not a module loader) because scheduled jobs
|
|
17
|
+
* receive the FULL Medusa app container — which can resolve the odoo module and
|
|
18
|
+
* run product workflows. The job fires on a cron but only starts the worker
|
|
19
|
+
* once; the long-lived worker then handles queued jobs in real time.
|
|
20
|
+
*/
|
|
21
|
+
let worker;
|
|
22
|
+
let orderWorker;
|
|
23
|
+
let customerWorker;
|
|
24
|
+
let inventoryWorker;
|
|
25
|
+
async function bootImportWorker(container) {
|
|
26
|
+
if (worker)
|
|
27
|
+
return; // already running
|
|
28
|
+
worker = new bullmq_1.Worker(queue_1.PRODUCT_IMPORT_QUEUE, async () => (0, sync_products_1.syncProducts)(container), { connection: queue_1.redisConnection, concurrency: 1 });
|
|
29
|
+
worker.on("failed", (_job, err) => console.error("[odoo] product import job failed:", err?.message));
|
|
30
|
+
worker.on("completed", (job) => console.log(`[odoo] product import job ${job.id} completed`));
|
|
31
|
+
console.log("[odoo] product import worker started");
|
|
32
|
+
// Order-sync worker — one job per order (job.data.orderId).
|
|
33
|
+
orderWorker = new bullmq_1.Worker(queue_1.ORDER_SYNC_QUEUE, async (job) => (0, sync_order_1.syncOrder)(container, job.data.orderId), { connection: queue_1.redisConnection, concurrency: 2 });
|
|
34
|
+
orderWorker.on("failed", (_job, err) => console.error("[odoo] order sync job failed:", err?.message));
|
|
35
|
+
console.log("[odoo] order sync worker started");
|
|
36
|
+
// Customer-sync worker — direction-aware, gated by the customerSync toggle.
|
|
37
|
+
customerWorker = new bullmq_1.Worker(queue_1.CUSTOMER_SYNC_QUEUE, async () => (0, sync_customers_1.syncCustomers)(container), { connection: queue_1.redisConnection, concurrency: 1 });
|
|
38
|
+
customerWorker.on("failed", (_job, err) => console.error("[odoo] customer sync job failed:", err?.message));
|
|
39
|
+
console.log("[odoo] customer sync worker started");
|
|
40
|
+
// Inventory-sync worker — direction-aware, gated by the stock toggle.
|
|
41
|
+
inventoryWorker = new bullmq_1.Worker(queue_1.INVENTORY_SYNC_QUEUE, async () => (0, sync_inventory_1.syncInventory)(container), { connection: queue_1.redisConnection, concurrency: 1 });
|
|
42
|
+
inventoryWorker.on("failed", (_job, err) => console.error("[odoo] inventory sync job failed:", err?.message));
|
|
43
|
+
console.log("[odoo] inventory sync worker started");
|
|
44
|
+
// Re-apply the saved import schedule (survives restarts even if Redis was
|
|
45
|
+
// wiped), clamped to the active plan.
|
|
46
|
+
try {
|
|
47
|
+
const svc = container.resolve(odoo_1.ODOO_MODULE);
|
|
48
|
+
const settings = await svc.getValue("section:product");
|
|
49
|
+
const plan = await (0, plan_1.getPlanLimits)(container);
|
|
50
|
+
await (0, queue_1.reconcileProductSchedule)((0, plan_1.clampSchedule)(settings?.importSchedule, plan));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* non-critical */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.config = {
|
|
57
|
+
name: "odoo-import-worker-boot",
|
|
58
|
+
// Every minute; starts the worker on first run, then no-ops.
|
|
59
|
+
schedule: "* * * * *",
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0LXdvcmtlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9qb2JzL2ltcG9ydC13b3JrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBK0JBLG1DQTJEQztBQXpGRCxtQ0FBK0I7QUFDL0Isd0NBT3FCO0FBQ3JCLHdEQUFtRDtBQUNuRCxrREFBNkM7QUFDN0MsMERBQXFEO0FBQ3JELDBEQUFxRDtBQUNyRCxzQ0FBMEQ7QUFDMUQsMENBQTZDO0FBRzdDOzs7Ozs7O0dBT0c7QUFDSCxJQUFJLE1BQTBCLENBQUE7QUFDOUIsSUFBSSxXQUErQixDQUFBO0FBQ25DLElBQUksY0FBa0MsQ0FBQTtBQUN0QyxJQUFJLGVBQW1DLENBQUE7QUFFeEIsS0FBSyxVQUFVLGdCQUFnQixDQUFDLFNBQTBCO0lBQ3ZFLElBQUksTUFBTTtRQUFFLE9BQU0sQ0FBQyxrQkFBa0I7SUFFckMsTUFBTSxHQUFHLElBQUksZUFBTSxDQUNqQiw0QkFBb0IsRUFDcEIsS0FBSyxJQUFJLEVBQUUsQ0FBQyxJQUFBLDRCQUFZLEVBQUMsU0FBUyxDQUFDLEVBQ25DLEVBQUUsVUFBVSxFQUFFLHVCQUFlLEVBQUUsV0FBVyxFQUFFLENBQUMsRUFBRSxDQUNoRCxDQUFBO0lBQ0QsTUFBTSxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLEVBQUUsQ0FDaEMsT0FBTyxDQUFDLEtBQUssQ0FBQyxtQ0FBbUMsRUFBRSxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQ2pFLENBQUE7SUFDRCxNQUFNLENBQUMsRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQzdCLE9BQU8sQ0FBQyxHQUFHLENBQUMsNkJBQTZCLEdBQUcsQ0FBQyxFQUFFLFlBQVksQ0FBQyxDQUM3RCxDQUFBO0lBQ0QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQ0FBc0MsQ0FBQyxDQUFBO0lBRW5ELDREQUE0RDtJQUM1RCxX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
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exportProducts = exportProducts;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const odoo_1 = require("../modules/odoo");
|
|
6
|
+
const odoo_api_client_1 = require("./odoo-api-client");
|
|
7
|
+
const plan_1 = require("./plan");
|
|
8
|
+
/**
|
|
9
|
+
* Medusa → Odoo product export. Pushes Medusa products into Odoo as
|
|
10
|
+
* product.template records. Idempotent via metadata.odoo_export_id. Skips
|
|
11
|
+
* products that were IMPORTED from Odoo (metadata.odoo_id) to avoid a loop.
|
|
12
|
+
*/
|
|
13
|
+
async function exportProducts(scope) {
|
|
14
|
+
const svc = scope.resolve(odoo_1.ODOO_MODULE);
|
|
15
|
+
const query = scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
16
|
+
const settings = ((await svc.getValue("section:product")) ?? {});
|
|
17
|
+
const mode = settings.creationMode ?? "create_update";
|
|
18
|
+
const { data: stores } = await query.graph({
|
|
19
|
+
entity: "store",
|
|
20
|
+
fields: ["supported_currencies.currency_code", "supported_currencies.is_default"],
|
|
21
|
+
});
|
|
22
|
+
const currency = stores[0]?.supported_currencies?.find((c) => c.is_default)?.currency_code ??
|
|
23
|
+
stores[0]?.supported_currencies?.[0]?.currency_code ??
|
|
24
|
+
"usd";
|
|
25
|
+
const { data: products } = await query.graph({
|
|
26
|
+
entity: "product",
|
|
27
|
+
fields: [
|
|
28
|
+
"id",
|
|
29
|
+
"title",
|
|
30
|
+
"description",
|
|
31
|
+
"metadata",
|
|
32
|
+
"options.title",
|
|
33
|
+
"options.values.value",
|
|
34
|
+
"variants.sku",
|
|
35
|
+
"variants.barcode",
|
|
36
|
+
"variants.prices.amount",
|
|
37
|
+
"variants.prices.currency_code",
|
|
38
|
+
"variants.options.value",
|
|
39
|
+
"variants.options.option.title",
|
|
40
|
+
],
|
|
41
|
+
pagination: { take: 1000 },
|
|
42
|
+
});
|
|
43
|
+
// Plan limit: cap the number of products pushed per run.
|
|
44
|
+
const plan = await (0, plan_1.getPlanLimits)(scope);
|
|
45
|
+
const capList = plan.productLimit != null && products.length > plan.productLimit
|
|
46
|
+
? products.slice(0, plan.productLimit)
|
|
47
|
+
: products;
|
|
48
|
+
const planCapped = products.length - capList.length;
|
|
49
|
+
const inputs = [];
|
|
50
|
+
const byId = new Map();
|
|
51
|
+
let skipped = 0;
|
|
52
|
+
for (const p of capList) {
|
|
53
|
+
// Don't push products that came FROM Odoo (avoid circular sync).
|
|
54
|
+
if (p.metadata?.odoo_id != null) {
|
|
55
|
+
skipped++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const exportId = p.metadata?.odoo_export_id ?? null;
|
|
59
|
+
if (exportId && mode === "create") {
|
|
60
|
+
skipped++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!exportId && mode === "update") {
|
|
64
|
+
skipped++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const priceOf = (v) => v?.prices?.find((pr) => pr.currency_code === currency)?.amount ??
|
|
68
|
+
v?.prices?.[0]?.amount ??
|
|
69
|
+
0;
|
|
70
|
+
const options = (p.options || []).map((o) => ({
|
|
71
|
+
title: o.title,
|
|
72
|
+
values: (o.values || []).map((vv) => vv.value),
|
|
73
|
+
}));
|
|
74
|
+
const variants = (p.variants || []).map((v) => ({
|
|
75
|
+
sku: v.sku ?? null,
|
|
76
|
+
barcode: v.barcode ?? null,
|
|
77
|
+
price: priceOf(v),
|
|
78
|
+
options: Object.fromEntries((v.options || [])
|
|
79
|
+
.map((o) => [o.option?.title, o.value])
|
|
80
|
+
.filter(([k]) => k)),
|
|
81
|
+
}));
|
|
82
|
+
inputs.push({
|
|
83
|
+
externalId: p.id,
|
|
84
|
+
odooId: exportId,
|
|
85
|
+
name: p.title,
|
|
86
|
+
description: p.description ?? null,
|
|
87
|
+
price: variants[0]?.price ?? 0,
|
|
88
|
+
options,
|
|
89
|
+
variants,
|
|
90
|
+
});
|
|
91
|
+
byId.set(p.id, p);
|
|
92
|
+
}
|
|
93
|
+
if (!inputs.length) {
|
|
94
|
+
return { ok: true, created: 0, updated: 0, skipped, failed: 0, message: "Medusa → Odoo: nothing to export." };
|
|
95
|
+
}
|
|
96
|
+
let resp;
|
|
97
|
+
try {
|
|
98
|
+
resp = await (0, odoo_api_client_1.odooApi)("/odoo/products-export", {
|
|
99
|
+
method: "POST",
|
|
100
|
+
body: JSON.stringify({ products: inputs }),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
return { ok: false, created: 0, updated: 0, skipped, failed: inputs.length, message: e?.message ?? "Export failed" };
|
|
105
|
+
}
|
|
106
|
+
let created = 0;
|
|
107
|
+
let updated = 0;
|
|
108
|
+
let failed = 0;
|
|
109
|
+
const updates = [];
|
|
110
|
+
for (const r of resp.results ?? []) {
|
|
111
|
+
if (r.error || !r.odooId) {
|
|
112
|
+
failed++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (r.created)
|
|
116
|
+
created++;
|
|
117
|
+
else
|
|
118
|
+
updated++;
|
|
119
|
+
const p = byId.get(r.externalId);
|
|
120
|
+
updates.push({ id: r.externalId, metadata: { ...(p?.metadata || {}), odoo_export_id: r.odooId } });
|
|
121
|
+
}
|
|
122
|
+
if (updates.length) {
|
|
123
|
+
const productModule = scope.resolve(utils_1.Modules.PRODUCT);
|
|
124
|
+
await productModule.updateProducts(updates);
|
|
125
|
+
}
|
|
126
|
+
const capNote = planCapped
|
|
127
|
+
? ` ${planCapped} product(s) skipped — ${plan.name} plan limit of ${plan.productLimit}.`
|
|
128
|
+
: "";
|
|
129
|
+
const message = `Medusa → Odoo: ${created} created, ${updated} updated, ${skipped} skipped${failed ? `, ${failed} failed` : ""}.${capNote}`;
|
|
130
|
+
try {
|
|
131
|
+
await (0, odoo_api_client_1.odooApi)("/odoo/logs", {
|
|
132
|
+
method: "POST",
|
|
133
|
+
body: JSON.stringify({ type: "product_sync", status: failed ? "failed" : "success", message }),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
/* non-critical */
|
|
138
|
+
}
|
|
139
|
+
return { ok: failed === 0, created, updated, skipped, failed, message };
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwb3J0LXByb2R1Y3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9leHBvcnQtcHJvZHVjdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUEyQkEsd0NBZ0pDO0FBMUtELHFEQUE4RTtBQUM5RSwwQ0FBNkM7QUFFN0MsdURBQTJDO0FBQzNDLGlDQUFzQztBQWlCdEM7Ozs7R0FJRztBQUNJLEtBQUssVUFBVSxjQUFjLENBQUMsS0FBc0I7SUFDekQsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBcUIsa0JBQVcsQ0FBQyxDQUFBO0lBQzFELE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDNUQsTUFBTSxRQUFRLEdBQUcsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLFFBQVEsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFvQixDQUFBO0lBQ25GLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxZQUFZLElBQUksZUFBZSxDQUFBO0lBRXJELE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDO1FBQ3pDLE1BQU0sRUFBRSxPQUFPO1FBQ2YsTUFBTSxFQUFFLENBQUMsb0NBQW9DLEVBQUUsaUNBQWlDLENBQUM7S0FDbEYsQ0FBQyxDQUFBO0lBQ0YsTUFBTSxRQUFRLEdBQ1osTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLG9CQUFvQixFQUFFLElBQUksQ0FBQyxDQUFDLENBQU0sRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxFQUFFLGFBQWE7UUFDOUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLG9CQUFvQixFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsYUFBYTtRQUNuRCxLQUFLLENBQUE7SUFFUCxNQUFNLEVBQUUsSUFBSSxFQUFFLFFBQVEs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
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.importProducts = importProducts;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const core_flows_1 = require("@medusajs/medusa/core-flows");
|
|
6
|
+
const odoo_1 = require("../modules/odoo");
|
|
7
|
+
const odoo_api_client_1 = require("./odoo-api-client");
|
|
8
|
+
const plan_1 = require("./plan");
|
|
9
|
+
const variantTitle = (v, fallback) => {
|
|
10
|
+
const vals = Object.values(v.options).filter((x) => x !== "Default");
|
|
11
|
+
return vals.length ? vals.join(" / ") : fallback;
|
|
12
|
+
};
|
|
13
|
+
async function importProducts(scope) {
|
|
14
|
+
const svc = scope.resolve(odoo_1.ODOO_MODULE);
|
|
15
|
+
const query = scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
16
|
+
const settings = ((await svc.getValue("section:product")) ?? {});
|
|
17
|
+
// 1) Read detailed products (templates + variants, optional images) from Odoo.
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
if (settings.odooCategoryIds?.length)
|
|
20
|
+
params.set("categoryIds", settings.odooCategoryIds.join(","));
|
|
21
|
+
if (settings.syncImages)
|
|
22
|
+
params.set("withImages", "1");
|
|
23
|
+
const resp = await (0, odoo_api_client_1.odooApi)(`/odoo/products${params.toString() ? `?${params}` : ""}`);
|
|
24
|
+
let products = resp.products ?? [];
|
|
25
|
+
if (resp.connected === false || resp.error) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
created: 0,
|
|
29
|
+
updated: 0,
|
|
30
|
+
skipped: 0,
|
|
31
|
+
failed: 0,
|
|
32
|
+
message: resp.error || "Could not reach Odoo.",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Plan limit: cap the number of products synced per run.
|
|
36
|
+
const plan = await (0, plan_1.getPlanLimits)(scope);
|
|
37
|
+
let planCapped = 0;
|
|
38
|
+
if (plan.productLimit != null && products.length > plan.productLimit) {
|
|
39
|
+
planCapped = products.length - plan.productLimit;
|
|
40
|
+
products = products.slice(0, plan.productLimit);
|
|
41
|
+
}
|
|
42
|
+
if (!products.length) {
|
|
43
|
+
return { ok: true, created: 0, updated: 0, skipped: 0, failed: 0, message: "No products to import from Odoo." };
|
|
44
|
+
}
|
|
45
|
+
// 2) Medusa defaults.
|
|
46
|
+
const { data: channels } = await query.graph({ entity: "sales_channel", fields: ["id"] });
|
|
47
|
+
const { data: profiles } = await query.graph({ entity: "shipping_profile", fields: ["id"] });
|
|
48
|
+
const { data: stores } = await query.graph({
|
|
49
|
+
entity: "store",
|
|
50
|
+
fields: ["supported_currencies.currency_code", "supported_currencies.is_default"],
|
|
51
|
+
});
|
|
52
|
+
const salesChannelId = channels[0]?.id;
|
|
53
|
+
const shippingProfileId = profiles[0]?.id;
|
|
54
|
+
const currency = stores[0]?.supported_currencies?.find((c) => c.is_default)?.currency_code ??
|
|
55
|
+
stores[0]?.supported_currencies?.[0]?.currency_code ??
|
|
56
|
+
"usd";
|
|
57
|
+
const status = settings.createDraft
|
|
58
|
+
? utils_1.ProductStatus.DRAFT
|
|
59
|
+
: settings.autoPublish
|
|
60
|
+
? utils_1.ProductStatus.PUBLISHED
|
|
61
|
+
: utils_1.ProductStatus.DRAFT;
|
|
62
|
+
const mode = settings.creationMode ?? "create_update";
|
|
63
|
+
const updateFields = settings.updateFields ?? [];
|
|
64
|
+
const collectionId = settings.medusaCollectionIds?.[0];
|
|
65
|
+
const skuOf = (v) => (settings.sku_mapping === "barcode" ? v.barcode : v.sku) ?? v.sku ?? v.barcode ?? undefined;
|
|
66
|
+
// 3) Existing products keyed by Odoo id (metadata.odoo_id), so the handle can
|
|
67
|
+
// be a readable title slug without breaking re-import matching.
|
|
68
|
+
const { data: existingAll } = await query.graph({
|
|
69
|
+
entity: "product",
|
|
70
|
+
fields: ["id", "handle", "metadata", "images.id", "variants.id", "variants.sku"],
|
|
71
|
+
pagination: { take: 1000 },
|
|
72
|
+
});
|
|
73
|
+
const byOdooId = new Map();
|
|
74
|
+
const bySku = new Map();
|
|
75
|
+
const byHandle = new Map();
|
|
76
|
+
const usedHandles = new Set();
|
|
77
|
+
for (const e of existingAll) {
|
|
78
|
+
const variantsBySku = new Map();
|
|
79
|
+
for (const v of e.variants || [])
|
|
80
|
+
if (v.sku)
|
|
81
|
+
variantsBySku.set(v.sku, v.id);
|
|
82
|
+
const info = { id: e.id, hasImage: (e.images?.length ?? 0) > 0, variantsBySku };
|
|
83
|
+
if (e.handle) {
|
|
84
|
+
usedHandles.add(e.handle);
|
|
85
|
+
byHandle.set(e.handle, info);
|
|
86
|
+
}
|
|
87
|
+
const oid = e.metadata?.odoo_id;
|
|
88
|
+
if (oid != null)
|
|
89
|
+
byOdooId.set(Number(oid), info);
|
|
90
|
+
for (const v of e.variants || [])
|
|
91
|
+
if (v.sku)
|
|
92
|
+
bySku.set(v.sku, info);
|
|
93
|
+
}
|
|
94
|
+
// "The 3p Fulfilled Snowboard" → "the-3p-fulfilled-snowboard", de-duped.
|
|
95
|
+
const slugify = (s) => s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "product";
|
|
96
|
+
const uniqueHandle = (title) => {
|
|
97
|
+
const base = slugify(title);
|
|
98
|
+
let h = base;
|
|
99
|
+
let i = 2;
|
|
100
|
+
while (usedHandles.has(h))
|
|
101
|
+
h = `${base}-${i++}`;
|
|
102
|
+
usedHandles.add(h);
|
|
103
|
+
return h;
|
|
104
|
+
};
|
|
105
|
+
// Upload an Odoo base64 image to Medusa, return its URL (best-effort).
|
|
106
|
+
const uploadImage = async (p) => {
|
|
107
|
+
if (!settings.syncImages || !p.imageBase64)
|
|
108
|
+
return undefined;
|
|
109
|
+
try {
|
|
110
|
+
const { result } = await (0, core_flows_1.uploadFilesWorkflow)(scope).run({
|
|
111
|
+
input: {
|
|
112
|
+
files: [
|
|
113
|
+
{
|
|
114
|
+
filename: `odoo-${p.id}.png`,
|
|
115
|
+
mimeType: "image/png",
|
|
116
|
+
content: p.imageBase64,
|
|
117
|
+
access: "public",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return result[0]?.url;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const toCreate = [];
|
|
129
|
+
const toUpdate = [];
|
|
130
|
+
const variantsToCreate = []; // new Odoo variants on existing products
|
|
131
|
+
const variantsToUpdate = []; // existing variants matched by SKU
|
|
132
|
+
let skipped = 0;
|
|
133
|
+
for (const p of products) {
|
|
134
|
+
// Match by Odoo id first; fall back to an existing product with the same
|
|
135
|
+
// variant SKU (so unlinked products re-link instead of failing on a
|
|
136
|
+
// duplicate-SKU create).
|
|
137
|
+
let found = byOdooId.get(p.id);
|
|
138
|
+
if (!found) {
|
|
139
|
+
// Fall back to the deterministic title-slug handle.
|
|
140
|
+
found = byHandle.get(slugify(p.name));
|
|
141
|
+
}
|
|
142
|
+
if (!found) {
|
|
143
|
+
// Then to any matching variant SKU.
|
|
144
|
+
for (const v of p.variants) {
|
|
145
|
+
const sku = skuOf(v);
|
|
146
|
+
if (sku && bySku.has(sku)) {
|
|
147
|
+
found = bySku.get(sku);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (found) {
|
|
153
|
+
if (mode === "create") {
|
|
154
|
+
skipped++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
// Always (re)set the Odoo link in case it was cleared.
|
|
158
|
+
const upd = { id: found.id, status, metadata: { odoo_id: p.id } };
|
|
159
|
+
if (updateFields.includes("title"))
|
|
160
|
+
upd.title = p.name;
|
|
161
|
+
if (updateFields.includes("description"))
|
|
162
|
+
upd.description = p.description ?? undefined;
|
|
163
|
+
// Only upload an image if the product doesn't already have one.
|
|
164
|
+
if (!found.hasImage) {
|
|
165
|
+
const imageUrl = await uploadImage(p);
|
|
166
|
+
if (imageUrl)
|
|
167
|
+
upd.images = [{ url: imageUrl }];
|
|
168
|
+
}
|
|
169
|
+
toUpdate.push(upd);
|
|
170
|
+
// Reconcile variants by SKU: UPDATE the ones that already exist, CREATE
|
|
171
|
+
// the ones new in Odoo. Never delete/recreate — variants present in
|
|
172
|
+
// Medusa but missing from Odoo are left untouched.
|
|
173
|
+
for (const v of p.variants) {
|
|
174
|
+
const sku = skuOf(v);
|
|
175
|
+
if (!sku)
|
|
176
|
+
continue;
|
|
177
|
+
const prices = [{ amount: v.price, currency_code: currency }];
|
|
178
|
+
const existingVariantId = found.variantsBySku.get(sku);
|
|
179
|
+
if (existingVariantId) {
|
|
180
|
+
variantsToUpdate.push({
|
|
181
|
+
id: existingVariantId,
|
|
182
|
+
title: variantTitle(v, p.name),
|
|
183
|
+
prices,
|
|
184
|
+
...(v.barcode ? { barcode: v.barcode } : {}),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else if (!bySku.has(sku)) {
|
|
188
|
+
// New SKU nowhere in Medusa → safe to create on this product.
|
|
189
|
+
// (If the SKU exists on a DIFFERENT product, skip to avoid a collision.)
|
|
190
|
+
variantsToCreate.push({
|
|
191
|
+
product_id: found.id,
|
|
192
|
+
title: variantTitle(v, p.name),
|
|
193
|
+
sku,
|
|
194
|
+
options: v.options,
|
|
195
|
+
prices,
|
|
196
|
+
manage_inventory: false,
|
|
197
|
+
...(v.barcode ? { barcode: v.barcode } : {}),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
if (mode === "update") {
|
|
204
|
+
skipped++;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const imageUrl = await uploadImage(p);
|
|
208
|
+
toCreate.push({
|
|
209
|
+
title: p.name,
|
|
210
|
+
handle: uniqueHandle(p.name),
|
|
211
|
+
status,
|
|
212
|
+
description: p.description ?? undefined,
|
|
213
|
+
...(shippingProfileId ? { shipping_profile_id: shippingProfileId } : {}),
|
|
214
|
+
options: p.options,
|
|
215
|
+
variants: p.variants.map((v) => ({
|
|
216
|
+
title: variantTitle(v, p.name),
|
|
217
|
+
sku: skuOf(v),
|
|
218
|
+
...(v.barcode ? { barcode: v.barcode } : {}),
|
|
219
|
+
options: v.options,
|
|
220
|
+
prices: [{ amount: v.price, currency_code: currency }],
|
|
221
|
+
manage_inventory: false,
|
|
222
|
+
})),
|
|
223
|
+
...(salesChannelId ? { sales_channels: [{ id: salesChannelId }] } : {}),
|
|
224
|
+
...(collectionId ? { collection_id: collectionId } : {}),
|
|
225
|
+
...(imageUrl ? { images: [{ url: imageUrl }] } : {}),
|
|
226
|
+
metadata: { odoo_id: p.id },
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
let created = 0;
|
|
231
|
+
let updated = 0;
|
|
232
|
+
let failed = 0;
|
|
233
|
+
const errors = [];
|
|
234
|
+
try {
|
|
235
|
+
if (toCreate.length) {
|
|
236
|
+
await (0, core_flows_1.createProductsWorkflow)(scope).run({ input: { products: toCreate } });
|
|
237
|
+
created = toCreate.length;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
failed += toCreate.length;
|
|
242
|
+
errors.push(`create: ${e?.message ?? e}`);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
if (toUpdate.length) {
|
|
246
|
+
await (0, core_flows_1.updateProductsWorkflow)(scope).run({ input: { products: toUpdate } });
|
|
247
|
+
updated = toUpdate.length;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
failed += toUpdate.length;
|
|
252
|
+
errors.push(`update: ${e?.message ?? e}`);
|
|
253
|
+
}
|
|
254
|
+
// Variant reconciliation on existing products (update old, create new).
|
|
255
|
+
let variantsUpdated = 0;
|
|
256
|
+
let variantsCreated = 0;
|
|
257
|
+
try {
|
|
258
|
+
// De-dupe by variant id (duplicate Odoo templates can map to the same
|
|
259
|
+
// Medusa variant) and guard against any falsy ids.
|
|
260
|
+
const seen = new Set();
|
|
261
|
+
const cleanUpdates = variantsToUpdate.filter((v) => typeof v.id === "string" && v.id && !seen.has(v.id) && seen.add(v.id));
|
|
262
|
+
if (cleanUpdates.length) {
|
|
263
|
+
await (0, core_flows_1.updateProductVariantsWorkflow)(scope).run({
|
|
264
|
+
input: { product_variants: cleanUpdates },
|
|
265
|
+
});
|
|
266
|
+
variantsUpdated = cleanUpdates.length;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
errors.push(`variant update: ${e?.message ?? e}`);
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
// De-dupe by SKU (duplicate Odoo templates can list the same new variant).
|
|
274
|
+
const seenSku = new Set();
|
|
275
|
+
const cleanCreates = variantsToCreate.filter((v) => v.sku && !seenSku.has(v.sku) && seenSku.add(v.sku));
|
|
276
|
+
if (cleanCreates.length) {
|
|
277
|
+
await (0, core_flows_1.createProductVariantsWorkflow)(scope).run({
|
|
278
|
+
input: { product_variants: cleanCreates },
|
|
279
|
+
});
|
|
280
|
+
variantsCreated = cleanCreates.length;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
errors.push(`variant create: ${e?.message ?? e}`);
|
|
285
|
+
}
|
|
286
|
+
const variantNote = variantsCreated || variantsUpdated
|
|
287
|
+
? ` (variants: ${variantsCreated} new, ${variantsUpdated} updated)`
|
|
288
|
+
: "";
|
|
289
|
+
const capNote = planCapped
|
|
290
|
+
? ` ${planCapped} product(s) skipped — ${plan.name} plan limit of ${plan.productLimit}.`
|
|
291
|
+
: "";
|
|
292
|
+
const message = `Odoo → Medusa: ${created} created, ${updated} updated, ${skipped} skipped${failed ? `, ${failed} failed` : ""}${variantNote}.${capNote}${errors.length ? ` ${errors.join("; ")}` : ""}`;
|
|
293
|
+
try {
|
|
294
|
+
await (0, odoo_api_client_1.odooApi)("/odoo/logs", {
|
|
295
|
+
method: "POST",
|
|
296
|
+
body: JSON.stringify({ type: "product_sync", status: failed ? "failed" : "success", message }),
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
/* non-critical */
|
|
301
|
+
}
|
|
302
|
+
return { ok: failed === 0, created, updated, skipped, failed, message };
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0LXByb2R1Y3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9pbXBvcnQtcHJvZHVjdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUF3REEsd0NBaVRDO0FBeFdELHFEQUFvRjtBQUNwRiw0REFNb0M7QUFDcEMsMENBQTZDO0FBRTdDLHVEQUEyQztBQUMzQyxpQ0FBc0M7QUF1Q3RDLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBYyxFQUFFLFFBQWdCLEVBQUUsRUFBRTtJQUN4RCxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxTQUFTLENBQUMsQ0FBQTtJQUNwRSxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQTtBQUNsRCxDQUFDLENBQUE7QUFFTSxLQUFLLFVBQVUsY0FBYyxDQUFDLEtBQXNCO0lBQ3pELE1BQU0sR0FBRyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQXFCLGtCQUFXLENBQUMsQ0FBQTtJQUMxRCxNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQzVELE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxRQUFRLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBb0IsQ0FBQTtJQUVuRiwrRUFBK0U7SUFDL0UsTUFBTSxNQUFNLEdBQUcsSUFBSSxlQUFlLEVBQUUsQ0FBQTtJQUNwQyxJQUFJLFFBQVEsQ0FBQyxlQUFlLEVBQUUsTUFBTTtRQUFFLE1BQU0sQ0FBQyxHQUFHLENBQUMsYUFBYSxFQUFFLFFBQVEsQ0FBQyxlQUFlLENBQUMs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
|