Fix/m sprint fixes (#613)
* fix(dashboard): exclude ignored and already-triaged transactions from stale count The "Gamla transaktioner" widget counted transactions that had been ignored or already marked as is_business=true but not yet booked, so users saw a nag for a row they had already dealt with — and the /transactions inbox correctly hid it. Align the count with the inbox criterion (is_business IS NULL, is_ignored = false) so the widget clears when the row leaves the inbox. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(transactions): read entity_type from settings response wrapper The transactions page read entityRes.entity_type directly, but /api/settings returns { data: { entity_type, ... } }. The expression was always undefined, so setEntityType never fired and entityType stayed at its initial 'enskild_firma'. The template picker's entity_type filter then dropped every aktiebolag-tagged user template for AB customers — only entity_type='all' templates made it through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * stale templates bank sync journal entry from transaction * fixed pr comments * fixed pr comment --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
fc7a46c3f2
commit
ea1bf01f1e
@@ -1,5 +1,12 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { calculateSalary, calculateKarensavdrag, calculateSjuklon, calculateAvgifterRate, calculateVacationAccrual } from '../calculation-engine'
|
||||
import {
|
||||
calculateSalary,
|
||||
calculateKarensavdrag,
|
||||
calculateSjuklon,
|
||||
calculateAvgifterRate,
|
||||
calculateVacationAccrual,
|
||||
prorateBaseSalaryForPeriod,
|
||||
} from '../calculation-engine'
|
||||
import type { PayrollConfig } from '../payroll-config'
|
||||
import type { TaxTableRate } from '../tax-tables'
|
||||
|
||||
@@ -384,6 +391,173 @@ describe('calculateSalary', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// Partial-month employment proration
|
||||
// ============================================================
|
||||
//
|
||||
// May 2026 calendar (Mon-Fri only):
|
||||
// May 1 (Fri), May 4-8 (5), May 11-15 (5), May 18-22 (5), May 25-29 (5)
|
||||
// → 21 workdays total in May.
|
||||
// An employee hired May 15 (Fri) works May 15, 18-22, 25-29 → 11 workdays.
|
||||
// 11 / 21 ≈ 0.5238 → 40 000 SEK × 0.5238 ≈ 20 952,38 SEK.
|
||||
|
||||
describe('partial-month employment proration', () => {
|
||||
it('prorates base salary for an employee hired mid-month', () => {
|
||||
const result = calculateSalary(
|
||||
makeBasicInput({
|
||||
periodStart: '2026-05-01',
|
||||
periodEnd: '2026-05-31',
|
||||
employmentStart: '2026-05-15',
|
||||
employmentEnd: null,
|
||||
}),
|
||||
config2026,
|
||||
emptyTaxRates,
|
||||
)
|
||||
|
||||
// 40 000 × 11 / 21 = 20 952,38 (rounded via engine's r())
|
||||
expect(result.grossSalary).toBeCloseTo(20952.38, 2)
|
||||
})
|
||||
|
||||
it('prorates base salary for an employee terminated mid-month', () => {
|
||||
const result = calculateSalary(
|
||||
makeBasicInput({
|
||||
periodStart: '2026-05-01',
|
||||
periodEnd: '2026-05-31',
|
||||
employmentStart: '2020-01-01',
|
||||
employmentEnd: '2026-05-15',
|
||||
}),
|
||||
config2026,
|
||||
emptyTaxRates,
|
||||
)
|
||||
|
||||
// May 1, 4-8, 11-15 = 11 workdays → 40 000 × 11 / 21
|
||||
expect(result.grossSalary).toBeCloseTo(20952.38, 2)
|
||||
})
|
||||
|
||||
it('does not prorate when the employee covers the full period', () => {
|
||||
const result = calculateSalary(
|
||||
makeBasicInput({
|
||||
periodStart: '2026-05-01',
|
||||
periodEnd: '2026-05-31',
|
||||
employmentStart: '2024-01-01',
|
||||
employmentEnd: null,
|
||||
}),
|
||||
config2026,
|
||||
emptyTaxRates,
|
||||
)
|
||||
|
||||
expect(result.grossSalary).toBe(40000)
|
||||
})
|
||||
|
||||
it('returns 0 gross when employment does not overlap the period', () => {
|
||||
const result = calculateSalary(
|
||||
makeBasicInput({
|
||||
periodStart: '2026-05-01',
|
||||
periodEnd: '2026-05-31',
|
||||
employmentStart: '2026-06-01',
|
||||
employmentEnd: null,
|
||||
}),
|
||||
config2026,
|
||||
emptyTaxRates,
|
||||
)
|
||||
|
||||
expect(result.grossSalary).toBe(0)
|
||||
})
|
||||
|
||||
it('combines employment proration with employment_degree', () => {
|
||||
const result = calculateSalary(
|
||||
makeBasicInput({
|
||||
employmentDegree: 50,
|
||||
periodStart: '2026-05-01',
|
||||
periodEnd: '2026-05-31',
|
||||
employmentStart: '2026-05-15',
|
||||
employmentEnd: null,
|
||||
}),
|
||||
config2026,
|
||||
emptyTaxRates,
|
||||
)
|
||||
|
||||
// 40 000 × 50% × 11 / 21 = 20 000 × 11 / 21 ≈ 10 476,19
|
||||
expect(result.grossSalary).toBeCloseTo(10476.19, 2)
|
||||
})
|
||||
|
||||
it('skips proration when period bounds are missing (backward compat)', () => {
|
||||
const result = calculateSalary(
|
||||
makeBasicInput({
|
||||
employmentStart: '2026-05-15',
|
||||
employmentEnd: null,
|
||||
}),
|
||||
config2026,
|
||||
emptyTaxRates,
|
||||
)
|
||||
|
||||
expect(result.grossSalary).toBe(40000)
|
||||
})
|
||||
|
||||
it('subtracts unpaid_leave once on top of proration (no double-counting)', () => {
|
||||
// 40 000 × 11/21 (mid-month hire) − 2 × 40 000/21 (two unpaid days)
|
||||
// = 20 952,38 − 3 809,52
|
||||
// = 17 142,86
|
||||
const result = calculateSalary(
|
||||
makeBasicInput({
|
||||
periodStart: '2026-05-01',
|
||||
periodEnd: '2026-05-31',
|
||||
employmentStart: '2026-05-15',
|
||||
employmentEnd: null,
|
||||
lineItems: [
|
||||
{
|
||||
itemType: 'unpaid_leave',
|
||||
amount: -3809.52, // = 2 × Math.round((40000/21) * 100) / 100
|
||||
isTaxable: true,
|
||||
isAvgiftBasis: true,
|
||||
isVacationBasis: false,
|
||||
isGrossDeduction: false,
|
||||
isNetDeduction: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
config2026,
|
||||
emptyTaxRates,
|
||||
)
|
||||
|
||||
expect(result.grossSalary).toBeCloseTo(17142.86, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('prorateBaseSalaryForPeriod', () => {
|
||||
it('returns 1 when all dates are missing', () => {
|
||||
expect(prorateBaseSalaryForPeriod(undefined, undefined, undefined, undefined)).toBe(1)
|
||||
})
|
||||
|
||||
it('returns 1 when employment fully covers the period', () => {
|
||||
expect(
|
||||
prorateBaseSalaryForPeriod('2020-01-01', null, '2026-05-01', '2026-05-31'),
|
||||
).toBe(1)
|
||||
})
|
||||
|
||||
it('returns 11/21 for May 2026 mid-month hire on the 15th', () => {
|
||||
const ratio = prorateBaseSalaryForPeriod(
|
||||
'2026-05-15',
|
||||
null,
|
||||
'2026-05-01',
|
||||
'2026-05-31',
|
||||
)
|
||||
expect(ratio).toBeCloseTo(11 / 21, 6)
|
||||
})
|
||||
|
||||
it('returns 0 when employment ends before the period starts', () => {
|
||||
expect(
|
||||
prorateBaseSalaryForPeriod('2020-01-01', '2026-04-30', '2026-05-01', '2026-05-31'),
|
||||
).toBe(0)
|
||||
})
|
||||
|
||||
it('returns 0 when employment starts after the period ends', () => {
|
||||
expect(
|
||||
prorateBaseSalaryForPeriod('2026-06-01', null, '2026-05-01', '2026-05-31'),
|
||||
).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// Hardening — realistic API flow & cross-rule invariants
|
||||
// ============================================================
|
||||
|
||||
@@ -237,11 +237,34 @@ describe('deriveAbsenceLineItems — parental', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('deriveAbsenceLineItems — unpaid_leave', () => {
|
||||
it('emits unpaid_leave line item with a per-day daily-rate deduction', () => {
|
||||
const result = deriveAbsenceLineItems(
|
||||
baseInput({
|
||||
monthlySalary: 42000, // dailyRate = 42 000 / 21 = 2 000
|
||||
periodDays: days([
|
||||
['2026-04-10', 'unpaid_leave'],
|
||||
['2026-04-13', 'unpaid_leave'],
|
||||
]),
|
||||
}),
|
||||
)
|
||||
const unpaid = result.lineItems.find(li => li.item_type === 'unpaid_leave')
|
||||
expect(unpaid).toBeDefined()
|
||||
expect(unpaid!.quantity).toBe(2)
|
||||
expect(unpaid!.amount).toBe(-4000)
|
||||
// false — engine's Step 3 absence sum already subtracts unpaid_leave;
|
||||
// setting the flag would double-count in Step 4 totalGrossDeductions.
|
||||
expect(unpaid!.is_gross_deduction).toBe(false)
|
||||
expect(unpaid!.is_vacation_basis).toBe(false)
|
||||
expect(result.aggregated.unpaidLeaveDays).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deriveAbsenceLineItems — empty', () => {
|
||||
it('returns empty result for no absence', () => {
|
||||
const result = deriveAbsenceLineItems(baseInput())
|
||||
expect(result.lineItems).toEqual([])
|
||||
expect(result.aggregated).toEqual({ sickDays: 0, vabDays: 0, parentalDays: 0 })
|
||||
expect(result.aggregated).toEqual({ sickDays: 0, vabDays: 0, parentalDays: 0, unpaidLeaveDays: 0 })
|
||||
expect(result.flagFkReporting).toBe(false)
|
||||
expect(result.flagLakarintyg).toBe(false)
|
||||
})
|
||||
|
||||
@@ -37,6 +37,7 @@ const LINE_ITEM_ACCOUNTS: Record<SalaryLineItemType, string> = {
|
||||
sick_day15_plus: '7281',
|
||||
vab: '7210',
|
||||
parental_leave: '7210',
|
||||
unpaid_leave: '7210',
|
||||
vacation: '7285',
|
||||
semesterersattning: '7285',
|
||||
// Travel
|
||||
|
||||
@@ -42,6 +42,17 @@ export interface SalaryCalculationInput {
|
||||
|
||||
/** Line items */
|
||||
lineItems: CalculationLineItem[]
|
||||
|
||||
/**
|
||||
* Pay period bounds (YYYY-MM-DD). Together with employmentStart/employmentEnd
|
||||
* they drive partial-month proration: an employee hired mid-period or
|
||||
* terminated mid-period receives only the workday-fraction of base salary.
|
||||
* When omitted, proration is skipped (ratio = 1).
|
||||
*/
|
||||
periodStart?: string
|
||||
periodEnd?: string
|
||||
employmentStart?: string
|
||||
employmentEnd?: string | null
|
||||
}
|
||||
|
||||
export interface CalculationLineItem {
|
||||
@@ -134,6 +145,80 @@ function fmtKr(amount: number): string {
|
||||
return `${Math.round(amount).toLocaleString('sv-SE')} kr`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Partial-month proration
|
||||
// ============================================================
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000
|
||||
|
||||
function parseIsoDateUtc(s: string): Date {
|
||||
return new Date(`${s}T00:00:00Z`)
|
||||
}
|
||||
|
||||
function maxDate(a: string, b: string): string {
|
||||
return a >= b ? a : b
|
||||
}
|
||||
|
||||
function minDate(a: string, b: string): string {
|
||||
return a <= b ? a : b
|
||||
}
|
||||
|
||||
/**
|
||||
* Count Mon–Fri days inclusive between start and end (YYYY-MM-DD). Returns 0
|
||||
* when start > end. Swedish bank holidays are NOT excluded — the engine uses
|
||||
* the same 21-workday convention used elsewhere (monthlySalary / 21), so a
|
||||
* variable workday count that excluded holidays would diverge from the
|
||||
* baseline daily rate convention.
|
||||
*/
|
||||
function countWorkdaysInclusive(start: string, end: string): number {
|
||||
if (start > end) return 0
|
||||
const startMs = parseIsoDateUtc(start).getTime()
|
||||
const endMs = parseIsoDateUtc(end).getTime()
|
||||
const totalDays = Math.round((endMs - startMs) / DAY_MS) + 1
|
||||
let workdays = 0
|
||||
for (let i = 0; i < totalDays; i++) {
|
||||
const d = new Date(startMs + i * DAY_MS)
|
||||
const dow = d.getUTCDay() // 0 = Sun, 6 = Sat
|
||||
if (dow >= 1 && dow <= 5) workdays += 1
|
||||
}
|
||||
return workdays
|
||||
}
|
||||
|
||||
/**
|
||||
* Fraction of the pay period the employee was actually employed, measured in
|
||||
* Mon–Fri workdays. Returns 1 when the employee was employed for the full
|
||||
* period (or when employment dates / period bounds are missing). Returns 0
|
||||
* when the employee was not employed at all during the period.
|
||||
*
|
||||
* This is the standard Swedish payroll convention for partial-month proration:
|
||||
* an employee hired 2026-05-15 gets workdays-in-(May 15–31) / workdays-in-May.
|
||||
* Hourly employees are not prorated here — they are paid for actually-worked
|
||||
* hours, so the calling code passes salaryType='monthly' to gate this.
|
||||
*/
|
||||
export function prorateBaseSalaryForPeriod(
|
||||
employmentStart: string | undefined,
|
||||
employmentEnd: string | null | undefined,
|
||||
periodStart: string | undefined,
|
||||
periodEnd: string | undefined,
|
||||
): number {
|
||||
if (!periodStart || !periodEnd) return 1
|
||||
if (!employmentStart) return 1
|
||||
const effectiveStart = maxDate(employmentStart, periodStart)
|
||||
const effectiveEnd = employmentEnd ? minDate(employmentEnd, periodEnd) : periodEnd
|
||||
if (effectiveStart > effectiveEnd) return 0
|
||||
// Fast path: employment fully covers the period.
|
||||
if (employmentStart <= periodStart && (!employmentEnd || employmentEnd >= periodEnd)) {
|
||||
return 1
|
||||
}
|
||||
const overlap = countWorkdaysInclusive(effectiveStart, effectiveEnd)
|
||||
const total = countWorkdaysInclusive(periodStart, periodEnd)
|
||||
if (total === 0) return 1
|
||||
const ratio = overlap / total
|
||||
if (ratio < 0) return 0
|
||||
if (ratio > 1) return 1
|
||||
return ratio
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Main calculation
|
||||
// ============================================================
|
||||
@@ -162,13 +247,43 @@ export function calculateSalary(
|
||||
// ─── Step 1: Base salary ───
|
||||
let baseSalary: number
|
||||
if (input.salaryType === 'monthly') {
|
||||
baseSalary = r(input.monthlySalary * (input.employmentDegree / 100))
|
||||
steps.push({
|
||||
label: 'Grundlön',
|
||||
formula: 'månadslön × (sysselsättningsgrad / 100)',
|
||||
input: { monthly_salary: input.monthlySalary, employment_degree: input.employmentDegree },
|
||||
output: baseSalary,
|
||||
})
|
||||
const degreeAdjusted = r(input.monthlySalary * (input.employmentDegree / 100))
|
||||
const prorationRatio = prorateBaseSalaryForPeriod(
|
||||
input.employmentStart,
|
||||
input.employmentEnd,
|
||||
input.periodStart,
|
||||
input.periodEnd,
|
||||
)
|
||||
if (prorationRatio < 1 && input.periodStart && input.periodEnd) {
|
||||
baseSalary = r(degreeAdjusted * prorationRatio)
|
||||
const overlapStart = input.employmentStart && input.employmentStart > input.periodStart
|
||||
? input.employmentStart
|
||||
: input.periodStart
|
||||
const overlapEnd = input.employmentEnd && input.employmentEnd < input.periodEnd
|
||||
? input.employmentEnd
|
||||
: input.periodEnd
|
||||
steps.push({
|
||||
label: 'Grundlön (proportionerad anställningsperiod)',
|
||||
formula: 'månadslön × (sysselsättningsgrad / 100) × (arbetsdagar i anställning / arbetsdagar i period)',
|
||||
input: {
|
||||
monthly_salary: input.monthlySalary,
|
||||
employment_degree: input.employmentDegree,
|
||||
degree_adjusted: degreeAdjusted,
|
||||
overlap_start: overlapStart,
|
||||
overlap_end: overlapEnd,
|
||||
proration_ratio: Math.round(prorationRatio * 10000) / 10000,
|
||||
},
|
||||
output: baseSalary,
|
||||
})
|
||||
} else {
|
||||
baseSalary = degreeAdjusted
|
||||
steps.push({
|
||||
label: 'Grundlön',
|
||||
formula: 'månadslön × (sysselsättningsgrad / 100)',
|
||||
input: { monthly_salary: input.monthlySalary, employment_degree: input.employmentDegree },
|
||||
output: baseSalary,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const hours = input.hoursWorked || 0
|
||||
const rate = input.hourlyRate || 0
|
||||
@@ -205,7 +320,7 @@ export function calculateSalary(
|
||||
|
||||
// ─── Step 3: Subtract absence deductions ───
|
||||
const absenceItems = input.lineItems.filter(
|
||||
li => ['sick_karens', 'sick_day2_14', 'sick_day15_plus', 'vab', 'parental_leave', 'vacation'].includes(li.itemType)
|
||||
li => ['sick_karens', 'sick_day2_14', 'sick_day15_plus', 'vab', 'parental_leave', 'unpaid_leave', 'vacation'].includes(li.itemType)
|
||||
)
|
||||
const totalAbsence = r(absenceItems.reduce((sum, li) => sum + li.amount, 0))
|
||||
if (totalAbsence !== 0) {
|
||||
|
||||
@@ -49,7 +49,21 @@ export async function createSalaryRunWithEmployees(
|
||||
.eq('company_id', companyId)
|
||||
.eq('is_active', true)
|
||||
|
||||
for (const emp of employees || []) {
|
||||
// Pay period bounds (inclusive) — used to skip employees whose employment
|
||||
// does not overlap the run. employment_start is NOT NULL on employees;
|
||||
// employment_end is nullable for ongoing employments.
|
||||
const periodStart = `${params.periodYear}-${String(params.periodMonth).padStart(2, '0')}-01`
|
||||
const periodEnd = new Date(Date.UTC(params.periodYear, params.periodMonth, 0))
|
||||
.toISOString()
|
||||
.slice(0, 10)
|
||||
|
||||
const eligibleEmployees = (employees || []).filter((emp) => {
|
||||
if (emp.employment_start && emp.employment_start > periodEnd) return false
|
||||
if (emp.employment_end && emp.employment_end < periodStart) return false
|
||||
return true
|
||||
})
|
||||
|
||||
for (const emp of eligibleEmployees) {
|
||||
const baseAmount =
|
||||
emp.salary_type === 'monthly'
|
||||
? Math.round((emp.monthly_salary || 0) * (emp.employment_degree / 100) * 100) / 100
|
||||
@@ -91,7 +105,7 @@ export async function createSalaryRunWithEmployees(
|
||||
}
|
||||
}
|
||||
|
||||
return { run: run as Record<string, unknown>, employeeCount: (employees || []).length }
|
||||
return { run: run as Record<string, unknown>, employeeCount: eligibleEmployees.length }
|
||||
} catch (err) {
|
||||
// Compensating delete — never leave a half-populated run. Cascade removes
|
||||
// any salary_run_employees / salary_line_items already inserted.
|
||||
|
||||
@@ -35,6 +35,7 @@ export type AbsenceType =
|
||||
| 'pregnancy'
|
||||
| 'care_relative'
|
||||
| 'study'
|
||||
| 'unpaid_leave'
|
||||
| 'other_leave'
|
||||
|
||||
export interface AbsenceDay {
|
||||
@@ -44,7 +45,7 @@ export interface AbsenceDay {
|
||||
}
|
||||
|
||||
export interface DerivedLineItem {
|
||||
item_type: 'sick_karens' | 'sick_day2_14' | 'sick_day15_plus' | 'vab' | 'parental_leave'
|
||||
item_type: 'sick_karens' | 'sick_day2_14' | 'sick_day15_plus' | 'vab' | 'parental_leave' | 'unpaid_leave'
|
||||
description: string
|
||||
quantity: number
|
||||
amount: number
|
||||
@@ -58,6 +59,7 @@ export interface AggregatedCounts {
|
||||
sickDays: number
|
||||
vabDays: number
|
||||
parentalDays: number
|
||||
unpaidLeaveDays: number
|
||||
}
|
||||
|
||||
export interface DeriveResult {
|
||||
@@ -173,6 +175,7 @@ export function deriveAbsenceLineItems(input: DeriveInput): DeriveResult {
|
||||
.map(d => d.absence_date)
|
||||
const vabDays = periodDays.filter(d => d.absence_type === 'vab')
|
||||
const parentalDays = periodDays.filter(d => d.absence_type === 'parental')
|
||||
const unpaidLeaveDays = periodDays.filter(d => d.absence_type === 'unpaid_leave')
|
||||
|
||||
let flagFkReporting = false
|
||||
let flagLakarintyg = false
|
||||
@@ -334,12 +337,37 @@ export function deriveAbsenceLineItems(input: DeriveInput): DeriveResult {
|
||||
})
|
||||
}
|
||||
|
||||
// ── Unpaid leave (tjänstledighet utan lön) ─────────────────────────────
|
||||
// Each day reduces gross pay by one daily rate (monthlySalary / 21 — same
|
||||
// convention used elsewhere in the engine). Not semestergrundande per SemL
|
||||
// 17 § (only paid leave types accrue vacation).
|
||||
//
|
||||
// is_gross_deduction is deliberately false: the engine's Step 3 absence
|
||||
// sum already subtracts items whose item_type is 'unpaid_leave', so setting
|
||||
// the flag would double-count the amount in Step 4's gross_deduction sum.
|
||||
const unpaidLeaveCount = unpaidLeaveDays.length
|
||||
if (unpaidLeaveCount > 0) {
|
||||
const dailyRate = r(monthlySalary / 21)
|
||||
const deduction = r(dailyRate * unpaidLeaveCount)
|
||||
lineItems.push({
|
||||
item_type: 'unpaid_leave',
|
||||
description: `Tjänstledighet utan lön (${unpaidLeaveCount} dagar)`,
|
||||
quantity: unpaidLeaveCount,
|
||||
amount: -deduction,
|
||||
is_taxable: true,
|
||||
is_avgift_basis: true,
|
||||
is_vacation_basis: false,
|
||||
is_gross_deduction: false,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
lineItems,
|
||||
aggregated: {
|
||||
sickDays: periodSickDates.length,
|
||||
vabDays: vabCount,
|
||||
parentalDays: parentalCount,
|
||||
unpaidLeaveDays: unpaidLeaveCount,
|
||||
},
|
||||
flagFkReporting,
|
||||
flagLakarintyg,
|
||||
|
||||
@@ -43,6 +43,7 @@ const DERIVED_ABSENCE_TYPES: SalaryLineItemType[] = [
|
||||
'sick_day15_plus',
|
||||
'vab',
|
||||
'parental_leave',
|
||||
'unpaid_leave',
|
||||
]
|
||||
|
||||
/**
|
||||
@@ -598,6 +599,10 @@ export async function runSalaryCalculation(
|
||||
vaxaStodStart: emp.vaxa_stod_start,
|
||||
vaxaStodEnd: emp.vaxa_stod_end,
|
||||
lineItems,
|
||||
periodStart,
|
||||
periodEnd,
|
||||
employmentStart: emp.employment_start,
|
||||
employmentEnd: emp.employment_end,
|
||||
},
|
||||
config,
|
||||
taxRates.map((r) => ({
|
||||
|
||||
Reference in New Issue
Block a user