feat(iva): implement G4A direct international operations with scoped evidence
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import type { OperationTreatment } from './iva-operations.ts';
|
||||
|
||||
const source = 'CIVA republicado pela Lei13/2016, BR156/10.º suplemento, art.13(1)(o/p/q/r/t), PDF12/p.1426(270); art.19(1)(b)(i), PDF15/p.1426(273). Lei22/2022 art.1, PDF3/p.2252(27), mantém19(1)(b)(i); Lei10/2025 art.1, PDF12–13/p.2180(12)–(13), não altera13 e mantém19(1)(b)(i). Âmbito técnico2026/MZN; classificação de vendas, sem dedução automática de compras ou declaraçãoAT.';
|
||||
const scope = 'Operação abrangida pelo CIVA em Moçambique, com territorialidade identificada; não inferida apenas da residência do cliente ou do sector';
|
||||
const make = (id:string,label:string,letter:string,conditions:string[]):OperationTreatment => ({id,label,field:'05',rate:0,article:`CIVA arts.13(1)(${letter}),19(1)(b)(i)`,source,logicVersion:'export-direct-operations/1',conditions:[scope,...conditions]});
|
||||
|
||||
export const directExportTreatments:OperationTreatment[] = [
|
||||
make('export_railway_cfm','Operação ferroviária dos CFM com companhia estrangeira','o',[
|
||||
'Transmissão de bens ou prestação de serviços efectuada pelos Caminhos de Ferro de Moçambique a companhia ferroviária estrangeira identificada; não basta ser qualquer operador ferroviário',
|
||||
'Operação no quadro da exploração de ferrovias ou dos seus equipamentos, com ligação identificada; não isenção de qualquer venda a companhia estrangeira',
|
||||
]),
|
||||
make('export_international_passengers','Transporte de passageiros proveniente do ou com destino ao estrangeiro','p',[
|
||||
'Serviço identificado como transporte de passageiros; não transporte de mercadorias nem serviço autónomo de alojamento ou agência',
|
||||
'Percurso proveniente do estrangeiro ou com destino ao estrangeiro, com origem e destino identificados no documento ou pressuposto; nacionalidade do passageiro não comprova o percurso',
|
||||
]),
|
||||
make('export_work_on_movables','Trabalhos em bens móveis para reexpedição ao estrangeiro','q',[
|
||||
'Prestação de serviços que consiste em trabalhos sobre bens móveis identificados, adquiridos ou importados para esses trabalhos em território nacional',
|
||||
'Os bens trabalhados são expedidos ou transportados de seguida para o estrangeiro, com correspondência entre entrada, trabalhos e saída identificada',
|
||||
'Expedição efectuada pelo prestador, pelo destinatário não estabelecido no território nacional ou por terceiro em nome e por conta de qualquer deles; interveniente e relação identificados',
|
||||
]),
|
||||
make('export_gold_bom','Ouro não trabalhado transmitido ao Banco de Moçambique','r',[
|
||||
'Bem identificado como ouro em barra ou noutra forma não trabalhada; não jóia nem artigo de ouro trabalhado',
|
||||
'Adquirente identificado como Banco de Moçambique; não qualquer banco ou comprador estrangeiro',
|
||||
]),
|
||||
make('export_packaging','Taras e embalagens para mercadorias a exportar','t',[
|
||||
'Transmissão de taras ou embalagens identificadas que servem de invólucro de mercadorias; não serviço autónomo de transporte ou armazenagem',
|
||||
'Mercadorias a exportar identificadas e afectação das taras/embalagens a essas mercadorias documentada ou expressamente pressuposta; não inferida só da actividade exportadora do comprador',
|
||||
]),
|
||||
];
|
||||
@@ -4,6 +4,7 @@ import { pharmaAnnexEntry, pharmaAnnexReference, pharmaAnnexSource, pharmaAnnexV
|
||||
import { annexIEntry, annexIReference, annexISource, annexIVersion } from './iva-annex-i.ts';
|
||||
import { productionOptionCheck, productionOptionVersion, type ProductionTaxOption } from './iva-production-option.ts';
|
||||
import { temporaryBenefits, temporaryBenefit, temporaryBenefitNotice, temporaryBenefitVersion } from './iva-temporary-benefits.ts';
|
||||
import { directExportTreatments } from './iva-export-operations.ts';
|
||||
import { monthlySalesFromDraft, readMonthlyIvaSettings } from './monthly-iva.ts';
|
||||
import { addFractions, decimalFraction, fraction, fractionLabel, fractionPresentation, type FiscalFraction } from './fiscal-rational.ts';
|
||||
|
||||
@@ -58,6 +59,7 @@ const listedGoodsTreatments:OperationTreatment[] = [
|
||||
['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[] = [
|
||||
...directExportTreatments,
|
||||
...temporaryBenefits.map(item=>({id:item.id,label:`Benefício terminado31/12/2025 — ${item.label}`,field:null,rate:null,article:`CIVA art.${item.article}`,source:`${item.source} CIVA republicado Lei13/2016,9(14),PDF10/p.1426(268),apenas para9(13)(b/f). Âmbito:cessação nas operações internas2026/MZN;sem cálculo histórico.`,conditions:[],logicVersion:temporaryBenefitVersion})),
|
||||
{id:'annexI_goods',label:'Equipamento, sementes e outros bens do Anexo I — artigo9(12)(h)',field:'07',rate:0,logicVersion:annexIVersion,article:'CIVA arts.9(12)(h),19(1); Lei22/2022 art.3(1)',source:`CIVA republicado Lei13/2016, PDF10, p.1426(268),9(12)(h), e PDF15, p.1426(273),19(1); rectificação BR89/2017, PDF1, p.569; ${annexISource}. Âmbito técnico2026/MZN`,conditions:[internalOperation,'O bem transmitido corresponde ao código e à designação da posição escolhida no Anexo I; não basta a actividade agrícola, a marca ou uma indicação genérica de equipamento','Qualificadores expressos na posição conferidos e fundamentados, incluindo natureza, destino, certificação, peso, capacidade ou potência quando exigidos nessa linha; não acrescentar condições a linhas que não as exigem']},
|
||||
{id:'pharma_manufacturing_inputs',label:'Bens do Anexo III para fabrico de medicamentos — estabelecimento integrado no SNS',field:'07',rate:0,logicVersion:pharmaAnnexVersion,article:'CIVA arts.9(1)(g),19(1); Lei22/2022 art.3(1)',source:`CIVA republicado Lei13/2016, PDF9, p.1426(267),9(1)(g), e PDF15, p.1426(273),19(1); rectificação BR89/2017, PDF1, p.569; ${pharmaAnnexSource}. Âmbito técnico2026/MZN`,conditions:[internalOperation,'Transmissão efectuada pelo próprio estabelecimento público ou privado identificado, integrado no Serviço Nacional de Saúde; não basta que o adquirente ou destino seja um hospital','Bens a utilizar como matérias-primas, produtos intermédios ou componentes para o fabrico de medicamentos, com utilização/fundamento identificados; não mera venda de produto acabado ou utilização genérica','Bem corresponde cumulativamente ao código e à designação da linha seleccionada no Anexo III; confirmar as especificações aplicáveis sem corrigir ou completar a pauta por suposição']},
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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 IvaExportDirectDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_direct_export_rules_recover_with_the_selected_opportunity_and_previous_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('outputs/iva-l1-g4-export-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']);
|
||||
$this->assertSame('opportunity:323', $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['salesOperations']['opportunity:323'];
|
||||
$this->assertSame('opportunity:323', $book['result']['opportunity']);
|
||||
$this->assertSame('MZN', $book['result']['currency']);
|
||||
$this->assertSame('2026-11', $book['result']['periods'][0]['period']);
|
||||
$this->assertSame('500.00', $book['result']['periods'][0]['fields']['05']);
|
||||
$this->assertCount(5, $book['rows']);
|
||||
foreach ($book['result']['rows'] as $row) {
|
||||
$this->assertSame('05', $row['field']);
|
||||
$this->assertSame('0.00', $row['vat']);
|
||||
}
|
||||
$frozen = json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-g3-inputs.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
foreach (json_decode($frozen['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['salesOperations'] as $context => $original) {
|
||||
$this->assertSame($original, $store['salesOperations'][$context]);
|
||||
}
|
||||
file_put_contents(base_path('outputs/iva-l1-g4-export-20261024/recovered.json'), json_encode($recovered, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { directExportTreatments } from '../../resources/js/lib/iva-export-operations.ts';
|
||||
import { newSalesOperation, evaluateSalesOperation, recordSales, saveSalesBook, salesBook, salesCurrent } from '../../resources/js/lib/iva-operations.ts';
|
||||
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
||||
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-g3-inputs.json',import.meta.url),'utf8'));
|
||||
function row(rule,index=0){return {...newSalesOperation(`g4a-${index}`,'2026-11','MZN'),description:`SYNTHETIC QA — ${rule.label}`,establishment:'SYNTHETIC QA estabelecimento',amount:'100',basis:index%2?'included':'net',treatment:rule.id,confirmed:rule.conditions.map((_,i)=>i),evidence:'SYNTHETIC QA — intervenientes,origem/destino e ligação descritos como pressupostos da projecção; não certificação documental'};}
|
||||
function fixture(){const draft={...structuredClone(frozen),context_opportunity:'opportunity:323',project_title:'SYNTHETIC QA — G4A / Exportações directas'};const book=recordSales(draft,{currency:'MZN',rows:directExportTreatments.map(row),periods:[{period:'2026-11',complete:true}]});draft.modelo_a_snapshot=saveSalesBook(draft,book);return {draft,book};}
|
||||
test('five autonomous article13 rules classify field05 only after their own facts and evidence are determined',()=>{
|
||||
assert.equal(directExportTreatments.length,5);
|
||||
for(const rule of directExportTreatments){const input=row(rule),result=evaluateSalesOperation(input,'MZN');assert.equal(result.field,'05');assert.equal(result.net,'100.00');assert.equal(result.vat,'0.00');assert.equal(result.gross,'100.00');assert.match(result.source,/13\(1\)/);
|
||||
for(const missing of input.confirmed){const r=evaluateSalesOperation({...input,confirmed:input.confirmed.filter(i=>i!==missing)},'MZN');assert.equal(r.status,'indeterminate');assert.equal(r.vat,null);}
|
||||
for(const patch of [{amount:''},{basis:''},{evidence:''},{description:''},{establishment:''},{sourceCurrency:'USD'},{period:'2025-11'}])assert.equal(evaluateSalesOperation({...input,...patch},'MZN').vat,null);
|
||||
assert.equal(evaluateSalesOperation({...input,amount:'0'},'MZN').net,'0.00');
|
||||
}
|
||||
assert.match(directExportTreatments[0].conditions[1],/Caminhos de Ferro de Moçambique/);
|
||||
assert.match(directExportTreatments[1].conditions[2],/nacionalidade/);
|
||||
assert.match(directExportTreatments[2].conditions[3],/não estabelecido/);
|
||||
assert.match(directExportTreatments[3].conditions[2],/Banco de Moçambique/);
|
||||
assert.match(directExportTreatments[4].conditions[2],/afectação/);
|
||||
});
|
||||
test('missing evidence leaves period indeterminate and recalculation preserves the previous result',()=>{
|
||||
const {draft,book}=fixture();const next=recordSales(draft,{...book,rows:book.rows.map((r,i)=>i===2?{...r,evidence:''}:r)});assert.equal(next.result.periods[0].fields['05'],null);assert.deepEqual(next.history.at(-1),book.result);
|
||||
assert.equal(book.result.periods[0].fields['05'],'500.00');assert.equal(book.result.periods[0].fields['01'],'0.00');
|
||||
});
|
||||
test('snapshot/report retain context and sources while all14 delivered books stay byte-equivalent and current',()=>{
|
||||
const {draft,book}=fixture();assert.equal(salesCurrent(draft,book),true);assert.deepEqual(salesBook(JSON.parse(JSON.stringify(draft))),JSON.parse(JSON.stringify(book)));
|
||||
const before=JSON.parse(frozen.modelo_a_snapshot).salesOperations,after=JSON.parse(draft.modelo_a_snapshot).salesOperations;assert.equal(Object.keys(before).length,14);
|
||||
for(const [context,original]of Object.entries(before)){assert.deepEqual(after[context],original);assert.equal(salesCurrent({...draft,context_opportunity:context},after[context]),true);}
|
||||
const model=createProjectDossierReport(draft,{selectedSections:new Set(['finance']),version:'QA G4A'});for(const value of ['500.00','opportunity:323','Manica','2026-11','MZN','13(1)(o)','13(1)(p)','13(1)(q)','13(1)(r)','13(1)(t)'])assert.ok(JSON.stringify(model).includes(value),value);
|
||||
if(process.env.IVA_G4A_CALCULATED)fs.writeFileSync(process.env.IVA_G4A_CALCULATED,JSON.stringify(draft));
|
||||
});
|
||||
Reference in New Issue
Block a user