@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.
Files changed (42) hide show
  1. package/.medusa/server/src/admin/index.js +2211 -0
  2. package/.medusa/server/src/admin/index.mjs +2212 -0
  3. package/.medusa/server/src/api/admin/odoo/billing/route.js +15 -0
  4. package/.medusa/server/src/api/admin/odoo/clear-orders/route.js +33 -0
  5. package/.medusa/server/src/api/admin/odoo/clear-products/route.js +30 -0
  6. package/.medusa/server/src/api/admin/odoo/config/route.js +44 -0
  7. package/.medusa/server/src/api/admin/odoo/customer-options/route.js +15 -0
  8. package/.medusa/server/src/api/admin/odoo/inventory-options/route.js +15 -0
  9. package/.medusa/server/src/api/admin/odoo/jobs/[id]/route.js +25 -0
  10. package/.medusa/server/src/api/admin/odoo/logs/route.js +27 -0
  11. package/.medusa/server/src/api/admin/odoo/options/route.js +15 -0
  12. package/.medusa/server/src/api/admin/odoo/order-options/route.js +15 -0
  13. package/.medusa/server/src/api/admin/odoo/product-options/route.js +15 -0
  14. package/.medusa/server/src/api/admin/odoo/sections/[section]/route.js +55 -0
  15. package/.medusa/server/src/api/admin/odoo/settings/route.js +91 -0
  16. package/.medusa/server/src/api/admin/odoo/stripe/change/route.js +20 -0
  17. package/.medusa/server/src/api/admin/odoo/stripe/checkout/route.js +20 -0
  18. package/.medusa/server/src/api/admin/odoo/stripe/portal/route.js +16 -0
  19. package/.medusa/server/src/api/admin/odoo/sync/[entity]/route.js +99 -0
  20. package/.medusa/server/src/api/admin/odoo/sync-order/[id]/route.js +17 -0
  21. package/.medusa/server/src/api/admin/odoo/verify-connection/route.js +39 -0
  22. package/.medusa/server/src/api/odoo/billing/sync/route.js +42 -0
  23. package/.medusa/server/src/index.js +3 -0
  24. package/.medusa/server/src/jobs/import-worker.js +61 -0
  25. package/.medusa/server/src/lib/export-products.js +141 -0
  26. package/.medusa/server/src/lib/import-products.js +304 -0
  27. package/.medusa/server/src/lib/odoo-api-client.js +32 -0
  28. package/.medusa/server/src/lib/plan.js +83 -0
  29. package/.medusa/server/src/lib/queue.js +79 -0
  30. package/.medusa/server/src/lib/sync-customers.js +183 -0
  31. package/.medusa/server/src/lib/sync-inventory.js +135 -0
  32. package/.medusa/server/src/lib/sync-order.js +134 -0
  33. package/.medusa/server/src/lib/sync-products.js +32 -0
  34. package/.medusa/server/src/modules/odoo/index.js +13 -0
  35. package/.medusa/server/src/modules/odoo/migrations/Migration20260619101616.js +17 -0
  36. package/.medusa/server/src/modules/odoo/models/odoo-setting.js +19 -0
  37. package/.medusa/server/src/modules/odoo/service.js +28 -0
  38. package/.medusa/server/src/subscribers/order-placed.js +24 -0
  39. package/.medusa/server/src/workflows/index.js +3 -0
  40. package/LICENSE +21 -0
  41. package/README.md +106 -0
  42. 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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -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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