Files
SGF/scripts/sgf-cash-http.mjs
2026-11-11 09:00:17 +02:00

13 lines
7.1 KiB
JavaScript

import fs from'node:fs';import assert from'node:assert/strict';import{session}from'./sgf-single-http.mjs';
const run=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')),root=run.root+'/private/receipt-composition-cc50a276c10c',file=root+'/cash-http-20261110.json',p=fs.existsSync(file)?JSON.parse(fs.readFileSync(file)):{operations:{},checks:[]},save=()=>fs.writeFileSync(file,JSON.stringify(p,null,2));const c={prepare:await session('sgf:finance_prepare','finance'),review:await session('sgf:finance_review','finance'),read:await session('sgf:read_only','finance'),ap:await session('sgf:accounting_prepare','accounting'),ar:await session('sgf:accounting_review','accounting')};
const ok=(v,t)=>{assert(v,t);if(!p.checks.includes(t))p.checks.push(t);save();};const state=async(role='read',query='')=>{const r=await c[role].get('/api/cash'+query);assert.equal(r.status,200,await r.clone().text());return r.json();};
async function step(name,role,action,kind,data={},proposal=null){let o=p.operations[name];if(!o){o=p.operations[name]={role,action,kind,data,proposal,key:crypto.randomUUID()};save();}assert.deepEqual({role,action,kind,data,proposal},{role:o.role,action:o.action,kind:o.kind,data:o.data,proposal:o.proposal});if(o.result)return o.result;let r=await c[role].get('/api/cash/receipts/'+o.key);if(r.status===200){o.result=(await r.json()).result;save();return o.result;}assert.equal(r.status,404,'historical command lookup');const s=await state(role);const cut=await c[role].command('/api/cash/preview',{action,kind,data,proposal_id:proposal,version:s.basis.revision,reason:'Conferência individual QA8085: '+name+'.'},o.key);assert.equal(cut.status,200,name+': '+JSON.stringify(cut.value));const done=await c[role].command('/api/cash/confirm',{preview:cut.value.preview});assert.equal(done.status,200,name+': '+JSON.stringify(done.value));o.result=done.value;save();const again=await c[role].command('/api/cash/confirm',{preview:cut.value.preview});ok(again.status===200&&again.value.replayed&&again.value.id===done.value.id,name+' one result on replay');return o.result;}
async function journal(name,role,path,body){let o=p.operations[name];if(!o){o=p.operations[name]={role,path,body,key:crypto.randomUUID()};save();}assert.deepEqual({role,path,body},{role:o.role,path:o.path,body:o.body});if(o.result)return o.result;const r=await c[role].command(path,body,o.key);assert.equal(r.status,200,name+': '+JSON.stringify(r.value));o.result=r.value;save();return o.result;}
let r=await c.prepare.get('/finance/cash',{headers:{Accept:'text/html'}});ok(r.status===200&&(await r.text()).includes('sgf-cash.mjs'),'authenticated cash screen available');const initial=await state();ok(initial.capabilities.prepare===false&&initial.capabilities.review===false,'read-only cash reader has no write powers');const open=await step('abrir-zero','prepare','prepare','open',{date:'2026-11-10',label:'Caixa QA integrada8085',opening:'0.00',evidence:'Saldo inicial explicitamente zero, sem dinheiro real.'});await step('conferir-abertura','review','review','open',{decision:'approved'},open.id);
const receipt=await step('classificar-1000','prepare','prepare','receipt',{date:'2026-11-10',receipt_id:initial.binding.receipt_id,medium:'cash',evidence:'Classificação explícita de custódia fictícia1000, QA-CASH-20261110.'});const received=await step('conferir-1000','review','review','receipt',{decision:'approved'},receipt.id);ok(received.impact.after_minor===100000&&!received.impact.new_invoice_or_receipt,'cash custody1000 reuses receipt without another debt or invoice');
const expense=await step('preparar-despesa50','prepare','prepare','expense',{date:'2026-11-10',amount:'50.00',beneficiary:'Beneficiário fictício QA',description:'Expediente sintético50MZN, sem compra ou pagamento real.',evidence:'QA-DESPESA-CAIXA-50-20261110, comprovativo de ensaio.'});const paid=await step('conferir-despesa50','review','review','expense',{decision:'approved'},expense.id);ok(paid.impact.after_minor===95000,'distinct expense review leaves950');
const count=await step('contar950','prepare','prepare','count',{date:'2026-11-10',counted:'950.00',evidence:'Contagem fictícia950MZN explicitamente conferida.'});const counted=await step('conferir-contagem','review','review','count',{decision:'approved'},count.id);const s=await state('review');const notReady=await c.review.command('/api/cash/preview',{action:'close',kind:'count',proposal_id:counted.id,version:s.basis.revision,data:{},reason:'Não fechar enquanto faltam as duas valorizações de Caixa.'});if(!p.operations['fechar-caixa']?.result)ok(notReady.status===409,'unposted cash facts refuse premature closing');
for(const [kind,id]of [['cash.receipt_classified',received.id],['cash.expense_confirmed',paid.id]]){const q=await journal(kind+'-preparar','ap','/api/general-ledger/effect-prepare',{effect_type:kind,source_id:id,journal_on:'2026-11-10',period_key:'QA-2026-INTEGRADO',rule_key:kind,reason:'Origem de Caixa QA individualmente seleccionada: '+kind+'.'});await journal(kind+'-rever','ar','/api/general-ledger/validate',{proposal_id:q.proposal_id,revision:q.revision,decision:'approved',reason:'Conferência independente da origem e tratamento de Caixa.'});await journal(kind+'-aplicar','ap','/api/general-ledger/post',{proposal_id:q.proposal_id,revision:q.revision,reason:'Aplicação única da reclassificação ou despesa de Caixa QA.'});}
await step('fechar-caixa','review','close','count',{},counted.id);const final=await state();ok(final.basis.closed&&final.basis.balance_minor===95000&&final.events.total===9,'selected cash closes950 with nine audit events');ok(final.period.opening_minor===0&&final.period.receipts_minor===100000&&final.period.expenses_minor===5000&&final.period.closing_minor===95000,'cash statement reconciles0+1000-50=950');const later=await state('read','?from=2026-11-11&per_page=100');ok(later.events.total===0&&later.period.opening_minor===95000&&later.period.closing_minor===95000,'next interval carries950 without extra movements');const denied=await c.read.command('/api/cash/preview',{action:'prepare',kind:'expense',version:9,data:{},reason:'Consulta não concede competência para despesa.'});ok(denied.status===403,'read-only cash writer403');
const recovered=await session('sgf:finance_review','finance');r=await recovered.get('/api/cash/receipts/'+p.operations['fechar-caixa'].key);const historical=await r.json();ok(r.status===200&&historical.historical_receipt_only&&!historical.operation_executed&&historical.result.state==='closed','new session recovers old closing receipt without executing it');r=await c.ap.get('/api/general-ledger');const gl=await r.json();ok(r.status===200&&gl.pagination.total===27&&gl.totals.debit_minor===gl.totals.credit_minor,'same ledger27 balanced journals');r=await c.ap.get('/api/general-ledger?page=2');const second=await r.json();ok(r.status===200&&gl.journals.length===25&&second.journals.length===2&&new Set([...gl.journals,...second.journals].map(x=>x.id)).size===27,'two journal pages expose27 unique records without treating page size as total');p.passed=true;p.browser_visual_review=false;p.money_real=false;save();console.log(JSON.stringify({passed:true,checks:p.checks.length,proof:file}));