25 lines
10 KiB
JavaScript
25 lines
10 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';
|
|
const root=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')).root+'/private/receipt-composition-cc50a276c10c',file=root+'/multiline-invoice-http-progress-private.json',saved=fs.existsSync(file)?JSON.parse(fs.readFileSync(file)):{steps:{}},save=()=>fs.writeFileSync(file,JSON.stringify(saved)),refs=JSON.parse(fs.readFileSync(root+'/multiline-invoice-inputs-private.json')),checks=[],ok=(v,n)=>{assert(v,n);checks.push(n);},base='/api/multiline-invoices',policy='SGF-MULTI-LINE-INVOICE-INTEGRATED-QA-v1';save();
|
|
const commercial=await session('sgf:commercial_prepare','commercial'),review=await session('sgf:commercial_review','commercial'),fiscal=await session('sgf:finance_prepare','commercial'),fp=await session('sgf:finance_prepare','finance'),fr=await session('sgf:finance_review','finance'),ap=await session('sgf:accounting_prepare','accounting'),ar=await session('sgf:accounting_review','accounting');
|
|
const get=async(s,p)=>{const r=await s.get(p);assert.equal(r.status,200,p);return r.json();};ok(commercial.identity.context.multiline_invoices_enabled===true,'same8085 typed editor enabled');
|
|
async function direct(name,s,path,body,{receipt=null}={}){const step=saved.steps[name]??={key:crypto.randomUUID(),body,path};save();assert.equal(JSON.stringify(step.body),JSON.stringify(body),'stable request '+name);if(step.result)return step.result;if(receipt){const r=await s.get(receipt+step.key);if(r.status===200){step.result=(await r.json()).result;save();return step.result;}assert.equal(r.status,404);}const r=await s.command(path,body,step.key);assert.equal(r.status,200,JSON.stringify(r.value));step.result=r.value;save();ok(true,name+' one explicit command');return r.value;}
|
|
async function composed(name,s,domain,body){const step=saved.steps[name]??={key:crypto.randomUUID(),body};save();assert.equal(JSON.stringify(step.body),JSON.stringify(body));if(step.result)return step.result;const receipt=domain==='ledger'?'/api/general-ledger/receipts/':base+'/receipts/';const old=await s.get(receipt+step.key);if(old.status===200){step.result=(await old.json()).result;save();return step.result;}assert.equal(old.status,404);if(domain==='ledger'){const out=await s.command('/api/general-ledger/'+body.action,body.data,step.key);assert.equal(out.status,200,JSON.stringify(out.value));step.result=out.value;save();ok(true,name+' existing individual ledger command');return out.value;}const b=base+'/'+domain;const cut=await s.command(b+'/preview',body,step.key);assert.equal(cut.status,200,JSON.stringify(cut.value));step.cut=cut.value;save();const out=await s.command(b+'/confirm',{preview:cut.value.preview},step.key);assert.equal(out.status,200,JSON.stringify(out.value));step.result=out.value;save();ok(true,name+' explicit preview and confirmation');return out.value;}
|
|
// Existing series administration is recoverable by its exact new identity, without a second proposal.
|
|
if(!saved.steps.seriesPrepare?.result){const state=await get(commercial,'/api/service-invoices'),matches=state.proposals.filter(p=>p.kind==='series'&&p.identifier==='TEST-MULTI-2026');assert(matches.length<=1);if(matches.length){saved.steps.seriesPrepare??={};saved.steps.seriesPrepare.result={id:matches[0].id,version:matches[0].version};save();}}
|
|
const series=saved.steps.seriesPrepare?.result??await direct('seriesPrepare',commercial,'/api/service-invoices/proposals',{kind:'series',identifier:'TEST-MULTI-2026',data:{code:'TEST-MULTI-2026',first_number:1,state:'open',valid_from:'2026-11-01',valid_until:'2026-11-30',evidence:'Série QA expressamente autorizada para dois documentos multilinha.'}});
|
|
if(!saved.steps.seriesReview?.result){const state=await get(review,'/api/service-invoices'),row=state.proposals.find(p=>p.id===series.id);if(row?.decision){assert.equal(row.decision.decision,'approved');saved.steps.seriesReview={result:{id:series.id,decision:'approved'}};save();}}
|
|
if(!saved.steps.seriesReview?.result)await direct('seriesReview',review,'/api/service-invoices/proposals/'+series.id+'/decision',{version:1,decision:'approved',reason:'Revisão independente da série QA e seu primeiro número, sem reiniciar séries anteriores.'});
|
|
for(const [mode,prices]of[['added',['400','600']],['included',['464','696']]]){
|
|
const draft=await composed(mode+'Draft',commercial,'drafts',{draft_id:null,version:0,source_version:0,preparation_version:0,name:mode==='added'?'QA-MULTI-ACRESCIDO-01':'QA-MULTI-INCLUIDO-01',client_id:refs.client_id,currency:'MZN',price_mode:mode,service_date:'2026-11-11',period_start:'2026-11-01',period_end:'2026-11-30',proposed_due_date:'2026-11-30',payment_terms:'Vencimento artificial30Nov, sem recebimento ou condição legal presumida.',lines:prices.map((p,i)=>({service_id:refs.service_id,description:'Serviço fictício da linha '+(i+1),quantity:'1',unit_price:p,unit:'UN'})),reason:'Preparação individual das duas linhas QA autorizadas e respectiva modalidade.'});
|
|
const calc=await composed(mode+'Calculation',fiscal,'calculations',{draft_id:draft.id,rule_event_id:refs.rule_event_id,draft_version:draft.version,preparation_version:draft.preparation_version,reason:'Conferência fiscal individual das linhas e da regra artificial16% já revista.'});ok(calc.result.base_document==='1000.00'&&calc.result.tax_document==='160.00'&&calc.result.total==='1160.00','documentary totals1000+160=1160 '+mode);
|
|
const temporal=await direct(mode+'Temporal',commercial,base+'/temporals',{kind:'temporal',identifier:'MULTI-DRAFT-'+draft.id,data:{calculation_policy:policy,common_calculation_id:calc.id,common_rule_event_id:refs.rule_event_id,draft_id:draft.id,draft_version:draft.version,preparation_version:draft.preparation_version,valid_from:'2026-11-11',valid_until:'2026-11-11',period_start:'2026-11-01',period_end:'2026-11-30',issue_window_start:'2026-11-11',issue_window_end:'2026-11-11',event:'on_issue_within_deadline',readiness_basis:'Origem, duas linhas e modalidade QA explicitamente conferidas antes de emitir.',evidence:'Autorização11Nov; emissão exclusivamente de ensaio sem validade fiscal.'}},{receipt:base+'/receipts/'});
|
|
await direct(mode+'TemporalReview',review,base+'/temporals/'+temporal.id+'/decision',{version:1,decision:'approved',reason:'Conferência independente da origem, cálculo conservado e janela de emissão QA.'},{receipt:base+'/receipts/'});
|
|
let invoice=saved.steps[mode+'Issue']?.result;if(!invoice){const cut=await commercial.command(base+'/drafts/'+draft.id+'/readiness',{series_id:series.id,rule_id:temporal.id});assert.equal(cut.status,200,JSON.stringify(cut.value));assert(cut.value.ready);invoice=await direct(mode+'Issue',commercial,base+'/drafts/'+draft.id+'/issue',{series_id:series.id,rule_id:temporal.id,review_hash:cut.value.review_hash},{receipt:base+'/invoice-receipts/'});}ok(invoice.series_code==='TEST-MULTI-2026'&&invoice.number===(mode==='added'?1:2),'new series exact numbering '+mode);
|
|
const shown=await get(commercial,'/api/service-invoices/'+invoice.id);ok(shown.snapshot.calculation_policy===policy&&shown.snapshot.calculation.common_document_result.total==='1160.00','immutable typed photograph '+mode);const pdf=await commercial.get('/api/service-invoices/'+invoice.id+'/pdf'),bytes=Buffer.from(await pdf.arrayBuffer());ok(pdf.status===200&&/application\/pdf/.test(pdf.headers.get('content-type'))&&crypto.createHash('sha256').update(bytes).digest('hex')===shown.pdf_sha256,'actual PDF MIME and stored SHA '+mode);fs.writeFileSync(root+'/multiline-'+mode+'-invoice-private.pdf',bytes);
|
|
const ob=await direct(mode+'Obligation',fp,'/api/service-receivables/proposals',{kind:'obligation',invoice_id:invoice.id,currency:'MZN',source:'SIM-MULTI-8085',external_id:'MULTI-8085-'+invoice.id,revision:0});await direct(mode+'ObligationReview',fr,'/api/service-receivables/'+ob.id+'/decision',{version:1,revision:0,decision:'approved',reason:'Reconhecimento distinto do recebível fictício1160, sem recebimento nem afectação.'});
|
|
const proposal=await composed(mode+'JournalPrepare',ap,'ledger',{action:'effect-prepare',data:{effect_type:'sales.invoice',source_id:invoice.id,journal_on:'2026-11-11',period_key:'QA-2026-INTEGRADO',rule_key:'sales.invoice',reason:'Preparação individual da origem multilinha QA no mesmo diário.'}});await composed(mode+'JournalReview',ar,'ledger',{action:'validate',data:{proposal_id:proposal.proposal_id,revision:proposal.revision,decision:'approved',reason:'Revisão distinta dos três movimentos1160/1000/160 e origem documental.'}});await composed(mode+'JournalPost',ap,'ledger',{action:'post',data:{proposal_id:proposal.proposal_id,revision:proposal.revision,reason:'Aplicação única do documento QA conferido no diário existente.'}});
|
|
const editor=await get(commercial,base+'/drafts/'+draft.id);ok(editor.invoice?.id===invoice.id&&editor.invoice.totals.total==='1160.00','unified editor retains issued totals '+mode);const financial=await get(fp,'/api/service-receivables');ok(financial.documents.some(d=>d.id===invoice.id),'finance reads qualified Commercial dependency in its own workspace '+mode);
|
|
}
|
|
const noRules=await commercial.get(base+'/options?kind=rules');ok(noRules.status===403,'commercial preparation cannot enumerate fiscal catalogue');const bad=await commercial.get(base+'/options?kind=drafts&company_id=99');ok(bad.status===422,'company injection denied');const own=await review.get(base+'/receipts/'+saved.steps.addedDraft.key);ok(own.status===404,'another actor receipt denied');const manifest=JSON.parse(fs.readFileSync(root+'/app/public/build/manifest.json'));for(const e of Object.values(manifest).filter(e=>e.isEntry||/MultiLineInvoiceWorkspace/.test(e.file))){const r=await fetch('http://127.0.0.1:8085/build/'+e.file),b=Buffer.from(await r.arrayBuffer());ok(r.status===200&&/javascript/.test(r.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 main/lazy MIME SHA '+e.file);}
|
|
const proof=root+'/multiline-invoice-http-20261111.json';fs.writeFileSync(proof,JSON.stringify({passed:true,checks,new_documents:2,new_receivables:2,new_journals:2,new_grants:0,new_accounts:0,new_demo:false,browser_visual_review:false},null,2));console.log(JSON.stringify({passed:true,checks:checks.length,proof}));
|