@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,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);
|