import "dotenv/config"; import { sdkLogger as logger } from "../../reporting-service/logger.js"; import { createConnectorFromPath } from "../../../utils/connector-factory.js"; import { getEnvString } from "../../../utils/env.js"; import { summarizeError } from "../../../utils/error-utils.js"; import knex from "knex"; import config from "../../../knexfile.js"; import { SQLiteCoordinator } from "../../sqlite-service/index.js"; import { signalBreeJobComplete } from "../utils/bree-worker-complete.js"; // Configure the logger with the correct log level logger.level = getEnvString("LOG_LEVEL", "info"); const ensureLocalDbMigrated = async (): Promise => { const db = knex(config.local); try { await db.migrate.latest(); } finally { await db.destroy(); } }; const main = async () => { try { logger.info("==========Starting reconcile-work-orders job cycle=========="); // Get the connector path from the environment variable const connectorPath = process.env.CONNECTOR_PATH; if (!connectorPath) { throw new Error( "Connector path not provided in environment variables (CONNECTOR_PATH)" ); } // Create a new connector instance for this job. Like from-erp and the retry job, // connector construction does not depend on the local DB being migrated. const connector = await createConnectorFromPath(connectorPath); if (typeof connector.reconcileWorkOrders !== "function") { // Expected steady state for connectors that haven't opted into reconciliation: // the job runs on schedule like every other job and simply no-ops — without // touching the local DB. logger.info( "reconcile-work-orders: connector does not implement reconcileWorkOrders(); skipping this cycle" ); return; } // The coordinator lock is a single lock shared by every lock-respecting job (to-erp, // cache cleanup) with a 30 s acquire timeout, so hold it only for the one step that // needs it: the schema migration (DDL must not race those jobs). The run itself is // network-bound — MM paging, ERP fetches, MM writes — and its only SQLite work, the // dedupe-cache clear, goes through BatchCacheManager exactly as from-erp's writes do, // without the coordinator lock. Bree never overlaps this job with itself. await SQLiteCoordinator.executeWithLock( "reconcile-work-orders", ensureLocalDbMigrated ); await connector.reconcileWorkOrders!(); await connector.reconcileWorkOrdersCompleted?.(); logger.info("==========Completed reconcile-work-orders job cycle=========="); } catch (error) { logger.error('Worker for job "reconcile-work-orders" had an error', { error: summarizeError(error), connectorPath: process.env.CONNECTOR_PATH, }); throw error; // Rethrow so Bree can handle it properly } }; // Cross-platform module detection fix for Bree compatibility // Windows: process.argv[1] uses backslashes, import.meta.url uses forward slashes // Linux/Mac: both use forward slashes, so this normalization is safe // argv[1] is undefined under `node -e`, REPL, or test imports: treat as "not main". const normalizedArgv1 = (process.argv[1] ?? "").replace(/\\/g, '/'); const fileUrl = normalizedArgv1.startsWith('/') ? `file://${normalizedArgv1}` : // Unix: file:// + /path = file:///path `file:///${normalizedArgv1}`; // Windows: file:/// + C:/path = file:///C:/path const isMainModule = import.meta.url === fileUrl; if (isMainModule) { // This is called when Bree runs this file as a worker try { await main(); signalBreeJobComplete(); } catch { process.exit(1); } } export default main;