@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.
Files changed (68) hide show
  1. package/bin/index.js +63 -0
  2. package/bin/mcp.js +63 -0
  3. package/dist/index.js +63 -0
  4. package/microservices/microservice-ads/package.json +27 -0
  5. package/microservices/microservice-ads/src/cli/index.ts +605 -0
  6. package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
  7. package/microservices/microservice-ads/src/db/database.ts +93 -0
  8. package/microservices/microservice-ads/src/db/migrations.ts +60 -0
  9. package/microservices/microservice-ads/src/index.ts +39 -0
  10. package/microservices/microservice-ads/src/mcp/index.ts +480 -0
  11. package/microservices/microservice-contracts/package.json +27 -0
  12. package/microservices/microservice-contracts/src/cli/index.ts +770 -0
  13. package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
  14. package/microservices/microservice-contracts/src/db/database.ts +93 -0
  15. package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
  16. package/microservices/microservice-contracts/src/index.ts +43 -0
  17. package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
  18. package/microservices/microservice-domains/package.json +27 -0
  19. package/microservices/microservice-domains/src/cli/index.ts +691 -0
  20. package/microservices/microservice-domains/src/db/database.ts +93 -0
  21. package/microservices/microservice-domains/src/db/domains.ts +1164 -0
  22. package/microservices/microservice-domains/src/db/migrations.ts +60 -0
  23. package/microservices/microservice-domains/src/index.ts +65 -0
  24. package/microservices/microservice-domains/src/mcp/index.ts +536 -0
  25. package/microservices/microservice-hiring/package.json +27 -0
  26. package/microservices/microservice-hiring/src/cli/index.ts +741 -0
  27. package/microservices/microservice-hiring/src/db/database.ts +93 -0
  28. package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
  29. package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
  30. package/microservices/microservice-hiring/src/index.ts +80 -0
  31. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  32. package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
  33. package/microservices/microservice-payments/package.json +27 -0
  34. package/microservices/microservice-payments/src/cli/index.ts +609 -0
  35. package/microservices/microservice-payments/src/db/database.ts +93 -0
  36. package/microservices/microservice-payments/src/db/migrations.ts +81 -0
  37. package/microservices/microservice-payments/src/db/payments.ts +1204 -0
  38. package/microservices/microservice-payments/src/index.ts +51 -0
  39. package/microservices/microservice-payments/src/mcp/index.ts +683 -0
  40. package/microservices/microservice-payroll/package.json +27 -0
  41. package/microservices/microservice-payroll/src/cli/index.ts +643 -0
  42. package/microservices/microservice-payroll/src/db/database.ts +93 -0
  43. package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
  44. package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
  45. package/microservices/microservice-payroll/src/index.ts +48 -0
  46. package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
  47. package/microservices/microservice-shipping/package.json +27 -0
  48. package/microservices/microservice-shipping/src/cli/index.ts +606 -0
  49. package/microservices/microservice-shipping/src/db/database.ts +93 -0
  50. package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
  51. package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
  52. package/microservices/microservice-shipping/src/index.ts +53 -0
  53. package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
  54. package/microservices/microservice-social/package.json +27 -0
  55. package/microservices/microservice-social/src/cli/index.ts +689 -0
  56. package/microservices/microservice-social/src/db/database.ts +93 -0
  57. package/microservices/microservice-social/src/db/migrations.ts +88 -0
  58. package/microservices/microservice-social/src/db/social.ts +1046 -0
  59. package/microservices/microservice-social/src/index.ts +46 -0
  60. package/microservices/microservice-social/src/mcp/index.ts +655 -0
  61. package/microservices/microservice-subscriptions/package.json +27 -0
  62. package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
  63. package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
  64. package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
  65. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
  66. package/microservices/microservice-subscriptions/src/index.ts +41 -0
  67. package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
  68. package/package.json +1 -1
@@ -0,0 +1,643 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createEmployee,
6
+ getEmployee,
7
+ listEmployees,
8
+ updateEmployee,
9
+ terminateEmployee,
10
+ createPayPeriod,
11
+ listPayPeriods,
12
+ getPayPeriod,
13
+ processPayroll,
14
+ listPayStubs,
15
+ getPayStub,
16
+ getPayrollReport,
17
+ getYtdReport,
18
+ getTaxSummary,
19
+ createBenefit,
20
+ listBenefits,
21
+ removeBenefit,
22
+ generateAchFile,
23
+ generateW2,
24
+ generate1099,
25
+ setSchedule,
26
+ getNextPayPeriod,
27
+ auditPayroll,
28
+ forecastPayroll,
29
+ checkOvertime,
30
+ } from "../db/payroll.js";
31
+
32
+ const program = new Command();
33
+
34
+ program
35
+ .name("microservice-payroll")
36
+ .description("Payroll management microservice")
37
+ .version("0.0.1");
38
+
39
+ // --- Employees ---
40
+
41
+ const employeeCmd = program
42
+ .command("employee")
43
+ .description("Employee management");
44
+
45
+ employeeCmd
46
+ .command("add")
47
+ .description("Add a new employee")
48
+ .requiredOption("--name <name>", "Full name")
49
+ .requiredOption("--pay-rate <rate>", "Pay rate (annual salary or hourly rate)")
50
+ .option("--email <email>", "Email address")
51
+ .option("--type <type>", "Type: employee or contractor", "employee")
52
+ .option("--department <dept>", "Department")
53
+ .option("--title <title>", "Job title")
54
+ .option("--pay-type <type>", "Pay type: salary or hourly", "salary")
55
+ .option("--currency <currency>", "Currency code", "USD")
56
+ .option("--start-date <date>", "Start date (YYYY-MM-DD)")
57
+ .option("--json", "Output as JSON", false)
58
+ .action((opts) => {
59
+ const employee = createEmployee({
60
+ name: opts.name,
61
+ email: opts.email,
62
+ type: opts.type,
63
+ department: opts.department,
64
+ title: opts.title,
65
+ pay_rate: parseFloat(opts.payRate),
66
+ pay_type: opts.payType,
67
+ currency: opts.currency,
68
+ start_date: opts.startDate,
69
+ });
70
+
71
+ if (opts.json) {
72
+ console.log(JSON.stringify(employee, null, 2));
73
+ } else {
74
+ console.log(`Created employee: ${employee.name} (${employee.id})`);
75
+ }
76
+ });
77
+
78
+ employeeCmd
79
+ .command("list")
80
+ .description("List employees")
81
+ .option("--status <status>", "Filter by status: active or terminated")
82
+ .option("--department <dept>", "Filter by department")
83
+ .option("--type <type>", "Filter by type: employee or contractor")
84
+ .option("--search <query>", "Search by name, email, or department")
85
+ .option("--json", "Output as JSON", false)
86
+ .action((opts) => {
87
+ const employees = listEmployees({
88
+ status: opts.status,
89
+ department: opts.department,
90
+ type: opts.type,
91
+ search: opts.search,
92
+ });
93
+
94
+ if (opts.json) {
95
+ console.log(JSON.stringify(employees, null, 2));
96
+ } else {
97
+ if (employees.length === 0) {
98
+ console.log("No employees found.");
99
+ return;
100
+ }
101
+ for (const e of employees) {
102
+ const dept = e.department ? ` (${e.department})` : "";
103
+ const status = e.status === "terminated" ? " [TERMINATED]" : "";
104
+ console.log(` ${e.name}${dept} — ${e.pay_type} $${e.pay_rate}${status}`);
105
+ }
106
+ console.log(`\n${employees.length} employee(s)`);
107
+ }
108
+ });
109
+
110
+ employeeCmd
111
+ .command("get")
112
+ .description("Get an employee by ID")
113
+ .argument("<id>", "Employee ID")
114
+ .option("--json", "Output as JSON", false)
115
+ .action((id, opts) => {
116
+ const employee = getEmployee(id);
117
+ if (!employee) {
118
+ console.error(`Employee '${id}' not found.`);
119
+ process.exit(1);
120
+ }
121
+
122
+ if (opts.json) {
123
+ console.log(JSON.stringify(employee, null, 2));
124
+ } else {
125
+ console.log(`${employee.name}`);
126
+ if (employee.email) console.log(` Email: ${employee.email}`);
127
+ console.log(` Type: ${employee.type}`);
128
+ console.log(` Status: ${employee.status}`);
129
+ if (employee.department) console.log(` Department: ${employee.department}`);
130
+ if (employee.title) console.log(` Title: ${employee.title}`);
131
+ console.log(` Pay: ${employee.pay_type} $${employee.pay_rate} ${employee.currency}`);
132
+ }
133
+ });
134
+
135
+ employeeCmd
136
+ .command("update")
137
+ .description("Update an employee")
138
+ .argument("<id>", "Employee ID")
139
+ .option("--name <name>", "Full name")
140
+ .option("--email <email>", "Email")
141
+ .option("--department <dept>", "Department")
142
+ .option("--title <title>", "Job title")
143
+ .option("--pay-rate <rate>", "Pay rate")
144
+ .option("--pay-type <type>", "Pay type")
145
+ .option("--json", "Output as JSON", false)
146
+ .action((id, opts) => {
147
+ const input: Record<string, unknown> = {};
148
+ if (opts.name !== undefined) input.name = opts.name;
149
+ if (opts.email !== undefined) input.email = opts.email;
150
+ if (opts.department !== undefined) input.department = opts.department;
151
+ if (opts.title !== undefined) input.title = opts.title;
152
+ if (opts.payRate !== undefined) input.pay_rate = parseFloat(opts.payRate);
153
+ if (opts.payType !== undefined) input.pay_type = opts.payType;
154
+
155
+ const employee = updateEmployee(id, input);
156
+ if (!employee) {
157
+ console.error(`Employee '${id}' not found.`);
158
+ process.exit(1);
159
+ }
160
+
161
+ if (opts.json) {
162
+ console.log(JSON.stringify(employee, null, 2));
163
+ } else {
164
+ console.log(`Updated: ${employee.name}`);
165
+ }
166
+ });
167
+
168
+ employeeCmd
169
+ .command("terminate")
170
+ .description("Terminate an employee")
171
+ .argument("<id>", "Employee ID")
172
+ .option("--end-date <date>", "End date (YYYY-MM-DD)")
173
+ .option("--json", "Output as JSON", false)
174
+ .action((id, opts) => {
175
+ const employee = terminateEmployee(id, opts.endDate);
176
+ if (!employee) {
177
+ console.error(`Employee '${id}' not found.`);
178
+ process.exit(1);
179
+ }
180
+
181
+ if (opts.json) {
182
+ console.log(JSON.stringify(employee, null, 2));
183
+ } else {
184
+ console.log(`Terminated: ${employee.name} (end date: ${employee.end_date})`);
185
+ }
186
+ });
187
+
188
+ // --- Pay Periods ---
189
+
190
+ const periodCmd = program
191
+ .command("payperiod")
192
+ .description("Pay period management");
193
+
194
+ periodCmd
195
+ .command("create")
196
+ .description("Create a new pay period")
197
+ .requiredOption("--start <date>", "Start date (YYYY-MM-DD)")
198
+ .requiredOption("--end <date>", "End date (YYYY-MM-DD)")
199
+ .option("--json", "Output as JSON", false)
200
+ .action((opts) => {
201
+ const period = createPayPeriod({
202
+ start_date: opts.start,
203
+ end_date: opts.end,
204
+ });
205
+
206
+ if (opts.json) {
207
+ console.log(JSON.stringify(period, null, 2));
208
+ } else {
209
+ console.log(`Created pay period: ${period.start_date} to ${period.end_date} (${period.id})`);
210
+ }
211
+ });
212
+
213
+ periodCmd
214
+ .command("list")
215
+ .description("List pay periods")
216
+ .option("--status <status>", "Filter by status")
217
+ .option("--json", "Output as JSON", false)
218
+ .action((opts) => {
219
+ const periods = listPayPeriods(opts.status);
220
+
221
+ if (opts.json) {
222
+ console.log(JSON.stringify(periods, null, 2));
223
+ } else {
224
+ if (periods.length === 0) {
225
+ console.log("No pay periods found.");
226
+ return;
227
+ }
228
+ for (const p of periods) {
229
+ console.log(` ${p.start_date} to ${p.end_date} [${p.status}] (${p.id})`);
230
+ }
231
+ console.log(`\n${periods.length} pay period(s)`);
232
+ }
233
+ });
234
+
235
+ periodCmd
236
+ .command("process")
237
+ .description("Process payroll for a pay period")
238
+ .argument("<id>", "Pay period ID")
239
+ .option("--json", "Output as JSON", false)
240
+ .action((id, opts) => {
241
+ try {
242
+ const stubs = processPayroll(id);
243
+ if (opts.json) {
244
+ console.log(JSON.stringify(stubs, null, 2));
245
+ } else {
246
+ console.log(`Processed payroll: ${stubs.length} pay stub(s) generated.`);
247
+ for (const s of stubs) {
248
+ console.log(` Employee ${s.employee_id}: gross=$${s.gross_pay} net=$${s.net_pay}`);
249
+ }
250
+ }
251
+ } catch (error) {
252
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
253
+ process.exit(1);
254
+ }
255
+ });
256
+
257
+ // --- Pay Stubs ---
258
+
259
+ const stubCmd = program
260
+ .command("paystub")
261
+ .description("Pay stub management");
262
+
263
+ stubCmd
264
+ .command("list")
265
+ .description("List pay stubs")
266
+ .option("--employee <id>", "Filter by employee ID")
267
+ .option("--period <id>", "Filter by pay period ID")
268
+ .option("--json", "Output as JSON", false)
269
+ .action((opts) => {
270
+ const stubs = listPayStubs({
271
+ employee_id: opts.employee,
272
+ pay_period_id: opts.period,
273
+ });
274
+
275
+ if (opts.json) {
276
+ console.log(JSON.stringify(stubs, null, 2));
277
+ } else {
278
+ if (stubs.length === 0) {
279
+ console.log("No pay stubs found.");
280
+ return;
281
+ }
282
+ for (const s of stubs) {
283
+ console.log(` ${s.id} — Employee ${s.employee_id}: gross=$${s.gross_pay} net=$${s.net_pay}`);
284
+ }
285
+ console.log(`\n${stubs.length} pay stub(s)`);
286
+ }
287
+ });
288
+
289
+ stubCmd
290
+ .command("get")
291
+ .description("Get a pay stub by ID")
292
+ .argument("<id>", "Pay stub ID")
293
+ .option("--json", "Output as JSON", false)
294
+ .action((id, opts) => {
295
+ const stub = getPayStub(id);
296
+ if (!stub) {
297
+ console.error(`Pay stub '${id}' not found.`);
298
+ process.exit(1);
299
+ }
300
+
301
+ if (opts.json) {
302
+ console.log(JSON.stringify(stub, null, 2));
303
+ } else {
304
+ console.log(`Pay Stub: ${stub.id}`);
305
+ console.log(` Employee: ${stub.employee_id}`);
306
+ console.log(` Period: ${stub.pay_period_id}`);
307
+ console.log(` Gross Pay: $${stub.gross_pay}`);
308
+ console.log(` Deductions: ${JSON.stringify(stub.deductions)}`);
309
+ console.log(` Net Pay: $${stub.net_pay}`);
310
+ if (stub.hours_worked !== null) console.log(` Hours: ${stub.hours_worked}`);
311
+ if (stub.overtime_hours) console.log(` Overtime: ${stub.overtime_hours}`);
312
+ }
313
+ });
314
+
315
+ // --- Run (process payroll shortcut) ---
316
+
317
+ program
318
+ .command("run")
319
+ .description("Process payroll for a pay period (shortcut)")
320
+ .argument("<period-id>", "Pay period ID")
321
+ .option("--json", "Output as JSON", false)
322
+ .action((periodId, opts) => {
323
+ try {
324
+ const stubs = processPayroll(periodId);
325
+ if (opts.json) {
326
+ console.log(JSON.stringify(stubs, null, 2));
327
+ } else {
328
+ console.log(`Payroll processed: ${stubs.length} pay stub(s) generated.`);
329
+ }
330
+ } catch (error) {
331
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
332
+ process.exit(1);
333
+ }
334
+ });
335
+
336
+ // --- Report ---
337
+
338
+ program
339
+ .command("report")
340
+ .description("Get payroll report for a pay period")
341
+ .argument("<period-id>", "Pay period ID")
342
+ .option("--json", "Output as JSON", false)
343
+ .action((periodId, opts) => {
344
+ const report = getPayrollReport(periodId);
345
+ if (!report) {
346
+ console.error(`Pay period '${periodId}' not found.`);
347
+ process.exit(1);
348
+ }
349
+
350
+ if (opts.json) {
351
+ console.log(JSON.stringify(report, null, 2));
352
+ } else {
353
+ console.log(`Payroll Report: ${report.period.start_date} to ${report.period.end_date}`);
354
+ console.log(` Status: ${report.period.status}`);
355
+ console.log(` Employees: ${report.employee_count}`);
356
+ console.log(` Total Gross: $${report.total_gross}`);
357
+ console.log(` Total Deductions: $${report.total_deductions}`);
358
+ console.log(` Total Net: $${report.total_net}`);
359
+ }
360
+ });
361
+
362
+ // --- Taxes ---
363
+
364
+ program
365
+ .command("taxes")
366
+ .description("Get tax summary for a year")
367
+ .argument("<year>", "Tax year")
368
+ .option("--json", "Output as JSON", false)
369
+ .action((year, opts) => {
370
+ const summary = getTaxSummary(parseInt(year));
371
+
372
+ if (opts.json) {
373
+ console.log(JSON.stringify(summary, null, 2));
374
+ } else {
375
+ if (summary.length === 0) {
376
+ console.log(`No tax data for ${year}.`);
377
+ return;
378
+ }
379
+ for (const entry of summary) {
380
+ console.log(` ${entry.employee_name}: gross=$${entry.total_gross} fed=$${entry.total_federal_tax} state=$${entry.total_state_tax} net=$${entry.total_net}`);
381
+ }
382
+ }
383
+ });
384
+
385
+ // --- Benefits ---
386
+
387
+ const benefitCmd = program
388
+ .command("benefit")
389
+ .description("Employee benefit management");
390
+
391
+ benefitCmd
392
+ .command("add")
393
+ .description("Add a benefit to an employee")
394
+ .requiredOption("--employee <id>", "Employee ID")
395
+ .requiredOption("--type <type>", "Benefit type: health, dental, vision, retirement, hsa, other")
396
+ .requiredOption("--amount <amount>", "Deduction amount")
397
+ .option("--description <desc>", "Description")
398
+ .option("--frequency <freq>", "Frequency: per_period, monthly, annual", "per_period")
399
+ .option("--json", "Output as JSON", false)
400
+ .action((opts) => {
401
+ try {
402
+ const benefit = createBenefit({
403
+ employee_id: opts.employee,
404
+ type: opts.type,
405
+ amount: parseFloat(opts.amount),
406
+ description: opts.description,
407
+ frequency: opts.frequency,
408
+ });
409
+
410
+ if (opts.json) {
411
+ console.log(JSON.stringify(benefit, null, 2));
412
+ } else {
413
+ console.log(`Created benefit: ${benefit.type} $${benefit.amount}/${benefit.frequency} (${benefit.id})`);
414
+ }
415
+ } catch (error) {
416
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
417
+ process.exit(1);
418
+ }
419
+ });
420
+
421
+ benefitCmd
422
+ .command("list")
423
+ .description("List benefits")
424
+ .option("--employee <id>", "Filter by employee ID")
425
+ .option("--json", "Output as JSON", false)
426
+ .action((opts) => {
427
+ const benefits = listBenefits(opts.employee);
428
+
429
+ if (opts.json) {
430
+ console.log(JSON.stringify(benefits, null, 2));
431
+ } else {
432
+ if (benefits.length === 0) {
433
+ console.log("No benefits found.");
434
+ return;
435
+ }
436
+ for (const b of benefits) {
437
+ const status = b.active ? "" : " [INACTIVE]";
438
+ console.log(` ${b.type} — $${b.amount}/${b.frequency} (${b.employee_id})${status}`);
439
+ }
440
+ console.log(`\n${benefits.length} benefit(s)`);
441
+ }
442
+ });
443
+
444
+ benefitCmd
445
+ .command("remove")
446
+ .description("Deactivate a benefit")
447
+ .argument("<id>", "Benefit ID")
448
+ .option("--json", "Output as JSON", false)
449
+ .action((id, opts) => {
450
+ const removed = removeBenefit(id);
451
+ if (opts.json) {
452
+ console.log(JSON.stringify({ removed }));
453
+ } else {
454
+ console.log(removed ? `Benefit ${id} deactivated.` : `Benefit '${id}' not found.`);
455
+ }
456
+ });
457
+
458
+ // --- ACH File Generation ---
459
+
460
+ program
461
+ .command("generate-ach")
462
+ .description("Generate NACHA-format ACH file for a pay period")
463
+ .requiredOption("--period <id>", "Pay period ID")
464
+ .requiredOption("--routing <number>", "Bank routing number")
465
+ .requiredOption("--account <number>", "Bank account number")
466
+ .option("--company <name>", "Company name", "PAYROLL CO")
467
+ .action((opts) => {
468
+ try {
469
+ const achContent = generateAchFile(opts.period, opts.routing, opts.account, opts.company);
470
+ console.log(achContent);
471
+ } catch (error) {
472
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
473
+ process.exit(1);
474
+ }
475
+ });
476
+
477
+ // --- W-2 Generation ---
478
+
479
+ program
480
+ .command("w2")
481
+ .description("Generate W-2 data for an employee")
482
+ .requiredOption("--year <year>", "Tax year")
483
+ .requiredOption("--employee <id>", "Employee ID")
484
+ .option("--json", "Output as JSON", false)
485
+ .action((opts) => {
486
+ const w2 = generateW2(opts.employee, parseInt(opts.year));
487
+ if (!w2) {
488
+ console.error("No W-2 data found (employee not found, is a contractor, or has no pay stubs).");
489
+ process.exit(1);
490
+ }
491
+
492
+ if (opts.json) {
493
+ console.log(JSON.stringify(w2, null, 2));
494
+ } else {
495
+ console.log(`W-2 for ${w2.employee_name} (${w2.year})`);
496
+ console.log(` Gross Wages: $${w2.gross}`);
497
+ console.log(` Federal Tax Withheld: $${w2.federal_withheld}`);
498
+ console.log(` State Tax Withheld: $${w2.state_withheld}`);
499
+ console.log(` Social Security: $${w2.social_security}`);
500
+ console.log(` Medicare: $${w2.medicare}`);
501
+ }
502
+ });
503
+
504
+ // --- 1099-NEC Generation ---
505
+
506
+ program
507
+ .command("1099")
508
+ .description("Generate 1099-NEC data for contractors")
509
+ .requiredOption("--year <year>", "Tax year")
510
+ .option("--employee <id>", "Specific contractor ID (optional)")
511
+ .option("--json", "Output as JSON", false)
512
+ .action((opts) => {
513
+ const forms = generate1099(opts.employee || null, parseInt(opts.year));
514
+
515
+ if (opts.json) {
516
+ console.log(JSON.stringify(forms, null, 2));
517
+ } else {
518
+ if (forms.length === 0) {
519
+ console.log("No 1099-NEC forms to generate (no contractors with >$600 compensation).");
520
+ return;
521
+ }
522
+ for (const f of forms) {
523
+ console.log(` ${f.employee_name}: $${f.total_compensation} (${f.year})`);
524
+ }
525
+ console.log(`\n${forms.length} form(s)`);
526
+ }
527
+ });
528
+
529
+ // --- Payroll Schedule ---
530
+
531
+ const scheduleCmd = program
532
+ .command("schedule")
533
+ .description("Payroll schedule management");
534
+
535
+ scheduleCmd
536
+ .command("set")
537
+ .description("Set the payroll schedule")
538
+ .requiredOption("--frequency <freq>", "Frequency: weekly, biweekly, semimonthly, monthly")
539
+ .requiredOption("--anchor <date>", "Anchor date (YYYY-MM-DD)")
540
+ .option("--json", "Output as JSON", false)
541
+ .action((opts) => {
542
+ const schedule = setSchedule(opts.frequency, opts.anchor);
543
+
544
+ if (opts.json) {
545
+ console.log(JSON.stringify(schedule, null, 2));
546
+ } else {
547
+ console.log(`Schedule set: ${schedule.frequency} starting ${schedule.anchor_date}`);
548
+ }
549
+ });
550
+
551
+ scheduleCmd
552
+ .command("next")
553
+ .description("Get the next pay period dates based on schedule")
554
+ .option("--json", "Output as JSON", false)
555
+ .action((opts) => {
556
+ const next = getNextPayPeriod();
557
+ if (!next) {
558
+ console.error("No payroll schedule configured. Use 'schedule set' first.");
559
+ process.exit(1);
560
+ }
561
+
562
+ if (opts.json) {
563
+ console.log(JSON.stringify(next, null, 2));
564
+ } else {
565
+ console.log(`Next pay period: ${next.start_date} to ${next.end_date}`);
566
+ }
567
+ });
568
+
569
+ // --- Audit ---
570
+
571
+ program
572
+ .command("audit")
573
+ .description("Audit a payroll period for issues")
574
+ .requiredOption("--period <id>", "Pay period ID")
575
+ .option("--json", "Output as JSON", false)
576
+ .action((opts) => {
577
+ try {
578
+ const result = auditPayroll(opts.period);
579
+
580
+ if (opts.json) {
581
+ console.log(JSON.stringify(result, null, 2));
582
+ } else {
583
+ if (result.passed) {
584
+ console.log("PASSED — No issues found.");
585
+ } else {
586
+ console.log(`FAILED — ${result.issues.length} issue(s):`);
587
+ for (const issue of result.issues) {
588
+ console.log(` - ${issue}`);
589
+ }
590
+ }
591
+ }
592
+ } catch (error) {
593
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
594
+ process.exit(1);
595
+ }
596
+ });
597
+
598
+ // --- Forecast ---
599
+
600
+ program
601
+ .command("forecast")
602
+ .description("Forecast future payroll costs")
603
+ .option("--months <n>", "Number of months to forecast", "3")
604
+ .option("--json", "Output as JSON", false)
605
+ .action((opts) => {
606
+ const result = forecastPayroll(parseInt(opts.months));
607
+
608
+ if (opts.json) {
609
+ console.log(JSON.stringify(result, null, 2));
610
+ } else {
611
+ console.log(`Payroll Forecast (${result.months} months):`);
612
+ for (const p of result.periods) {
613
+ console.log(` ${p.month}: gross=$${p.estimated_gross} deductions=$${p.estimated_deductions} net=$${p.estimated_net}`);
614
+ }
615
+ console.log(`\nTotal: gross=$${result.total_estimated_gross} deductions=$${result.total_estimated_deductions} net=$${result.total_estimated_net}`);
616
+ }
617
+ });
618
+
619
+ // --- Overtime Check ---
620
+
621
+ program
622
+ .command("overtime-check")
623
+ .description("Check for employees exceeding weekly hours threshold")
624
+ .option("--threshold <hours>", "Hours threshold", "40")
625
+ .option("--json", "Output as JSON", false)
626
+ .action((opts) => {
627
+ const alerts = checkOvertime(parseFloat(opts.threshold));
628
+
629
+ if (opts.json) {
630
+ console.log(JSON.stringify(alerts, null, 2));
631
+ } else {
632
+ if (alerts.length === 0) {
633
+ console.log("No overtime alerts.");
634
+ return;
635
+ }
636
+ for (const a of alerts) {
637
+ console.log(` ${a.employee_name}: ${a.total_hours}h total (${a.overtime_hours}h over ${a.threshold}h threshold)`);
638
+ }
639
+ console.log(`\n${alerts.length} alert(s)`);
640
+ }
641
+ });
642
+
643
+ program.parse(process.argv);