first commit
This commit is contained in:
163
src/sync.js
Normal file
163
src/sync.js
Normal file
@@ -0,0 +1,163 @@
|
||||
const logger = require('./logger');
|
||||
const { publicConfig, resolveSyncDateRange } = require('./config');
|
||||
const { mapProductionOrder } = require('./mapper');
|
||||
const { TinyClient, findArrayPaths, getTopLevelShape } = require('./tinyClient');
|
||||
const { ProductionOrderRepository } = require('./db');
|
||||
|
||||
const safeRawPreview = (raw, maxKeys = 25) => {
|
||||
if (!raw || typeof raw !== 'object') return raw;
|
||||
const keys = Object.keys(raw).slice(0, maxKeys);
|
||||
return keys.reduce((preview, key) => {
|
||||
preview[key] = raw[key];
|
||||
return preview;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const orderWritePreview = (order, markers) => ({
|
||||
production_orders: {
|
||||
tiny_id: order.tinyId,
|
||||
number: order.number,
|
||||
status: order.status,
|
||||
order_reference: order.orderReference,
|
||||
issue_date: order.issueDate,
|
||||
expected_date: order.expectedDate,
|
||||
product_sku: order.productSku,
|
||||
product_description: order.productDescription,
|
||||
quantity: order.quantity,
|
||||
unit: order.unit,
|
||||
integration_status: order.integrationStatus,
|
||||
tiny_payload_keys: order.tinyPayload && typeof order.tinyPayload === 'object' ? Object.keys(order.tinyPayload) : []
|
||||
},
|
||||
production_order_markers: markers
|
||||
});
|
||||
|
||||
const runSync = async (config) => {
|
||||
const syncDateRange = resolveSyncDateRange(config);
|
||||
logger.info('Start config', {
|
||||
...publicConfig(config),
|
||||
resolvedDateRange: syncDateRange
|
||||
});
|
||||
|
||||
const tinyClient = new TinyClient(config);
|
||||
const repository = config.dryRun ? null : new ProductionOrderRepository(config.databaseUrl);
|
||||
|
||||
const stats = {
|
||||
pagesFetched: 0,
|
||||
ordersFound: 0,
|
||||
rowsMapped: 0,
|
||||
rowsWouldUpsert: 0,
|
||||
rowsUpserted: 0,
|
||||
skippedInvalidRows: 0,
|
||||
detailErrors: 0
|
||||
};
|
||||
|
||||
if (repository) await repository.connect();
|
||||
|
||||
try {
|
||||
let page = 1;
|
||||
let totalPages = 1;
|
||||
|
||||
do {
|
||||
const pageResult = await tinyClient.listProductionOrders(page, {
|
||||
startDate: syncDateRange.startDate,
|
||||
endDate: syncDateRange.endDate
|
||||
});
|
||||
|
||||
stats.pagesFetched += 1;
|
||||
totalPages = pageResult.totalPages;
|
||||
logger.info('Tiny production orders page fetched', {
|
||||
page,
|
||||
totalPages,
|
||||
dateRange: {
|
||||
startDate: syncDateRange.startDate,
|
||||
endDate: syncDateRange.endDate,
|
||||
mode: syncDateRange.mode,
|
||||
source: syncDateRange.source
|
||||
},
|
||||
endpoint: config.tinyListEndpoint,
|
||||
ordersFound: pageResult.orders.length
|
||||
});
|
||||
|
||||
if (config.dryRun) {
|
||||
logger.info('DRY_RUN Tiny payload shape', {
|
||||
page,
|
||||
endpoint: config.tinyListEndpoint,
|
||||
shape: getTopLevelShape(pageResult.raw),
|
||||
arrayPaths: findArrayPaths(pageResult.raw)
|
||||
});
|
||||
logger.info('DRY_RUN extracted order preview', {
|
||||
page,
|
||||
extractedOrders: pageResult.orders.length,
|
||||
firstOrderRawPreview: safeRawPreview(pageResult.orders[0] || null)
|
||||
});
|
||||
}
|
||||
|
||||
for (const summaryRecord of pageResult.orders) {
|
||||
stats.ordersFound += 1;
|
||||
let record = summaryRecord;
|
||||
|
||||
if (config.tinyFetchDetails) {
|
||||
const mappedSummary = mapProductionOrder(summaryRecord);
|
||||
const tinyId = mappedSummary.order?.tinyId;
|
||||
if (tinyId) {
|
||||
try {
|
||||
record = await tinyClient.getProductionOrderDetail(tinyId);
|
||||
} catch (error) {
|
||||
stats.detailErrors += 1;
|
||||
logger.warn('Tiny detail fetch failed, using list summary payload', {
|
||||
tinyId,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapped = mapProductionOrder(record);
|
||||
if (!mapped.order) {
|
||||
stats.skippedInvalidRows += 1;
|
||||
logger.warn('Skipped invalid Tiny production order row', {
|
||||
reason: mapped.reason,
|
||||
rawPreview: safeRawPreview(mapped.raw || record)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
stats.rowsMapped += 1;
|
||||
|
||||
if (config.dryRun) {
|
||||
stats.rowsWouldUpsert += 1;
|
||||
logger.info('DRY_RUN would upsert production order', {
|
||||
tinyId: mapped.order.tinyId,
|
||||
number: mapped.order.number,
|
||||
status: mapped.order.status,
|
||||
markers: mapped.markers.length,
|
||||
writePreview: orderWritePreview(mapped.order, mapped.markers),
|
||||
rawPreview: safeRawPreview(mapped.raw)
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
await repository.upsertOrder(mapped.order, mapped.markers);
|
||||
stats.rowsUpserted += 1;
|
||||
}
|
||||
|
||||
logger.info('Page sync complete', {
|
||||
page,
|
||||
rowsMappedSoFar: stats.rowsMapped,
|
||||
rowsWouldUpsertSoFar: stats.rowsWouldUpsert,
|
||||
rowsUpsertedSoFar: stats.rowsUpserted,
|
||||
skippedInvalidRowsSoFar: stats.skippedInvalidRows
|
||||
});
|
||||
|
||||
page += 1;
|
||||
} while (page <= totalPages);
|
||||
|
||||
logger.info('End summary', stats);
|
||||
return stats;
|
||||
} finally {
|
||||
if (repository) await repository.close();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
runSync
|
||||
};
|
||||
Reference in New Issue
Block a user