@hasna/microservices 0.0.4 → 0.0.5
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.
- package/microservices/microservice-ads/src/cli/index.ts +198 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +304 -0
- package/microservices/microservice-ads/src/mcp/index.ts +160 -0
- package/microservices/microservice-contracts/src/cli/index.ts +410 -23
- package/microservices/microservice-contracts/src/db/contracts.ts +430 -1
- package/microservices/microservice-contracts/src/db/migrations.ts +83 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +312 -3
- package/microservices/microservice-domains/src/cli/index.ts +253 -0
- package/microservices/microservice-domains/src/db/domains.ts +613 -0
- package/microservices/microservice-domains/src/index.ts +21 -0
- package/microservices/microservice-domains/src/mcp/index.ts +168 -0
- package/microservices/microservice-hiring/src/cli/index.ts +318 -8
- package/microservices/microservice-hiring/src/db/hiring.ts +503 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +21 -0
- package/microservices/microservice-hiring/src/index.ts +29 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +245 -0
- package/microservices/microservice-payments/src/cli/index.ts +255 -3
- package/microservices/microservice-payments/src/db/migrations.ts +18 -0
- package/microservices/microservice-payments/src/db/payments.ts +552 -0
- package/microservices/microservice-payments/src/mcp/index.ts +223 -0
- package/microservices/microservice-payroll/src/cli/index.ts +269 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +26 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +636 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +246 -0
- package/microservices/microservice-shipping/src/cli/index.ts +211 -3
- package/microservices/microservice-shipping/src/db/migrations.ts +8 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +453 -3
- package/microservices/microservice-shipping/src/mcp/index.ts +149 -1
- package/microservices/microservice-social/src/cli/index.ts +244 -2
- package/microservices/microservice-social/src/db/migrations.ts +33 -0
- package/microservices/microservice-social/src/db/social.ts +378 -4
- package/microservices/microservice-social/src/mcp/index.ts +221 -1
- package/microservices/microservice-subscriptions/src/cli/index.ts +315 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +68 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +567 -3
- package/microservices/microservice-subscriptions/src/mcp/index.ts +267 -1
- package/package.json +1 -1
|
@@ -23,6 +23,17 @@ import {
|
|
|
23
23
|
getPayrollReport,
|
|
24
24
|
getYtdReport,
|
|
25
25
|
getTaxSummary,
|
|
26
|
+
createBenefit,
|
|
27
|
+
listBenefits,
|
|
28
|
+
removeBenefit,
|
|
29
|
+
generateAchFile,
|
|
30
|
+
generateW2,
|
|
31
|
+
generate1099,
|
|
32
|
+
setSchedule,
|
|
33
|
+
getNextPayPeriod,
|
|
34
|
+
auditPayroll,
|
|
35
|
+
forecastPayroll,
|
|
36
|
+
checkOvertime,
|
|
26
37
|
} from "../db/payroll.js";
|
|
27
38
|
|
|
28
39
|
const server = new McpServer({
|
|
@@ -407,6 +418,241 @@ server.registerTool(
|
|
|
407
418
|
}
|
|
408
419
|
);
|
|
409
420
|
|
|
421
|
+
// --- Benefits ---
|
|
422
|
+
|
|
423
|
+
server.registerTool(
|
|
424
|
+
"create_benefit",
|
|
425
|
+
{
|
|
426
|
+
title: "Create Benefit",
|
|
427
|
+
description: "Add a benefit deduction to an employee (health, dental, vision, retirement, hsa, other).",
|
|
428
|
+
inputSchema: {
|
|
429
|
+
employee_id: z.string(),
|
|
430
|
+
type: z.enum(["health", "dental", "vision", "retirement", "hsa", "other"]),
|
|
431
|
+
description: z.string().optional(),
|
|
432
|
+
amount: z.number(),
|
|
433
|
+
frequency: z.enum(["per_period", "monthly", "annual"]).optional(),
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
async (params) => {
|
|
437
|
+
try {
|
|
438
|
+
const benefit = createBenefit(params);
|
|
439
|
+
return { content: [{ type: "text", text: JSON.stringify(benefit, null, 2) }] };
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return {
|
|
442
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
443
|
+
isError: true,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
server.registerTool(
|
|
450
|
+
"list_benefits",
|
|
451
|
+
{
|
|
452
|
+
title: "List Benefits",
|
|
453
|
+
description: "List benefit deductions, optionally filtered by employee.",
|
|
454
|
+
inputSchema: {
|
|
455
|
+
employee_id: z.string().optional(),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
async ({ employee_id }) => {
|
|
459
|
+
const benefits = listBenefits(employee_id);
|
|
460
|
+
return {
|
|
461
|
+
content: [
|
|
462
|
+
{ type: "text", text: JSON.stringify({ benefits, count: benefits.length }, null, 2) },
|
|
463
|
+
],
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
server.registerTool(
|
|
469
|
+
"remove_benefit",
|
|
470
|
+
{
|
|
471
|
+
title: "Remove Benefit",
|
|
472
|
+
description: "Deactivate a benefit by ID.",
|
|
473
|
+
inputSchema: { id: z.string() },
|
|
474
|
+
},
|
|
475
|
+
async ({ id }) => {
|
|
476
|
+
const removed = removeBenefit(id);
|
|
477
|
+
if (!removed) {
|
|
478
|
+
return { content: [{ type: "text", text: `Benefit '${id}' not found.` }], isError: true };
|
|
479
|
+
}
|
|
480
|
+
return { content: [{ type: "text", text: JSON.stringify({ removed: true, id }) }] };
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// --- ACH File Generation ---
|
|
485
|
+
|
|
486
|
+
server.registerTool(
|
|
487
|
+
"generate_ach_file",
|
|
488
|
+
{
|
|
489
|
+
title: "Generate ACH File",
|
|
490
|
+
description: "Generate a NACHA-format ACH file for a completed pay period.",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
period_id: z.string(),
|
|
493
|
+
bank_routing: z.string(),
|
|
494
|
+
bank_account: z.string(),
|
|
495
|
+
company_name: z.string().optional(),
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
async ({ period_id, bank_routing, bank_account, company_name }) => {
|
|
499
|
+
try {
|
|
500
|
+
const content = generateAchFile(period_id, bank_routing, bank_account, company_name);
|
|
501
|
+
return { content: [{ type: "text", text: content }] };
|
|
502
|
+
} catch (error) {
|
|
503
|
+
return {
|
|
504
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
505
|
+
isError: true,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
// --- W-2 ---
|
|
512
|
+
|
|
513
|
+
server.registerTool(
|
|
514
|
+
"generate_w2",
|
|
515
|
+
{
|
|
516
|
+
title: "Generate W-2",
|
|
517
|
+
description: "Generate W-2 data for an employee for a given tax year.",
|
|
518
|
+
inputSchema: {
|
|
519
|
+
employee_id: z.string(),
|
|
520
|
+
year: z.number(),
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
async ({ employee_id, year }) => {
|
|
524
|
+
const w2 = generateW2(employee_id, year);
|
|
525
|
+
if (!w2) {
|
|
526
|
+
return {
|
|
527
|
+
content: [{ type: "text", text: "No W-2 data found (employee not found, is a contractor, or has no pay stubs)." }],
|
|
528
|
+
isError: true,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
return { content: [{ type: "text", text: JSON.stringify(w2, null, 2) }] };
|
|
532
|
+
}
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
// --- 1099-NEC ---
|
|
536
|
+
|
|
537
|
+
server.registerTool(
|
|
538
|
+
"generate_1099",
|
|
539
|
+
{
|
|
540
|
+
title: "Generate 1099-NEC",
|
|
541
|
+
description: "Generate 1099-NEC data for contractors with >$600 compensation in a given year.",
|
|
542
|
+
inputSchema: {
|
|
543
|
+
employee_id: z.string().optional(),
|
|
544
|
+
year: z.number(),
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
async ({ employee_id, year }) => {
|
|
548
|
+
const forms = generate1099(employee_id || null, year);
|
|
549
|
+
return {
|
|
550
|
+
content: [
|
|
551
|
+
{ type: "text", text: JSON.stringify({ forms, count: forms.length }, null, 2) },
|
|
552
|
+
],
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
// --- Payroll Schedule ---
|
|
558
|
+
|
|
559
|
+
server.registerTool(
|
|
560
|
+
"set_schedule",
|
|
561
|
+
{
|
|
562
|
+
title: "Set Payroll Schedule",
|
|
563
|
+
description: "Set the payroll schedule frequency and anchor date.",
|
|
564
|
+
inputSchema: {
|
|
565
|
+
frequency: z.enum(["weekly", "biweekly", "semimonthly", "monthly"]),
|
|
566
|
+
anchor_date: z.string(),
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
async ({ frequency, anchor_date }) => {
|
|
570
|
+
const schedule = setSchedule(frequency, anchor_date);
|
|
571
|
+
return { content: [{ type: "text", text: JSON.stringify(schedule, null, 2) }] };
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
server.registerTool(
|
|
576
|
+
"next_pay_period",
|
|
577
|
+
{
|
|
578
|
+
title: "Next Pay Period",
|
|
579
|
+
description: "Get the next pay period dates based on the configured payroll schedule.",
|
|
580
|
+
inputSchema: {},
|
|
581
|
+
},
|
|
582
|
+
async () => {
|
|
583
|
+
const next = getNextPayPeriod();
|
|
584
|
+
if (!next) {
|
|
585
|
+
return {
|
|
586
|
+
content: [{ type: "text", text: "No payroll schedule configured. Use set_schedule first." }],
|
|
587
|
+
isError: true,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
return { content: [{ type: "text", text: JSON.stringify(next, null, 2) }] };
|
|
591
|
+
}
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
// --- Audit ---
|
|
595
|
+
|
|
596
|
+
server.registerTool(
|
|
597
|
+
"audit_payroll",
|
|
598
|
+
{
|
|
599
|
+
title: "Audit Payroll",
|
|
600
|
+
description: "Audit a payroll period for issues: missing stubs, bad net_pay, deduction mismatches, duplicates.",
|
|
601
|
+
inputSchema: {
|
|
602
|
+
period_id: z.string(),
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
async ({ period_id }) => {
|
|
606
|
+
try {
|
|
607
|
+
const result = auditPayroll(period_id);
|
|
608
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
609
|
+
} catch (error) {
|
|
610
|
+
return {
|
|
611
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
612
|
+
isError: true,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
// --- Forecast ---
|
|
619
|
+
|
|
620
|
+
server.registerTool(
|
|
621
|
+
"forecast_payroll",
|
|
622
|
+
{
|
|
623
|
+
title: "Forecast Payroll",
|
|
624
|
+
description: "Project future payroll costs for a given number of months.",
|
|
625
|
+
inputSchema: {
|
|
626
|
+
months: z.number().min(1).max(60),
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
async ({ months }) => {
|
|
630
|
+
const result = forecastPayroll(months);
|
|
631
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
// --- Overtime ---
|
|
636
|
+
|
|
637
|
+
server.registerTool(
|
|
638
|
+
"check_overtime",
|
|
639
|
+
{
|
|
640
|
+
title: "Check Overtime",
|
|
641
|
+
description: "Flag employees exceeding a weekly hours threshold in the most recent completed period.",
|
|
642
|
+
inputSchema: {
|
|
643
|
+
threshold: z.number().optional(),
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
async ({ threshold }) => {
|
|
647
|
+
const alerts = checkOvertime(threshold);
|
|
648
|
+
return {
|
|
649
|
+
content: [
|
|
650
|
+
{ type: "text", text: JSON.stringify({ alerts, count: alerts.length }, null, 2) },
|
|
651
|
+
],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
|
|
410
656
|
// --- Start ---
|
|
411
657
|
async function main() {
|
|
412
658
|
const transport = new StdioServerTransport();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
4
5
|
import {
|
|
5
6
|
createOrder,
|
|
6
7
|
getOrder,
|
|
@@ -9,6 +10,9 @@ import {
|
|
|
9
10
|
deleteOrder,
|
|
10
11
|
searchOrders,
|
|
11
12
|
listByStatus,
|
|
13
|
+
bulkImportOrders,
|
|
14
|
+
exportOrders,
|
|
15
|
+
getOrderTimeline,
|
|
12
16
|
} from "../db/shipping.js";
|
|
13
17
|
import {
|
|
14
18
|
createShipment,
|
|
@@ -25,6 +29,11 @@ import {
|
|
|
25
29
|
import {
|
|
26
30
|
getShippingStats,
|
|
27
31
|
getCostsByCarrier,
|
|
32
|
+
getDeliveryStats,
|
|
33
|
+
listOverdueShipments,
|
|
34
|
+
getCustomerHistory,
|
|
35
|
+
getCarrierPerformance,
|
|
36
|
+
optimizeCost,
|
|
28
37
|
} from "../db/shipping.js";
|
|
29
38
|
|
|
30
39
|
const program = new Command();
|
|
@@ -152,6 +161,61 @@ orderCmd
|
|
|
152
161
|
}
|
|
153
162
|
});
|
|
154
163
|
|
|
164
|
+
orderCmd
|
|
165
|
+
.command("import")
|
|
166
|
+
.description("Bulk import orders from a CSV file")
|
|
167
|
+
.requiredOption("--file <path>", "Path to CSV file")
|
|
168
|
+
.option("--json", "Output as JSON", false)
|
|
169
|
+
.action((opts) => {
|
|
170
|
+
const csvData = readFileSync(opts.file, "utf-8");
|
|
171
|
+
const result = bulkImportOrders(csvData);
|
|
172
|
+
|
|
173
|
+
if (opts.json) {
|
|
174
|
+
console.log(JSON.stringify(result, null, 2));
|
|
175
|
+
} else {
|
|
176
|
+
console.log(`Imported ${result.imported} order(s)`);
|
|
177
|
+
if (result.errors.length > 0) {
|
|
178
|
+
console.log(`Errors:`);
|
|
179
|
+
for (const err of result.errors) {
|
|
180
|
+
console.log(` Line ${err.line}: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
orderCmd
|
|
187
|
+
.command("export")
|
|
188
|
+
.description("Export orders to CSV or JSON")
|
|
189
|
+
.option("--format <format>", "Output format (csv/json)", "csv")
|
|
190
|
+
.option("--from <date>", "Filter from date (YYYY-MM-DD)")
|
|
191
|
+
.option("--to <date>", "Filter to date (YYYY-MM-DD)")
|
|
192
|
+
.action((opts) => {
|
|
193
|
+
const output = exportOrders(opts.format as "csv" | "json", opts.from, opts.to);
|
|
194
|
+
console.log(output);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
orderCmd
|
|
198
|
+
.command("timeline")
|
|
199
|
+
.description("Show full event timeline for an order")
|
|
200
|
+
.argument("<id>", "Order ID")
|
|
201
|
+
.option("--json", "Output as JSON", false)
|
|
202
|
+
.action((id, opts) => {
|
|
203
|
+
const timeline = getOrderTimeline(id);
|
|
204
|
+
if (timeline.length === 0) {
|
|
205
|
+
console.error(`No events found for order '${id}'.`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (opts.json) {
|
|
210
|
+
console.log(JSON.stringify(timeline, null, 2));
|
|
211
|
+
} else {
|
|
212
|
+
console.log(`Timeline for order ${id}:`);
|
|
213
|
+
for (const event of timeline) {
|
|
214
|
+
console.log(` [${event.timestamp}] ${event.type}: ${event.details}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
155
219
|
// --- Ship (create shipment) ---
|
|
156
220
|
|
|
157
221
|
program
|
|
@@ -259,17 +323,20 @@ returnCmd
|
|
|
259
323
|
.description("Create a return request")
|
|
260
324
|
.requiredOption("--order <id>", "Order ID")
|
|
261
325
|
.option("--reason <reason>", "Return reason")
|
|
326
|
+
.option("--auto-rma", "Auto-generate RMA code", false)
|
|
262
327
|
.option("--json", "Output as JSON", false)
|
|
263
328
|
.action((opts) => {
|
|
264
329
|
const ret = createReturn({
|
|
265
330
|
order_id: opts.order,
|
|
266
331
|
reason: opts.reason,
|
|
332
|
+
auto_rma: opts.autoRma,
|
|
267
333
|
});
|
|
268
334
|
|
|
269
335
|
if (opts.json) {
|
|
270
336
|
console.log(JSON.stringify(ret, null, 2));
|
|
271
337
|
} else {
|
|
272
338
|
console.log(`Created return: ${ret.id} for order ${ret.order_id} [${ret.status}]`);
|
|
339
|
+
if (ret.rma_code) console.log(` RMA Code: ${ret.rma_code}`);
|
|
273
340
|
}
|
|
274
341
|
});
|
|
275
342
|
|
|
@@ -347,9 +414,11 @@ program
|
|
|
347
414
|
|
|
348
415
|
// --- Stats ---
|
|
349
416
|
|
|
350
|
-
program
|
|
351
|
-
|
|
352
|
-
|
|
417
|
+
const statsCmd = program.command("stats").description("Shipping statistics and analytics");
|
|
418
|
+
|
|
419
|
+
statsCmd
|
|
420
|
+
.command("overview")
|
|
421
|
+
.description("Show overall shipping statistics")
|
|
353
422
|
.option("--json", "Output as JSON", false)
|
|
354
423
|
.action((opts) => {
|
|
355
424
|
const stats = getShippingStats();
|
|
@@ -372,6 +441,56 @@ program
|
|
|
372
441
|
}
|
|
373
442
|
});
|
|
374
443
|
|
|
444
|
+
statsCmd
|
|
445
|
+
.command("delivery-times")
|
|
446
|
+
.description("Delivery timeline analytics per carrier/service")
|
|
447
|
+
.option("--carrier <carrier>", "Filter by carrier")
|
|
448
|
+
.option("--json", "Output as JSON", false)
|
|
449
|
+
.action((opts) => {
|
|
450
|
+
const stats = getDeliveryStats(opts.carrier);
|
|
451
|
+
|
|
452
|
+
if (opts.json) {
|
|
453
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
454
|
+
} else {
|
|
455
|
+
if (stats.length === 0) {
|
|
456
|
+
console.log("No delivery data available (need shipments with shipped_at and delivered_at).");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
console.log("Delivery Timeline Analytics:");
|
|
460
|
+
for (const s of stats) {
|
|
461
|
+
console.log(` ${s.carrier.toUpperCase()}/${s.service}:`);
|
|
462
|
+
console.log(` Shipments: ${s.total_shipments} (${s.delivered_count} delivered)`);
|
|
463
|
+
console.log(` Avg delivery: ${s.avg_delivery_days.toFixed(1)} days`);
|
|
464
|
+
console.log(` On-time: ${s.on_time_pct.toFixed(1)}%, Late: ${s.late_pct.toFixed(1)}%`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
statsCmd
|
|
470
|
+
.command("carrier-performance")
|
|
471
|
+
.description("Rank carriers by on-time %, avg cost, avg delivery days")
|
|
472
|
+
.option("--json", "Output as JSON", false)
|
|
473
|
+
.action((opts) => {
|
|
474
|
+
const perf = getCarrierPerformance();
|
|
475
|
+
|
|
476
|
+
if (opts.json) {
|
|
477
|
+
console.log(JSON.stringify(perf, null, 2));
|
|
478
|
+
} else {
|
|
479
|
+
if (perf.length === 0) {
|
|
480
|
+
console.log("No carrier data available.");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
console.log("Carrier Performance (ranked by on-time %):");
|
|
484
|
+
for (const p of perf) {
|
|
485
|
+
console.log(` ${p.carrier.toUpperCase()}:`);
|
|
486
|
+
console.log(` Shipments: ${p.total_shipments} (${p.delivered_count} delivered)`);
|
|
487
|
+
console.log(` On-time: ${p.on_time_pct.toFixed(1)}%`);
|
|
488
|
+
console.log(` Avg cost: $${p.avg_cost.toFixed(2)}`);
|
|
489
|
+
console.log(` Avg delivery: ${p.avg_delivery_days.toFixed(1)} days`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
|
|
375
494
|
// --- Costs ---
|
|
376
495
|
|
|
377
496
|
program
|
|
@@ -395,4 +514,93 @@ program
|
|
|
395
514
|
}
|
|
396
515
|
});
|
|
397
516
|
|
|
517
|
+
// --- Late Delivery Alerts ---
|
|
518
|
+
|
|
519
|
+
program
|
|
520
|
+
.command("check-late")
|
|
521
|
+
.description("List overdue shipments past estimated delivery + grace days")
|
|
522
|
+
.option("--days <n>", "Grace days beyond estimated delivery", "0")
|
|
523
|
+
.option("--json", "Output as JSON", false)
|
|
524
|
+
.action((opts) => {
|
|
525
|
+
const overdue = listOverdueShipments(parseInt(opts.days));
|
|
526
|
+
|
|
527
|
+
if (opts.json) {
|
|
528
|
+
console.log(JSON.stringify(overdue, null, 2));
|
|
529
|
+
} else {
|
|
530
|
+
if (overdue.length === 0) {
|
|
531
|
+
console.log("No overdue shipments found.");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
console.log(`Overdue Shipments (grace: ${opts.days} day(s)):`);
|
|
535
|
+
for (const s of overdue) {
|
|
536
|
+
const tracking = s.tracking_number ? ` (${s.tracking_number})` : "";
|
|
537
|
+
console.log(` ${s.shipment_id.slice(0, 8)}... ${s.carrier}/${s.service}${tracking}`);
|
|
538
|
+
console.log(` ETA: ${s.estimated_delivery}, ${s.days_overdue} day(s) overdue [${s.status}]`);
|
|
539
|
+
}
|
|
540
|
+
console.log(`\n${overdue.length} overdue shipment(s)`);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// --- Customer History ---
|
|
545
|
+
|
|
546
|
+
program
|
|
547
|
+
.command("customer-history")
|
|
548
|
+
.description("Show all orders, shipments, and returns for a customer")
|
|
549
|
+
.argument("<email>", "Customer email")
|
|
550
|
+
.option("--json", "Output as JSON", false)
|
|
551
|
+
.action((email, opts) => {
|
|
552
|
+
const history = getCustomerHistory(email);
|
|
553
|
+
|
|
554
|
+
if (opts.json) {
|
|
555
|
+
console.log(JSON.stringify(history, null, 2));
|
|
556
|
+
} else {
|
|
557
|
+
console.log(`Customer History for ${email}:`);
|
|
558
|
+
console.log(` Orders: ${history.orders.length}`);
|
|
559
|
+
for (const o of history.orders) {
|
|
560
|
+
console.log(` [${o.status}] ${o.id.slice(0, 8)}... $${o.total_value} ${o.currency}`);
|
|
561
|
+
}
|
|
562
|
+
console.log(` Shipments: ${history.shipments.length}`);
|
|
563
|
+
for (const s of history.shipments) {
|
|
564
|
+
console.log(` [${s.status}] ${s.id.slice(0, 8)}... ${s.carrier}/${s.service}`);
|
|
565
|
+
}
|
|
566
|
+
console.log(` Returns: ${history.returns.length}`);
|
|
567
|
+
for (const r of history.returns) {
|
|
568
|
+
const rma = r.rma_code ? ` (${r.rma_code})` : "";
|
|
569
|
+
console.log(` [${r.status}] ${r.id.slice(0, 8)}...${rma}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// --- Cost Optimizer ---
|
|
575
|
+
|
|
576
|
+
program
|
|
577
|
+
.command("optimize-cost")
|
|
578
|
+
.description("Recommend cheapest carrier/service based on historical data")
|
|
579
|
+
.requiredOption("--weight <kg>", "Package weight in kg")
|
|
580
|
+
.option("--from <zip>", "Origin zip code")
|
|
581
|
+
.option("--to <zip>", "Destination zip code")
|
|
582
|
+
.option("--json", "Output as JSON", false)
|
|
583
|
+
.action((opts) => {
|
|
584
|
+
const recommendations = optimizeCost(
|
|
585
|
+
parseFloat(opts.weight),
|
|
586
|
+
opts.from,
|
|
587
|
+
opts.to
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
if (opts.json) {
|
|
591
|
+
console.log(JSON.stringify(recommendations, null, 2));
|
|
592
|
+
} else {
|
|
593
|
+
if (recommendations.length === 0) {
|
|
594
|
+
console.log("No historical shipping data found for the given weight range.");
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
console.log(`Cost recommendations for ${opts.weight}kg package:`);
|
|
598
|
+
for (let i = 0; i < recommendations.length; i++) {
|
|
599
|
+
const r = recommendations[i];
|
|
600
|
+
const deliveryInfo = r.avg_delivery_days ? `, ~${r.avg_delivery_days.toFixed(1)} days` : "";
|
|
601
|
+
console.log(` ${i + 1}. ${r.carrier.toUpperCase()}/${r.service}: $${r.avg_cost.toFixed(2)} avg (${r.shipment_count} samples${deliveryInfo})`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
398
606
|
program.parse(process.argv);
|
|
@@ -58,4 +58,12 @@ export const MIGRATIONS: MigrationEntry[] = [
|
|
|
58
58
|
CREATE INDEX IF NOT EXISTS idx_returns_status ON returns(status);
|
|
59
59
|
`,
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
id: 2,
|
|
63
|
+
name: "add_rma_code",
|
|
64
|
+
sql: `
|
|
65
|
+
ALTER TABLE returns ADD COLUMN rma_code TEXT;
|
|
66
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_returns_rma_code ON returns(rma_code);
|
|
67
|
+
`,
|
|
68
|
+
},
|
|
61
69
|
];
|