@hasna/microservices 0.0.4 → 0.0.6
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 +9 -1
- package/bin/mcp.js +9 -1
- package/dist/index.js +9 -1
- 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-company/package.json +27 -0
- package/microservices/microservice-company/src/cli/index.ts +1126 -0
- package/microservices/microservice-company/src/db/company.ts +854 -0
- package/microservices/microservice-company/src/db/database.ts +93 -0
- package/microservices/microservice-company/src/db/migrations.ts +214 -0
- package/microservices/microservice-company/src/db/workflow-migrations.ts +44 -0
- package/microservices/microservice-company/src/index.ts +60 -0
- package/microservices/microservice-company/src/lib/audit.ts +168 -0
- package/microservices/microservice-company/src/lib/finance.ts +299 -0
- package/microservices/microservice-company/src/lib/settings.ts +85 -0
- package/microservices/microservice-company/src/lib/workflows.ts +698 -0
- package/microservices/microservice-company/src/mcp/index.ts +991 -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 +673 -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/lib/brandsight.ts +285 -0
- package/microservices/microservice-domains/src/lib/godaddy.ts +328 -0
- package/microservices/microservice-domains/src/lib/namecheap.ts +474 -0
- package/microservices/microservice-domains/src/lib/registrar.ts +355 -0
- package/microservices/microservice-domains/src/mcp/index.ts +413 -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
|
@@ -83,6 +83,7 @@ export interface Return {
|
|
|
83
83
|
id: string;
|
|
84
84
|
order_id: string;
|
|
85
85
|
reason: string | null;
|
|
86
|
+
rma_code: string | null;
|
|
86
87
|
status: "requested" | "approved" | "received" | "refunded";
|
|
87
88
|
created_at: string;
|
|
88
89
|
updated_at: string;
|
|
@@ -92,6 +93,7 @@ interface ReturnRow {
|
|
|
92
93
|
id: string;
|
|
93
94
|
order_id: string;
|
|
94
95
|
reason: string | null;
|
|
96
|
+
rma_code: string | null;
|
|
95
97
|
status: string;
|
|
96
98
|
created_at: string;
|
|
97
99
|
updated_at: string;
|
|
@@ -476,20 +478,32 @@ export interface CreateReturnInput {
|
|
|
476
478
|
order_id: string;
|
|
477
479
|
reason?: string;
|
|
478
480
|
status?: Return["status"];
|
|
481
|
+
auto_rma?: boolean;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function generateRmaCode(): string {
|
|
485
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
486
|
+
let code = "RMA-";
|
|
487
|
+
for (let i = 0; i < 8; i++) {
|
|
488
|
+
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
489
|
+
}
|
|
490
|
+
return code;
|
|
479
491
|
}
|
|
480
492
|
|
|
481
493
|
export function createReturn(input: CreateReturnInput): Return {
|
|
482
494
|
const db = getDatabase();
|
|
483
495
|
const id = crypto.randomUUID();
|
|
496
|
+
const rmaCode = input.auto_rma ? generateRmaCode() : null;
|
|
484
497
|
|
|
485
498
|
db.prepare(
|
|
486
|
-
`INSERT INTO returns (id, order_id, reason, status)
|
|
487
|
-
VALUES (?, ?, ?, ?)`
|
|
499
|
+
`INSERT INTO returns (id, order_id, reason, status, rma_code)
|
|
500
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
488
501
|
).run(
|
|
489
502
|
id,
|
|
490
503
|
input.order_id,
|
|
491
504
|
input.reason || null,
|
|
492
|
-
input.status || "requested"
|
|
505
|
+
input.status || "requested",
|
|
506
|
+
rmaCode
|
|
493
507
|
);
|
|
494
508
|
|
|
495
509
|
return getReturn(id)!;
|
|
@@ -641,3 +655,439 @@ export function getCostsByCarrier(): CarrierCosts[] {
|
|
|
641
655
|
).all() as CarrierCosts[];
|
|
642
656
|
return rows;
|
|
643
657
|
}
|
|
658
|
+
|
|
659
|
+
// ─── Bulk Import/Export ─────────────────────────────────────────────────────
|
|
660
|
+
|
|
661
|
+
export interface BulkImportResult {
|
|
662
|
+
imported: number;
|
|
663
|
+
errors: { line: number; message: string }[];
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export function bulkImportOrders(csvData: string): BulkImportResult {
|
|
667
|
+
const lines = csvData.trim().split("\n");
|
|
668
|
+
if (lines.length < 2) return { imported: 0, errors: [{ line: 1, message: "No data rows found" }] };
|
|
669
|
+
|
|
670
|
+
const header = lines[0].split(",").map((h) => h.trim().toLowerCase());
|
|
671
|
+
const result: BulkImportResult = { imported: 0, errors: [] };
|
|
672
|
+
|
|
673
|
+
for (let i = 1; i < lines.length; i++) {
|
|
674
|
+
try {
|
|
675
|
+
const values = parseCSVLine(lines[i]);
|
|
676
|
+
const row: Record<string, string> = {};
|
|
677
|
+
header.forEach((h, idx) => {
|
|
678
|
+
row[h] = (values[idx] || "").trim();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const address: Address = {
|
|
682
|
+
street: row["street"] || "",
|
|
683
|
+
city: row["city"] || "",
|
|
684
|
+
state: row["state"] || "",
|
|
685
|
+
zip: row["zip"] || "",
|
|
686
|
+
country: row["country"] || "US",
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
let items: OrderItem[] = [];
|
|
690
|
+
if (row["items_json"]) {
|
|
691
|
+
try {
|
|
692
|
+
items = JSON.parse(row["items_json"]);
|
|
693
|
+
} catch {
|
|
694
|
+
items = [];
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
createOrder({
|
|
699
|
+
customer_name: row["customer_name"] || "Unknown",
|
|
700
|
+
customer_email: row["customer_email"] || undefined,
|
|
701
|
+
address,
|
|
702
|
+
items,
|
|
703
|
+
total_value: row["total_value"] ? parseFloat(row["total_value"]) : undefined,
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
result.imported++;
|
|
707
|
+
} catch (err) {
|
|
708
|
+
result.errors.push({
|
|
709
|
+
line: i + 1,
|
|
710
|
+
message: err instanceof Error ? err.message : String(err),
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return result;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function parseCSVLine(line: string): string[] {
|
|
719
|
+
const result: string[] = [];
|
|
720
|
+
let current = "";
|
|
721
|
+
let inQuotes = false;
|
|
722
|
+
|
|
723
|
+
for (let i = 0; i < line.length; i++) {
|
|
724
|
+
const ch = line[i];
|
|
725
|
+
if (inQuotes) {
|
|
726
|
+
if (ch === '"' && line[i + 1] === '"') {
|
|
727
|
+
current += '"';
|
|
728
|
+
i++;
|
|
729
|
+
} else if (ch === '"') {
|
|
730
|
+
inQuotes = false;
|
|
731
|
+
} else {
|
|
732
|
+
current += ch;
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
if (ch === '"') {
|
|
736
|
+
inQuotes = true;
|
|
737
|
+
} else if (ch === ",") {
|
|
738
|
+
result.push(current);
|
|
739
|
+
current = "";
|
|
740
|
+
} else {
|
|
741
|
+
current += ch;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
result.push(current);
|
|
746
|
+
return result;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export function exportOrders(
|
|
750
|
+
format: "csv" | "json",
|
|
751
|
+
dateFrom?: string,
|
|
752
|
+
dateTo?: string
|
|
753
|
+
): string {
|
|
754
|
+
const db = getDatabase();
|
|
755
|
+
const conditions: string[] = [];
|
|
756
|
+
const params: unknown[] = [];
|
|
757
|
+
|
|
758
|
+
if (dateFrom) {
|
|
759
|
+
conditions.push("created_at >= ?");
|
|
760
|
+
params.push(dateFrom);
|
|
761
|
+
}
|
|
762
|
+
if (dateTo) {
|
|
763
|
+
conditions.push("created_at <= ?");
|
|
764
|
+
params.push(dateTo);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
let sql = "SELECT * FROM orders";
|
|
768
|
+
if (conditions.length > 0) {
|
|
769
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
770
|
+
}
|
|
771
|
+
sql += " ORDER BY created_at DESC";
|
|
772
|
+
|
|
773
|
+
const rows = db.prepare(sql).all(...params) as OrderRow[];
|
|
774
|
+
const orders = rows.map(rowToOrder);
|
|
775
|
+
|
|
776
|
+
if (format === "json") {
|
|
777
|
+
return JSON.stringify(orders, null, 2);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// CSV format
|
|
781
|
+
const csvHeader = "id,customer_name,customer_email,street,city,state,zip,country,items_json,total_value,currency,status,created_at";
|
|
782
|
+
const csvRows = orders.map((o) => {
|
|
783
|
+
const itemsJson = JSON.stringify(o.items).replace(/"/g, '""');
|
|
784
|
+
return [
|
|
785
|
+
o.id,
|
|
786
|
+
escapeCSV(o.customer_name),
|
|
787
|
+
o.customer_email || "",
|
|
788
|
+
escapeCSV(o.address.street),
|
|
789
|
+
escapeCSV(o.address.city),
|
|
790
|
+
escapeCSV(o.address.state),
|
|
791
|
+
o.address.zip,
|
|
792
|
+
o.address.country,
|
|
793
|
+
`"${itemsJson}"`,
|
|
794
|
+
o.total_value.toString(),
|
|
795
|
+
o.currency,
|
|
796
|
+
o.status,
|
|
797
|
+
o.created_at,
|
|
798
|
+
].join(",");
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
return [csvHeader, ...csvRows].join("\n");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function escapeCSV(value: string): string {
|
|
805
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
806
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
807
|
+
}
|
|
808
|
+
return value;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// ─── Delivery Timeline Analytics ────────────────────────────────────────────
|
|
812
|
+
|
|
813
|
+
export interface DeliveryStats {
|
|
814
|
+
carrier: string;
|
|
815
|
+
service: string;
|
|
816
|
+
total_shipments: number;
|
|
817
|
+
delivered_count: number;
|
|
818
|
+
avg_delivery_days: number;
|
|
819
|
+
on_time_pct: number;
|
|
820
|
+
late_pct: number;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
export function getDeliveryStats(carrier?: string): DeliveryStats[] {
|
|
824
|
+
const db = getDatabase();
|
|
825
|
+
const conditions: string[] = ["delivered_at IS NOT NULL", "shipped_at IS NOT NULL"];
|
|
826
|
+
const params: unknown[] = [];
|
|
827
|
+
|
|
828
|
+
if (carrier) {
|
|
829
|
+
conditions.push("carrier = ?");
|
|
830
|
+
params.push(carrier);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const sql = `
|
|
834
|
+
SELECT
|
|
835
|
+
carrier,
|
|
836
|
+
service,
|
|
837
|
+
COUNT(*) as total_shipments,
|
|
838
|
+
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered_count,
|
|
839
|
+
AVG(julianday(delivered_at) - julianday(shipped_at)) as avg_delivery_days,
|
|
840
|
+
SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at <= estimated_delivery THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as on_time_pct,
|
|
841
|
+
SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at > estimated_delivery THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as late_pct
|
|
842
|
+
FROM shipments
|
|
843
|
+
WHERE ${conditions.join(" AND ")}
|
|
844
|
+
GROUP BY carrier, service
|
|
845
|
+
ORDER BY carrier, service
|
|
846
|
+
`;
|
|
847
|
+
|
|
848
|
+
return db.prepare(sql).all(...params) as DeliveryStats[];
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// ─── Late Delivery Alerts ───────────────────────────────────────────────────
|
|
852
|
+
|
|
853
|
+
export interface OverdueShipment {
|
|
854
|
+
shipment_id: string;
|
|
855
|
+
order_id: string;
|
|
856
|
+
carrier: string;
|
|
857
|
+
service: string;
|
|
858
|
+
tracking_number: string | null;
|
|
859
|
+
estimated_delivery: string;
|
|
860
|
+
days_overdue: number;
|
|
861
|
+
status: string;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
export function listOverdueShipments(graceDays: number = 0): OverdueShipment[] {
|
|
865
|
+
const db = getDatabase();
|
|
866
|
+
|
|
867
|
+
const sql = `
|
|
868
|
+
SELECT
|
|
869
|
+
id as shipment_id,
|
|
870
|
+
order_id,
|
|
871
|
+
carrier,
|
|
872
|
+
service,
|
|
873
|
+
tracking_number,
|
|
874
|
+
estimated_delivery,
|
|
875
|
+
CAST(julianday('now') - julianday(estimated_delivery) - ? AS INTEGER) as days_overdue,
|
|
876
|
+
status
|
|
877
|
+
FROM shipments
|
|
878
|
+
WHERE estimated_delivery IS NOT NULL
|
|
879
|
+
AND status != 'delivered'
|
|
880
|
+
AND julianday('now') > julianday(estimated_delivery) + ?
|
|
881
|
+
ORDER BY days_overdue DESC
|
|
882
|
+
`;
|
|
883
|
+
|
|
884
|
+
return db.prepare(sql).all(graceDays, graceDays) as OverdueShipment[];
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// ─── Customer History ───────────────────────────────────────────────────────
|
|
888
|
+
|
|
889
|
+
export interface CustomerHistory {
|
|
890
|
+
customer_email: string;
|
|
891
|
+
orders: Order[];
|
|
892
|
+
shipments: Shipment[];
|
|
893
|
+
returns: Return[];
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export function getCustomerHistory(email: string): CustomerHistory {
|
|
897
|
+
const db = getDatabase();
|
|
898
|
+
|
|
899
|
+
const orderRows = db.prepare(
|
|
900
|
+
"SELECT * FROM orders WHERE customer_email = ? ORDER BY created_at DESC"
|
|
901
|
+
).all(email) as OrderRow[];
|
|
902
|
+
const orders = orderRows.map(rowToOrder);
|
|
903
|
+
|
|
904
|
+
const orderIds = orders.map((o) => o.id);
|
|
905
|
+
let shipments: Shipment[] = [];
|
|
906
|
+
let returns: Return[] = [];
|
|
907
|
+
|
|
908
|
+
if (orderIds.length > 0) {
|
|
909
|
+
const placeholders = orderIds.map(() => "?").join(",");
|
|
910
|
+
|
|
911
|
+
const shipmentRows = db.prepare(
|
|
912
|
+
`SELECT * FROM shipments WHERE order_id IN (${placeholders}) ORDER BY created_at DESC`
|
|
913
|
+
).all(...orderIds) as ShipmentRow[];
|
|
914
|
+
shipments = shipmentRows.map(rowToShipment);
|
|
915
|
+
|
|
916
|
+
const returnRows = db.prepare(
|
|
917
|
+
`SELECT * FROM returns WHERE order_id IN (${placeholders}) ORDER BY created_at DESC`
|
|
918
|
+
).all(...orderIds) as ReturnRow[];
|
|
919
|
+
returns = returnRows.map(rowToReturn);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
return { customer_email: email, orders, shipments, returns };
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ─── Carrier Performance ────────────────────────────────────────────────────
|
|
926
|
+
|
|
927
|
+
export interface CarrierPerformance {
|
|
928
|
+
carrier: string;
|
|
929
|
+
total_shipments: number;
|
|
930
|
+
delivered_count: number;
|
|
931
|
+
on_time_pct: number;
|
|
932
|
+
avg_cost: number;
|
|
933
|
+
avg_delivery_days: number;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export function getCarrierPerformance(): CarrierPerformance[] {
|
|
937
|
+
const db = getDatabase();
|
|
938
|
+
|
|
939
|
+
const sql = `
|
|
940
|
+
SELECT
|
|
941
|
+
carrier,
|
|
942
|
+
COUNT(*) as total_shipments,
|
|
943
|
+
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered_count,
|
|
944
|
+
COALESCE(
|
|
945
|
+
SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at IS NOT NULL AND delivered_at <= estimated_delivery THEN 1 ELSE 0 END) * 100.0
|
|
946
|
+
/ NULLIF(SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at IS NOT NULL THEN 1 ELSE 0 END), 0),
|
|
947
|
+
0
|
|
948
|
+
) as on_time_pct,
|
|
949
|
+
COALESCE(AVG(cost), 0) as avg_cost,
|
|
950
|
+
COALESCE(
|
|
951
|
+
AVG(CASE WHEN delivered_at IS NOT NULL AND shipped_at IS NOT NULL THEN julianday(delivered_at) - julianday(shipped_at) END),
|
|
952
|
+
0
|
|
953
|
+
) as avg_delivery_days
|
|
954
|
+
FROM shipments
|
|
955
|
+
GROUP BY carrier
|
|
956
|
+
ORDER BY on_time_pct DESC
|
|
957
|
+
`;
|
|
958
|
+
|
|
959
|
+
return db.prepare(sql).all() as CarrierPerformance[];
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// ─── Cost Optimizer ─────────────────────────────────────────────────────────
|
|
963
|
+
|
|
964
|
+
export interface CostRecommendation {
|
|
965
|
+
carrier: string;
|
|
966
|
+
service: string;
|
|
967
|
+
avg_cost: number;
|
|
968
|
+
avg_delivery_days: number;
|
|
969
|
+
shipment_count: number;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
export function optimizeCost(
|
|
973
|
+
weight: number,
|
|
974
|
+
_fromZip?: string,
|
|
975
|
+
_toZip?: string
|
|
976
|
+
): CostRecommendation[] {
|
|
977
|
+
const db = getDatabase();
|
|
978
|
+
|
|
979
|
+
// Find historical shipments with similar weight (within 50% range)
|
|
980
|
+
const minWeight = weight * 0.5;
|
|
981
|
+
const maxWeight = weight * 1.5;
|
|
982
|
+
|
|
983
|
+
const sql = `
|
|
984
|
+
SELECT
|
|
985
|
+
carrier,
|
|
986
|
+
service,
|
|
987
|
+
AVG(cost) as avg_cost,
|
|
988
|
+
AVG(CASE WHEN delivered_at IS NOT NULL AND shipped_at IS NOT NULL THEN julianday(delivered_at) - julianday(shipped_at) END) as avg_delivery_days,
|
|
989
|
+
COUNT(*) as shipment_count
|
|
990
|
+
FROM shipments
|
|
991
|
+
WHERE cost IS NOT NULL
|
|
992
|
+
AND weight IS NOT NULL
|
|
993
|
+
AND weight BETWEEN ? AND ?
|
|
994
|
+
GROUP BY carrier, service
|
|
995
|
+
HAVING shipment_count >= 1
|
|
996
|
+
ORDER BY avg_cost ASC
|
|
997
|
+
`;
|
|
998
|
+
|
|
999
|
+
return db.prepare(sql).all(minWeight, maxWeight) as CostRecommendation[];
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ─── Order Timeline ─────────────────────────────────────────────────────────
|
|
1003
|
+
|
|
1004
|
+
export interface TimelineEvent {
|
|
1005
|
+
timestamp: string;
|
|
1006
|
+
type: "order_created" | "order_updated" | "shipment_created" | "shipment_updated" | "return_created" | "return_updated";
|
|
1007
|
+
entity_id: string;
|
|
1008
|
+
details: string;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
export function getOrderTimeline(orderId: string): TimelineEvent[] {
|
|
1012
|
+
const db = getDatabase();
|
|
1013
|
+
const events: TimelineEvent[] = [];
|
|
1014
|
+
|
|
1015
|
+
// Order creation
|
|
1016
|
+
const order = db.prepare("SELECT * FROM orders WHERE id = ?").get(orderId) as OrderRow | null;
|
|
1017
|
+
if (!order) return [];
|
|
1018
|
+
|
|
1019
|
+
events.push({
|
|
1020
|
+
timestamp: order.created_at,
|
|
1021
|
+
type: "order_created",
|
|
1022
|
+
entity_id: order.id,
|
|
1023
|
+
details: `Order created for ${order.customer_name} — $${order.total_value} ${order.currency} [${order.status}]`,
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
if (order.updated_at !== order.created_at) {
|
|
1027
|
+
events.push({
|
|
1028
|
+
timestamp: order.updated_at,
|
|
1029
|
+
type: "order_updated",
|
|
1030
|
+
entity_id: order.id,
|
|
1031
|
+
details: `Order updated — status: ${order.status}`,
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Shipments
|
|
1036
|
+
const shipmentRows = db.prepare(
|
|
1037
|
+
"SELECT * FROM shipments WHERE order_id = ? ORDER BY created_at ASC"
|
|
1038
|
+
).all(orderId) as ShipmentRow[];
|
|
1039
|
+
|
|
1040
|
+
for (const s of shipmentRows) {
|
|
1041
|
+
events.push({
|
|
1042
|
+
timestamp: s.created_at,
|
|
1043
|
+
type: "shipment_created",
|
|
1044
|
+
entity_id: s.id,
|
|
1045
|
+
details: `Shipment created via ${s.carrier}/${s.service}${s.tracking_number ? ` (${s.tracking_number})` : ""} [${s.status}]`,
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
if (s.shipped_at) {
|
|
1049
|
+
events.push({
|
|
1050
|
+
timestamp: s.shipped_at,
|
|
1051
|
+
type: "shipment_updated",
|
|
1052
|
+
entity_id: s.id,
|
|
1053
|
+
details: `Shipment shipped via ${s.carrier}`,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
if (s.delivered_at) {
|
|
1057
|
+
events.push({
|
|
1058
|
+
timestamp: s.delivered_at,
|
|
1059
|
+
type: "shipment_updated",
|
|
1060
|
+
entity_id: s.id,
|
|
1061
|
+
details: `Shipment delivered via ${s.carrier}`,
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Returns
|
|
1067
|
+
const returnRows = db.prepare(
|
|
1068
|
+
"SELECT * FROM returns WHERE order_id = ? ORDER BY created_at ASC"
|
|
1069
|
+
).all(orderId) as ReturnRow[];
|
|
1070
|
+
|
|
1071
|
+
for (const r of returnRows) {
|
|
1072
|
+
events.push({
|
|
1073
|
+
timestamp: r.created_at,
|
|
1074
|
+
type: "return_created",
|
|
1075
|
+
entity_id: r.id,
|
|
1076
|
+
details: `Return requested${r.reason ? `: ${r.reason}` : ""}${r.rma_code ? ` (${r.rma_code})` : ""} [${r.status}]`,
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
if (r.updated_at !== r.created_at) {
|
|
1080
|
+
events.push({
|
|
1081
|
+
timestamp: r.updated_at,
|
|
1082
|
+
type: "return_updated",
|
|
1083
|
+
entity_id: r.id,
|
|
1084
|
+
details: `Return updated — status: ${r.status}`,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Sort by timestamp
|
|
1090
|
+
events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
1091
|
+
|
|
1092
|
+
return events;
|
|
1093
|
+
}
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
deleteOrder,
|
|
12
12
|
searchOrders,
|
|
13
13
|
listByStatus,
|
|
14
|
+
bulkImportOrders,
|
|
15
|
+
exportOrders,
|
|
16
|
+
getOrderTimeline,
|
|
14
17
|
} from "../db/shipping.js";
|
|
15
18
|
import {
|
|
16
19
|
createShipment,
|
|
@@ -28,6 +31,11 @@ import {
|
|
|
28
31
|
import {
|
|
29
32
|
getShippingStats,
|
|
30
33
|
getCostsByCarrier,
|
|
34
|
+
getDeliveryStats,
|
|
35
|
+
listOverdueShipments,
|
|
36
|
+
getCustomerHistory,
|
|
37
|
+
getCarrierPerformance,
|
|
38
|
+
optimizeCost,
|
|
31
39
|
} from "../db/shipping.js";
|
|
32
40
|
|
|
33
41
|
const server = new McpServer({
|
|
@@ -275,10 +283,11 @@ server.registerTool(
|
|
|
275
283
|
"create_return",
|
|
276
284
|
{
|
|
277
285
|
title: "Create Return",
|
|
278
|
-
description: "Create a return request for an order.",
|
|
286
|
+
description: "Create a return request for an order. Set auto_rma to true to generate a unique RMA code.",
|
|
279
287
|
inputSchema: {
|
|
280
288
|
order_id: z.string(),
|
|
281
289
|
reason: z.string().optional(),
|
|
290
|
+
auto_rma: z.boolean().optional(),
|
|
282
291
|
},
|
|
283
292
|
},
|
|
284
293
|
async (params) => {
|
|
@@ -372,6 +381,145 @@ server.registerTool(
|
|
|
372
381
|
}
|
|
373
382
|
);
|
|
374
383
|
|
|
384
|
+
// ─── Bulk Import/Export ──────────────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
server.registerTool(
|
|
387
|
+
"bulk_import_orders",
|
|
388
|
+
{
|
|
389
|
+
title: "Bulk Import Orders",
|
|
390
|
+
description: "Import orders from CSV data. Columns: customer_name,customer_email,street,city,state,zip,country,items_json,total_value.",
|
|
391
|
+
inputSchema: {
|
|
392
|
+
csv_data: z.string().describe("CSV data with header row"),
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
async ({ csv_data }) => {
|
|
396
|
+
const result = bulkImportOrders(csv_data);
|
|
397
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
server.registerTool(
|
|
402
|
+
"export_orders",
|
|
403
|
+
{
|
|
404
|
+
title: "Export Orders",
|
|
405
|
+
description: "Export orders in CSV or JSON format, optionally filtered by date range.",
|
|
406
|
+
inputSchema: {
|
|
407
|
+
format: z.enum(["csv", "json"]).describe("Output format"),
|
|
408
|
+
date_from: z.string().optional().describe("Filter from date (YYYY-MM-DD)"),
|
|
409
|
+
date_to: z.string().optional().describe("Filter to date (YYYY-MM-DD)"),
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
async ({ format, date_from, date_to }) => {
|
|
413
|
+
const output = exportOrders(format, date_from, date_to);
|
|
414
|
+
return { content: [{ type: "text", text: output }] };
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
// ─── Delivery Timeline Analytics ────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
server.registerTool(
|
|
421
|
+
"delivery_timeline_stats",
|
|
422
|
+
{
|
|
423
|
+
title: "Delivery Timeline Stats",
|
|
424
|
+
description: "Get delivery time analytics (avg days, on-time %, late %) per carrier and service level.",
|
|
425
|
+
inputSchema: {
|
|
426
|
+
carrier: z.string().optional().describe("Filter by carrier"),
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
async ({ carrier }) => {
|
|
430
|
+
const stats = getDeliveryStats(carrier);
|
|
431
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// ─── Late Delivery Alerts ───────────────────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
server.registerTool(
|
|
438
|
+
"list_overdue_shipments",
|
|
439
|
+
{
|
|
440
|
+
title: "List Overdue Shipments",
|
|
441
|
+
description: "Find shipments past their estimated delivery date + grace period that are not yet delivered.",
|
|
442
|
+
inputSchema: {
|
|
443
|
+
grace_days: z.number().optional().describe("Grace days beyond estimated delivery (default 0)"),
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
async ({ grace_days }) => {
|
|
447
|
+
const overdue = listOverdueShipments(grace_days ?? 0);
|
|
448
|
+
return { content: [{ type: "text", text: JSON.stringify({ overdue, count: overdue.length }, null, 2) }] };
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// ─── Customer History ───────────────────────────────────────────────────────
|
|
453
|
+
|
|
454
|
+
server.registerTool(
|
|
455
|
+
"customer_shipping_history",
|
|
456
|
+
{
|
|
457
|
+
title: "Customer Shipping History",
|
|
458
|
+
description: "Get all orders, shipments, and returns for a customer by email.",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
email: z.string().describe("Customer email address"),
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
async ({ email }) => {
|
|
464
|
+
const history = getCustomerHistory(email);
|
|
465
|
+
return { content: [{ type: "text", text: JSON.stringify(history, null, 2) }] };
|
|
466
|
+
}
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
// ─── Carrier Performance ────────────────────────────────────────────────────
|
|
470
|
+
|
|
471
|
+
server.registerTool(
|
|
472
|
+
"carrier_performance",
|
|
473
|
+
{
|
|
474
|
+
title: "Carrier Performance",
|
|
475
|
+
description: "Rank carriers by on-time delivery %, average cost, and average delivery days.",
|
|
476
|
+
inputSchema: {},
|
|
477
|
+
},
|
|
478
|
+
async () => {
|
|
479
|
+
const perf = getCarrierPerformance();
|
|
480
|
+
return { content: [{ type: "text", text: JSON.stringify(perf, null, 2) }] };
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// ─── Cost Optimizer ─────────────────────────────────────────────────────────
|
|
485
|
+
|
|
486
|
+
server.registerTool(
|
|
487
|
+
"optimize_shipping_cost",
|
|
488
|
+
{
|
|
489
|
+
title: "Optimize Shipping Cost",
|
|
490
|
+
description: "Recommend cheapest carrier/service based on historical cost data for a given package weight.",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
weight: z.number().describe("Package weight in kg"),
|
|
493
|
+
from_zip: z.string().optional().describe("Origin zip code"),
|
|
494
|
+
to_zip: z.string().optional().describe("Destination zip code"),
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
async ({ weight, from_zip, to_zip }) => {
|
|
498
|
+
const recommendations = optimizeCost(weight, from_zip, to_zip);
|
|
499
|
+
return { content: [{ type: "text", text: JSON.stringify(recommendations, null, 2) }] };
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
// ─── Order Timeline ─────────────────────────────────────────────────────────
|
|
504
|
+
|
|
505
|
+
server.registerTool(
|
|
506
|
+
"order_timeline",
|
|
507
|
+
{
|
|
508
|
+
title: "Order Timeline",
|
|
509
|
+
description: "Get the full event history for an order, including shipments and returns.",
|
|
510
|
+
inputSchema: {
|
|
511
|
+
order_id: z.string().describe("Order ID"),
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
async ({ order_id }) => {
|
|
515
|
+
const timeline = getOrderTimeline(order_id);
|
|
516
|
+
if (timeline.length === 0) {
|
|
517
|
+
return { content: [{ type: "text", text: `No events found for order '${order_id}'.` }], isError: true };
|
|
518
|
+
}
|
|
519
|
+
return { content: [{ type: "text", text: JSON.stringify(timeline, null, 2) }] };
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
|
|
375
523
|
// --- Start ---
|
|
376
524
|
async function main() {
|
|
377
525
|
const transport = new StdioServerTransport();
|