feat(iva): add eighteen enumerated products within P026 in QA

This commit is contained in:
2026-10-24 15:12:14 +02:00
parent 2440cb448a
commit 48146089cd
7 changed files with 162 additions and 2 deletions
@@ -6120,5 +6120,67 @@
"report-qa.png": "7f9e4bf3c76e258f1cf6729589ffbbadb48bae0f9c6ac543d75cee24dcc3c0f0",
"ui-calculated.json": "bb8fa5ca332e5fc6e556984cb4a853255fbb4fb72f2b4e8c4ae3e71226bbbdb8"
}
},
"iva_l1_g2_listed_qa_20261024": {
"candidate": "CANDIDATE_G2H",
"base_QA": "9dbfdd67",
"base_QA_record": "2440cb44",
"base_delivered": "9a56d982",
"status": "demonstrated_QA_not_installed",
"group": "G2H within G2",
"branches": [
"AUD-SIM-L1-P026"
],
"treatments": [
"listed_maize",
"listed_maize_flour",
"listed_rice",
"listed_bread",
"listed_iodised_salt",
"listed_infant_powdered_milk",
"listed_wheat",
"listed_wheat_flour",
"listed_fresh_chilled_tomato",
"listed_potato",
"listed_onion",
"listed_frozen_horse_mackerel",
"listed_lighting_kerosene",
"listed_domestic_lpg",
"listed_jet_fuel",
"listed_eligible_bicycles",
"listed_condoms",
"listed_insecticides"
],
"branch_count": 1,
"treatment_count": 18,
"scope": "2026/MZN;9(10)Lei22/2022;19(1)(b)(v);field05",
"tests": {
"frontend": 2,
"api_tests": 1,
"api_assertions": 28,
"UI_to_API_to_report": true,
"types": "passed",
"build": "passed"
},
"preservation": "Eight frozen books unchanged/current without recalculation;G2G and24delivered rows unchanged;all prior receipt objects unchanged",
"counts": {
"delivered": 24,
"demonstrated_QA": 5,
"functional_pending": 103,
"documentary_pending": 1,
"total": 133
},
"homologation_unchanged": true,
"P001_05_06_resolved": false,
"P001_blocks_independent": false,
"L1_complete": false,
"artifacts": {
"api-ui-tests.xml": "0cc9a1f62257a373aa99984c3961bbf388840f4db4f0a25fb62ac9a650a74547",
"listed-model.json": "5018293c6d064de9a64e6be2672416bf4d806d9f76f8c2e3868e6323475a58f5",
"listed-report.html": "0281e8ba1b21606871803229e57048ca4c0f9e9e15a3f0db729f68022eef046a",
"recovered.json": "e2145655f003b3f170a9676eb277de276201654af7b01e645d1dac5bd23f92d3",
"report-qa.png": "58dc4e267145def3b2700807922598cc70448c01c756cf808d9e3399fbfdd83a",
"ui-calculated.json": "778b127532976cf6d3b8c672f05dd2a8973ef79afa3ae26f37832b34a9012367"
}
}
}
@@ -1,5 +1,20 @@
# Rastreabilidade, utilização e formação
## 24/10/2026 — G2H produtos enumerados demonstrado em QA
**Candidata CANDIDATE_G2H; P026 é um ramo com18tratamentos de produto.** G2G9dbfdd67/2440cb44 permanece com4ramos/6tratamentos emQA para entrega conjunta.24ramos entregues mantêm os fechos;homologação9a56d982inalterada.
**Enumeração executável:**milho,farinha de milho,arroz,pão,sal iodado,leite em pó para lactente até um ano,trigo,farinha de trigo,tomate fresco ou refrigerado,batata,cebola,carapau congelado,petróleo de iluminação,gás domésticoGPL,jetfuel,bicicletas comuns e de ferro até4velocidades,preservativos,insecticidas. Cada opção guarda identidade `listed_*`,natureza/qualificador e fundamento próprios; não se generaliza a todos os alimentos,combustíveis,produtos químicos ou material médico. Texto das bicicletas conserva a enumeração legal,sem inventar nova categoria ou regra de velocidades.
**Fonte exacta:**Lei22/2022,artigo1,PDF2,p.2252(26),9(10),substitui a enumeração2016(incluindo bicicletas comuns e de ferro até4velocidades). CIVA republicado Lei13/2016,PDF15,p.1426(273),19(1)(b)(v),inclui9(10)nasoperaçõescomdireitoàdedução. Reutilizado o cotejo posterior registado;Lei10/2025nãoaltera9e mantém19(1). Âmbito técnico Janeiro–Dezembro2026/MZN. Campo05;isenção comdireito nas condiçõeslegais,nãotaxazero nem dedução automática de compras. P00105/06nãoéresolvidoporestaopção05nem pela distinção05/07.
**Motor e reutilização:**sócatálogodeoperações,comcampos/componentesexistentes;oportunidade/localização,moeda,valor,representação do preço,período e fundamento reutilizados. Sem schema,API,migração,importação,permissão,fórmulaououtroimpostonovo. Seleccionar produto enumerado e completar condições em falta;desconhecido nãoézero nem produto elegível.
**Provas dirigidas:**2testesfrontend novos cobrem18opções,100,00MZNcada,05=1800,00,07=0,00,IVA0,00,preçoslíquidos/incluídos;retiradaindividualdecada condição/fundamento,moeda/períodoincompatível e identidade de produto fora do catálogo→indeterminado. FixturecongeladaG2G preserva oitocontextosanteriores completos eactuais,semrecálculo. APIprivadaPOST→GET,QAisolada `simulator_fiscal_delta_20261021_test:55441`,guardadedestino/transacçãorevertida,1teste/28asserções;18linhas e snapshotintegralrecuperados. Interface real:leite sem confirmação de destino para lactente atéumano→indeterminado;completado→calcular/guardar revisão3;payloadreal→API→recuperação→relatórioHTML. Relatório18linhas,05=1800,00,contexto317/Manica,2026-09,MZN,origem/fontes/versão;semtratamentosdosoutroscontextos. Tipos/buildpassaram. Word/PDF/fracçõesaceitesreutilizados,renderizadoresinalterados,semnovaprova de paginação. Ensaiosdos24entregues eG2Gnãorepetidos.
**Saldo:**133IDs=24entregues+5QA(G2G4/G2H1)+103funcionaispendentes+P001documental.G2total32=20entregues+5QA+7pendentes.Motor63tratamentoscondicionados(10iniciais+53dosgrupos),mais2opçõesindeterminadas.18opçõesnãoacrescentam18IDs. Próximaentrega conjuntaG2G/G2H pelo Git existente com destino/concorrênciafrescos,backup/restauro,activaçãoeconferênciaapenasdelta. L1aberto;estatísticas/IAadiadas. Artefactosprivados `outputs/iva-l1-g2-listed-20261024/`.
## 24/10/2026 — G2G condicionado demonstrado em QA
**Candidata 9dbfdd67ad2d2ad54c9bb8a12fb8dae0f053c226; quatro ramos, seis tratamentos.** Prosseguimento do G2 sobre a entrega9a56d982/recibo541ea704. Os24entregues permanecem encerrados. Reutilizado o motor,os campos,API e relatórios existentes;sem schema,importação,migração ou cálculo de outro imposto. Homologação inalterada nesta prova.
File diff suppressed because one or more lines are too long
+21
View File
@@ -32,7 +32,28 @@ const culturalTreatments:OperationTreatment[] = ([false,true] as const).flatMap(
];
});
culturalTreatments.push({id:'statutory_membership',label:'Serviços colectivos aos associados por quota estatutária',field:'07',rate:0,article:'CIVA arts.9(2)(g),10(2),19(1)',source:culturalSource,conditions:[internalOperation,...nonprofitConditions,'Serviços efectuados no interesse colectivo dos próprios associados por organismo sem finalidade lucrativa','Objectivos de natureza política, sindical, religiosa, patriótica, filantrópica, recreativa, desportiva, cultural, cívica ou de representação de interesses económicos, identificados','A única contraprestação é uma quota fixada nos respectivos estatutos; não pagamentos adicionais por serviços individuais']});
const listedGoodsTreatments:OperationTreatment[] = [
['maize','Milho','Bem transmitido identificado como milho'],
['maize_flour','Farinha de milho','Bem transmitido identificado como farinha de milho'],
['rice','Arroz','Bem transmitido identificado como arroz'],
['bread','Pão','Bem transmitido identificado como pão'],
['iodised_salt','Sal iodado','Sal identificado como iodado; não qualquer sal'],
['infant_powdered_milk','Leite em pó para lactente até um ano','Leite em pó identificado como destinado a lactente até um ano; não qualquer leite ou alimento infantil'],
['wheat','Trigo','Bem transmitido identificado como trigo'],
['wheat_flour','Farinha de trigo','Bem transmitido identificado como farinha de trigo'],
['fresh_chilled_tomato','Tomate fresco ou refrigerado','Tomate identificado como fresco ou refrigerado; não alargar automaticamente a conserva ou preparação'],
['potato','Batata','Bem transmitido identificado como batata'],
['onion','Cebola','Bem transmitido identificado como cebola'],
['frozen_horse_mackerel','Carapau congelado','Peixe identificado como carapau congelado; não qualquer espécie ou estado de conservação'],
['lighting_kerosene','Petróleo de iluminação','Produto identificado como petróleo de iluminação; não qualquer combustível'],
['domestic_lpg','Gás doméstico — GPL','Produto identificado como gás doméstico GPL; não qualquer gás industrial'],
['jet_fuel','Jet fuel','Produto identificado como jet fuel; não qualquer combustível de transporte'],
['eligible_bicycles','Bicicletas comuns e de ferro até 4 velocidades','Produto identificado na enumeração legal «bicicletas comuns e de ferro até 4 velocidades», com características sustentadas; não qualquer veículo'],
['condoms','Preservativos','Bem transmitido identificado como preservativo'],
['insecticides','Insecticidas','Bem transmitido identificado como insecticida; não qualquer produto químico'],
].map(([id,label,condition])=>({id:`listed_${id}`,label:`Lista legal — ${label}`,field:'05',rate:0,article:'CIVA arts.9(10),19(1)(b)(v), redacção Lei22/2022',source:'Lei22/2022 art.1, PDF2, p.2252(26),9(10); CIVA republicado Lei13/2016, PDF15, p.1426(273),19(1)(b)(v). Âmbito técnico2026/MZN',conditions:[internalOperation,'Transmissão do próprio bem enumerado, identificado; não um serviço autónomo nem extensão por sector, marca ou destino',condition]}));
export const operationTreatments:OperationTreatment[] = [
...listedGoodsTreatments,
{...socialTreatment('public_security','Bens e serviços ligados à segurança por entidade pública','b',['Transmissão de bens ou prestação ligada à segurança identificada e efectuada pela própria entidade pública; não presumir segurança social nem extensão a prestador privado contratado'],false),source:'CIVA republicado Lei13/2016, PDF9, p.1426(267),9(2)(b); PDF15, p.1426(273),19(1); Lei22/2022 art.1, PDF1, p.2252(25), mantém9(2)(b); PDF2, p.2252(26),10(1). Âmbito técnico2026/MZN'},
{id:'official_military_supplies',label:'Fornecimentos oficiais militares/paramilitares elegíveis',field:'07',rate:0,article:'CIVA arts.9(11),19(1)',source:'CIVA republicado Lei13/2016, PDF10, p.1426(268),9(11); PDF15, p.1426(273),19(1); Lei22/2022 art.1, PDF2, p.2252(26), mantém9(11). Âmbito técnico2026/MZN',conditions:[internalOperation,'Bem/serviço no fornecimento de material de guerra e de aquartelamento ou fardamento militar/paramilitar, identificado','Destino de utilização oficial das Forças de Defesa e Segurança Nacional; actividade efectuada exclusivamente para aqueles serviços','Estabelecimento reconhecido pelo Ministério competente, com reconhecimento identificado; não basta o cliente ser militar']},
{id:'sisa_subject_operation',label:'Operação sujeita a SISA, ainda que dela isenta',field:'07',rate:0,article:'CIVA arts.9(12)(d),19(1)',source:'CIVA republicado Lei13/2016, PDF10, p.1426(268),9(12)(d); PDF15, p.1426(273),19(1); Lei22/2022 art.1, PDF2, p.2252(26), mantém9(12)(d). Âmbito técnico2026/MZN',conditions:[internalOperation,'Operação e fundamento de sujeição a SISA identificados, ainda que exista isenção desse imposto; não qualquer operação imobiliária','Esta linha corresponde à operação sujeita a SISA, não a locação, mediação ou outro serviço autónomo; este bloco não calcula SISA']},
+33
View File
@@ -0,0 +1,33 @@
<?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 IvaListedGoodsDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_listed_context_preserves_accepted_opportunities(): 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(getenv('IVA_G2H_INPUT') ?: base_path('outputs/iva-l1-g2-listed-20261024/calculated.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']);
$book = json_decode($recovered['modelo_a_snapshot'], true)['salesOperations']['opportunity:317'];
$this->assertCount(18, $book['result']['rows']);
$this->assertSame('0.00', $book['result']['periods'][0]['fields']['07']);
$this->assertSame('1800.00', $book['result']['periods'][0]['fields']['05']);
$this->assertSame(json_decode($draft['modelo_a_snapshot'], true)['salesOperations']['opportunity:310'], json_decode($recovered['modelo_a_snapshot'], true)['salesOperations']['opportunity:310']);
$this->assertSame(json_decode($draft['modelo_a_snapshot'], true)['salesOperations']['opportunity:309'], json_decode($recovered['modelo_a_snapshot'], true)['salesOperations']['opportunity:309']);
foreach ($book['result']['rows'] as $row) $this->assertSame('0.00', $row['vat']);
if (getenv('IVA_G2H_RECOVERED')) file_put_contents(getenv('IVA_G2H_RECOVERED'), json_encode($recovered, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
}
}
File diff suppressed because one or more lines are too long
+28
View File
@@ -0,0 +1,28 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import fs from 'node:fs';
import {operationTreatments,newSalesOperation,evaluateSalesOperation,recordSales,saveSalesBook,salesBook,salesCurrent} from '../../resources/js/lib/iva-operations.ts';
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
const ids=['listed_maize', 'listed_maize_flour', 'listed_rice', 'listed_bread', 'listed_iodised_salt', 'listed_infant_powdered_milk', 'listed_wheat', 'listed_wheat_flour', 'listed_fresh_chilled_tomato', 'listed_potato', 'listed_onion', 'listed_frozen_horse_mackerel', 'listed_lighting_kerosene', 'listed_domestic_lpg', 'listed_jet_fuel', 'listed_eligible_bicycles', 'listed_condoms', 'listed_insecticides'];
function seed(){return JSON.parse(fs.readFileSync(new URL('./fixtures/iva-conditional.json',import.meta.url),'utf8'));}
function fixture(){const draft=seed();draft.context_opportunity='opportunity:317';draft.project_title='SYNTHETIC QA — G2H social';
let book={currency:'MZN',rows:ids.map((id,i)=>({...newSalesOperation(`g2h-${i}`,'2026-09','MZN'),description:`SYNTHETIC QA ${id}`,establishment:'SYNTHETIC QA equipamento Manica',amount:'100',basis:i%2?'included':'net',treatment:id,confirmed:operationTreatments.find(t=>t.id===id).conditions.map((_,n)=>n),evidence:'SYNTHETIC QA — natureza, entidade, reconhecimento e condições cumulativas pressupostos documentados; sem certificação'})),periods:[{period:'2026-09',complete:true}]};book=recordSales(draft,book);draft.modelo_a_snapshot=saveSalesBook(draft,book);return {draft,book};}
test('G2H one branch with eighteen specifically enumerated products require their own operation and eligibility conditions',()=>{
const {book}=fixture();for(const row of book.rows){const rule=operationTreatments.find(t=>t.id===row.treatment),r=evaluateSalesOperation(row,'MZN');assert.equal(r.field,'05');assert.equal(r.net,'100.00');assert.equal(r.vat,'0.00');assert.equal(r.gross,'100.00');assert.match(r.article,/9\(10\)/);
assert.ok(rule.conditions.length>=3);
for(const missing of row.confirmed)assert.equal(evaluateSalesOperation({...row,confirmed:row.confirmed.filter(n=>n!==missing)},'MZN').vat,null);
assert.equal(evaluateSalesOperation({...row,evidence:''},'MZN').vat,null);assert.equal(evaluateSalesOperation({...row,treatment:'listed_banana'},'MZN').vat,null); assert.equal(evaluateSalesOperation({...row,period:'2025-09'},'MZN').vat,null); assert.equal(evaluateSalesOperation(row,'USD').vat,null);
}
assert.equal(book.result.periods[0].fields['07'],'0.00');assert.equal(book.result.periods[0].fields['05'],'1800.00');assert.ok(book.result.rows.every(r=>r.reason.includes('com direito')));
});
test('new listed-goods context round-trips and reports only its operations, preserving eight accepted books without recalculation',()=>{
const {draft,book}=fixture(),original=seed();const old=JSON.parse(original.modelo_a_snapshot).salesOperations;
const recovered=JSON.parse(JSON.stringify(draft)),store=JSON.parse(recovered.modelo_a_snapshot).salesOperations;
for(const id of ['opportunity:309','opportunity:310','opportunity:311','opportunity:312','opportunity:313','opportunity:314','opportunity:315','opportunity:316']){assert.deepEqual(store[id],old[id]);assert.equal(salesCurrent({...recovered,context_opportunity:id},store[id]),true);}
assert.equal(salesCurrent(recovered,salesBook(recovered)),true);
const model=JSON.stringify(createProjectDossierReport(recovered,{selectedSections:new Set(['finance'])}));
for(const token of [...ids,'1800.00','opportunity:317','Manica','2026-09','MZN'])assert.ok(model.includes(token),token);
for(const token of ['financial_stamp','patient_transport','social_assistance_public','statutory_membership','author_rights','industrial_feed','special_tax_games','public_security'])assert.equal(model.includes(token),false);
assert.equal(book.result.rows.length,18);assert.equal(draft.currency,original.currency);assert.deepEqual(draft.selection_labels,original.selection_labels);
});
if(process.env.IVA_G2H_OUT)fs.writeFileSync(process.env.IVA_G2H_OUT,JSON.stringify(fixture().draft,null,2));