noxctl 0.3.0 → 0.4.1
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/README.md +56 -6
- package/dist/auth.d.ts +6 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +61 -21
- package/dist/auth.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +391 -6
- package/dist/cli.js.map +1 -1
- package/dist/credentials-store.d.ts +1 -1
- package/dist/credentials-store.d.ts.map +1 -1
- package/dist/credentials-store.js +12 -11
- package/dist/credentials-store.js.map +1 -1
- package/dist/fortnox-client.d.ts +6 -1
- package/dist/fortnox-client.d.ts.map +1 -1
- package/dist/fortnox-client.js +150 -62
- package/dist/fortnox-client.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/operations/absencetransactions.d.ts +21 -0
- package/dist/operations/absencetransactions.d.ts.map +1 -0
- package/dist/operations/absencetransactions.js +36 -0
- package/dist/operations/absencetransactions.js.map +1 -0
- package/dist/operations/attendancetransactions.d.ts +21 -0
- package/dist/operations/attendancetransactions.d.ts.map +1 -0
- package/dist/operations/attendancetransactions.js +40 -0
- package/dist/operations/attendancetransactions.js.map +1 -0
- package/dist/operations/employees.d.ts +19 -0
- package/dist/operations/employees.d.ts.map +1 -0
- package/dist/operations/employees.js +34 -0
- package/dist/operations/employees.js.map +1 -0
- package/dist/operations/salarytransactions.d.ts +21 -0
- package/dist/operations/salarytransactions.d.ts.map +1 -0
- package/dist/operations/salarytransactions.js +35 -0
- package/dist/operations/salarytransactions.js.map +1 -0
- package/dist/operations/scheduletimes.d.ts +4 -0
- package/dist/operations/scheduletimes.d.ts.map +1 -0
- package/dist/operations/scheduletimes.js +24 -0
- package/dist/operations/scheduletimes.js.map +1 -0
- package/dist/tools/absencetransactions.d.ts +3 -0
- package/dist/tools/absencetransactions.d.ts.map +1 -0
- package/dist/tools/absencetransactions.js +66 -0
- package/dist/tools/absencetransactions.js.map +1 -0
- package/dist/tools/attendancetransactions.d.ts +3 -0
- package/dist/tools/attendancetransactions.d.ts.map +1 -0
- package/dist/tools/attendancetransactions.js +63 -0
- package/dist/tools/attendancetransactions.js.map +1 -0
- package/dist/tools/employees.d.ts +3 -0
- package/dist/tools/employees.d.ts.map +1 -0
- package/dist/tools/employees.js +119 -0
- package/dist/tools/employees.js.map +1 -0
- package/dist/tools/salarytransactions.d.ts +3 -0
- package/dist/tools/salarytransactions.d.ts.map +1 -0
- package/dist/tools/salarytransactions.js +70 -0
- package/dist/tools/salarytransactions.js.map +1 -0
- package/dist/tools/scheduletimes.d.ts +3 -0
- package/dist/tools/scheduletimes.d.ts.map +1 -0
- package/dist/tools/scheduletimes.js +62 -0
- package/dist/tools/scheduletimes.js.map +1 -0
- package/dist/tools/status.d.ts.map +1 -1
- package/dist/tools/status.js +4 -2
- package/dist/tools/status.js.map +1 -1
- package/dist/views.d.ts +9 -0
- package/dist/views.d.ts.map +1 -1
- package/dist/views.js +115 -0
- package/dist/views.js.map +1 -1
- package/package.json +6 -2
package/dist/cli.js
CHANGED
|
@@ -7,12 +7,12 @@ import { readActivePointer, readActivePointerOutcome, writeActivePointer, delete
|
|
|
7
7
|
import { setResolvedProfile } from './auth.js';
|
|
8
8
|
import { applyPeriod } from './date-periods.js';
|
|
9
9
|
import { DEFAULT_PROFILE, InvalidProfileNameError } from './profile-name.js';
|
|
10
|
-
import { invoiceListColumns, invoiceDetailColumns, invoiceConfirmColumns, customerListColumns, customerDetailColumns, voucherListColumns, voucherDetailColumns, voucherRowColumns, accountListColumns, companyDetailColumns, articleListColumns, articleDetailColumns, supplierListColumns, supplierDetailColumns, supplierInvoiceListColumns, supplierInvoiceDetailColumns, supplierInvoiceConfirmColumns, invoicePaymentListColumns, invoicePaymentDetailColumns, supplierInvoicePaymentListColumns, supplierInvoicePaymentDetailColumns, offerListColumns, offerDetailColumns, orderListColumns, orderDetailColumns, projectListColumns, projectDetailColumns, costCenterListColumns, costCenterDetailColumns, taxReductionListColumns, taxReductionDetailColumns, priceListListColumns, priceListDetailColumns, priceListColumns, priceDetailColumns, financialYearListColumns, financialYearDetailColumns, lockedPeriodDetailColumns, contractListColumns, contractDetailColumns, topCustomerColumns, monthlyRevenueColumns, voucherAttachmentColumns, } from './views.js';
|
|
10
|
+
import { invoiceListColumns, invoiceDetailColumns, invoiceConfirmColumns, customerListColumns, customerDetailColumns, voucherListColumns, voucherDetailColumns, voucherRowColumns, accountListColumns, companyDetailColumns, articleListColumns, articleDetailColumns, supplierListColumns, supplierDetailColumns, supplierInvoiceListColumns, supplierInvoiceDetailColumns, supplierInvoiceConfirmColumns, invoicePaymentListColumns, invoicePaymentDetailColumns, supplierInvoicePaymentListColumns, supplierInvoicePaymentDetailColumns, offerListColumns, offerDetailColumns, orderListColumns, orderDetailColumns, projectListColumns, projectDetailColumns, costCenterListColumns, costCenterDetailColumns, taxReductionListColumns, taxReductionDetailColumns, priceListListColumns, priceListDetailColumns, priceListColumns, priceDetailColumns, financialYearListColumns, financialYearDetailColumns, lockedPeriodDetailColumns, contractListColumns, contractDetailColumns, topCustomerColumns, monthlyRevenueColumns, voucherAttachmentColumns, employeeListColumns, employeeDetailColumns, salaryTransactionListColumns, salaryTransactionDetailColumns, attendanceTransactionListColumns, attendanceTransactionDetailColumns, absenceTransactionListColumns, absenceTransactionDetailColumns, scheduleTimeDetailColumns, } from './views.js';
|
|
11
11
|
const program = new Command();
|
|
12
12
|
program
|
|
13
13
|
.name('noxctl')
|
|
14
14
|
.description('CLI and MCP server for Fortnox accounting')
|
|
15
|
-
.version('0.
|
|
15
|
+
.version('0.4.1')
|
|
16
16
|
.addOption(new Option('-o, --output <format>', 'Output format (default: table on TTY, json when piped)')
|
|
17
17
|
.choices(['json', 'table'])
|
|
18
18
|
.default(undefined))
|
|
@@ -186,9 +186,13 @@ program
|
|
|
186
186
|
.command('init')
|
|
187
187
|
.description('Interactive setup wizard — recommended onboarding path')
|
|
188
188
|
.option('--profile <name>', 'Profile to create/re-auth (defaults to resolved profile or "default")')
|
|
189
|
+
.option('--with-salary', 'Also request the Lön (salary/payroll) scope — requires the Lön permission enabled on your Fortnox app')
|
|
189
190
|
.action(async (initOpts) => {
|
|
190
|
-
const { loadCredentials, runOAuthSetup } = await import('./auth.js');
|
|
191
|
+
const { loadCredentials, runOAuthSetup, SCOPES, SALARY_SCOPE } = await import('./auth.js');
|
|
191
192
|
const { validateProfileName } = await import('./profile-name.js');
|
|
193
|
+
// Opt-in salary scope: flag for TTY, env var for non-interactive/CI runs.
|
|
194
|
+
const withSalary = Boolean(initOpts.withSalary) || process.env.FORTNOX_WITH_SALARY === '1';
|
|
195
|
+
const scopes = withSalary ? `${SCOPES} ${SALARY_SCOPE}` : SCOPES;
|
|
192
196
|
let targetProfile;
|
|
193
197
|
try {
|
|
194
198
|
targetProfile = validateProfileName(initOpts.profile ?? resolvedProfileInfo.name);
|
|
@@ -238,6 +242,9 @@ program
|
|
|
238
242
|
console.log("You'll need a Fortnox app from developer.fortnox.se with:");
|
|
239
243
|
console.log(' - Redirect URI: http://localhost:9876/callback');
|
|
240
244
|
console.log(' - Scopes (Behörigheter): Artikel, Bokföring, Faktura, Företagsinformation, Inställningar, Kund, Leverantör, Leverantörsfaktura');
|
|
245
|
+
if (withSalary) {
|
|
246
|
+
console.log(' - Lön (krävs eftersom du kör med --with-salary)');
|
|
247
|
+
}
|
|
241
248
|
console.log(' - Service account enabled (recommended)');
|
|
242
249
|
console.log('');
|
|
243
250
|
console.log('See the README for detailed portal instructions.');
|
|
@@ -339,7 +346,7 @@ program
|
|
|
339
346
|
}
|
|
340
347
|
}
|
|
341
348
|
// Step 6: Run OAuth flow
|
|
342
|
-
await runOAuthSetup({ clientId, clientSecret, serviceAccount }, targetProfile);
|
|
349
|
+
await runOAuthSetup({ clientId, clientSecret, serviceAccount }, targetProfile, scopes);
|
|
343
350
|
// Step 6b: Set the active pointer if this is the first profile or no pointer exists.
|
|
344
351
|
try {
|
|
345
352
|
const idx = await readProfileIndex();
|
|
@@ -715,7 +722,7 @@ program
|
|
|
715
722
|
return;
|
|
716
723
|
}
|
|
717
724
|
// 8. Scope validation — probe each scope with a lightweight GET
|
|
718
|
-
const {
|
|
725
|
+
const { effectiveScopes } = await import('./auth.js');
|
|
719
726
|
const { fortnoxRequest, FortnoxApiError } = await import('./fortnox-client.js');
|
|
720
727
|
const scopeEndpoints = {
|
|
721
728
|
article: 'articles?limit=1',
|
|
@@ -729,8 +736,9 @@ program
|
|
|
729
736
|
settings: 'settings/company',
|
|
730
737
|
inbox: 'inbox',
|
|
731
738
|
connectfile: 'voucherfileconnections?limit=1',
|
|
739
|
+
salary: 'employees?limit=1',
|
|
732
740
|
};
|
|
733
|
-
const required =
|
|
741
|
+
const required = effectiveScopes(creds).split(' ');
|
|
734
742
|
const missing = [];
|
|
735
743
|
for (const scope of required) {
|
|
736
744
|
const endpoint = scopeEndpoints[scope];
|
|
@@ -2105,6 +2113,383 @@ costcenters
|
|
|
2105
2113
|
await deleteCostCenter(code);
|
|
2106
2114
|
outputConfirmation(`Cost center ${code} deleted.`, json(), { Code: code, deleted: true }, undefined, 'CostCenter');
|
|
2107
2115
|
});
|
|
2116
|
+
// --- employees (payroll / Lön) ---
|
|
2117
|
+
const employees = program
|
|
2118
|
+
.command('employees')
|
|
2119
|
+
.description('Employee operations (requires the Lön scope — see `noxctl init --with-salary`)');
|
|
2120
|
+
employees
|
|
2121
|
+
.command('list')
|
|
2122
|
+
.description('List employees')
|
|
2123
|
+
.option('--page <number>', 'Page number', parseInt)
|
|
2124
|
+
.option('--limit <number>', 'Results per page', parseInt)
|
|
2125
|
+
.option('-a, --all', 'Fetch all pages')
|
|
2126
|
+
.action(async (opts) => {
|
|
2127
|
+
const { listEmployees } = await import('./operations/employees.js');
|
|
2128
|
+
const data = await listEmployees({
|
|
2129
|
+
page: opts.page,
|
|
2130
|
+
limit: opts.limit,
|
|
2131
|
+
all: opts.all,
|
|
2132
|
+
});
|
|
2133
|
+
const envelope = data;
|
|
2134
|
+
outputList(envelope.Employees ?? [], employeeListColumns, json(), data, envelope.MetaInformation);
|
|
2135
|
+
});
|
|
2136
|
+
employees
|
|
2137
|
+
.command('get <employeeId>')
|
|
2138
|
+
.description('Get a single employee')
|
|
2139
|
+
.action(async (employeeId) => {
|
|
2140
|
+
const { getEmployee } = await import('./operations/employees.js');
|
|
2141
|
+
const data = await getEmployee(employeeId);
|
|
2142
|
+
outputDetail(data, employeeDetailColumns, json(), 'Employee');
|
|
2143
|
+
});
|
|
2144
|
+
employees
|
|
2145
|
+
.command('create')
|
|
2146
|
+
.description('Create an employee. Tip: set --employment-form, --personel-type and --salary-form, otherwise Fortnox cannot assign an employment agreement (företagsavtal).')
|
|
2147
|
+
.requiredOption('--first-name <name>', 'First name')
|
|
2148
|
+
.requiredOption('--last-name <name>', 'Last name')
|
|
2149
|
+
.requiredOption('--email <email>', 'Email address')
|
|
2150
|
+
.option('--employment-form <form>', 'Employment form: TV, PRO, TID, SVT, VIK, PRJ, PRA, FER, SES, NEJ')
|
|
2151
|
+
.option('--personel-type <type>', 'Personnel type: TJM (tjänsteman) or ARB (arbetare)')
|
|
2152
|
+
.option('--salary-form <form>', 'Salary form: MAN (monthly) or TIM (hourly)')
|
|
2153
|
+
.option('--employment-date <date>', 'Employment start date (YYYY-MM-DD)')
|
|
2154
|
+
.option('--monthly-salary <amount>', 'Monthly salary')
|
|
2155
|
+
.option('--hourly-pay <amount>', 'Hourly pay')
|
|
2156
|
+
.option('--input <file>', 'Additional employee data as JSON file (or - for stdin)')
|
|
2157
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2158
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2159
|
+
.action(async (opts) => {
|
|
2160
|
+
const { createEmployee } = await import('./operations/employees.js');
|
|
2161
|
+
let input = {};
|
|
2162
|
+
if (opts.input) {
|
|
2163
|
+
const raw = opts.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(opts.input, 'utf-8');
|
|
2164
|
+
input = JSON.parse(raw);
|
|
2165
|
+
}
|
|
2166
|
+
const params = {
|
|
2167
|
+
...input,
|
|
2168
|
+
FirstName: opts.firstName,
|
|
2169
|
+
LastName: opts.lastName,
|
|
2170
|
+
Email: opts.email,
|
|
2171
|
+
};
|
|
2172
|
+
if (opts.employmentForm !== undefined)
|
|
2173
|
+
params.EmploymentForm = opts.employmentForm;
|
|
2174
|
+
if (opts.personelType !== undefined)
|
|
2175
|
+
params.PersonelType = opts.personelType;
|
|
2176
|
+
if (opts.salaryForm !== undefined)
|
|
2177
|
+
params.SalaryForm = opts.salaryForm;
|
|
2178
|
+
if (opts.employmentDate !== undefined)
|
|
2179
|
+
params.EmploymentDate = opts.employmentDate;
|
|
2180
|
+
if (opts.monthlySalary !== undefined)
|
|
2181
|
+
params.MonthlySalary = opts.monthlySalary;
|
|
2182
|
+
if (opts.hourlyPay !== undefined)
|
|
2183
|
+
params.HourlyPay = opts.hourlyPay;
|
|
2184
|
+
if (!(await confirmMutation(`Create employee "${opts.firstName} ${opts.lastName}"`, opts, {
|
|
2185
|
+
Employee: params,
|
|
2186
|
+
}))) {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
const data = await createEmployee(params);
|
|
2190
|
+
outputDetail(data, employeeDetailColumns, json(), 'Employee');
|
|
2191
|
+
});
|
|
2192
|
+
employees
|
|
2193
|
+
.command('update <employeeId>')
|
|
2194
|
+
.description('Update an employee')
|
|
2195
|
+
.requiredOption('--input <file>', 'Employee data as JSON file (or - for stdin)')
|
|
2196
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2197
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2198
|
+
.action(async (employeeId, opts) => {
|
|
2199
|
+
const { updateEmployee } = await import('./operations/employees.js');
|
|
2200
|
+
const raw = opts.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(opts.input, 'utf-8');
|
|
2201
|
+
const fields = JSON.parse(raw);
|
|
2202
|
+
if (!(await confirmMutation(`Update employee ${employeeId}`, opts, { Employee: fields }))) {
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
const data = await updateEmployee(employeeId, fields);
|
|
2206
|
+
outputDetail(data, employeeDetailColumns, json(), 'Employee');
|
|
2207
|
+
});
|
|
2208
|
+
// --- salary transactions (Lön) ---
|
|
2209
|
+
const salarytransactions = program
|
|
2210
|
+
.command('salary-transactions')
|
|
2211
|
+
.description('Salary transaction operations (requires the Lön scope)');
|
|
2212
|
+
salarytransactions
|
|
2213
|
+
.command('list')
|
|
2214
|
+
.description('List salary transactions')
|
|
2215
|
+
.option('--employee <id>', 'Filter by employee ID')
|
|
2216
|
+
.option('--date <date>', 'Filter by date (YYYY-MM-DD)')
|
|
2217
|
+
.option('--page <number>', 'Page number', parseInt)
|
|
2218
|
+
.option('--limit <number>', 'Results per page', parseInt)
|
|
2219
|
+
.option('-a, --all', 'Fetch all pages')
|
|
2220
|
+
.action(async (opts) => {
|
|
2221
|
+
const { listSalaryTransactions } = await import('./operations/salarytransactions.js');
|
|
2222
|
+
const data = await listSalaryTransactions({
|
|
2223
|
+
employeeId: opts.employee,
|
|
2224
|
+
date: opts.date,
|
|
2225
|
+
page: opts.page,
|
|
2226
|
+
limit: opts.limit,
|
|
2227
|
+
all: opts.all,
|
|
2228
|
+
});
|
|
2229
|
+
const envelope = data;
|
|
2230
|
+
outputList(envelope.SalaryTransactions ?? [], salaryTransactionListColumns, json(), data, envelope.MetaInformation);
|
|
2231
|
+
});
|
|
2232
|
+
salarytransactions
|
|
2233
|
+
.command('get <salaryRow>')
|
|
2234
|
+
.description('Get a single salary transaction')
|
|
2235
|
+
.action(async (salaryRow) => {
|
|
2236
|
+
const { getSalaryTransaction } = await import('./operations/salarytransactions.js');
|
|
2237
|
+
const data = await getSalaryTransaction(salaryRow);
|
|
2238
|
+
outputDetail(data, salaryTransactionDetailColumns, json(), 'SalaryTransaction');
|
|
2239
|
+
});
|
|
2240
|
+
salarytransactions
|
|
2241
|
+
.command('create')
|
|
2242
|
+
.description('Create a salary transaction')
|
|
2243
|
+
.requiredOption('--employee <id>', 'Employee ID')
|
|
2244
|
+
.requiredOption('--salary-code <code>', 'Salary code')
|
|
2245
|
+
.requiredOption('--date <date>', 'Date (YYYY-MM-DD)')
|
|
2246
|
+
.option('--amount <amount>', 'Amount')
|
|
2247
|
+
.option('--input <file>', 'Additional data as JSON file (or - for stdin)')
|
|
2248
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2249
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2250
|
+
.action(async (opts) => {
|
|
2251
|
+
const { createSalaryTransaction } = await import('./operations/salarytransactions.js');
|
|
2252
|
+
let input = {};
|
|
2253
|
+
if (opts.input) {
|
|
2254
|
+
const raw = opts.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(opts.input, 'utf-8');
|
|
2255
|
+
input = JSON.parse(raw);
|
|
2256
|
+
}
|
|
2257
|
+
const params = {
|
|
2258
|
+
...input,
|
|
2259
|
+
EmployeeId: opts.employee,
|
|
2260
|
+
SalaryCode: opts.salaryCode,
|
|
2261
|
+
Date: opts.date,
|
|
2262
|
+
};
|
|
2263
|
+
if (opts.amount !== undefined)
|
|
2264
|
+
params.Amount = opts.amount;
|
|
2265
|
+
if (!(await confirmMutation(`Create salary transaction for employee ${opts.employee}`, opts, {
|
|
2266
|
+
SalaryTransaction: params,
|
|
2267
|
+
}))) {
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
const data = await createSalaryTransaction(params);
|
|
2271
|
+
outputDetail(data, salaryTransactionDetailColumns, json(), 'SalaryTransaction');
|
|
2272
|
+
});
|
|
2273
|
+
salarytransactions
|
|
2274
|
+
.command('delete <salaryRow>')
|
|
2275
|
+
.description('Delete a salary transaction')
|
|
2276
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2277
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2278
|
+
.action(async (salaryRow, opts) => {
|
|
2279
|
+
const { deleteSalaryTransaction } = await import('./operations/salarytransactions.js');
|
|
2280
|
+
if (!(await confirmMutation(`Delete salary transaction ${salaryRow}`, opts))) {
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
await deleteSalaryTransaction(salaryRow);
|
|
2284
|
+
outputConfirmation(`Salary transaction ${salaryRow} deleted.`, json(), { SalaryRow: salaryRow, deleted: true }, undefined, 'SalaryTransaction');
|
|
2285
|
+
});
|
|
2286
|
+
// --- attendance transactions (närvaro / Lön) ---
|
|
2287
|
+
const attendancetransactions = program
|
|
2288
|
+
.command('attendance-transactions')
|
|
2289
|
+
.description('Attendance transaction operations (requires the Lön scope)');
|
|
2290
|
+
attendancetransactions
|
|
2291
|
+
.command('list')
|
|
2292
|
+
.description('List attendance transactions')
|
|
2293
|
+
.option('--employee <id>', 'Filter by employee ID')
|
|
2294
|
+
.option('--date <date>', 'Filter by date (YYYY-MM-DD)')
|
|
2295
|
+
.option('--page <number>', 'Page number', parseInt)
|
|
2296
|
+
.option('--limit <number>', 'Results per page', parseInt)
|
|
2297
|
+
.option('-a, --all', 'Fetch all pages')
|
|
2298
|
+
.action(async (opts) => {
|
|
2299
|
+
const { listAttendanceTransactions } = await import('./operations/attendancetransactions.js');
|
|
2300
|
+
const data = await listAttendanceTransactions({
|
|
2301
|
+
employeeId: opts.employee,
|
|
2302
|
+
date: opts.date,
|
|
2303
|
+
page: opts.page,
|
|
2304
|
+
limit: opts.limit,
|
|
2305
|
+
all: opts.all,
|
|
2306
|
+
});
|
|
2307
|
+
const envelope = data;
|
|
2308
|
+
outputList(envelope.AttendanceTransactions ?? [], attendanceTransactionListColumns, json(), data, envelope.MetaInformation);
|
|
2309
|
+
});
|
|
2310
|
+
attendancetransactions
|
|
2311
|
+
.command('get <id>')
|
|
2312
|
+
.description('Get a single attendance transaction')
|
|
2313
|
+
.action(async (id) => {
|
|
2314
|
+
const { getAttendanceTransaction } = await import('./operations/attendancetransactions.js');
|
|
2315
|
+
const data = await getAttendanceTransaction(id);
|
|
2316
|
+
outputDetail(data, attendanceTransactionDetailColumns, json(), 'AttendanceTransaction');
|
|
2317
|
+
});
|
|
2318
|
+
attendancetransactions
|
|
2319
|
+
.command('create')
|
|
2320
|
+
.description('Create an attendance transaction')
|
|
2321
|
+
.requiredOption('--employee <id>', 'Employee ID')
|
|
2322
|
+
.requiredOption('--cause-code <code>', 'Cause code (e.g. ARB, FLX, OT1)')
|
|
2323
|
+
.requiredOption('--date <date>', 'Date (YYYY-MM-DD)')
|
|
2324
|
+
.option('--hours <hours>', 'Hours')
|
|
2325
|
+
.option('--input <file>', 'Additional data as JSON file (or - for stdin)')
|
|
2326
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2327
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2328
|
+
.action(async (opts) => {
|
|
2329
|
+
const { createAttendanceTransaction } = await import('./operations/attendancetransactions.js');
|
|
2330
|
+
let input = {};
|
|
2331
|
+
if (opts.input) {
|
|
2332
|
+
const raw = opts.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(opts.input, 'utf-8');
|
|
2333
|
+
input = JSON.parse(raw);
|
|
2334
|
+
}
|
|
2335
|
+
const params = {
|
|
2336
|
+
...input,
|
|
2337
|
+
EmployeeId: opts.employee,
|
|
2338
|
+
CauseCode: opts.causeCode,
|
|
2339
|
+
Date: opts.date,
|
|
2340
|
+
};
|
|
2341
|
+
if (opts.hours !== undefined)
|
|
2342
|
+
params.Hours = opts.hours;
|
|
2343
|
+
if (!(await confirmMutation(`Create attendance transaction for employee ${opts.employee}`, opts, {
|
|
2344
|
+
AttendanceTransaction: params,
|
|
2345
|
+
}))) {
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
const data = await createAttendanceTransaction(params);
|
|
2349
|
+
outputDetail(data, attendanceTransactionDetailColumns, json(), 'AttendanceTransaction');
|
|
2350
|
+
});
|
|
2351
|
+
attendancetransactions
|
|
2352
|
+
.command('delete <id>')
|
|
2353
|
+
.description('Delete an attendance transaction')
|
|
2354
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2355
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2356
|
+
.action(async (id, opts) => {
|
|
2357
|
+
const { deleteAttendanceTransaction } = await import('./operations/attendancetransactions.js');
|
|
2358
|
+
if (!(await confirmMutation(`Delete attendance transaction ${id}`, opts))) {
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
await deleteAttendanceTransaction(id);
|
|
2362
|
+
outputConfirmation(`Attendance transaction ${id} deleted.`, json(), { id, deleted: true }, undefined, 'AttendanceTransaction');
|
|
2363
|
+
});
|
|
2364
|
+
// --- absence transactions (frånvaro / Lön) ---
|
|
2365
|
+
const absencetransactions = program
|
|
2366
|
+
.command('absence-transactions')
|
|
2367
|
+
.description('Absence transaction operations (requires the Lön scope)');
|
|
2368
|
+
absencetransactions
|
|
2369
|
+
.command('list')
|
|
2370
|
+
.description('List absence transactions')
|
|
2371
|
+
.option('--employee <id>', 'Filter by employee ID')
|
|
2372
|
+
.option('--date <date>', 'Filter by date (YYYY-MM-DD)')
|
|
2373
|
+
.option('--page <number>', 'Page number', parseInt)
|
|
2374
|
+
.option('--limit <number>', 'Results per page', parseInt)
|
|
2375
|
+
.option('-a, --all', 'Fetch all pages')
|
|
2376
|
+
.action(async (opts) => {
|
|
2377
|
+
const { listAbsenceTransactions } = await import('./operations/absencetransactions.js');
|
|
2378
|
+
const data = await listAbsenceTransactions({
|
|
2379
|
+
employeeId: opts.employee,
|
|
2380
|
+
date: opts.date,
|
|
2381
|
+
page: opts.page,
|
|
2382
|
+
limit: opts.limit,
|
|
2383
|
+
all: opts.all,
|
|
2384
|
+
});
|
|
2385
|
+
const envelope = data;
|
|
2386
|
+
outputList(envelope.AbsenceTransactions ?? [], absenceTransactionListColumns, json(), data, envelope.MetaInformation);
|
|
2387
|
+
});
|
|
2388
|
+
absencetransactions
|
|
2389
|
+
.command('get <id>')
|
|
2390
|
+
.description('Get a single absence transaction')
|
|
2391
|
+
.action(async (id) => {
|
|
2392
|
+
const { getAbsenceTransaction } = await import('./operations/absencetransactions.js');
|
|
2393
|
+
const data = await getAbsenceTransaction(id);
|
|
2394
|
+
outputDetail(data, absenceTransactionDetailColumns, json(), 'AbsenceTransaction');
|
|
2395
|
+
});
|
|
2396
|
+
absencetransactions
|
|
2397
|
+
.command('create')
|
|
2398
|
+
.description('Create an absence transaction')
|
|
2399
|
+
.requiredOption('--employee <id>', 'Employee ID')
|
|
2400
|
+
.requiredOption('--cause-code <code>', 'Cause code (e.g. SEM, SJK, VAB)')
|
|
2401
|
+
.requiredOption('--date <date>', 'Date (YYYY-MM-DD)')
|
|
2402
|
+
.option('--hours <hours>', 'Hours (number, e.g. 8)', parseFloat)
|
|
2403
|
+
.option('--extent <extent>', 'Extent / percentage absent (number, e.g. 50)', parseFloat)
|
|
2404
|
+
.option('--input <file>', 'Additional data as JSON file (or - for stdin)')
|
|
2405
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2406
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2407
|
+
.action(async (opts) => {
|
|
2408
|
+
const { createAbsenceTransaction } = await import('./operations/absencetransactions.js');
|
|
2409
|
+
let input = {};
|
|
2410
|
+
if (opts.input) {
|
|
2411
|
+
const raw = opts.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(opts.input, 'utf-8');
|
|
2412
|
+
input = JSON.parse(raw);
|
|
2413
|
+
}
|
|
2414
|
+
const params = {
|
|
2415
|
+
...input,
|
|
2416
|
+
EmployeeId: opts.employee,
|
|
2417
|
+
CauseCode: opts.causeCode,
|
|
2418
|
+
Date: opts.date,
|
|
2419
|
+
};
|
|
2420
|
+
if (opts.hours !== undefined)
|
|
2421
|
+
params.Hours = opts.hours;
|
|
2422
|
+
if (opts.extent !== undefined)
|
|
2423
|
+
params.Extent = opts.extent;
|
|
2424
|
+
if (!(await confirmMutation(`Create absence transaction for employee ${opts.employee}`, opts, {
|
|
2425
|
+
AbsenceTransaction: params,
|
|
2426
|
+
}))) {
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
const data = await createAbsenceTransaction(params);
|
|
2430
|
+
outputDetail(data, absenceTransactionDetailColumns, json(), 'AbsenceTransaction');
|
|
2431
|
+
});
|
|
2432
|
+
absencetransactions
|
|
2433
|
+
.command('delete <id>')
|
|
2434
|
+
.description('Delete an absence transaction')
|
|
2435
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2436
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2437
|
+
.action(async (id, opts) => {
|
|
2438
|
+
const { deleteAbsenceTransaction } = await import('./operations/absencetransactions.js');
|
|
2439
|
+
if (!(await confirmMutation(`Delete absence transaction ${id}`, opts))) {
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
await deleteAbsenceTransaction(id);
|
|
2443
|
+
outputConfirmation(`Absence transaction ${id} deleted.`, json(), { id, deleted: true }, undefined, 'AbsenceTransaction');
|
|
2444
|
+
});
|
|
2445
|
+
// --- schedule times (schematider / Lön) ---
|
|
2446
|
+
const scheduletimes = program
|
|
2447
|
+
.command('schedule-times')
|
|
2448
|
+
.description('Schedule time operations (requires the Lön scope)');
|
|
2449
|
+
scheduletimes
|
|
2450
|
+
.command('get <employeeId> <date>')
|
|
2451
|
+
.description('Get the schedule for an employee on a date (YYYY-MM-DD)')
|
|
2452
|
+
.action(async (employeeId, date) => {
|
|
2453
|
+
const { getScheduleTime } = await import('./operations/scheduletimes.js');
|
|
2454
|
+
const data = await getScheduleTime(employeeId, date);
|
|
2455
|
+
outputDetail(data, scheduleTimeDetailColumns, json(), 'ScheduleTime');
|
|
2456
|
+
});
|
|
2457
|
+
scheduletimes
|
|
2458
|
+
.command('update <employeeId> <date>')
|
|
2459
|
+
.description('Update the schedule for an employee on a date')
|
|
2460
|
+
.requiredOption('--input <file>', 'Schedule data as JSON file (or - for stdin)')
|
|
2461
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2462
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2463
|
+
.action(async (employeeId, date, opts) => {
|
|
2464
|
+
const { updateScheduleTime } = await import('./operations/scheduletimes.js');
|
|
2465
|
+
const raw = opts.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(opts.input, 'utf-8');
|
|
2466
|
+
const fields = JSON.parse(raw);
|
|
2467
|
+
if (!(await confirmMutation(`Update schedule for ${employeeId} on ${date}`, opts, {
|
|
2468
|
+
ScheduleTime: fields,
|
|
2469
|
+
}))) {
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2472
|
+
const data = await updateScheduleTime(employeeId, date, fields);
|
|
2473
|
+
outputDetail(data, scheduleTimeDetailColumns, json(), 'ScheduleTime');
|
|
2474
|
+
});
|
|
2475
|
+
scheduletimes
|
|
2476
|
+
.command('reset-day <employeeId> <date>')
|
|
2477
|
+
.description('Update the schedule and reset the day for an employee on a date')
|
|
2478
|
+
.requiredOption('--input <file>', 'Schedule data as JSON file (or - for stdin)')
|
|
2479
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
2480
|
+
.option('--dry-run', 'Preview the request without sending it')
|
|
2481
|
+
.action(async (employeeId, date, opts) => {
|
|
2482
|
+
const { resetScheduleTimeDay } = await import('./operations/scheduletimes.js');
|
|
2483
|
+
const raw = opts.input === '-' ? readFileSync(0, 'utf-8') : readFileSync(opts.input, 'utf-8');
|
|
2484
|
+
const fields = JSON.parse(raw);
|
|
2485
|
+
if (!(await confirmMutation(`Reset schedule day for ${employeeId} on ${date}`, opts, {
|
|
2486
|
+
ScheduleTime: fields,
|
|
2487
|
+
}))) {
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
const data = await resetScheduleTimeDay(employeeId, date, fields);
|
|
2491
|
+
outputDetail(data, scheduleTimeDetailColumns, json(), 'ScheduleTime');
|
|
2492
|
+
});
|
|
2108
2493
|
// --- tax reductions (ROT/RUT) ---
|
|
2109
2494
|
const taxreductions = program
|
|
2110
2495
|
.command('tax-reductions')
|