Ctrl K
ComunidadGuías…

scripts/list-incomplete-jamkning.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * 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)
})