Bug/accounting suggestion (#456)
* feat: add bike benefit handling and optional vacation accrual - Introduced bike benefit (cykelförmån) with calculations for annual market value and monthly taxable value. - Updated schemas to include new benefit types and validation rules. - Implemented API routes for creating, updating, and deleting employee benefits. - Enhanced salary calculation logic to accommodate new vacation rule options, including a 'none' option for no accrual. - Added UI components for managing employee benefits, including input for bike benefit specifics. - Created database migrations for employee benefits and updated salary line items to support new benefit types. * chore: remove Langfuse env var checks Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: enhance OAuth callback URL handling and update default scopes for Visma integration * feat: remove trade_name field and simplify company naming in invoices * refactor: destructure canWrite from useCanWrite for consistency across components * feat: enhance PATCH endpoint to validate existing benefits and handle bike benefit updates * feat: add missing label for bike benefit in salary line item types --------- 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
cd96e5ec26
commit
3fa871c742
+44
-3
@@ -359,7 +359,6 @@ export const MatchSupplierInvoiceSchema = z.object({
|
||||
export const UpdateSettingsSchema = z.object({
|
||||
entity_type: EntityTypeSchema.optional(),
|
||||
company_name: z.string().optional(),
|
||||
trade_name: z.string().nullable().optional(),
|
||||
org_number: z.string().optional(),
|
||||
address_line1: z.string().optional(),
|
||||
address_line2: z.string().optional(),
|
||||
@@ -399,6 +398,7 @@ export const UpdateSettingsSchema = z.object({
|
||||
invoice_show_ocr: z.boolean().optional(),
|
||||
invoice_show_bankgiro: z.boolean().optional(),
|
||||
invoice_show_plusgiro: z.boolean().optional(),
|
||||
invoice_show_logo: z.boolean().optional(),
|
||||
invoice_late_fee_text: z.string().nullable().optional(),
|
||||
invoice_credit_terms_text: z.string().nullable().optional(),
|
||||
// AI agent flow
|
||||
@@ -677,13 +677,13 @@ export const SupplierImportExecuteSchema = z.object({
|
||||
export const EmploymentTypeSchema = z.enum(['employee', 'company_owner', 'board_member'])
|
||||
export const SalaryTypeSchema = z.enum(['monthly', 'hourly'])
|
||||
export const FSkattStatusSchema = z.enum(['a_skatt', 'f_skatt', 'fa_skatt', 'not_verified'])
|
||||
export const VacationRuleSchema = z.enum(['procentregeln', 'sammaloneregeln'])
|
||||
export const VacationRuleSchema = z.enum(['procentregeln', 'sammaloneregeln', 'none'])
|
||||
export const SalaryRunStatusSchema = z.enum(['draft', 'review', 'approved', 'paid', 'booked', 'corrected'])
|
||||
|
||||
export const SalaryLineItemTypeSchema = z.enum([
|
||||
'monthly_salary', 'hourly_salary', 'overtime', 'bonus', 'commission',
|
||||
'gross_deduction_pension', 'gross_deduction_other',
|
||||
'benefit_car', 'benefit_housing', 'benefit_meals', 'benefit_wellness', 'benefit_other',
|
||||
'benefit_car', 'benefit_housing', 'benefit_meals', 'benefit_wellness', 'benefit_bike', 'benefit_other',
|
||||
'sick_karens', 'sick_day2_14', 'sick_day15_plus',
|
||||
'vab', 'parental_leave', 'vacation',
|
||||
'traktamente_taxfree', 'traktamente_taxable',
|
||||
@@ -795,6 +795,47 @@ export const UpdateEmployeeSchema = EmployeeSchemaBase.partial().superRefine((da
|
||||
}
|
||||
})
|
||||
|
||||
export const EmployeeBenefitTypeSchema = z.enum(['bike', 'car', 'meals', 'housing', 'wellness', 'other'])
|
||||
|
||||
export const CreateEmployeeBenefitSchema = z.object({
|
||||
benefit_type: EmployeeBenefitTypeSchema,
|
||||
description: z.string().min(1).max(200),
|
||||
monthly_value: z.number().nonnegative().optional(),
|
||||
/** For bike benefit: annual market value of the förmån. The server computes
|
||||
* monthly_value = max(0, annual − 3000) / 12 per Skatteverket schablon. */
|
||||
annual_market_value: z.number().nonnegative().optional(),
|
||||
valid_from: isoDate,
|
||||
valid_to: isoDate.optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
is_active: z.boolean().optional(),
|
||||
}).superRefine((data, ctx) => {
|
||||
if (data.benefit_type === 'bike') {
|
||||
if (data.annual_market_value === undefined && data.monthly_value === undefined) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Cykelförmån kräver årligt marknadsvärde',
|
||||
path: ['annual_market_value'],
|
||||
})
|
||||
}
|
||||
} else if (data.monthly_value === undefined) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Månatligt förmånsvärde krävs',
|
||||
path: ['monthly_value'],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const UpdateEmployeeBenefitSchema = z.object({
|
||||
description: z.string().min(1).max(200).optional(),
|
||||
monthly_value: z.number().nonnegative().optional(),
|
||||
annual_market_value: z.number().nonnegative().optional(),
|
||||
valid_from: isoDate.optional(),
|
||||
valid_to: isoDate.nullable().optional(),
|
||||
metadata: z.record(z.string(), z.unknown()).optional(),
|
||||
is_active: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const CreateSalaryRunSchema = z.object({
|
||||
period_year: z.number().int().min(2020).max(2100),
|
||||
period_month: z.number().int().min(1).max(12),
|
||||
|
||||
Reference in New Issue
Block a user