scripts/sie-import/acceptance-manual-review.ts
Rama main · réplica de lectura del repositorio original.
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
import assert from 'node:assert/strict'
import { randomUUID } from 'node:crypto'
import dotenv from 'dotenv'
import { createClient } from '@supabase/supabase-js'
// Uses only the existing synthetic browser company on designated staging.
// Leaves the renewed review visible for browser verification.
async function main() {
Object.assign(process.env, dotenv.parse(readFileSync('.env.sie-runtime.local')), { SIE_IMPORT_JOBS: 'true' })
assert.equal(process.env.NEXT_PUBLIC_SUPABASE_URL, 'https://metjnjrhvujscngnpzdv.supabase.co')
const fixture = JSON.parse(readFileSync('.env.sie-ui.json', 'utf8'))
const client = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, {
auth: { persistSession: false, autoRefreshToken: false },
global: { fetch: (input, init) => fetch(input, { ...init, signal: AbortSignal.timeout(10000) }) },
})
const company = await client.from('companies').select('name').eq('id', fixture.company).single()
assert.ok(company.data?.name.startsWith('SIE acceptance'))
assert.ok(fixture.email.endsWith('@test.invalid'))
const { createJournalEntry } = await import('../../src/lib/bookkeeping/engine')
const { submitSIEJob, requestSIEJobAction } = await import('../../src/lib/import/sie-jobs')
const { runSIEWorker } = await import('../../src/lib/import/sie-job-worker')
const reportPath = '.env.sie-manual-review.json'
let report: { sourcePeriod:string; nextPeriod:string; openingEntry:string; job?:string; staleMs?:number }
if (existsSync(reportPath)) report = JSON.parse(readFileSync(reportPath, 'utf8'))
else {
const sourcePeriod=randomUUID(), nextPeriod=randomUUID()
for (const [id,year] of [[sourcePeriod,2030],[nextPeriod,2031]] as const) {
const period=await client.from('fiscal_periods').insert({id,company_id:fixture.company,user_id:fixture.user,
name:String(year),period_start:`${year}-01-01`,period_end:`${year}-12-31`})
if(period.error) throw period.error
}
const opening=await createJournalEntry(client,fixture.company,fixture.user,{
fiscal_period_id:nextPeriod,entry_date:'2031-01-01',description:'Synthetic retained opening balance',
source_type:'opening_balance',voucher_series:'IB',
lines:[{account_number:'1930',debit_amount:100,credit_amount:0},{account_number:'2091',debit_amount:0,credit_amount:100}],
})
const linked=await client.from('fiscal_periods').update({opening_balance_entry_id:opening.id,opening_balances_set:true}).eq('company_id',fixture.company).eq('id',nextPeriod)
if(linked.error) throw linked.error
report={sourcePeriod,nextPeriod,openingEntry:opening.id}
writeFileSync(reportPath,JSON.stringify(report,null,2))
}
async function rows() {
const header=await client.from('journal_entries').select('*').eq('company_id',fixture.company).eq('id',report.openingEntry).single()
const lines=await client.from('journal_entry_lines').select('*').eq('journal_entry_id',report.openingEntry).order('id')
if(header.error || lines.error) throw header.error || lines.error
return {header:header.data,lines:lines.data}
}
const before=await rows()
let supersedesImportId:string|undefined
if(report.job) {
const prior=await client.from('sie_imports').select('job_state').eq('id',report.job).single()
const target=await client.from('fiscal_periods').select('opening_balance_review_token').eq('id',report.nextPeriod).single()
if(prior.data?.job_state==='undone' && !target.data?.opening_balance_review_token) {
supersedesImportId=report.job
report.job=undefined
}
}
if(!report.job) {
const content='#FLAGGA 0\n#PROGRAM "Manual review acceptance" 1\n#SIETYP 4\n#FNAMN "Synthetic AB"\n#RAR 0 20300101 20301231\n#KONTO 1930 "Bank"\n#KONTO 3001 "Sales"\n#VER A 1 20300201 "Test"\n{\n#TRANS 1930 {} 100\n#TRANS 3001 {} -100\n}'
const mappings=['1930','3001'].map(number=>({sourceAccount:number,targetAccount:number,sourceName:number,
targetName:number,confidence:1,matchType:'exact' as const,isOverride:false}))
const job=await submitSIEJob(client,fixture.company,fixture.user,content,mappings,{
filename:'manual-review.se',createFiscalPeriod:false,importTransactions:true,importOpeningBalances:false,updateAccountNames:false,
...(supersedesImportId ? {supersedesImportId,onExistingPeriod:'replace' as const} : {}),
})
report.job=job.id
writeFileSync(reportPath,JSON.stringify(report,null,2))
}
await runSIEWorker({supabase:client,importId:report.job,budgetMs:60000})
let job=(await client.from('sie_imports').select('*').eq('id',report.job).single()).data!
if(job.job_state==='completed') {
const review=job.job_result.nextPeriodOpeningBalanceReview
assert.equal(review.nextPeriodId,report.nextPeriod)
assert.equal(review.openingBalanceEntryId,report.openingEntry)
const args={p_company_id:fixture.company,p_period_id:report.nextPeriod,p_actor:fixture.user,
p_review_token:review.reviewToken,p_expected_entry_id:report.openingEntry}
const acknowledged=await client.rpc('acknowledge_sie_opening_balance_review',args)
if(acknowledged.error) throw acknowledged.error
const started=performance.now()
const stale=await client.rpc('acknowledge_sie_opening_balance_review',args)
report.staleMs=performance.now()-started
assert.equal(stale.error?.code,'55000')
assert.ok(report.staleMs<5000,'Stale response must return promptly')
await requestSIEJobAction(client,fixture.company,fixture.user,report.job!,'undo')
await runSIEWorker({supabase:client,importId:report.job,budgetMs:60000})
}
job=(await client.from('sie_imports').select('*').eq('id',report.job).single()).data!
assert.equal(job.job_state,'undone')
const next=(await client.from('fiscal_periods').select('*').eq('id',report.nextPeriod).single()).data!
assert.equal(next.opening_balance_review_reason,'undo')
assert.ok(next.opening_balance_review_token)
assert.equal(next.opening_balance_entry_id,report.openingEntry)
assert.deepEqual(await rows(),before)
writeFileSync(reportPath,JSON.stringify(report,null,2))
console.log(JSON.stringify({checks:'HTTP completion, acknowledgment, stale rejection, undo review renewal, unchanged next-year voucher',staleResponseMs:report.staleMs}))
}
void main().catch(error => { console.error(error); process.exitCode=1 })