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

10 lines
8.8 KiB
JavaScript

import fs from 'node:fs';import assert from 'node:assert/strict';import crypto from 'node:crypto';import {session} from './sgf-single-http.mjs';import {checkedStatements} from '../resources/js/internal-statements-view.js';
const root=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')).root+'/private/receipt-composition-cc50a276c10c',progress=root+'/statements-http-progress.json',saved=fs.existsSync(progress)?JSON.parse(fs.readFileSync(progress)):{},save=()=>fs.writeFileSync(progress,JSON.stringify(saved)),checks=[],ok=(v,n)=>{assert(v,n);checks.push(n);},base='/api/general-ledger/statements';
const prep=await session('sgf:accounting_prepare','accounting'),review=await session('sgf:accounting_review','accounting'),read=await session('sgf:read_only','accounting');const get=async(q={view:'history',page:1,per_page:25},s=read)=>{const r=await s.get(base+'?'+new URLSearchParams(q));assert.equal(r.status,200);return checkedStatements(await r.json(),q);};let state=await get();ok(prep.identity.context.internal_statements_enabled===true,'selected installation feature enabled');const names=s=>s.identity.context.grants.filter(g=>g.permission.startsWith('general_ledger.statements.')).map(g=>g.permission);ok(JSON.stringify(names(prep))===JSON.stringify(['general_ledger.statements.prepare'])&&JSON.stringify(names(review))===JSON.stringify(['general_ledger.statements.review'])&&names(read).length===0,'exact approved preparation review rights only');
const accounts=await get({view:'accounts',page:1,per_page:100},prep),categories={'QA-CAIXA':'asset','QA-CLIENTES':'asset','QA-STOCK':'asset','QA-FORNECEDOR':'liability','QA-VENDAS':'income','QA-CONSUMO-STOCK':'expense','QA-CUSTO-DIRECTO':'expense','QA-CUSTO-INDUSTRIAL':'expense','QA-DESPESA-CAIXA':'expense','QA-DIFERENCA-INVENTARIO':'expense','QA-COMPENSACAO-ENSAIO':'unclassified','QA-COMPENSACAO-INDUSTRIAL':'unclassified','QA-COMPENSACAO-STOCK':'unclassified','QA-CONTRAPARTIDA':'unclassified','QA-DEVOLUCAO-FORNECEDOR':'unclassified','QA-IMPOSTO-TESTE':'unclassified','QA-LIQUIDACAO':'unclassified','QA-LIQUIDACAO-CLIENTE':'unclassified','QA-REDUCAO-CLIENTE':'unclassified'};const entries=Object.entries(categories).map(([code,category])=>{const row=accounts.records.data.find(a=>a.code===code);assert(row,'Explicit approved QA account missing '+code);return{account_id:row.id,category};});ok(entries.length===19&&entries.filter(e=>e.category==='unclassified').length===9,'explicit19 approved choices nine pending');const input=(kind,action,version,data)=>({kind,action,version,data,reason:'Decisão individual QA de demonstrações internas parciais, sem classificação legal ou novos lançamentos.'});
async function perform(name,s,body){saved[name]??={key:crypto.randomUUID()};save();const step=saved[name];if(step.result)return step.result;const old=await s.get(base+'/receipts/'+step.key);if(old.status===200){step.result=(await old.json()).result;save();return step.result;}assert.equal(old.status,404);const cut=await s.command(base+'/preview',body,step.key);assert.equal(cut.status,200,JSON.stringify(cut.value));step.cut=cut.value;save();const out=await s.command(base+'/confirm',{preview:cut.value.preview},step.key);assert.equal(out.status,201,JSON.stringify(out.value));step.result=out.value;save();return out.value;}
const m1=await perform('mappingPrepare',prep,input('mapping','prepare',0,{entries,evidence:'As19 correspondências artificiais são as discriminadas no âmbito autorizado, com nove pendências explícitas.'}));ok(m1.version===1&&m1.state==='prepared','actual mapping prepared v1');if(!saved.replayChecked){const r=await prep.command(base+'/confirm',{preview:saved.mappingPrepare.cut.preview},saved.mappingPrepare.key);assert.equal(r.status,200);assert(r.value.replayed);saved.replayChecked=true;save();}ok(saved.replayChecked,'same mapping confirmation recovered once');const m2=await perform('mappingReview',review,input('mapping','review',1,{decision:'approved'}));ok(m2.version===2&&m2.state==='approved','mapping independently reviewed v2');const resultData={mapping_id:m2.id,from_on:'2026-11-10',to_on:'2026-11-10',per_page:25,evidence:'Resultado interno parcial sobre os27 lançamentos QA existentes; capital indeterminado e nove contas pendentes.'};const r1=await perform('resultPrepare',prep,input('result','prepare',0,resultData));ok(r1.version===1&&r1.state==='prepared','actual result prepared v1');const r2=await perform('resultReview',review,input('result','review',1,{decision:'approved'}));ok(r2.version===2&&r2.state==='approved','result independently reviewed v2');
const query={view:'result',version_id:r2.id,page:1,per_page:25},report=await get(query),summary=report.snapshot.summary,c=summary.currencies.MZN;ok(summary.accounts===19&&summary.basis.journals===27&&report.current_source_matches===true,'effective19 accounts27 journals stable source');ok(c.groups.asset.presentation_minor==='215500'&&c.groups.liability.presentation_minor==='10000','classified asset2155 and supplier100');ok(c.groups.income.presentation_minor==='258621'&&c.groups.expense.presentation_minor==='220000'&&c.known_result_subtotal_minor==='38621','classified income expense subtotal exact');ok(c.unclassified_accounts===9&&c.complete_result_minor===null&&c.equity_minor===null&&!c.complete_balance_sheet,'nine pending no full result or capital fabrication');const larger=await get({...query,per_page:100});ok(JSON.stringify(larger.records.data)===JSON.stringify(report.records.data),'frozen result25/100 equivalent');const original=await read.get('/api/general-ledger/trial-balance?from_on=2026-11-10&to_on=2026-11-10&per_page=100'),trial=await original.json();assert.equal(original.status,200);for(const row of trial.rows){const line=report.records.data.find(r=>r.account_id===row.account_id);ok(line&&String(row.opening_net_minor)===line.opening_net_minor&&String(row.debit_minor)===line.debit_minor&&String(row.credit_minor)===line.credit_minor&&String(row.closing_net_minor)===line.closing_net_minor,'frozen original values '+row.code);}
let response=await read.get(base+'/exports/'+r2.id);const bytes=Buffer.from(await response.arrayBuffer());ok(response.status===200&&crypto.createHash('sha256').update(bytes).digest('hex')===report.snapshot.csv_sha256&&response.headers.get('x-content-sha256')===report.snapshot.csv_sha256,'frozen export bytes and SHA');ok(bytes.toString().includes('SEM MODELO LEGAL')&&bytes.toString().includes('INDETERMINADO'),'export explicitly partial capital unknown');response=await read.get(base+'/exports/'+r1.id);ok(response.status===403,'prepared result not exported as reviewed');const far=await get({...query,page:100000});ok(far.records.data.length===0,'far page no fallback');const bad=await read.command(base+'/preview',input('mapping','prepare',2,{entries,evidence:'QA denied read-only preparation.'}));ok(bad.status===403,'reader cannot prepare');const own=await prep.command(base+'/preview',input('mapping','review',2,{decision:'approved'}));ok(own.status===403,'preparer cannot review');const other=await session('sgf:read_only','finance');response=await other.get(base);ok([403,409].includes(response.status),'other module refused');response=await read.get(base+'?tenant_id=2');ok(response.status===422,'tenant override refused');response=await read.get(base+'?per_page=1000');ok(response.status===422,'unbounded page refused');response=await review.get(base+'/receipts/'+saved.mappingPrepare.key);ok(response.status===404,'other actor receipt hidden');const fresh=await session('sgf:accounting_prepare','accounting');response=await fresh.get(base+'/receipts/'+saved.mappingPrepare.key);ok(response.status===200&&(await response.json()).result.id===m1.id,'own receipt recovered fresh session');const exact=report.records.data.find(r=>r.code==='QA-CAIXA');response=await read.get('/general-ledger/account-ledger?'+new URLSearchParams({...summary.query,account_id:exact.account_id,per_page:25}));ok(response.status===200&&(await response.text()).includes('QA-CAIXA'),'exact account dates drilldown to original reader');
const manifest=JSON.parse(fs.readFileSync(root+'/app/public/build/manifest.json'));for(const e of Object.values(manifest).filter(v=>v.isEntry||v.file.includes('InternalStatementsWorkspace'))){response=await fetch('http://127.0.0.1:8085/build/'+e.file);const b=Buffer.from(await response.arrayBuffer());ok(response.status===200&&/javascript/.test(response.headers.get('content-type'))&&crypto.createHash('sha256').update(b).digest('hex')===crypto.createHash('sha256').update(fs.readFileSync(root+'/app/public/build/'+e.file)).digest('hex'),'actual JS delivery '+e.file);}const file=root+'/statements-http-20261111.json';fs.writeFileSync(root+'/statements-sample-private.json',JSON.stringify(report));fs.writeFileSync(file,JSON.stringify({passed:true,checks,administrative_commands:4,new_accounts:0,new_journals:0,new_demo:false,browser_visual_review:false},null,2));console.log(JSON.stringify({passed:true,checks:checks.length,proof:file}));