@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,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.odooApi = odooApi;
4
+ /**
5
+ * Server-side client for the standalone odoo-api service.
6
+ *
7
+ * Runs inside Medusa (Node), so the API key never reaches the browser. Configure
8
+ * via env on the Medusa app:
9
+ * ODOO_API_URL (default http://localhost:4000)
10
+ * ODOO_API_KEY (default dev-odoo-api-key)
11
+ */
12
+ const BASE = process.env.ODOO_API_URL || "http://localhost:4000";
13
+ const KEY = process.env.ODOO_API_KEY || "dev-odoo-api-key";
14
+ async function odooApi(path, init = {}) {
15
+ const res = await fetch(`${BASE}${path}`, {
16
+ ...init,
17
+ headers: {
18
+ "Content-Type": "application/json",
19
+ "x-api-key": KEY,
20
+ ...(init.headers || {}),
21
+ },
22
+ });
23
+ const text = await res.text();
24
+ const data = text ? JSON.parse(text) : null;
25
+ if (!res.ok) {
26
+ const message = (data && (data.message || data.error)) ||
27
+ `odoo-api request failed (${res.status})`;
28
+ throw new Error(message);
29
+ }
30
+ return data;
31
+ }
32
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2Rvby1hcGktY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9vZG9vLWFwaS1jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFXQSwwQkFxQkM7QUFoQ0Q7Ozs7Ozs7R0FPRztBQUNILE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxJQUFJLHVCQUF1QixDQUFBO0FBQ2hFLE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxJQUFJLGtCQUFrQixDQUFBO0FBRW5ELEtBQUssVUFBVSxPQUFPLENBQzNCLElBQVksRUFDWixPQUFvQixFQUFFO0lBRXRCLE1BQU0sR0FBRyxHQUFHLE1BQU0sS0FBSyxDQUFDLEdBQUcsSUFBSSxHQUFHLElBQUksRUFBRSxFQUFFO1FBQ3hDLEdBQUcsSUFBSTtRQUNQLE9BQU8sRUFBRTtZQUNQLGNBQWMsRUFBRSxrQkFBa0I7WUFDbEMsV0FBVyxFQUFFLEdBQUc7WUFDaEIsR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDO1NBQ3hCO0tBQ0YsQ0FBQyxDQUFBO0lBQ0YsTUFBTSxJQUFJLEdBQUcsTUFBTSxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUE7SUFDN0IsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7SUFDM0MsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUNaLE1BQU0sT0FBTyxHQUNYLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDdEMsNEJBQTRCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQTtRQUMzQyxNQUFNLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBQzFCLENBQUM7SUFDRCxPQUFPLElBQVMsQ0FBQTtBQUNsQixDQUFDIn0=
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ENTITY_MIN_PLAN = exports.PLANS = void 0;
4
+ exports.getPlanLimits = getPlanLimits;
5
+ exports.clampSchedule = clampSchedule;
6
+ const odoo_1 = require("../modules/odoo");
7
+ exports.PLANS = {
8
+ free: {
9
+ id: "free",
10
+ name: "Free",
11
+ productLimit: 100,
12
+ bothDirections: false,
13
+ maxSchedule: "off",
14
+ entities: { products: true, orders: false, inventory: false, customers: false },
15
+ },
16
+ starter: {
17
+ id: "starter",
18
+ name: "Starter",
19
+ productLimit: 1000,
20
+ bothDirections: true,
21
+ maxSchedule: "daily",
22
+ entities: { products: true, orders: true, inventory: false, customers: false },
23
+ },
24
+ pro: {
25
+ id: "pro",
26
+ name: "Pro",
27
+ productLimit: 10000,
28
+ bothDirections: true,
29
+ maxSchedule: "hourly",
30
+ entities: { products: true, orders: true, inventory: true, customers: true },
31
+ },
32
+ enterprise: {
33
+ id: "enterprise",
34
+ name: "Enterprise",
35
+ productLimit: null,
36
+ bothDirections: true,
37
+ maxSchedule: "realtime",
38
+ entities: { products: true, orders: true, inventory: true, customers: true },
39
+ },
40
+ };
41
+ /** Reads the active plan from settings; defaults to Free. */
42
+ async function getPlanLimits(scope) {
43
+ try {
44
+ const svc = scope.resolve(odoo_1.ODOO_MODULE);
45
+ const s = (await svc.getValue("section:plans"));
46
+ return (s?.planId && exports.PLANS[s.planId]) || exports.PLANS.free;
47
+ }
48
+ catch {
49
+ return exports.PLANS.free;
50
+ }
51
+ }
52
+ /** Which plan first unlocks each entity (for upgrade messaging). */
53
+ exports.ENTITY_MIN_PLAN = {
54
+ products: "Free",
55
+ orders: "Starter",
56
+ inventory: "Pro",
57
+ customers: "Pro",
58
+ };
59
+ // Schedule frequency ordering (coarse → fine).
60
+ const SCHED_RANK = {
61
+ off: 0,
62
+ daily: 1,
63
+ twice_daily: 2,
64
+ hourly: 3,
65
+ "30min": 4,
66
+ };
67
+ const PLAN_MAX_SCHEDULE_NAME = {
68
+ off: "off",
69
+ daily: "daily",
70
+ hourly: "hourly",
71
+ realtime: "30min",
72
+ };
73
+ /**
74
+ * Clamps a requested import schedule to what the plan allows. Anything finer
75
+ * than the plan's ceiling is reduced to that ceiling (Free → always "off").
76
+ */
77
+ function clampSchedule(requested, plan) {
78
+ const maxName = PLAN_MAX_SCHEDULE_NAME[plan.maxSchedule];
79
+ const reqRank = SCHED_RANK[requested ?? "off"] ?? 0;
80
+ const maxRank = SCHED_RANK[maxName] ?? 0;
81
+ return reqRank <= maxRank ? requested ?? "off" : maxName;
82
+ }
83
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGxhbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvcGxhbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUE0REEsc0NBUUM7QUFnQ0Qsc0NBS0M7QUF4R0QsMENBQTZDO0FBdUJoQyxRQUFBLEtBQUssR0FBK0I7SUFDL0MsSUFBSSxFQUFFO1FBQ0osRUFBRSxFQUFFLE1BQU07UUFDVixJQUFJLEVBQUUsTUFBTTtRQUNaLFlBQVksRUFBRSxHQUFHO1FBQ2pCLGNBQWMsRUFBRSxLQUFLO1FBQ3JCLFdBQVcsRUFBRSxLQUFLO1FBQ2xCLFFBQVEsRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUU7S0FDaEY7SUFDRCxPQUFPLEVBQUU7UUFDUCxFQUFFLEVBQUUsU0FBUztRQUNiLElBQUksRUFBRSxTQUFTO1FBQ2YsWUFBWSxFQUFFLElBQUk7UUFDbEIsY0FBYyxFQUFFLElBQUk7UUFDcEIsV0FBVyxFQUFFLE9BQU87UUFDcEIsUUFBUSxFQUFFLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRTtLQUMvRTtJQUNELEdBQUcsRUFBRTtRQUNILEVBQUUsRUFBRSxLQUFLO1FBQ1QsSUFBSSxFQUFFLEtBQUs7UUFDWCxZQUFZLEVBQUUsS0FBSztRQUNuQixjQUFjLEVBQUUsSUFBSTtRQUNwQixXQUFXLEVBQUUsUUFBUTtRQUNyQixRQUFRLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFO0tBQzdFO0lBQ0QsVUFBVSxFQUFFO1FBQ1YsRUFBRSxFQUFFLFlBQVk7UUFDaEIsSUFBSSxFQUFFLFlBQVk7UUFDbEIsWUFBWSxFQUFFLElBQUk7UUFDbEIsY0FBYyxFQUFFLElBQUk7UUFDcEIsV0FBVyxFQUFFLFVBQVU7UUFDdkIsUUFBUSxFQUFFLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRTtLQUM3RTtDQUNGLENBQUE7QUFFRCw2REFBNkQ7QUFDdEQsS0FBSyxVQUFVLGFBQWEsQ0FBQyxLQUFzQjtJQUN4RCxJQUFJLENBQUM7UUFDSCxNQUFNLEdBQUcsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFxQixrQkFBVyxDQUFDLENBQUE7UUFDMUQsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxRQUFRLENBQUMsZUFBZSxDQUFDLENBQStCLENBQUE7UUFDN0UsT0FBTyxDQUFDLENBQUMsRUFBRSxNQUFNLElBQUksYUFBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLGFBQUssQ0FBQyxJQUFJLENBQUE7SUFDckQsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sYUFBSyxDQUFDLElBQUksQ0FBQTtJQUNuQixDQUFDO0FBQ0gsQ0FBQztBQUtELG9FQUFvRTtBQUN2RCxRQUFBLGVBQWUsR0FBZ0M7SUFDMUQsUUFBUSxFQUFFLE1BQU07SUFDaEIsTUFBTSxFQUFFLFNBQVM7SUFDakIsU0FBUyxFQUFFLEtBQUs7SUFDaEIsU0FBUyxFQUFFLEtBQUs7Q0FDakIsQ0FBQTtBQUVELCtDQUErQztBQUMvQyxNQUFNLFVBQVUsR0FBMkI7SUFDekMsR0FBRyxFQUFFLENBQUM7SUFDTixLQUFLLEVBQUUsQ0FBQztJQUNSLFdBQVcsRUFBRSxDQUFDO0lBQ2QsTUFBTSxFQUFFLENBQUM7SUFDVCxPQUFPLEVBQUUsQ0FBQztDQUNYLENBQUE7QUFDRCxNQUFNLHNCQUFzQixHQUFpQztJQUMzRCxHQUFHLEVBQUUsS0FBSztJQUNWLEtBQUssRUFBRSxPQUFPO0lBQ2QsTUFBTSxFQUFFLFFBQVE7SUFDaEIsUUFBUSxFQUFFLE9BQU87Q0FDbEIsQ0FBQTtBQUVEOzs7R0FHRztBQUNILFNBQWdCLGFBQWEsQ0FBQyxTQUE2QixFQUFFLElBQWdCO0lBQzNFLE1BQU0sT0FBTyxHQUFHLHNCQUFzQixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQTtJQUN4RCxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsU0FBUyxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQTtJQUNuRCxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO0lBQ3hDLE9BQU8sT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLENBQUMsU0FBUyxJQUFJLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFBO0FBQzFELENBQUMifQ==
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRODUCT_SCHEDULER_ID = exports.redisConnection = exports.INVENTORY_SYNC_QUEUE = exports.CUSTOMER_SYNC_QUEUE = exports.ORDER_SYNC_QUEUE = exports.PRODUCT_IMPORT_QUEUE = exports.REDIS_URL = void 0;
4
+ exports.getProductImportQueue = getProductImportQueue;
5
+ exports.getOrderSyncQueue = getOrderSyncQueue;
6
+ exports.getCustomerSyncQueue = getCustomerSyncQueue;
7
+ exports.getInventorySyncQueue = getInventorySyncQueue;
8
+ exports.reconcileProductSchedule = reconcileProductSchedule;
9
+ const bullmq_1 = require("bullmq");
10
+ exports.REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
11
+ exports.PRODUCT_IMPORT_QUEUE = "odoo-product-import";
12
+ exports.ORDER_SYNC_QUEUE = "odoo-order-sync";
13
+ exports.CUSTOMER_SYNC_QUEUE = "odoo-customer-sync";
14
+ exports.INVENTORY_SYNC_QUEUE = "odoo-inventory-sync";
15
+ /**
16
+ * Connection options for BullMQ (it creates its own ioredis internally — don't
17
+ * pass a separately-installed instance or you hit a dual-package type clash).
18
+ * `maxRetriesPerRequest: null` is required by BullMQ workers.
19
+ */
20
+ const u = new URL(exports.REDIS_URL);
21
+ exports.redisConnection = {
22
+ host: u.hostname || "localhost",
23
+ port: Number(u.port || 6379),
24
+ ...(u.password ? { password: u.password } : {}),
25
+ ...(u.username ? { username: u.username } : {}),
26
+ maxRetriesPerRequest: null,
27
+ };
28
+ /** Single lazily-created producer queue (opens Redis on first use). */
29
+ let _queue;
30
+ function getProductImportQueue() {
31
+ if (!_queue) {
32
+ _queue = new bullmq_1.Queue(exports.PRODUCT_IMPORT_QUEUE, { connection: exports.redisConnection });
33
+ }
34
+ return _queue;
35
+ }
36
+ let _orderQueue;
37
+ function getOrderSyncQueue() {
38
+ if (!_orderQueue) {
39
+ _orderQueue = new bullmq_1.Queue(exports.ORDER_SYNC_QUEUE, { connection: exports.redisConnection });
40
+ }
41
+ return _orderQueue;
42
+ }
43
+ let _customerQueue;
44
+ function getCustomerSyncQueue() {
45
+ if (!_customerQueue) {
46
+ _customerQueue = new bullmq_1.Queue(exports.CUSTOMER_SYNC_QUEUE, { connection: exports.redisConnection });
47
+ }
48
+ return _customerQueue;
49
+ }
50
+ let _inventoryQueue;
51
+ function getInventorySyncQueue() {
52
+ if (!_inventoryQueue) {
53
+ _inventoryQueue = new bullmq_1.Queue(exports.INVENTORY_SYNC_QUEUE, { connection: exports.redisConnection });
54
+ }
55
+ return _inventoryQueue;
56
+ }
57
+ exports.PRODUCT_SCHEDULER_ID = "product-import-schedule";
58
+ /** Cron pattern per schedule option (null = no schedule). */
59
+ const SCHEDULE_CRON = {
60
+ off: null,
61
+ "30min": "*/30 * * * *",
62
+ hourly: "0 * * * *",
63
+ twice_daily: "0 0,12 * * *",
64
+ daily: "0 0 * * *",
65
+ };
66
+ /**
67
+ * Creates/updates/removes the repeatable import job to match the chosen
68
+ * schedule. Idempotent — safe to call on every settings save and on boot.
69
+ */
70
+ async function reconcileProductSchedule(schedule) {
71
+ const queue = getProductImportQueue();
72
+ const pattern = SCHEDULE_CRON[schedule ?? "off"];
73
+ if (!pattern) {
74
+ await queue.removeJobScheduler(exports.PRODUCT_SCHEDULER_ID).catch(() => { });
75
+ return;
76
+ }
77
+ await queue.upsertJobScheduler(exports.PRODUCT_SCHEDULER_ID, { pattern }, { name: "import", data: { scheduled: true } });
78
+ }
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVldWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3F1ZXVlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQXdCQSxzREFLQztBQUdELDhDQUtDO0FBR0Qsb0RBS0M7QUFHRCxzREFLQztBQWlCRCw0REFZQztBQWxGRCxtQ0FBc0Q7QUFFekMsUUFBQSxTQUFTLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLElBQUksd0JBQXdCLENBQUE7QUFDN0QsUUFBQSxvQkFBb0IsR0FBRyxxQkFBcUIsQ0FBQTtBQUM1QyxRQUFBLGdCQUFnQixHQUFHLGlCQUFpQixDQUFBO0FBQ3BDLFFBQUEsbUJBQW1CLEdBQUcsb0JBQW9CLENBQUE7QUFDMUMsUUFBQSxvQkFBb0IsR0FBRyxxQkFBcUIsQ0FBQTtBQUV6RDs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsaUJBQVMsQ0FBQyxDQUFBO0FBQ2YsUUFBQSxlQUFlLEdBQXNCO0lBQ2hELElBQUksRUFBRSxDQUFDLENBQUMsUUFBUSxJQUFJLFdBQVc7SUFDL0IsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQztJQUM1QixHQUFHLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDL0MsR0FBRyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQy9DLG9CQUFvQixFQUFFLElBQUk7Q0FDM0IsQ0FBQTtBQUVELHVFQUF1RTtBQUN2RSxJQUFJLE1BQXlCLENBQUE7QUFDN0IsU0FBZ0IscUJBQXFCO0lBQ25DLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNaLE1BQU0sR0FBRyxJQUFJLGNBQUssQ0FBQyw0QkFBb0IsRUFBRSxFQUFFLFVBQVUsRUFBRSx1QkFBZSxFQUFFLENBQUMsQ0FBQTtJQUMzRSxDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUE7QUFDZixDQUFDO0FBRUQsSUFBSSxXQUE4QixDQUFBO0FBQ2xDLFNBQWdCLGlCQUFpQjtJQUMvQixJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDakIsV0FBVyxHQUFHLElBQUksY0FBSyxDQUFDLHdCQUFnQixFQUFFLEVBQUUsVUFBVSxFQUFFLHVCQUFlLEVBQUUsQ0FBQyxDQUFBO0lBQzVFLENBQUM7SUFDRCxPQUFPLFdBQVcsQ0FBQTtBQUNwQixDQUFDO0FBRUQsSUFBSSxjQUFpQyxDQUFBO0FBQ3JDLFNBQWdCLG9CQUFvQjtJQUNsQyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDcEIsY0FBYyxHQUFHLElBQUksY0FBSyxDQUFDLDJCQUFtQixFQUFFLEVBQUUsVUFBVSxFQUFFLHVCQUFlLEVBQUUsQ0FBQyxDQUFBO0lBQ2xGLENBQUM7SUFDRCxPQUFPLGNBQWMsQ0FBQTtBQUN2QixDQUFDO0FBRUQsSUFBSSxlQUFrQyxDQUFBO0FBQ3RDLFNBQWdCLHFCQUFxQjtJQUNuQyxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7UUFDckIsZUFBZSxHQUFHLElBQUksY0FBSyxDQUFDLDRCQUFvQixFQUFFLEVBQUUsVUFBVSxFQUFFLHVCQUFlLEVBQUUsQ0FBQyxDQUFBO0lBQ3BGLENBQUM7SUFDRCxPQUFPLGVBQWUsQ0FBQTtBQUN4QixDQUFDO0FBRVksUUFBQSxvQkFBb0IsR0FBRyx5QkFBeUIsQ0FBQTtBQUU3RCw2REFBNkQ7QUFDN0QsTUFBTSxhQUFhLEdBQWtDO0lBQ25ELEdBQUcsRUFBRSxJQUFJO0lBQ1QsT0FBTyxFQUFFLGNBQWM7SUFDdkIsTUFBTSxFQUFFLFdBQVc7SUFDbkIsV0FBVyxFQUFFLGNBQWM7SUFDM0IsS0FBSyxFQUFFLFdBQVc7Q0FDbkIsQ0FBQTtBQUVEOzs7R0FHRztBQUNJLEtBQUssVUFBVSx3QkFBd0IsQ0FBQyxRQUFpQjtJQUM5RCxNQUFNLEtBQUssR0FBRyxxQkFBcUIsRUFBRSxDQUFBO0lBQ3JDLE1BQU0sT0FBTyxHQUFHLGFBQWEsQ0FBQyxRQUFRLElBQUksS0FBSyxDQUFDLENBQUE7SUFDaEQsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2IsTUFBTSxLQUFLLENBQUMsa0JBQWtCLENBQUMsNEJBQW9CLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLEdBQUUsQ0FBQyxDQUFDLENBQUE7UUFDcEUsT0FBTTtJQUNSLENBQUM7SUFDRCxNQUFNLEtBQUssQ0FBQyxrQkFBa0IsQ0FDNUIsNEJBQW9CLEVBQ3BCLEVBQUUsT0FBTyxFQUFFLEVBQ1gsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsRUFBRSxDQUM5QyxDQUFBO0FBQ0gsQ0FBQyJ9
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importCustomers = importCustomers;
4
+ exports.exportCustomers = exportCustomers;
5
+ exports.syncCustomers = syncCustomers;
6
+ const utils_1 = require("@medusajs/framework/utils");
7
+ const odoo_1 = require("../modules/odoo");
8
+ const odoo_api_client_1 = require("./odoo-api-client");
9
+ const plan_1 = require("./plan");
10
+ const splitName = (name) => {
11
+ const parts = (name || "").trim().split(/\s+/);
12
+ return { first: parts[0] || undefined, last: parts.slice(1).join(" ") || undefined };
13
+ };
14
+ const log = async (status, message) => {
15
+ try {
16
+ await (0, odoo_api_client_1.odooApi)("/odoo/logs", {
17
+ method: "POST",
18
+ body: JSON.stringify({ type: "customer_sync", status, message }),
19
+ });
20
+ }
21
+ catch {
22
+ /* non-critical */
23
+ }
24
+ };
25
+ /** Odoo → Medusa: create/update Medusa customers from Odoo res.partner (by email). */
26
+ async function importCustomers(scope) {
27
+ const svc = scope.resolve(odoo_1.ODOO_MODULE);
28
+ const query = scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
29
+ const settings = ((await svc.getValue("section:customers")) ?? {});
30
+ const mode = settings.creationMode ?? "create_update";
31
+ const resp = await (0, odoo_api_client_1.odooApi)("/odoo/customers");
32
+ if (resp.connected === false || resp.error) {
33
+ return { ok: false, created: 0, updated: 0, skipped: 0, failed: 0, message: resp.error || "Could not reach Odoo." };
34
+ }
35
+ const customers = resp.customers ?? [];
36
+ if (!customers.length) {
37
+ return { ok: true, created: 0, updated: 0, skipped: 0, failed: 0, message: "No customers to import from Odoo." };
38
+ }
39
+ const { data: existing } = await query.graph({
40
+ entity: "customer",
41
+ fields: ["id", "email"],
42
+ pagination: { take: 2000 },
43
+ });
44
+ const byEmail = new Map(existing.map((c) => [(c.email || "").toLowerCase(), c.id]));
45
+ const toCreate = [];
46
+ const toUpdate = [];
47
+ let skipped = 0;
48
+ for (const c of customers) {
49
+ const email = (c.email || "").toLowerCase();
50
+ if (!email) {
51
+ skipped++;
52
+ continue;
53
+ }
54
+ const { first, last } = splitName(c.name);
55
+ const existingId = byEmail.get(email);
56
+ if (existingId) {
57
+ if (mode === "create") {
58
+ skipped++;
59
+ continue;
60
+ }
61
+ toUpdate.push({ id: existingId, first_name: first, last_name: last, phone: c.phone || undefined });
62
+ }
63
+ else {
64
+ if (mode === "update") {
65
+ skipped++;
66
+ continue;
67
+ }
68
+ toCreate.push({ email: c.email, first_name: first, last_name: last, phone: c.phone || undefined });
69
+ }
70
+ }
71
+ const customerModule = scope.resolve(utils_1.Modules.CUSTOMER);
72
+ let created = 0;
73
+ let updated = 0;
74
+ let failed = 0;
75
+ try {
76
+ if (toCreate.length) {
77
+ await customerModule.createCustomers(toCreate);
78
+ created = toCreate.length;
79
+ }
80
+ }
81
+ catch {
82
+ failed += toCreate.length;
83
+ }
84
+ try {
85
+ if (toUpdate.length) {
86
+ await customerModule.updateCustomers(toUpdate);
87
+ updated = toUpdate.length;
88
+ }
89
+ }
90
+ catch {
91
+ failed += toUpdate.length;
92
+ }
93
+ const message = `Odoo → Medusa customers: ${created} created, ${updated} updated, ${skipped} skipped${failed ? `, ${failed} failed` : ""}.`;
94
+ await log(failed ? "failed" : "success", message);
95
+ return { ok: failed === 0, created, updated, skipped, failed, message };
96
+ }
97
+ /** Medusa → Odoo: push Medusa customers into Odoo res.partner. */
98
+ async function exportCustomers(scope) {
99
+ const svc = scope.resolve(odoo_1.ODOO_MODULE);
100
+ const query = scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
101
+ const settings = ((await svc.getValue("section:customers")) ?? {});
102
+ const mode = settings.creationMode ?? "create_update";
103
+ const { data: customers } = await query.graph({
104
+ entity: "customer",
105
+ fields: ["id", "email", "first_name", "last_name", "phone", "metadata"],
106
+ pagination: { take: 2000 },
107
+ });
108
+ const inputs = [];
109
+ const byId = new Map();
110
+ let skipped = 0;
111
+ for (const c of customers) {
112
+ if (!c.email) {
113
+ skipped++;
114
+ continue;
115
+ }
116
+ const odooId = c.metadata?.odoo_id ?? null;
117
+ if (odooId && mode === "create") {
118
+ skipped++;
119
+ continue;
120
+ }
121
+ if (!odooId && mode === "update") {
122
+ skipped++;
123
+ continue;
124
+ }
125
+ inputs.push({
126
+ externalId: c.id,
127
+ odooId,
128
+ name: [c.first_name, c.last_name].filter(Boolean).join(" ") || c.email,
129
+ email: c.email,
130
+ phone: c.phone || null,
131
+ });
132
+ byId.set(c.id, c);
133
+ }
134
+ if (!inputs.length) {
135
+ return { ok: true, created: 0, updated: 0, skipped, failed: 0, message: "Medusa → Odoo customers: nothing to export." };
136
+ }
137
+ let resp;
138
+ try {
139
+ resp = await (0, odoo_api_client_1.odooApi)("/odoo/customers-export", { method: "POST", body: JSON.stringify({ customers: inputs }) });
140
+ }
141
+ catch (e) {
142
+ return { ok: false, created: 0, updated: 0, skipped, failed: inputs.length, message: e?.message ?? "Export failed" };
143
+ }
144
+ let created = 0;
145
+ let updated = 0;
146
+ let failed = 0;
147
+ const updates = [];
148
+ for (const r of resp.results ?? []) {
149
+ if (r.error || !r.odooId) {
150
+ failed++;
151
+ continue;
152
+ }
153
+ if (r.created)
154
+ created++;
155
+ else
156
+ updated++;
157
+ const c = byId.get(r.externalId);
158
+ updates.push({ id: r.externalId, metadata: { ...(c?.metadata || {}), odoo_id: r.odooId } });
159
+ }
160
+ if (updates.length) {
161
+ const customerModule = scope.resolve(utils_1.Modules.CUSTOMER);
162
+ await customerModule.updateCustomers(updates);
163
+ }
164
+ const message = `Medusa → Odoo customers: ${created} created, ${updated} updated, ${skipped} skipped${failed ? `, ${failed} failed` : ""}.`;
165
+ await log(failed ? "failed" : "success", message);
166
+ return { ok: failed === 0, created, updated, skipped, failed, message };
167
+ }
168
+ /** Routes by direction; gated by the customerSync toggle and the active plan. */
169
+ async function syncCustomers(scope) {
170
+ const svc = scope.resolve(odoo_1.ODOO_MODULE);
171
+ const settings = ((await svc.getValue("section:customers")) ?? {});
172
+ if (!settings.customerSync) {
173
+ return { ok: true, created: 0, updated: 0, skipped: 0, failed: 0, message: "Customer sync is disabled." };
174
+ }
175
+ const plan = await (0, plan_1.getPlanLimits)(scope);
176
+ if (!plan.entities.customers) {
177
+ const message = `Customer sync requires the Pro plan or higher (current: ${plan.name}).`;
178
+ await log("skipped", message);
179
+ return { ok: false, created: 0, updated: 0, skipped: 0, failed: 0, message };
180
+ }
181
+ return settings.direction === "medusa_to_odoo" ? exportCustomers(scope) : importCustomers(scope);
182
+ }
183
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1jdXN0b21lcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3N5bmMtY3VzdG9tZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBdUNBLDBDQXdFQztBQUdELDBDQXVFQztBQUdELHNDQWFDO0FBeE1ELHFEQUE4RTtBQUM5RSwwQ0FBNkM7QUFFN0MsdURBQTJDO0FBQzNDLGlDQUFzQztBQWlCdEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRTtJQUNqQyxNQUFNLEtBQUssR0FBRyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDOUMsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxTQUFTLEVBQUUsQ0FBQTtBQUN0RixDQUFDLENBQUE7QUFFRCxNQUFNLEdBQUcsR0FBRyxLQUFLLEVBQUUsTUFBYyxFQUFFLE9BQWUsRUFBRSxFQUFFO0lBQ3BELElBQUksQ0FBQztRQUNILE1BQU0sSUFBQSx5QkFBTyxFQUFDLFlBQVksRUFBRTtZQUMxQixNQUFNLEVBQUUsTUFBTTtZQUNkLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLGVBQWUsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7U0FDakUsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLGtCQUFrQjtJQUNwQixDQUFDO0FBQ0gsQ0FBQyxDQUFBO0FBRUQsc0ZBQXNGO0FBQy9FLEtBQUssVUFBVSxlQUFlLENBQUMsS0FBc0I7SUFDMUQsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBcUIsa0JBQVcs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
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importInventory = importInventory;
4
+ exports.exportInventory = exportInventory;
5
+ exports.syncInventory = syncInventory;
6
+ const utils_1 = require("@medusajs/framework/utils");
7
+ const odoo_1 = require("../modules/odoo");
8
+ const odoo_api_client_1 = require("./odoo-api-client");
9
+ const plan_1 = require("./plan");
10
+ const log = async (status, message) => {
11
+ try {
12
+ await (0, odoo_api_client_1.odooApi)("/odoo/logs", {
13
+ method: "POST",
14
+ body: JSON.stringify({ type: "inventory_sync", status, message }),
15
+ });
16
+ }
17
+ catch {
18
+ /* non-critical */
19
+ }
20
+ };
21
+ /** Odoo → Medusa: read Odoo stock per mapped warehouse, set Medusa levels by SKU. */
22
+ async function importInventory(scope) {
23
+ const svc = scope.resolve(odoo_1.ODOO_MODULE);
24
+ const query = scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
25
+ const inventory = scope.resolve(utils_1.Modules.INVENTORY);
26
+ const settings = ((await svc.getValue("section:inventory")) ?? {});
27
+ if (!settings.enableStock) {
28
+ return { ok: true, updated: 0, skipped: 0, failed: 0, message: "Stock sync is disabled." };
29
+ }
30
+ const mappings = (settings.locationMappings ?? []).filter((m) => m.medusaLocationId && m.odooWarehouseId);
31
+ if (!mappings.length) {
32
+ return { ok: true, updated: 0, skipped: 0, failed: 0, message: "No location mappings configured." };
33
+ }
34
+ const { data: items } = await query.graph({
35
+ entity: "inventory_item",
36
+ fields: ["id", "sku"],
37
+ pagination: { take: 5000 },
38
+ });
39
+ const itemBySku = new Map(items.filter((i) => i.sku).map((i) => [i.sku, i.id]));
40
+ const skus = [...itemBySku.keys()];
41
+ if (!skus.length) {
42
+ return { ok: true, updated: 0, skipped: 0, failed: 0, message: "No SKUs to sync." };
43
+ }
44
+ let updated = 0;
45
+ let skipped = 0;
46
+ let failed = 0;
47
+ for (const map of mappings) {
48
+ const resp = await (0, odoo_api_client_1.odooApi)("/odoo/inventory/read", { method: "POST", body: JSON.stringify({ skus, warehouseId: map.odooWarehouseId, qtyType: settings.qtyType }) });
49
+ if (resp.connected === false || resp.error) {
50
+ return { ok: false, updated, skipped, failed, message: resp.error || "Could not reach Odoo." };
51
+ }
52
+ for (const lvl of resp.levels ?? []) {
53
+ const itemId = itemBySku.get(lvl.sku);
54
+ if (!itemId)
55
+ continue;
56
+ if (settings.doNotSyncZeroStock && lvl.qty === 0) {
57
+ skipped++;
58
+ continue;
59
+ }
60
+ try {
61
+ const existing = await inventory.listInventoryLevels({
62
+ inventory_item_id: itemId,
63
+ location_id: map.medusaLocationId,
64
+ });
65
+ if (existing[0]) {
66
+ await inventory.updateInventoryLevels([{ id: existing[0].id, stocked_quantity: lvl.qty }]);
67
+ }
68
+ else {
69
+ await inventory.createInventoryLevels([
70
+ { inventory_item_id: itemId, location_id: map.medusaLocationId, stocked_quantity: lvl.qty },
71
+ ]);
72
+ }
73
+ updated++;
74
+ }
75
+ catch {
76
+ failed++;
77
+ }
78
+ }
79
+ }
80
+ const message = `Odoo → Medusa inventory: ${updated} levels updated, ${skipped} skipped${failed ? `, ${failed} failed` : ""}.`;
81
+ await log(failed ? "failed" : "success", message);
82
+ return { ok: failed === 0, updated, skipped, failed, message };
83
+ }
84
+ /** Medusa → Odoo: push Medusa levels at mapped locations to the Odoo warehouse. */
85
+ async function exportInventory(scope) {
86
+ const svc = scope.resolve(odoo_1.ODOO_MODULE);
87
+ const query = scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
88
+ const inventory = scope.resolve(utils_1.Modules.INVENTORY);
89
+ const settings = ((await svc.getValue("section:inventory")) ?? {});
90
+ if (!settings.enableStock) {
91
+ return { ok: true, updated: 0, skipped: 0, failed: 0, message: "Stock sync is disabled." };
92
+ }
93
+ const mappings = (settings.locationMappings ?? []).filter((m) => m.medusaLocationId && m.odooWarehouseId);
94
+ if (!mappings.length) {
95
+ return { ok: true, updated: 0, skipped: 0, failed: 0, message: "No location mappings configured." };
96
+ }
97
+ const { data: items } = await query.graph({
98
+ entity: "inventory_item",
99
+ fields: ["id", "sku"],
100
+ pagination: { take: 5000 },
101
+ });
102
+ const skuByItem = new Map(items.filter((i) => i.sku).map((i) => [i.id, i.sku]));
103
+ let updated = 0;
104
+ let failed = 0;
105
+ for (const map of mappings) {
106
+ const levels = await inventory.listInventoryLevels({ location_id: map.medusaLocationId });
107
+ const out = levels
108
+ .map((l) => ({ sku: skuByItem.get(l.inventory_item_id), qty: l.stocked_quantity ?? 0 }))
109
+ .filter((x) => x.sku);
110
+ if (!out.length)
111
+ continue;
112
+ const resp = await (0, odoo_api_client_1.odooApi)("/odoo/inventory/write", { method: "POST", body: JSON.stringify({ levels: out, warehouseId: map.odooWarehouseId }) });
113
+ updated += resp.updated ?? 0;
114
+ failed += resp.failed ?? 0;
115
+ }
116
+ const message = `Medusa → Odoo inventory: ${updated} levels written${failed ? `, ${failed} failed` : ""}.`;
117
+ await log(failed ? "failed" : "success", message);
118
+ return { ok: failed === 0, updated, skipped: 0, failed, message };
119
+ }
120
+ /** Routes by direction; gated by the stock toggle and the active plan. */
121
+ async function syncInventory(scope) {
122
+ const svc = scope.resolve(odoo_1.ODOO_MODULE);
123
+ const settings = ((await svc.getValue("section:inventory")) ?? {});
124
+ if (!settings.enableStock) {
125
+ return { ok: true, updated: 0, skipped: 0, failed: 0, message: "Stock sync is disabled." };
126
+ }
127
+ const plan = await (0, plan_1.getPlanLimits)(scope);
128
+ if (!plan.entities.inventory) {
129
+ const message = `Inventory sync requires the Pro plan or higher (current: ${plan.name}).`;
130
+ await log("skipped", message);
131
+ return { ok: false, updated: 0, skipped: 0, failed: 0, message };
132
+ }
133
+ return settings.direction === "medusa_to_odoo" ? exportInventory(scope) : importInventory(scope);
134
+ }
135
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1pbnZlbnRvcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3N5bmMtaW52ZW50b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBb0NBLDBDQWtFQztBQUdELDBDQXdDQztBQUdELHNDQWFDO0FBaEtELHFEQUE4RTtBQUM5RSwwQ0FBNkM7QUFFN0MsdURBQTJDO0FBQzNDLGlDQUFzQztBQW1CdEMsTUFBTSxHQUFHLEdBQUcsS0FBSyxFQUFFLE1BQWMsRUFBRSxPQUFlLEVBQUUsRUFBRTtJQUNwRCxJQUFJLENBQUM7UUFDSCxNQUFNLElBQUEseUJBQU8sRUFBQyxZQUFZLEVBQUU7WUFDMUIsTUFBTSxFQUFFLE1BQU07WUFDZCxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLElBQUksRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7U0FDbEUsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLGtCQUFrQjtJQUNwQixDQUFDO0FBQ0gsQ0FBQyxDQUFBO0FBRUQscUZBQXFGO0FBQzlFLEtBQUssVUFBVSxlQUFlLENBQUMsS0FBc0I7SUFDMUQsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBcUIsa0JBQVcsQ0FBQyxDQUFBO0lBQzFELE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDNUQsTUFBTSxTQUFTLEdBQVEsS0FBSyxDQUFDLE9BQU8sQ0FBQyxlQUFPLENBQUMsU0FBUyxDQUFDLENBQUE7SUFDdkQsTUFBTSxRQUFRLEdBQUcsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLFFBQVEsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFzQixDQUFBO0lBRXZGLElBQUksQ0FBQyxRQUFRLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDMUIsT0FBTyxF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