Make product structure sync use product export
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m23s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m23s
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@ dist
|
|||||||
.env
|
.env
|
||||||
graphs_backfill_state.json
|
graphs_backfill_state.json
|
||||||
graphs_backfill_stop.json
|
graphs_backfill_stop.json
|
||||||
|
product_structures_sync_state.json
|
||||||
|
product_structures_sync_stop.json
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ RUN npm install --omit=dev
|
|||||||
|
|
||||||
# Copy built code from the builder stage
|
# Copy built code from the builder stage
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
|
COPY --from=builder /app/data ./data
|
||||||
|
|
||||||
# Expose the port the app runs on
|
# Expose the port the app runs on
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
7621
data/product-structure-products.csv
Normal file
7621
data/product-structure-products.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,10 @@ services:
|
|||||||
- GRAPHS_BACKFILL_ORDER_DELAY_MS=${GRAPHS_BACKFILL_ORDER_DELAY_MS:-0}
|
- GRAPHS_BACKFILL_ORDER_DELAY_MS=${GRAPHS_BACKFILL_ORDER_DELAY_MS:-0}
|
||||||
- GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS=${GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS:-600000}
|
- GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS=${GRAPHS_BACKFILL_TINY_BLOCK_DELAY_MS:-600000}
|
||||||
- GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES=${GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES:-0}
|
- GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES=${GRAPHS_BACKFILL_MAX_TINY_BLOCK_RETRIES:-0}
|
||||||
|
- PRODUCT_STRUCTURES_GRAPHS_API_URL=${PRODUCT_STRUCTURES_GRAPHS_API_URL}
|
||||||
|
- PRODUCT_STRUCTURES_GRAPHS_API_KEY=${PRODUCT_STRUCTURES_GRAPHS_API_KEY}
|
||||||
|
- PRODUCT_STRUCTURES_REQUEST_DELAY_MS=${PRODUCT_STRUCTURES_REQUEST_DELAY_MS:-2500}
|
||||||
|
- PRODUCT_STRUCTURES_FETCH_COMPONENT_UNITS=${PRODUCT_STRUCTURES_FETCH_COMPONENT_UNITS:-true}
|
||||||
- TINY_FETCH_SELLER_DETAILS=${TINY_FETCH_SELLER_DETAILS:-false}
|
- TINY_FETCH_SELLER_DETAILS=${TINY_FETCH_SELLER_DETAILS:-false}
|
||||||
- TINY_ORDER_DETAILS_CACHE_TTL_MS=${TINY_ORDER_DETAILS_CACHE_TTL_MS:-120000}
|
- TINY_ORDER_DETAILS_CACHE_TTL_MS=${TINY_ORDER_DETAILS_CACHE_TTL_MS:-120000}
|
||||||
- TINY_LIVE_REQUEST_DELAY_MS=${TINY_LIVE_REQUEST_DELAY_MS:-6000}
|
- TINY_LIVE_REQUEST_DELAY_MS=${TINY_LIVE_REQUEST_DELAY_MS:-6000}
|
||||||
|
|||||||
16
src/index.ts
16
src/index.ts
@@ -10,6 +10,18 @@ dotenv.config();
|
|||||||
const app: Express = express();
|
const app: Express = express();
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
function deriveProductionOrdersUrl(env: NodeJS.ProcessEnv) {
|
||||||
|
const raw = env.GRAPHS_API_URL || env.NEXSTAR_GRAPHS_API_URL || '';
|
||||||
|
if (!raw) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(raw);
|
||||||
|
return `${parsed.origin}/api/production-orders/tiny-sync`;
|
||||||
|
} catch {
|
||||||
|
return raw.replace(/\/api\/data.*$/, '/api/production-orders/tiny-sync');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
@@ -228,8 +240,8 @@ app.get('/api/trigger-product-structures-sync', (req: Request, res: Response) =>
|
|||||||
options: {
|
options: {
|
||||||
skus: env.PRODUCT_STRUCTURE_SKUS || null,
|
skus: env.PRODUCT_STRUCTURE_SKUS || null,
|
||||||
productIds: env.PRODUCT_STRUCTURE_PRODUCT_IDS || null,
|
productIds: env.PRODUCT_STRUCTURE_PRODUCT_IDS || null,
|
||||||
inputFile: env.PRODUCT_STRUCTURES_INPUT_FILE || null,
|
inputFile: env.PRODUCT_STRUCTURES_INPUT_FILE || '/app/data/product-structure-products.csv',
|
||||||
graphsUrl: env.PRODUCT_STRUCTURES_GRAPHS_API_URL || env.PRODUCTION_ORDERS_GRAPHS_API_URL || env.GRAPHS_PRODUCTION_ORDERS_API_URL || null,
|
graphsUrl: env.PRODUCT_STRUCTURES_GRAPHS_API_URL || env.PRODUCTION_ORDERS_GRAPHS_API_URL || env.GRAPHS_PRODUCTION_ORDERS_API_URL || deriveProductionOrdersUrl(env),
|
||||||
dryRun: env.PRODUCT_STRUCTURES_DRY_RUN || 'false',
|
dryRun: env.PRODUCT_STRUCTURES_DRY_RUN || 'false',
|
||||||
maxProducts: env.PRODUCT_STRUCTURES_MAX_PRODUCTS || null,
|
maxProducts: env.PRODUCT_STRUCTURES_MAX_PRODUCTS || null,
|
||||||
requestDelayMs: env.PRODUCT_STRUCTURES_REQUEST_DELAY_MS || '2500',
|
requestDelayMs: env.PRODUCT_STRUCTURES_REQUEST_DELAY_MS || '2500',
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ type ProductStructurePayload = {
|
|||||||
type SyncState = {
|
type SyncState = {
|
||||||
status?: 'running' | 'stopped' | 'completed';
|
status?: 'running' | 'stopped' | 'completed';
|
||||||
processedProducts?: number;
|
processedProducts?: number;
|
||||||
|
skippedProducts?: number;
|
||||||
failedProducts?: number;
|
failedProducts?: number;
|
||||||
lastSku?: string;
|
lastSku?: string;
|
||||||
lastFailedSku?: string;
|
lastFailedSku?: string;
|
||||||
@@ -60,12 +61,25 @@ type RequestedProduct = {
|
|||||||
sku: string;
|
sku: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function deriveProductionOrdersUrl() {
|
||||||
|
const raw = process.env.GRAPHS_API_URL || process.env.NEXSTAR_GRAPHS_API_URL || '';
|
||||||
|
if (!raw) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(raw);
|
||||||
|
return `${parsed.origin}/api/production-orders/tiny-sync`;
|
||||||
|
} catch {
|
||||||
|
return raw.replace(/\/api\/data.*$/, '/api/production-orders/tiny-sync');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const TINY_API_TOKEN = process.env.TINY_API_TOKEN;
|
const TINY_API_TOKEN = process.env.TINY_API_TOKEN;
|
||||||
const GRAPHS_PRODUCTION_ORDERS_URL = (
|
const GRAPHS_PRODUCTION_ORDERS_URL = (
|
||||||
process.env.PRODUCT_STRUCTURES_GRAPHS_API_URL ||
|
process.env.PRODUCT_STRUCTURES_GRAPHS_API_URL ||
|
||||||
process.env.PRODUCTION_ORDERS_GRAPHS_API_URL ||
|
process.env.PRODUCTION_ORDERS_GRAPHS_API_URL ||
|
||||||
process.env.GRAPHS_PRODUCTION_ORDERS_API_URL ||
|
process.env.GRAPHS_PRODUCTION_ORDERS_API_URL ||
|
||||||
(process.env.GRAPHS_API_BASE_URL ? `${process.env.GRAPHS_API_BASE_URL.replace(/\/+$/, '')}/api/production-orders/tiny-sync` : '') ||
|
(process.env.GRAPHS_API_BASE_URL ? `${process.env.GRAPHS_API_BASE_URL.replace(/\/+$/, '')}/api/production-orders/tiny-sync` : '') ||
|
||||||
|
deriveProductionOrdersUrl() ||
|
||||||
'http://localhost:3004/api/production-orders/tiny-sync'
|
'http://localhost:3004/api/production-orders/tiny-sync'
|
||||||
);
|
);
|
||||||
const GRAPHS_PRODUCTION_ORDERS_API_KEY = (
|
const GRAPHS_PRODUCTION_ORDERS_API_KEY = (
|
||||||
@@ -78,7 +92,8 @@ const GRAPHS_PRODUCTION_ORDERS_API_KEY = (
|
|||||||
process.env.API_KEY ||
|
process.env.API_KEY ||
|
||||||
'nexstar_secret_key_123'
|
'nexstar_secret_key_123'
|
||||||
);
|
);
|
||||||
const INPUT_FILE = process.env.PRODUCT_STRUCTURES_INPUT_FILE || '';
|
const DEFAULT_INPUT_FILE = path.join(process.cwd(), 'data', 'product-structure-products.csv');
|
||||||
|
const INPUT_FILE = process.env.PRODUCT_STRUCTURES_INPUT_FILE || (fs.existsSync(DEFAULT_INPUT_FILE) ? DEFAULT_INPUT_FILE : '');
|
||||||
const SKUS = String(process.env.PRODUCT_STRUCTURE_SKUS || '')
|
const SKUS = String(process.env.PRODUCT_STRUCTURE_SKUS || '')
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(value => value.trim())
|
.map(value => value.trim())
|
||||||
@@ -447,16 +462,18 @@ async function runSync() {
|
|||||||
saveState({
|
saveState({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
processedProducts: 0,
|
processedProducts: 0,
|
||||||
|
skippedProducts: 0,
|
||||||
failedProducts: 0,
|
failedProducts: 0,
|
||||||
lastMessage: 'Product structures sync started'
|
lastMessage: 'Product structures sync started'
|
||||||
});
|
});
|
||||||
|
|
||||||
let processedProducts = 0;
|
let processedProducts = 0;
|
||||||
|
let skippedProducts = 0;
|
||||||
let failedProducts = 0;
|
let failedProducts = 0;
|
||||||
|
|
||||||
for (const requested of requestedProducts) {
|
for (const requested of requestedProducts) {
|
||||||
assertNotStopped();
|
assertNotStopped();
|
||||||
if (MAX_PRODUCTS && processedProducts + failedProducts >= MAX_PRODUCTS) {
|
if (MAX_PRODUCTS && processedProducts + skippedProducts + failedProducts >= MAX_PRODUCTS) {
|
||||||
console.log(`[limit] Stopped after ${MAX_PRODUCTS} product(s).`);
|
console.log(`[limit] Stopped after ${MAX_PRODUCTS} product(s).`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -470,6 +487,7 @@ async function runSync() {
|
|||||||
saveState({
|
saveState({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
processedProducts,
|
processedProducts,
|
||||||
|
skippedProducts,
|
||||||
failedProducts,
|
failedProducts,
|
||||||
lastFailedSku: label,
|
lastFailedSku: label,
|
||||||
lastMessage: `Product not found for ${label}`
|
lastMessage: `Product not found for ${label}`
|
||||||
@@ -483,6 +501,7 @@ async function runSync() {
|
|||||||
saveState({
|
saveState({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
processedProducts,
|
processedProducts,
|
||||||
|
skippedProducts,
|
||||||
failedProducts,
|
failedProducts,
|
||||||
lastFailedSku: product.codigo || product.id,
|
lastFailedSku: product.codigo || product.id,
|
||||||
lastMessage: `Could not get structure for ${product.codigo || product.id}`
|
lastMessage: `Could not get structure for ${product.codigo || product.id}`
|
||||||
@@ -490,6 +509,20 @@ async function runSync() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!payload.components.length) {
|
||||||
|
skippedProducts += 1;
|
||||||
|
console.log(`[Tiny] No structure components for ${payload.order.productSku}; skipping Graphs send.`);
|
||||||
|
saveState({
|
||||||
|
status: 'running',
|
||||||
|
processedProducts,
|
||||||
|
skippedProducts,
|
||||||
|
failedProducts,
|
||||||
|
lastSku: payload.order.productSku,
|
||||||
|
lastMessage: `Skipped ${payload.order.productSku}; no structure components`
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const sent = await sendStructure(payload);
|
const sent = await sendStructure(payload);
|
||||||
if (sent) {
|
if (sent) {
|
||||||
processedProducts += 1;
|
processedProducts += 1;
|
||||||
@@ -500,6 +533,7 @@ async function runSync() {
|
|||||||
saveState({
|
saveState({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
processedProducts,
|
processedProducts,
|
||||||
|
skippedProducts,
|
||||||
failedProducts,
|
failedProducts,
|
||||||
lastSku: sent ? payload.order.productSku : undefined,
|
lastSku: sent ? payload.order.productSku : undefined,
|
||||||
lastFailedSku: sent ? undefined : payload.order.productSku,
|
lastFailedSku: sent ? undefined : payload.order.productSku,
|
||||||
@@ -512,6 +546,7 @@ async function runSync() {
|
|||||||
saveState({
|
saveState({
|
||||||
status: 'running',
|
status: 'running',
|
||||||
processedProducts,
|
processedProducts,
|
||||||
|
skippedProducts,
|
||||||
failedProducts,
|
failedProducts,
|
||||||
lastFailedSku: label,
|
lastFailedSku: label,
|
||||||
lastMessage: `Failed product ${label}; continuing`
|
lastMessage: `Failed product ${label}; continuing`
|
||||||
@@ -522,10 +557,11 @@ async function runSync() {
|
|||||||
saveState({
|
saveState({
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
processedProducts,
|
processedProducts,
|
||||||
|
skippedProducts,
|
||||||
failedProducts,
|
failedProducts,
|
||||||
lastMessage: failedProducts ? `Product structures sync completed with ${failedProducts} failed product(s)` : 'Product structures sync completed'
|
lastMessage: failedProducts ? `Product structures sync completed with ${failedProducts} failed product(s)` : 'Product structures sync completed'
|
||||||
});
|
});
|
||||||
console.log(`Done. Synced ${processedProducts} product structure(s). Failed products: ${failedProducts}.`);
|
console.log(`Done. Synced ${processedProducts} product structure(s). Skipped products: ${skippedProducts}. Failed products: ${failedProducts}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
runSync().catch((error: any) => {
|
runSync().catch((error: any) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user