@hasna/microservices 0.0.3 → 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/bin/index.js +63 -0
- package/bin/mcp.js +63 -0
- package/dist/index.js +63 -0
- package/microservices/microservice-ads/package.json +27 -0
- package/microservices/microservice-ads/src/cli/index.ts +605 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
- package/microservices/microservice-ads/src/db/database.ts +93 -0
- package/microservices/microservice-ads/src/db/migrations.ts +60 -0
- package/microservices/microservice-ads/src/index.ts +39 -0
- package/microservices/microservice-ads/src/mcp/index.ts +480 -0
- package/microservices/microservice-contracts/package.json +27 -0
- package/microservices/microservice-contracts/src/cli/index.ts +770 -0
- package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
- package/microservices/microservice-contracts/src/db/database.ts +93 -0
- package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
- package/microservices/microservice-contracts/src/index.ts +43 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
- package/microservices/microservice-domains/package.json +27 -0
- package/microservices/microservice-domains/src/cli/index.ts +691 -0
- package/microservices/microservice-domains/src/db/database.ts +93 -0
- package/microservices/microservice-domains/src/db/domains.ts +1164 -0
- package/microservices/microservice-domains/src/db/migrations.ts +60 -0
- package/microservices/microservice-domains/src/index.ts +65 -0
- package/microservices/microservice-domains/src/mcp/index.ts +536 -0
- package/microservices/microservice-hiring/package.json +27 -0
- package/microservices/microservice-hiring/src/cli/index.ts +741 -0
- package/microservices/microservice-hiring/src/db/database.ts +93 -0
- package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
- package/microservices/microservice-hiring/src/index.ts +80 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
- package/microservices/microservice-payments/package.json +27 -0
- package/microservices/microservice-payments/src/cli/index.ts +609 -0
- package/microservices/microservice-payments/src/db/database.ts +93 -0
- package/microservices/microservice-payments/src/db/migrations.ts +81 -0
- package/microservices/microservice-payments/src/db/payments.ts +1204 -0
- package/microservices/microservice-payments/src/index.ts +51 -0
- package/microservices/microservice-payments/src/mcp/index.ts +683 -0
- package/microservices/microservice-payroll/package.json +27 -0
- package/microservices/microservice-payroll/src/cli/index.ts +643 -0
- package/microservices/microservice-payroll/src/db/database.ts +93 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
- package/microservices/microservice-payroll/src/index.ts +48 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
- package/microservices/microservice-shipping/package.json +27 -0
- package/microservices/microservice-shipping/src/cli/index.ts +606 -0
- package/microservices/microservice-shipping/src/db/database.ts +93 -0
- package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
- package/microservices/microservice-shipping/src/index.ts +53 -0
- package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
- package/microservices/microservice-social/package.json +27 -0
- package/microservices/microservice-social/src/cli/index.ts +689 -0
- package/microservices/microservice-social/src/db/database.ts +93 -0
- package/microservices/microservice-social/src/db/migrations.ts +88 -0
- package/microservices/microservice-social/src/db/social.ts +1046 -0
- package/microservices/microservice-social/src/index.ts +46 -0
- package/microservices/microservice-social/src/mcp/index.ts +655 -0
- package/microservices/microservice-subscriptions/package.json +27 -0
- package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
- package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
- package/microservices/microservice-subscriptions/src/index.ts +41 -0
- package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/microservice-shipping",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Shipping and order management microservice with SQLite — manage orders, shipments, tracking, and returns",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"microservice-shipping": "./src/cli/index.ts",
|
|
8
|
+
"microservice-shipping-mcp": "./src/mcp/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "bun run ./src/cli/index.ts",
|
|
15
|
+
"test": "bun test"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
19
|
+
"commander": "^12.1.0",
|
|
20
|
+
"zod": "^3.24.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "Apache-2.0",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org",
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import {
|
|
6
|
+
createOrder,
|
|
7
|
+
getOrder,
|
|
8
|
+
listOrders,
|
|
9
|
+
updateOrder,
|
|
10
|
+
deleteOrder,
|
|
11
|
+
searchOrders,
|
|
12
|
+
listByStatus,
|
|
13
|
+
bulkImportOrders,
|
|
14
|
+
exportOrders,
|
|
15
|
+
getOrderTimeline,
|
|
16
|
+
} from "../db/shipping.js";
|
|
17
|
+
import {
|
|
18
|
+
createShipment,
|
|
19
|
+
getShipment,
|
|
20
|
+
getShipmentByTracking,
|
|
21
|
+
listShipments,
|
|
22
|
+
updateShipment,
|
|
23
|
+
} from "../db/shipping.js";
|
|
24
|
+
import {
|
|
25
|
+
createReturn,
|
|
26
|
+
listReturns,
|
|
27
|
+
updateReturn,
|
|
28
|
+
} from "../db/shipping.js";
|
|
29
|
+
import {
|
|
30
|
+
getShippingStats,
|
|
31
|
+
getCostsByCarrier,
|
|
32
|
+
getDeliveryStats,
|
|
33
|
+
listOverdueShipments,
|
|
34
|
+
getCustomerHistory,
|
|
35
|
+
getCarrierPerformance,
|
|
36
|
+
optimizeCost,
|
|
37
|
+
} from "../db/shipping.js";
|
|
38
|
+
|
|
39
|
+
const program = new Command();
|
|
40
|
+
|
|
41
|
+
program
|
|
42
|
+
.name("microservice-shipping")
|
|
43
|
+
.description("Shipping and order management microservice")
|
|
44
|
+
.version("0.0.1");
|
|
45
|
+
|
|
46
|
+
// --- Orders ---
|
|
47
|
+
|
|
48
|
+
const orderCmd = program.command("order").description("Order management");
|
|
49
|
+
|
|
50
|
+
orderCmd
|
|
51
|
+
.command("create")
|
|
52
|
+
.description("Create a new order")
|
|
53
|
+
.requiredOption("--customer-name <name>", "Customer name")
|
|
54
|
+
.option("--customer-email <email>", "Customer email")
|
|
55
|
+
.option("--address <json>", "Address as JSON {street,city,state,zip,country}")
|
|
56
|
+
.option("--items <json>", "Items as JSON array [{name,qty,weight,value}]")
|
|
57
|
+
.option("--currency <code>", "Currency code", "USD")
|
|
58
|
+
.option("--json", "Output as JSON", false)
|
|
59
|
+
.action((opts) => {
|
|
60
|
+
const address = opts.address ? JSON.parse(opts.address) : { street: "", city: "", state: "", zip: "", country: "" };
|
|
61
|
+
const items = opts.items ? JSON.parse(opts.items) : [];
|
|
62
|
+
|
|
63
|
+
const order = createOrder({
|
|
64
|
+
customer_name: opts.customerName,
|
|
65
|
+
customer_email: opts.customerEmail,
|
|
66
|
+
address,
|
|
67
|
+
items,
|
|
68
|
+
currency: opts.currency,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (opts.json) {
|
|
72
|
+
console.log(JSON.stringify(order, null, 2));
|
|
73
|
+
} else {
|
|
74
|
+
console.log(`Created order: ${order.id} for ${order.customer_name} ($${order.total_value} ${order.currency})`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
orderCmd
|
|
79
|
+
.command("list")
|
|
80
|
+
.description("List orders")
|
|
81
|
+
.option("--status <status>", "Filter by status")
|
|
82
|
+
.option("--search <query>", "Search by customer name or email")
|
|
83
|
+
.option("--limit <n>", "Limit results")
|
|
84
|
+
.option("--json", "Output as JSON", false)
|
|
85
|
+
.action((opts) => {
|
|
86
|
+
const orders = listOrders({
|
|
87
|
+
status: opts.status,
|
|
88
|
+
search: opts.search,
|
|
89
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (opts.json) {
|
|
93
|
+
console.log(JSON.stringify(orders, null, 2));
|
|
94
|
+
} else {
|
|
95
|
+
if (orders.length === 0) {
|
|
96
|
+
console.log("No orders found.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (const o of orders) {
|
|
100
|
+
console.log(` [${o.status}] ${o.id.slice(0, 8)}... ${o.customer_name} — $${o.total_value} ${o.currency}`);
|
|
101
|
+
}
|
|
102
|
+
console.log(`\n${orders.length} order(s)`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
orderCmd
|
|
107
|
+
.command("get")
|
|
108
|
+
.description("Get an order by ID")
|
|
109
|
+
.argument("<id>", "Order ID")
|
|
110
|
+
.option("--json", "Output as JSON", false)
|
|
111
|
+
.action((id, opts) => {
|
|
112
|
+
const order = getOrder(id);
|
|
113
|
+
if (!order) {
|
|
114
|
+
console.error(`Order '${id}' not found.`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (opts.json) {
|
|
119
|
+
console.log(JSON.stringify(order, null, 2));
|
|
120
|
+
} else {
|
|
121
|
+
console.log(`Order: ${order.id}`);
|
|
122
|
+
console.log(` Customer: ${order.customer_name}`);
|
|
123
|
+
if (order.customer_email) console.log(` Email: ${order.customer_email}`);
|
|
124
|
+
console.log(` Status: ${order.status}`);
|
|
125
|
+
console.log(` Total: $${order.total_value} ${order.currency}`);
|
|
126
|
+
console.log(` Items: ${order.items.length}`);
|
|
127
|
+
console.log(` Address: ${order.address.street}, ${order.address.city}, ${order.address.state} ${order.address.zip}`);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
orderCmd
|
|
132
|
+
.command("update")
|
|
133
|
+
.description("Update an order")
|
|
134
|
+
.argument("<id>", "Order ID")
|
|
135
|
+
.option("--customer-name <name>", "Customer name")
|
|
136
|
+
.option("--customer-email <email>", "Customer email")
|
|
137
|
+
.option("--status <status>", "Order status")
|
|
138
|
+
.option("--address <json>", "Address as JSON")
|
|
139
|
+
.option("--items <json>", "Items as JSON")
|
|
140
|
+
.option("--currency <code>", "Currency code")
|
|
141
|
+
.option("--json", "Output as JSON", false)
|
|
142
|
+
.action((id, opts) => {
|
|
143
|
+
const input: Record<string, unknown> = {};
|
|
144
|
+
if (opts.customerName !== undefined) input.customer_name = opts.customerName;
|
|
145
|
+
if (opts.customerEmail !== undefined) input.customer_email = opts.customerEmail;
|
|
146
|
+
if (opts.status !== undefined) input.status = opts.status;
|
|
147
|
+
if (opts.address !== undefined) input.address = JSON.parse(opts.address);
|
|
148
|
+
if (opts.items !== undefined) input.items = JSON.parse(opts.items);
|
|
149
|
+
if (opts.currency !== undefined) input.currency = opts.currency;
|
|
150
|
+
|
|
151
|
+
const order = updateOrder(id, input);
|
|
152
|
+
if (!order) {
|
|
153
|
+
console.error(`Order '${id}' not found.`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (opts.json) {
|
|
158
|
+
console.log(JSON.stringify(order, null, 2));
|
|
159
|
+
} else {
|
|
160
|
+
console.log(`Updated order: ${order.id} [${order.status}]`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
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
|
+
|
|
219
|
+
// --- Ship (create shipment) ---
|
|
220
|
+
|
|
221
|
+
program
|
|
222
|
+
.command("ship")
|
|
223
|
+
.description("Create a shipment for an order")
|
|
224
|
+
.requiredOption("--order <id>", "Order ID")
|
|
225
|
+
.requiredOption("--carrier <carrier>", "Carrier (ups/fedex/usps/dhl)")
|
|
226
|
+
.option("--tracking <number>", "Tracking number")
|
|
227
|
+
.option("--service <service>", "Service level (ground/express/overnight)", "ground")
|
|
228
|
+
.option("--cost <amount>", "Shipping cost")
|
|
229
|
+
.option("--weight <kg>", "Package weight")
|
|
230
|
+
.option("--estimated-delivery <date>", "Estimated delivery date")
|
|
231
|
+
.option("--json", "Output as JSON", false)
|
|
232
|
+
.action((opts) => {
|
|
233
|
+
const shipment = createShipment({
|
|
234
|
+
order_id: opts.order,
|
|
235
|
+
carrier: opts.carrier,
|
|
236
|
+
tracking_number: opts.tracking,
|
|
237
|
+
service: opts.service,
|
|
238
|
+
cost: opts.cost ? parseFloat(opts.cost) : undefined,
|
|
239
|
+
weight: opts.weight ? parseFloat(opts.weight) : undefined,
|
|
240
|
+
estimated_delivery: opts.estimatedDelivery,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (opts.json) {
|
|
244
|
+
console.log(JSON.stringify(shipment, null, 2));
|
|
245
|
+
} else {
|
|
246
|
+
console.log(`Created shipment: ${shipment.id} via ${shipment.carrier} (${shipment.service})`);
|
|
247
|
+
if (shipment.tracking_number) console.log(` Tracking: ${shipment.tracking_number}`);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// --- Track ---
|
|
252
|
+
|
|
253
|
+
program
|
|
254
|
+
.command("track")
|
|
255
|
+
.description("Track a shipment by tracking number or shipment ID")
|
|
256
|
+
.argument("<identifier>", "Tracking number or shipment ID")
|
|
257
|
+
.option("--json", "Output as JSON", false)
|
|
258
|
+
.action((identifier, opts) => {
|
|
259
|
+
let shipment = getShipmentByTracking(identifier);
|
|
260
|
+
if (!shipment) {
|
|
261
|
+
shipment = getShipment(identifier);
|
|
262
|
+
}
|
|
263
|
+
if (!shipment) {
|
|
264
|
+
console.error(`Shipment '${identifier}' not found.`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (opts.json) {
|
|
269
|
+
console.log(JSON.stringify(shipment, null, 2));
|
|
270
|
+
} else {
|
|
271
|
+
console.log(`Shipment: ${shipment.id}`);
|
|
272
|
+
console.log(` Carrier: ${shipment.carrier}`);
|
|
273
|
+
console.log(` Service: ${shipment.service}`);
|
|
274
|
+
console.log(` Status: ${shipment.status}`);
|
|
275
|
+
if (shipment.tracking_number) console.log(` Tracking: ${shipment.tracking_number}`);
|
|
276
|
+
if (shipment.shipped_at) console.log(` Shipped: ${shipment.shipped_at}`);
|
|
277
|
+
if (shipment.estimated_delivery) console.log(` ETA: ${shipment.estimated_delivery}`);
|
|
278
|
+
if (shipment.delivered_at) console.log(` Delivered: ${shipment.delivered_at}`);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// --- Shipments ---
|
|
283
|
+
|
|
284
|
+
const shipmentsCmd = program.command("shipments").description("Shipment management");
|
|
285
|
+
|
|
286
|
+
shipmentsCmd
|
|
287
|
+
.command("list")
|
|
288
|
+
.description("List shipments")
|
|
289
|
+
.option("--order <id>", "Filter by order ID")
|
|
290
|
+
.option("--carrier <carrier>", "Filter by carrier")
|
|
291
|
+
.option("--status <status>", "Filter by status")
|
|
292
|
+
.option("--limit <n>", "Limit results")
|
|
293
|
+
.option("--json", "Output as JSON", false)
|
|
294
|
+
.action((opts) => {
|
|
295
|
+
const shipments = listShipments({
|
|
296
|
+
order_id: opts.order,
|
|
297
|
+
carrier: opts.carrier,
|
|
298
|
+
status: opts.status,
|
|
299
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (opts.json) {
|
|
303
|
+
console.log(JSON.stringify(shipments, null, 2));
|
|
304
|
+
} else {
|
|
305
|
+
if (shipments.length === 0) {
|
|
306
|
+
console.log("No shipments found.");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
for (const s of shipments) {
|
|
310
|
+
const tracking = s.tracking_number ? ` (${s.tracking_number})` : "";
|
|
311
|
+
console.log(` [${s.status}] ${s.id.slice(0, 8)}... ${s.carrier}/${s.service}${tracking}`);
|
|
312
|
+
}
|
|
313
|
+
console.log(`\n${shipments.length} shipment(s)`);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// --- Returns ---
|
|
318
|
+
|
|
319
|
+
const returnCmd = program.command("return").description("Return management");
|
|
320
|
+
|
|
321
|
+
returnCmd
|
|
322
|
+
.command("create")
|
|
323
|
+
.description("Create a return request")
|
|
324
|
+
.requiredOption("--order <id>", "Order ID")
|
|
325
|
+
.option("--reason <reason>", "Return reason")
|
|
326
|
+
.option("--auto-rma", "Auto-generate RMA code", false)
|
|
327
|
+
.option("--json", "Output as JSON", false)
|
|
328
|
+
.action((opts) => {
|
|
329
|
+
const ret = createReturn({
|
|
330
|
+
order_id: opts.order,
|
|
331
|
+
reason: opts.reason,
|
|
332
|
+
auto_rma: opts.autoRma,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (opts.json) {
|
|
336
|
+
console.log(JSON.stringify(ret, null, 2));
|
|
337
|
+
} else {
|
|
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}`);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
returnCmd
|
|
344
|
+
.command("list")
|
|
345
|
+
.description("List returns")
|
|
346
|
+
.option("--order <id>", "Filter by order ID")
|
|
347
|
+
.option("--status <status>", "Filter by status")
|
|
348
|
+
.option("--limit <n>", "Limit results")
|
|
349
|
+
.option("--json", "Output as JSON", false)
|
|
350
|
+
.action((opts) => {
|
|
351
|
+
const returns = listReturns({
|
|
352
|
+
order_id: opts.order,
|
|
353
|
+
status: opts.status,
|
|
354
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (opts.json) {
|
|
358
|
+
console.log(JSON.stringify(returns, null, 2));
|
|
359
|
+
} else {
|
|
360
|
+
if (returns.length === 0) {
|
|
361
|
+
console.log("No returns found.");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
for (const r of returns) {
|
|
365
|
+
const reason = r.reason ? ` — ${r.reason}` : "";
|
|
366
|
+
console.log(` [${r.status}] ${r.id.slice(0, 8)}... order ${r.order_id.slice(0, 8)}...${reason}`);
|
|
367
|
+
}
|
|
368
|
+
console.log(`\n${returns.length} return(s)`);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
returnCmd
|
|
373
|
+
.command("process")
|
|
374
|
+
.description("Process a return (update status)")
|
|
375
|
+
.argument("<id>", "Return ID")
|
|
376
|
+
.requiredOption("--status <status>", "New status (approved/received/refunded)")
|
|
377
|
+
.option("--json", "Output as JSON", false)
|
|
378
|
+
.action((id, opts) => {
|
|
379
|
+
const ret = updateReturn(id, { status: opts.status });
|
|
380
|
+
if (!ret) {
|
|
381
|
+
console.error(`Return '${id}' not found.`);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (opts.json) {
|
|
386
|
+
console.log(JSON.stringify(ret, null, 2));
|
|
387
|
+
} else {
|
|
388
|
+
console.log(`Updated return: ${ret.id} [${ret.status}]`);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// --- Carriers ---
|
|
393
|
+
|
|
394
|
+
program
|
|
395
|
+
.command("carriers")
|
|
396
|
+
.description("List supported carriers")
|
|
397
|
+
.option("--json", "Output as JSON", false)
|
|
398
|
+
.action((opts) => {
|
|
399
|
+
const carriers = [
|
|
400
|
+
{ code: "ups", name: "UPS", services: ["ground", "express", "overnight"] },
|
|
401
|
+
{ code: "fedex", name: "FedEx", services: ["ground", "express", "overnight"] },
|
|
402
|
+
{ code: "usps", name: "USPS", services: ["ground", "express"] },
|
|
403
|
+
{ code: "dhl", name: "DHL", services: ["ground", "express", "overnight"] },
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
if (opts.json) {
|
|
407
|
+
console.log(JSON.stringify(carriers, null, 2));
|
|
408
|
+
} else {
|
|
409
|
+
for (const c of carriers) {
|
|
410
|
+
console.log(` ${c.code.toUpperCase()} (${c.name}) — ${c.services.join(", ")}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// --- Stats ---
|
|
416
|
+
|
|
417
|
+
const statsCmd = program.command("stats").description("Shipping statistics and analytics");
|
|
418
|
+
|
|
419
|
+
statsCmd
|
|
420
|
+
.command("overview")
|
|
421
|
+
.description("Show overall shipping statistics")
|
|
422
|
+
.option("--json", "Output as JSON", false)
|
|
423
|
+
.action((opts) => {
|
|
424
|
+
const stats = getShippingStats();
|
|
425
|
+
|
|
426
|
+
if (opts.json) {
|
|
427
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
428
|
+
} else {
|
|
429
|
+
console.log("Shipping Statistics:");
|
|
430
|
+
console.log(` Orders: ${stats.total_orders}`);
|
|
431
|
+
for (const [status, count] of Object.entries(stats.orders_by_status)) {
|
|
432
|
+
console.log(` ${status}: ${count}`);
|
|
433
|
+
}
|
|
434
|
+
console.log(` Shipments: ${stats.total_shipments}`);
|
|
435
|
+
for (const [status, count] of Object.entries(stats.shipments_by_status)) {
|
|
436
|
+
console.log(` ${status}: ${count}`);
|
|
437
|
+
}
|
|
438
|
+
console.log(` Returns: ${stats.total_returns}`);
|
|
439
|
+
console.log(` Revenue: $${stats.total_revenue.toFixed(2)}`);
|
|
440
|
+
console.log(` Shipping Cost: $${stats.total_shipping_cost.toFixed(2)}`);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
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
|
+
|
|
494
|
+
// --- Costs ---
|
|
495
|
+
|
|
496
|
+
program
|
|
497
|
+
.command("costs")
|
|
498
|
+
.description("Show shipping costs by carrier")
|
|
499
|
+
.option("--json", "Output as JSON", false)
|
|
500
|
+
.action((opts) => {
|
|
501
|
+
const costs = getCostsByCarrier();
|
|
502
|
+
|
|
503
|
+
if (opts.json) {
|
|
504
|
+
console.log(JSON.stringify(costs, null, 2));
|
|
505
|
+
} else {
|
|
506
|
+
if (costs.length === 0) {
|
|
507
|
+
console.log("No shipping cost data.");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
console.log("Costs by Carrier:");
|
|
511
|
+
for (const c of costs) {
|
|
512
|
+
console.log(` ${c.carrier.toUpperCase()}: $${c.total_cost.toFixed(2)} total, ${c.shipment_count} shipments, $${c.avg_cost.toFixed(2)} avg`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
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
|
+
|
|
606
|
+
program.parse(process.argv);
|