Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma strängvärde byggs med konkatenering i stället för att matcha mönstret.
105 lines
4.0 KiB
TypeScript
105 lines
4.0 KiB
TypeScript
/**
|
|
* Read-only lister for #2058: employees whose jämkningsbeslut is stored but
|
|
* can never be applied because jamkning_valid_to is null.
|
|
*
|
|
* WHY: until #2058 the v1 API and MCP accepted jamkning_percentage +
|
|
* jamkning_valid_from without jamkning_valid_to. isJamkningValid in
|
|
* lib/salary/calculation-engine.ts applies a beslut only when BOTH dates are
|
|
* set, so these rows fall back to the tax table on every payslip and AGI
|
|
* while the stored beslut says otherwise. Every write path now rejects the
|
|
* shape; this script finds the rows that were stored before that.
|
|
*
|
|
* WHAT IT DOES: lists the rows per company with employee id, percentage and
|
|
* start date. No names or other PII are selected: the employee page shows the
|
|
* name once you open the id. It writes NOTHING. The repair is a per-company
|
|
* decision (set an end date, or clear the beslut): either changes the next
|
|
* payslip, so it is made by a human through the employee page or the API,
|
|
* not by this script.
|
|
*
|
|
* Usage:
|
|
* npx tsx scripts/list-incomplete-jamkning.ts
|
|
*
|
|
* Reads NEXT_PUBLIC_SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY from .env.local.
|
|
* Treat .env.local as pointing at PRODUCTION (read-only here, still: be sure
|
|
* which project you are looking at).
|
|
*/
|
|
import { createClient } from '@supabase/supabase-js'
|
|
import { config as dotenv } from 'dotenv'
|
|
import { resolve } from 'node:path'
|
|
|
|
dotenv({ path: resolve(process.cwd(), '.env.local') })
|
|
|
|
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL
|
|
const SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY
|
|
if (!SUPABASE_URL || !SERVICE_KEY) {
|
|
console.error('Missing NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY in .env.local')
|
|
process.exit(1)
|
|
}
|
|
|
|
const supabase = createClient(SUPABASE_URL, SERVICE_KEY, { auth: { persistSession: false } })
|
|
|
|
interface Row {
|
|
id: string
|
|
company_id: string
|
|
is_active: boolean
|
|
jamkning_percentage: number
|
|
jamkning_valid_from: string | null
|
|
jamkning_valid_to: string | null
|
|
}
|
|
|
|
async function main() {
|
|
const PAGE = 1000
|
|
const rows: Row[] = []
|
|
for (let from = 0; ; from += PAGE) {
|
|
const { data, error } = await supabase
|
|
.from('employees')
|
|
.select('id, company_id, is_active, jamkning_percentage, jamkning_valid_from, jamkning_valid_to')
|
|
.not('jamkning_percentage', 'is', null)
|
|
.is('jamkning_valid_to', null)
|
|
.order('company_id')
|
|
.order('id')
|
|
.range(from, from + PAGE - 1)
|
|
if (error) {
|
|
console.error('employees query failed:', error.message)
|
|
process.exit(1)
|
|
}
|
|
rows.push(...((data ?? []) as Row[]))
|
|
if (!data || data.length < PAGE) break
|
|
}
|
|
|
|
if (rows.length === 0) {
|
|
console.log('No employees with a jämkning percentage but no valid_to. Nothing to decide.')
|
|
return
|
|
}
|
|
|
|
const companyIds = [...new Set(rows.map((r) => r.company_id))]
|
|
const { data: companies, error: companiesError } = await supabase
|
|
.from('companies')
|
|
.select('id, name')
|
|
.in('id', companyIds)
|
|
if (companiesError) {
|
|
console.error('companies query failed:', companiesError.message)
|
|
process.exit(1)
|
|
}
|
|
const companyName = new Map((companies ?? []).map((c) => [c.id as string, c.name as string]))
|
|
|
|
console.log(`${rows.length} employee row(s) across ${companyIds.length} company(ies) with an inert jämkningsbeslut:\n`)
|
|
for (const companyId of companyIds) {
|
|
console.log(`${companyName.get(companyId) ?? '(unknown company)'} ${companyId}`)
|
|
for (const r of rows.filter((x) => x.company_id === companyId)) {
|
|
const state = r.is_active ? 'active ' : 'inactive'
|
|
const from = r.jamkning_valid_from ?? '(no start date)'
|
|
console.log(` ${r.id} ${state} ${r.jamkning_percentage} % from ${from} to (null)`)
|
|
}
|
|
console.log('')
|
|
}
|
|
console.log('Decide per company: set jamkning_valid_to (a beslut normally runs to 31 December of')
|
|
console.log('the from-year) or clear jamkning_percentage. Either changes the next payslip; the')
|
|
console.log('engine ignores these rows until then. Inactive employees can usually be cleared.')
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err)
|
|
process.exit(1)
|
|
})
|