feat(iva): prepare distinct P084 P085 offer drafts without applying benefits
This commit is contained in:
@@ -6,6 +6,7 @@ import { type ApiSimulationExecutionV1 } from '@/lib/simulation-contract';
|
||||
import { SimulationModeloA } from './simulation-modelo-a';
|
||||
import { SimulationAcquisitions } from './simulation-acquisitions';
|
||||
import { SimulationImportExemptions } from './simulation-import-exemptions';
|
||||
import { SimulationOfferPreparations } from './simulation-offer-preparations';
|
||||
import { SimulationOperations } from './simulation-operations';
|
||||
|
||||
export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
@@ -89,6 +90,7 @@ export function SimulationMonthlyIva({draft, busy, onCalculate, onChange}: {
|
||||
<SimulationOperations draft={draft} onChange={onChange} />
|
||||
<SimulationAcquisitions draft={draft} onChange={onChange} />
|
||||
<SimulationImportExemptions draft={draft} onChange={onChange} />
|
||||
<SimulationOfferPreparations draft={draft} onChange={onChange} />
|
||||
<SimulationModeloA draft={draft} />
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useState } from 'react';
|
||||
import { newOfferPreparation,offerPreparationBook,offerPreparationSource,offerReusableData,recordOfferPreparations,reuseOfferData,saveOfferPreparationBook,type OfferPreparation,type OfferPreparationBook } from '@/lib/iva-offer-preparations';
|
||||
|
||||
export function SimulationOfferPreparations({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}) {
|
||||
const [error,setError]=useState('');let book:OfferPreparationBook;let reusable:ReturnType<typeof offerReusableData>;
|
||||
try{book=offerPreparationBook(draft);reusable=offerReusableData(draft);}catch(cause){return <p role="alert">{String(cause)}</p>;}
|
||||
const run=(action:()=>void)=>{try{action();setError('');}catch(cause){setError(String(cause));}};
|
||||
const save=(next:OfferPreparationBook)=>onChange('modelo_a_snapshot',saveOfferPreparationBook(draft,next));
|
||||
const change=(id:string,patch:Partial<OfferPreparation>)=>run(()=>save({...book,rows:book.rows.map(row=>row.id===id?{...row,...patch}:row)}));
|
||||
const question=(row:OfferPreparation,key:'nationalInstitution'|'publicSocialInstitution'|'goodsSuitable'|'publicInterestUse'|'mitigation',label:string)=><label className="block" key={key}>{label}<select className="block rounded border p-2" value={row[key]} onChange={e=>change(row.id,{[key]:e.target.value})}><option value="">Por indicar</option><option value="yes">Sim</option><option value="no">Não</option></select></label>;
|
||||
return <details className="space-y-3 rounded border p-3"><summary>Ofertas a instituições e para calamidades — preparar rascunhos</summary>
|
||||
<p>Prepare apenas as ofertas relevantes para a oportunidade {String(draft.context_opportunity||'entrada geral')}. Pode guardar dados incompletos e reutilizar referências existentes. A reutilização preenche apenas campos vazios; as suas edições mantêm-se.</p>
|
||||
<p>O procedimento de aplicação do benefício está em pesquisa. Este rascunho não concede isenção, não calcula IVA nem altera a dedução no campo11. Os restantes enquadramentos continuam disponíveis.</p>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" className="underline" onClick={()=>run(()=>save({...book,rows:[...book.rows,newOfferPreparation(crypto.randomUUID(),'P084',String(draft.currency??''))]}))}>Preparar oferta a instituição</button>{' '}
|
||||
<button type="button" className="underline" onClick={()=>run(()=>save({...book,rows:[...book.rows,newOfferPreparation(crypto.randomUUID(),'P085',String(draft.currency??''))]}))}>Preparar oferta para calamidade</button>
|
||||
{book.rows.map(row=><details key={row.id} className="rounded border p-3"><summary>{row.branch} — {row.description||'Bens por identificar'}</summary>
|
||||
<p>{row.branch==='P084'?'Artigo14(2)(a): aquisição ou importação de bens para ofertas a instituições nacionais de interesse público e relevantes fins sociais.':'Artigo14(2)(b): aquisição de bens para ofertas destinadas a atenuar efeitos de calamidades naturais.'} {offerPreparationSource}</p>
|
||||
{row.branch==='P084'&&<label>Operação<select className="block rounded border p-2" value={row.operation} onChange={e=>change(row.id,{operation:e.target.value as OfferPreparation['operation']})}><option value="">Por indicar</option><option value="acquisition">Aquisição</option><option value="import">Importação</option></select></label>}
|
||||
<label>Reutilizar dados desta oportunidade<select className="block rounded border p-2" value="" onChange={e=>run(()=>{const source=reusable.find(item=>item.key===e.target.value);if(source)save({...book,rows:book.rows.map(item=>item.id===row.id?reuseOfferData(item,source):item)});})}><option value="">Escolher referência existente</option>{reusable.filter(item=>(row.branch==='P084'||item.operation==='acquisition')&&(!row.operation||item.operation===row.operation)).map(item=><option key={item.key} value={item.key}>{item.label}</option>)}</select></label>
|
||||
{row.reusedFrom&&<p>Referência reutilizada: {row.reusedFrom.key}. A origem foi conservada; a aquisição/importação original não foi alterada.</p>}
|
||||
<div className="grid gap-3 md:grid-cols-2">{(['description','period','currency','amount','counterparty','documentRef','documentDate','beneficiary','purpose','evidence'] as const).map((key,index)=><label key={key}>{['Bens destinados à oferta','Período previsto / efectivo','Moeda','Valor de referência, sem cálculo fiscal','Fornecedor / importador','Documento / referência da previsão','Data do documento / prevista','Instituição / destinatário da oferta','Finalidade concreta da oferta','Fundamento factual / pressupostos'][index]}<input className="block rounded border p-2" type={key==='period'?'month':key==='documentDate'?'date':'text'} value={row[key]} onChange={e=>change(row.id,{[key]:e.target.value})}/></label>)}</div>
|
||||
{row.branch==='P084'?<fieldset><legend>Condições próprias da oferta à instituição</legend>{question(row,'nationalInstitution','A instituição beneficiária é nacional')}{question(row,'publicSocialInstitution','A instituição tem interesse público e relevantes fins sociais')}{question(row,'goodsSuitable','Os bens são inteiramente adequados à natureza da instituição')}{question(row,'publicInterestUse','A instituição utilizará os bens em actividades de evidente interesse público')}</fieldset>:<fieldset><legend>Finalidade própria da oferta para calamidade</legend><label>Calamidade e contexto afectados<input className="block rounded border p-2" value={row.disaster} onChange={e=>change(row.id,{disaster:e.target.value})}/></label><label>Natureza da calamidade<input className="block rounded border p-2" value={row.disasterKind} placeholder="Cheias, tempestades, secas, ciclones, sismos ou outra de idêntica natureza" onChange={e=>change(row.id,{disasterKind:e.target.value})}/></label>{question(row,'mitigation','Os bens destinam-se a ofertas para atenuar os efeitos da calamidade identificada')}</fieldset>}
|
||||
</details>)}
|
||||
{!!book.rows.length&&<><button type="button" className="rounded border p-2" onClick={()=>run(()=>save(recordOfferPreparations(book)))}>Guardar versão dos rascunhos de ofertas</button><p role="status">{book.saved?'Versão de preparação conservada; alterações seguintes não substituem essa versão.':'Preparação em curso; pode guardar mesmo com informação em falta.'} Benefício não aplicado. {book.history?.length??0} versões anteriores.</p></>}
|
||||
</details>;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { acquisitionBook, acquisitionScope } from './iva-acquisitions.ts';
|
||||
import { importExemptionBook } from './iva-import-exemptions.ts';
|
||||
|
||||
type Choice = ''|'yes'|'no';
|
||||
export const offerPreparationSource='CIVA,Lei13/2016,14(2)(a/b) e14(3),PDF13/p.1426(271). Preparação de dados; procedimento regulamentar de aplicação ainda em pesquisa. Não concede isenção nem preenche dedução/campo11.';
|
||||
export type OfferPreparation = {id:string;branch:'P084'|'P085';operation:''|'acquisition'|'import';description:string;period:string;currency:string;amount:string;counterparty:string;documentRef:string;documentDate:string;beneficiary:string;purpose:string;evidence:string;nationalInstitution:Choice;publicSocialInstitution:Choice;goodsSuitable:Choice;publicInterestUse:Choice;disaster:string;disasterKind:string;mitigation:Choice;reusedFrom?:{key:string;copiedAt:string}};
|
||||
export type OfferPreparationBook={rows:OfferPreparation[];history?:Array<{savedAt:string;rows:OfferPreparation[]}>;saved?:{savedAt:string;rows:OfferPreparation[]}};
|
||||
type Store={version:1;books:Record<string,unknown>;offerPreparations?:Record<string,OfferPreparationBook>;[key:string]:unknown};
|
||||
export function newOfferPreparation(id:string,branch:OfferPreparation['branch'],currency:string):OfferPreparation{return {id,branch,operation:branch==='P085'?'acquisition':'',description:'',period:'',currency,amount:'',counterparty:'',documentRef:'',documentDate:'',beneficiary:'',purpose:'',evidence:'',nationalInstitution:'',publicSocialInstitution:'',goodsSuitable:'',publicInterestUse:'',disaster:'',disasterKind:'',mitigation:''};}
|
||||
function read(draft:Record<string,unknown>):Store {
|
||||
const value:Store=draft.modelo_a_snapshot?JSON.parse(String(draft.modelo_a_snapshot)):{version:1,books:{}};
|
||||
if(value?.version!==1||!value.books||typeof value.books!=='object'||Array.isArray(value.books))throw new Error('Dados fiscais não recuperáveis; originais preservados.');
|
||||
if(value.offerPreparations!==undefined&&(!value.offerPreparations||typeof value.offerPreparations!=='object'||Array.isArray(value.offerPreparations)))throw new Error('Rascunhos de ofertas não recuperáveis; sem substituição por lista vazia.');
|
||||
for(const book of Object.values(value.offerPreparations??{})){
|
||||
if(!book||!Array.isArray(book.rows)||(book.history!==undefined&&!Array.isArray(book.history)))throw new Error('Formato de rascunhos de ofertas não recuperável; originais preservados.');
|
||||
for(const row of book.rows)if(!row||!['P084','P085'].includes(row.branch)||Object.keys(newOfferPreparation('','P084','')).some(key=>typeof row[key as keyof OfferPreparation]!=='string')||(row.branch==='P085'&&row.operation==='import'))throw new Error('Rascunho de oferta não recuperável ou operação fora do ramo; dados conservados.');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
export function offerPreparationBook(draft:Record<string,unknown>):OfferPreparationBook{return read(draft).offerPreparations?.[acquisitionScope(draft)]??{rows:[]};}
|
||||
export function saveOfferPreparationBook(draft:Record<string,unknown>,book:OfferPreparationBook):string{
|
||||
const previous=read(draft);const next={...previous,offerPreparations:{...previous.offerPreparations,[acquisitionScope(draft)]:book}};const encoded=JSON.stringify(next);read({...draft,modelo_a_snapshot:encoded});return encoded;
|
||||
}
|
||||
export function recordOfferPreparations(book:OfferPreparationBook):OfferPreparationBook{
|
||||
if(!book.rows.length||new Set(book.rows.map(r=>r.id)).size!==book.rows.length)throw new Error('Indique rascunhos com identificadores distintos.');
|
||||
return {...book,history:[...(book.history??[]),...(book.saved?[structuredClone(book.saved)]:[])],saved:{savedAt:new Date().toISOString(),rows:structuredClone(book.rows)}};
|
||||
}
|
||||
export type OfferReusable={key:string;label:string;operation:'acquisition'|'import';values:Partial<OfferPreparation>};
|
||||
export function offerReusableData(draft:Record<string,unknown>):OfferReusable[]{
|
||||
const purchases=acquisitionBook(draft).rows.map(row=>({key:'acquisition:'+row.id,label:'Aquisição: '+(row.description||row.documentRef||row.id),operation:'acquisition' as const,values:{description:row.description,period:row.period,currency:row.sourceCurrency,amount:row.net,documentRef:row.documentRef,documentDate:row.documentDate,counterparty:row.supplier}}));
|
||||
const imports=importExemptionBook(draft).rows.map(row=>({key:'import:'+row.id,label:'Importação: '+(row.description||row.documentRef||row.id),operation:'import' as const,values:{description:row.description,period:row.period,currency:row.currency,amount:row.declaredValue,documentRef:row.documentRef,documentDate:row.documentDate,counterparty:row.importer}}));
|
||||
return [...purchases,...imports];
|
||||
}
|
||||
export function reuseOfferData(row:OfferPreparation,source:OfferReusable):OfferPreparation {
|
||||
if(row.branch==='P085'&&source.operation==='import')throw new Error('P085 abrange aquisição; esta referência de importação conserva-se no seu percurso.');
|
||||
const next={...row};
|
||||
if(next.currency&&source.values.currency&&next.currency!==source.values.currency)throw new Error('Moeda da referência divergente; valores e edições conservados, sem conversão.');
|
||||
// Only fill empty inputs. Deliberate edits and answers are never replaced.
|
||||
if(!next.operation)next.operation=source.operation;
|
||||
if(next.operation!==source.operation)throw new Error('A operação já escolhida diverge da origem; rascunho conservado.');
|
||||
for(const [key,value] of Object.entries(source.values))if(!next[key as keyof OfferPreparation]&&typeof value==='string')Object.assign(next,{[key]:value});
|
||||
return {...next,reusedFrom:{key:source.key,copiedAt:new Date().toISOString()}};
|
||||
}
|
||||
export function offerPreparationReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
||||
try{return offerPreparationBook(draft).rows.map(row=>['IVA mensal',`Rascunho de oferta ${row.branch} — ${row.id}`,`${row.description||'Bens por indicar'}; preparação, benefício não aplicado; ${row.amount||'Valor não informado'} ${row.currency}; ${row.period||'Período não informado'}`,`${offerPreparationSource} Oportunidade ${acquisitionScope(draft)}; operação ${row.operation||'por indicar'}; contraparte ${row.counterparty||'por indicar'}; documento ${row.documentRef||'por indicar'},${row.documentDate||'data por indicar'}; beneficiário ${row.beneficiary||'por indicar'}; finalidade ${row.purpose||'por indicar'}; ${row.branch==='P084'?`instituição nacional ${row.nationalInstitution||'por indicar'}, interesse público/relevantes fins sociais ${row.publicSocialInstitution||'por indicar'}, bens adequados ${row.goodsSuitable||'por indicar'}, utilização de interesse público ${row.publicInterestUse||'por indicar'}`:`calamidade ${row.disaster||'por indicar'}, natureza ${row.disasterKind||'por indicar'}, atenuação dos efeitos ${row.mitigation||'por indicar'}`}; fundamento ${row.evidence||'por indicar'}; origem reutilizada ${row.reusedFrom?.key||'entrada directa'}; sem cálculo de IVA,isenção automática ou dedução.`]);}
|
||||
catch{return [['IVA mensal','Rascunhos de ofertas','Dados não recuperáveis','Originais preservados; sem substituição por zero.']];}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { monthlyIvaInput, monthlyIvaKey, readMonthlyIvaRecord, readMonthlyIvaSet
|
||||
import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
|
||||
import { acquisitionReportRows } from './iva-acquisitions.ts';
|
||||
import { importExemptionReportRows } from './iva-import-exemptions.ts';
|
||||
import { offerPreparationReportRows } from './iva-offer-preparations.ts';
|
||||
import { salesReportRows } from './iva-operations.ts';
|
||||
import { fiscalQaRows } from './fiscal-qa-preview.ts';
|
||||
import { csvCellWithDelimiter } from './csv.js';
|
||||
@@ -619,6 +620,7 @@ export function buildProjectDossierRows(
|
||||
}
|
||||
rows.push(...acquisitionReportRows(draft));
|
||||
rows.push(...importExemptionReportRows(draft));
|
||||
rows.push(...offerPreparationReportRows(draft));
|
||||
const operationRows = salesReportRows(draft);
|
||||
rows.push(...operationRows);
|
||||
if (draft.monthly_iva_snapshot) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class IvaOfferPreparationsDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_import_classification_recovers_privately_without_changing_delivered_books(): void
|
||||
{
|
||||
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('simulator_fiscal_delta_20261021_test', $target->name);
|
||||
$this->assertSame(55441, (int) $target->port);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$this->actingAs(User::factory()->create(['email_verified_at' => now()]));
|
||||
$draft = json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-offer-preparations.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$id = $this->postJson('/api/v1/simulation-drafts', ['personal_snapshot' => [], 'economic_snapshot' => $draft], ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
|
||||
$recovered = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk()->json('data.economic_snapshot');
|
||||
$this->assertSame($draft['modelo_a_snapshot'], $recovered['modelo_a_snapshot']);
|
||||
$this->assertSame('opportunity:334', $recovered['context_opportunity']);
|
||||
$this->assertSame('Manica', $recovered['selection_labels']['province_id']);
|
||||
$store = json_decode($recovered['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$book = $store['offerPreparations']['opportunity:334'];
|
||||
$this->assertCount(2, $book['rows']);
|
||||
$this->assertSame(['P084','P085'], array_column($book['rows'], 'branch'));
|
||||
$this->assertSame(['import','acquisition'], array_column($book['rows'], 'operation'));
|
||||
$this->assertArrayNotHasKey('result', $book);
|
||||
$this->assertCount(1, $book['history']);
|
||||
$this->assertSame('Edição posterior', $book['saved']['rows'][0]['purpose']);
|
||||
$this->assertSame('SYNTHETIC QA cheias', $book['rows'][1]['disaster']);
|
||||
$frozen = json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-g5c-inputs.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$prior = json_decode($frozen['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$this->assertSame($prior['salesOperations'], $store['salesOperations']);
|
||||
$this->assertSame($prior['books'], $store['books']);
|
||||
$this->assertSame($prior['importExemptions']['opportunity:330'], $store['importExemptions']['opportunity:330']);
|
||||
$this->assertSame($prior['importExemptions']['opportunity:331'], $store['importExemptions']['opportunity:331']);
|
||||
$this->assertSame($prior['importExemptions']['opportunity:332'], $store['importExemptions']['opportunity:332']);
|
||||
$this->assertSame($prior['importExemptions'], $store['importExemptions']);
|
||||
$this->assertNull($store['books']['opportunity:329']['result']['periods'][0]['fields']['11']);
|
||||
$this->assertNull($store['books']['opportunity:329']['result']['periods'][0]['fields']['17']);
|
||||
$this->assertStringContainsString('BR89', $store['books']['opportunity:329']['result']['sources']);
|
||||
file_put_contents(base_path('outputs/iva-l1-g5d-offer-drafts-20261024/recovered.json'), json_encode($recovered, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { newOfferPreparation,offerPreparationBook,saveOfferPreparationBook,recordOfferPreparations,offerReusableData,reuseOfferData,offerPreparationReportRows } from '../../resources/js/lib/iva-offer-preparations.ts';
|
||||
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
||||
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-g5c-inputs.json',import.meta.url),'utf8'));
|
||||
const draft=()=>structuredClone(frozen);
|
||||
test('incomplete P084/P085 drafts save without authorizations,zeroVAT or a benefit result;prior26books remain exact',()=>{
|
||||
const d=draft(),before=JSON.parse(d.modelo_a_snapshot),rows=['P084','P085'].map(branch=>newOfferPreparation('gift-'+branch,branch,'MZN'));d.modelo_a_snapshot=saveOfferPreparationBook(d,{rows});const next=JSON.parse(d.modelo_a_snapshot);for(const key of Object.keys(before))assert.deepEqual(next[key],before[key]);assert.deepEqual(offerPreparationBook(JSON.parse(JSON.stringify(d))).rows,rows);assert.equal(rows[1].operation,'acquisition');assert.equal('result' in next.offerPreparations[d.context_opportunity],false);for(const row of offerPreparationReportRows(d))assert.match(row[2],/bénéfice|benefício não aplicado/);assert.equal(next.books['opportunity:329'].result.periods[0].fields['11'],null);
|
||||
});
|
||||
test('explicit reuse fills only empty fields,same opportunity;currency mismatch and P085 import rejected without mutation',()=>{
|
||||
const d=draft(),sources=offerReusableData(d);assert.equal(sources.length,6);const source=sources.find(x=>x.operation==='import'),original=structuredClone(source),row={...newOfferPreparation('gift','P084','MZN'),description:'Edição deliberada',beneficiary:'Instituição QA'};const next=reuseOfferData(row,source);assert.equal(next.description,row.description);assert.equal(next.beneficiary,row.beneficiary);assert.equal(next.documentRef,source.values.documentRef);assert.equal(next.operation,'import');assert.equal(next.reusedFrom.key,source.key);assert.deepEqual(source,original);assert.equal(row.documentRef,'');assert.throws(()=>reuseOfferData(newOfferPreparation('g','P085','MZN'),source),/P085/);assert.throws(()=>reuseOfferData({...row,currency:'USD'},source),/Moeda/);assert.equal(offerReusableData({...d,context_opportunity:'opportunity:does-not-exist'}).length,0);
|
||||
});
|
||||
test('versions preserve deliberate edits and own branch conditions;report from saved/recovered draft does not extend benefit or field11',()=>{
|
||||
const d=draft();let p84=reuseOfferData(newOfferPreparation('gift84','P084','MZN'),offerReusableData(d).find(x=>x.operation==='import'));p84={...p84,beneficiary:'SYNTHETIC QA instituição',nationalInstitution:'yes',publicSocialInstitution:'yes',goodsSuitable:'yes',publicInterestUse:'yes'};const p85={...newOfferPreparation('gift85','P085','MZN'),description:'SYNTHETIC QA bens para cheias',disaster:'SYNTHETIC QA cheias',disasterKind:'Cheias',mitigation:'yes'};let book=recordOfferPreparations({rows:[p84,p85]});book=recordOfferPreparations({...book,rows:[{...p84,purpose:'Edição posterior'},p85]});assert.equal(book.history.length,1);assert.equal(book.history[0].rows[0].purpose,'');d.modelo_a_snapshot=saveOfferPreparationBook(d,book);assert.deepEqual(offerPreparationBook(JSON.parse(JSON.stringify(d))),book);const report=JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance']),version:'QA drafts'}));for(const token of ['P084','P085','benefício não aplicado','SYNTHETIC QA cheias','Edição posterior','14(2)(a/b)','opportunity:334'])assert.ok(report.includes(token),token);assert.equal(offerPreparationBook({...d,context_opportunity:'opportunity:333'}).rows.length,0);
|
||||
if(process.env.IVA_OFFERS_CALCULATED)fs.writeFileSync(process.env.IVA_OFFERS_CALCULATED,JSON.stringify(d));
|
||||
});
|
||||
test('corrupt draft data cannot be replaced with empty book and P085 never accepts imported operation',()=>{
|
||||
for(const offerPreparations of [[],null,{'opportunity:334':{rows:[{id:'broken'}]}}]){const d=draft(),s=JSON.parse(d.modelo_a_snapshot);s.offerPreparations=offerPreparations;d.modelo_a_snapshot=JSON.stringify(s);assert.throws(()=>saveOfferPreparationBook(d,{rows:[]}));}
|
||||
assert.throws(()=>saveOfferPreparationBook(draft(),{rows:[{...newOfferPreparation('x','P085','MZN'),operation:'import'}]}),/operação/);
|
||||
});
|
||||
Reference in New Issue
Block a user