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

11 lines
5.4 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',commercial=JSON.parse(fs.readFileSync(root+'/single-commercial.json')),workshop=JSON.parse(fs.readFileSync(root+'/single-workshop-stock-input.json')),file=root+'/commercial-quotes-http-20261110.json',progress=fs.existsSync(file)?JSON.parse(fs.readFileSync(file)):{operations:{},quotes:{},checks:[]},save=()=>fs.writeFileSync(file,JSON.stringify(progress,null,2));
const clients={prepare:await session('sgf:commercial_prepare','commercial'),review:await session('sgf:commercial_review','commercial'),workshopPrepare:await session('sgf:finance_prepare','commercial'),workshopReview:await session('sgf:finance_review','commercial'),read:await session('sgf:read_only','commercial')};const ok=(v,label)=>{assert(v,label);if(!progress.checks.includes(label))progress.checks.push(label);save();};
const step=async(name,role,body)=>{let op=progress.operations[name];if(!op){op=progress.operations[name]={role,body,key:crypto.randomUUID()};save();}assert.deepEqual(op.body,body);assert.equal(op.role,role);if(op.result)return op.result;const p=await clients[role].command('/api/commercial-quotes/preview',body,op.key);assert.equal(p.status,200,name+': '+JSON.stringify(p.value));const result=await clients[role].command('/api/commercial-quotes/confirm',{preview:p.value.preview});assert.equal(result.status,200,name+': '+JSON.stringify(result.value));op.result=result.value;save();const again=await clients[role].command('/api/commercial-quotes/confirm',{preview:p.value.preview});ok(again.status===200&&again.value.replayed&&again.value.id===result.value.id,name+' same confirmation replays one event');return result.value;};
for(const[kind,amount,preparer,reviewer,order]of[['commercial','1000','prepare','review',null],['workshop','500','workshopPrepare','workshopReview',workshop.order]]){
progress.quotes[kind]??=crypto.randomUUID();save();const quote=progress.quotes[kind];const prepared=await step(kind+'-prepare',preparer,{action:'prepare',quote_key:quote,version:0,reason:'Preparação individual do orçamento '+kind+' com origem e base explícitas.',data:{client_id:commercial.client,currency:'MZN',valid_until:'2026-12-31',source_order_id:order,description:'Proposta sintética '+kind+'; impostos por conferir separadamente.',lines:[{service_id:commercial.service,quantity:'1',unit_price:amount}]}});ok(prepared.impact.base===amount+'.00'&&!prepared.impact.invoice_issued,kind+' proposal preserves base and declares no invoice');
await step(kind+'-review',reviewer,{action:'review',quote_key:quote,version:1,reason:'Conferência comercial distinta do preço, cliente, origem e validade.',data:{decision:'approved'}});await step(kind+'-customer',preparer,{action:'customer',quote_key:quote,version:2,reason:'Registo explícito da aceitação fictícia do cliente com prova identificada.',data:{decision:'accepted',representative:'Representante fictício QA',capacity:'Cliente QA autorizado',evidence:'QA-ACEITACAO-'+kind.toUpperCase()+'-20261110',decided_on:'2026-11-10'}});const converted=await step(kind+'-convert',preparer,{action:'convert',quote_key:quote,version:3,reason:'Converter a proposta aceite uma única vez nos rascunhos originais.',data:{}});ok(converted.state==='converted'&&Number.isInteger(converted.drafts.billing_draft_id),kind+' one explicit converted draft');
let r=await clients[preparer].get('/api/a/billing_drafts/'+converted.drafts.billing_draft_id);ok(r.status===200,kind+' linked draft accessible in Commercial');const draft=await r.json();ok(draft.client_id===commercial.client&&draft.base_amount===amount,kind+' draft retains exact client and price');r=await clients[preparer].get('/commercial-quotes/'+quote+'/print?version=4',{headers:{Accept:'text/html'}});ok(r.status===200,kind+' authorised exact-version print view200');const html=await r.text();ok(html.includes('SEM VALIDADE FISCAL')&&html.includes('QA-ACEITACAO-')&&html.includes(amount+'.00'),kind+' print preserves acceptance and proposed amount without fiscal claim');
}
let r=await clients.read.get('/api/commercial-quotes?per_page=100');ok(r.status===200,'read-only bounded quote list100');const list=await r.json();ok(list.quotes.total===2,'same two completed quotes in selected installation');r=await clients.read.get('/api/commercial-quotes/'+progress.quotes.commercial+'/history');ok(r.status===200,'read-only quote history');const h=await r.json();ok(h.events.total===4,'all four decisions retained');const denied=await clients.read.command('/api/commercial-quotes/preview',{action:'prepare',quote_key:crypto.randomUUID(),version:0,reason:'Tentativa de preparação por perfil de consulta.',data:{}});ok(denied.status===403,'read-only quote writer403');
r=await clients.prepare.get('/api/service-invoices');ok(r.status===200,'original issued invoice source remains readable');const invoices=await r.json();ok(invoices.invoices.length===1,'accepting/converting quotes emits zero additional invoices');const accounting=await session('sgf:accounting_prepare','accounting');r=await accounting.get('/api/general-ledger');const ledger=await r.json();ok(r.status===200&&ledger.journals.length===18,'quotes preserve existing18 journals');progress.passed=true;progress.browser_visual_review=false;save();console.log(JSON.stringify({passed:true,checks:progress.checks.length,proof:file}));