feat(iva): add author rights and eligible publications in QA
This commit is contained in:
@@ -5497,5 +5497,49 @@
|
||||
"source-9.png": "97bf79b8ac52dfd9068deca630afbd87094da8f030e3565e9fc71cb1fc33a1bf",
|
||||
"ui-calculated.json": "0a1899f5a1d439403869977b80da259d87572d31ce3b2f2fb91606531467a3ce"
|
||||
}
|
||||
},
|
||||
"iva_l1_g2_publications_qa_20261024": {
|
||||
"candidate": "CANDIDATE_G2D",
|
||||
"base_QA": "122f4f04",
|
||||
"base_QA_record": "d32ad1e0",
|
||||
"base_installed": "490527b9",
|
||||
"status": "demonstrated_QA_not_installed",
|
||||
"group": "G2D within G2",
|
||||
"branches": [
|
||||
"AUD-SIM-L1-P024",
|
||||
"AUD-SIM-L1-P025"
|
||||
],
|
||||
"treatments": [
|
||||
"author_rights",
|
||||
"cultural_publications"
|
||||
],
|
||||
"scope": "2026/MZN;9(9)(a–b);19(1);field07",
|
||||
"tests": {
|
||||
"frontend": 2,
|
||||
"api_tests": 1,
|
||||
"api_assertions": 11,
|
||||
"UI_to_API_to_report": true,
|
||||
"types": "passed",
|
||||
"build": "passed"
|
||||
},
|
||||
"preservation": "Four frozen books unchanged/current without recalculation;G2C/12delivered matrix rows unchanged;prior receipt objects unchanged",
|
||||
"counts": {
|
||||
"delivered": 12,
|
||||
"demonstrated_QA": 5,
|
||||
"functional_pending": 115,
|
||||
"documentary_pending": 1,
|
||||
"total": 133
|
||||
},
|
||||
"P001_blocks_independent": false,
|
||||
"L1_complete": false,
|
||||
"homologation_unchanged": true,
|
||||
"artifacts": {
|
||||
"api-ui-tests.xml": "2e3e86e800a784248f7964b6574189d950cff1754226d81aaa7050e393ca8ec6",
|
||||
"publications-model.json": "0c8dbb6ed6a60b0847b299d4d19a407ede968cead5bd0869e0883fff981977a8",
|
||||
"publications-report.html": "0ef6ee7ea0d2acfd0cc0b33ff47491074a6bc1a522dd6110b4543e204baabcf9",
|
||||
"recovered.json": "277ef75809032a40879be14683540efd71d2c756ec860b409753779871af6f7a",
|
||||
"report-qa.png": "aa66c79b1e7674a70a8fc2b4ffe3fb7e9e261c5da421cc7cba1a94d48434fb5c",
|
||||
"ui-calculated.json": "4f326423f54cd8fa1f896f1db9e00eac2f3a75341705a14f2742ac94b427b1ae"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# Rastreabilidade, utilização e formação
|
||||
|
||||
## 24/10/2026 — G2D direitos de autor e publicações em QA
|
||||
|
||||
**Dois ramos P024/P025 demonstrados, candidata CANDIDATE_G2D; G2C preservado para entrega conjunta.** Incremento do G2 existente sobre122f4f04/registod32ad1e0. Homologação mantém490527b9;L1continuaaberto.
|
||||
|
||||
| Ramo / tratamento | Fonte e condição concreta | Resultado |
|
||||
| --- | --- | --- |
|
||||
| P024 / author_rights | CIVA9(9)(a): transmissão de direitos de autor ou autorização para utilização de obra intelectual, efectuada pelo próprio autor, seu herdeiro ou legatário; identificar operação e qualidade | Isenção sem direito à dedução nesta operação;07 |
|
||||
| P025 / cultural_publications | CIVA9(9)(b): transmissão de jornais, revistas e livros de natureza cultural, educativa, técnica ou recreativa; identificar publicação/natureza, sem generalizar a todo material escolar ou serviço editorial | Isenção sem direito à dedução nesta operação;07 |
|
||||
|
||||
**Cotejo:** original aceite Lei13/2016/CIVA, PDF10/p.1426(268),9(9)(a–b); PDF15/p.1426(273),19(1). Lei22/2022 artigo1, PDF2/p.2252(26), mantém9(9) e suas alíneas. Reutilizado o cotejo posterior registado: Lei16/2020 afecta9(13), não9(9);Lei10/2025 não altera9/10 nem19(1). Sem divergência documental dependente deP001 neste recorte; não foi inventada lista de exclusões ou autorização ministerial para estas operações. Aplicação técnica Janeiro–Dezembro2026/MZN; outros períodos permanecem indeterminados.
|
||||
|
||||
**Reutilização:** mesmos campos de oportunidade/localização, período, moeda, valor e preço líquido/incluído; acrescentadas apenas as duas opções e condições específicas no catálogo. Natureza da operação, titularidade/natureza da publicação e fundamento/pressuposto são recolhidos pelos campos existentes. Sem schema,importação,migração,permissão,API ou fórmula nova. Uma condição desconhecida não é não aplicabilidade nem isenção presumida; deve ser completada ou revista a classificação.
|
||||
|
||||
**Provas:**2testesfrontend novos passam: duas operações,100,00MZNcada,base líquida/incluída,IVA0,00,campo07total200,00; ausência individual de cada condição,fundamento vazio,moeda/período fora da cobertura→indeterminado. Fixture congelada G2C conserva quatro livros anteriores, respectivas entradas/resultados/histórico e chaves actuais sem recálculo; relatório do contexto313 exclui tratamentos dos outros livros. API privadaPOST→GET na baseQAisolada `simulator_fiscal_delta_20261021_test:55441`,guardadedestino/transacçãorevertida,1teste/11asserções; snapshot integral recuperado. Interface real: titularidade sem confirmação→indeterminado;completada→revisão3 calculada/guardada; payload da interface enviado à API→recuperado→relatórioHTML real conferido com linhas/valores,2026-05,MZN,opportunity:313/Manica,fontes/versão. Tipos/compilação passaram. Os12ramosentregues e G2C não foram reensaiados nem recalculados. Renderizadores não alterados; Word/PDF/fraccões aceites reutilizados, sem afirmar nova prova de paginação.
|
||||
|
||||
**Saldo:**133IDs=12entregues+5QA(G2CP013–P015;G2DP024–P025)+115funcionaispendentes+P001documental. G2total32=8entregues+5QA+19pendentes. Motor32tratamentoscondicionados=10iniciais+22dosgrupos;duasopçõesindeterminadasmantidas. NenhumIDfundido/eliminado. P001isolado;estatísticas/IAadiadas.
|
||||
|
||||
**Próxima entrega conjunta:** G2C+G2D aguardam integraçãoGit autorizada com conferência fresca do destino/concorrência,backup/restauro e apenasdelta. Catálogo é único ficheiro funcional alterado neste incremento; activação comcomponenteexistente. Preservar catálogo/leitores/resultados numa contenção, suspendendo entradas por mudança explícita posterior em vez de eliminar regras usadas. Sem nova entrega ou reversão executada. Artefactos privados: `outputs/iva-l1-g2-publications-20261024/`.
|
||||
|
||||
|
||||
## 24/10/2026 — G2C cultural/associativo executável em QA
|
||||
|
||||
**Três ramos P013–P015, cinco tratamentos condicionais, candidata 122f4f04b9e39d28e7b5922142c4ebc6889a68c1; L1 aberto.** Continuação do agrupamento G2 existente, sobre a entrega490527b9/recibo dacda11c. Os12ramos entregues permanecem encerrados; não foram recalculados nem repetidos os seus ensaios. A homologação não foi alterada.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -33,6 +33,8 @@ 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']});
|
||||
export const operationTreatments:OperationTreatment[] = [
|
||||
{id:'author_rights',label:'Direitos de autor pelo autor, herdeiro ou legatário',field:'07',rate:0,article:'CIVA arts.9(9)(a),19(1)',source:'CIVA republicado Lei13/2016, PDF10, p.1426(268),9(9)(a); PDF15, p.1426(273),19(1); Lei22/2022 art.1, PDF2, p.2252(26), mantém9(9). Âmbito técnico2026/MZN',conditions:[internalOperation,'Operação identificada como transmissão de direitos de autor ou autorização para utilização de obra intelectual','Efectuada pelo próprio autor, seu herdeiro ou legatário, com qualidade identificada; não presumir isenção de revenda/intermediação por terceiro']},
|
||||
{id:'cultural_publications',label:'Jornais, revistas e livros de natureza elegível',field:'07',rate:0,article:'CIVA arts.9(9)(b),19(1)',source:'CIVA republicado Lei13/2016, PDF10, p.1426(268),9(9)(b); PDF15, p.1426(273),19(1); Lei22/2022 art.1, PDF2, p.2252(26), mantém9(9). Âmbito técnico2026/MZN',conditions:[internalOperation,'Transmissão de jornal, revista ou livro identificado; não qualquer material escolar, serviço editorial ou fornecimento autónomo','Natureza cultural, educativa, técnica ou recreativa da publicação identificada e sustentada; não inferida apenas do nome do fornecedor ou sector']},
|
||||
...culturalTreatments,
|
||||
{id:'general',label:'Operação interna geral',field:'01',rate:16,article:'CIVA arts.1,6,15(1),17(1–2); art.34 (Lei10/2025)',source:`${source2016}; ${source2022}; ${source2025}`,conditions:['Operação localizada e tributável em Moçambique, no regime normal','Contraprestação completa; sem base especial do art.15(2), isenção ou taxa reduzida']},
|
||||
{id:'private_health',label:'Serviços médicos e sanitários privados',field:'03',rate:5,article:'CIVA arts.15(10),17-A(a)',source:source2022,conditions:['Serviço médico/sanitário ou operação estreitamente conexa','Prestador hospitalar privado, clínica, dispensário ou similar']},
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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 IvaPublicationsDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
public function test_publications_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_G2D_INPUT') ?: base_path('outputs/iva-l1-g2-publications-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:313'];
|
||||
$this->assertCount(2, $book['result']['rows']);
|
||||
$this->assertSame('200.00', $book['result']['periods'][0]['fields']['07']);
|
||||
$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_G2D_RECOVERED')) file_put_contents(getenv('IVA_G2D_RECOVERED'), json_encode($recovered, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -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=['author_rights','cultural_publications'];
|
||||
function seed(){return JSON.parse(fs.readFileSync(new URL('./fixtures/iva-cultural.json',import.meta.url),'utf8'));}
|
||||
function fixture(){const draft=seed();draft.context_opportunity='opportunity:313';draft.project_title='SYNTHETIC QA — G2D social';
|
||||
let book={currency:'MZN',rows:ids.map((id,i)=>({...newSalesOperation(`g2d-${i}`,'2026-05','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-05',complete:true}]};book=recordSales(draft,book);draft.modelo_a_snapshot=saveSalesBook(draft,book);return {draft,book};}
|
||||
test('G2D two branches 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,'07');assert.equal(r.net,'100.00');assert.equal(r.vat,'0.00');assert.equal(r.gross,'100.00');assert.match(r.article,/9\(9\)/);
|
||||
assert.equal(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,period:'2025-05'},'MZN').vat,null); assert.equal(evaluateSalesOperation(row,'USD').vat,null);
|
||||
}
|
||||
assert.equal(book.result.periods[0].fields['07'],'200.00');
|
||||
});
|
||||
test('new publications context round-trips and reports only its operations, preserving four 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']){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,'200.00','opportunity:313','Manica','2026-05','MZN'])assert.ok(model.includes(token),token);
|
||||
for(const token of ['financial_stamp','patient_transport','social_assistance_public','statutory_membership'])assert.equal(model.includes(token),false);
|
||||
assert.equal(book.result.rows.length,2);assert.equal(draft.currency,original.currency);assert.deepEqual(draft.selection_labels,original.selection_labels);
|
||||
});
|
||||
if(process.env.IVA_G2D_OUT)fs.writeFileSync(process.env.IVA_G2D_OUT,JSON.stringify(fixture().draft,null,2));
|
||||
Reference in New Issue
Block a user