fix(offers): preserve description in authorised investment context
This commit is contained in:
@@ -193,6 +193,17 @@ final class InvestorContext
|
||||
$href = $published ? '/oportunidades/'.$o->slug : '/minhas-oportunidades/'.$o->id.'/view';
|
||||
$version = $o->management_version;
|
||||
$fields = ['project_title' => $title, 'project_summary' => $draft['canonical']['executive_summary'] ?? $o->summary];
|
||||
if ($o->isPrivateOfferPreparation()) {
|
||||
// Offers reuse Opportunity, but their preparation writes description,
|
||||
// not the canonical executive summary. Keep this authorised private
|
||||
// projection explicit: never copy private_offer (price/finances/contact).
|
||||
$title = $draft['title'] ?? $o->title;
|
||||
$summary = $fields['project_summary'];
|
||||
$fields['project_title'] = $title;
|
||||
$fields['project_summary'] = is_string($summary) && trim($summary) !== ''
|
||||
? $summary : ($draft['description'] ?? null);
|
||||
$href = '/minhas-oportunidades/ofertas/'.$o->id;
|
||||
}
|
||||
if ($o->sector) {
|
||||
$fields['sector_id'] = (string) $o->sector_id;
|
||||
$fields['sector'] = $o->sector->name;
|
||||
|
||||
@@ -10202,3 +10202,6 @@ Estado: cinco fases preparadas em QA; F1-F4 aceites tecnicamente com ressalvas,
|
||||
# Auditoria de aceitação Ofertas — 14/11/2026, antes de instalar
|
||||
|
||||
Referência: `../v2/auditoria-portal/ofertas-aceitacao-final-20261114.md`. Candidata `528a2597`; commits e hashes F1–F5 conferidos, JUnit aceite 59 testes/780 asserções confirmado sem o apresentar como nova execução. Nova regressão dos componentes nativos: 14/227 aprovada. Novo teste de integração: 2/11, um aprovado e uma falha na reutilização da descrição da Oferta pelo contexto de investimento; recuperação financeira privada e isolamento passaram. A aceitação integral permanece bloqueada: falta demonstrar Oferta → dossier → viabilidade em função da localização → relatório → recuperação. Publicação efectiva não implementada/activada; NDA real, visual/móvel, imagens e entrega real de email mantêm os estados específicos do relatório. Plano de instalação preparado, zero novas migrações no delta, schema do destino e reversão compatível por conferir. Nenhuma instalação ou alteração funcional efectuada nesta auditoria. RF-MN-003 actualizado sem alterar os restantes requisitos.
|
||||
# Correcção Ofertas → contexto de investimento — QA, 14/11/2026
|
||||
|
||||
Após `ed2fd86f`, foi autorizada exclusivamente a correcção da continuidade. `InvestorContext` passa a recuperar a descrição da Oferta quando não existe resumo, mantendo a mesma Opportunity, os identificadores e a autorização privada. Resumo existente e edições deliberadas no dossier prevalecem. Não se copiam dados empresariais confidenciais, contactos ou preço privado; NDA/autorização do vendedor não concedem acesso ao contexto privado. A prova sintética percorre criação, submissão, revisão/aprovação, projecção pública isolada de teste, contexto/dossier, localização, pressupostos financeiros declarados, gravação/recuperação e relatórios HTML/Word. Não representa publicação real nem rentabilidade garantida. Recibo e resultados em `../v2/auditoria-portal/ofertas-correcao-contexto-qa-20261114.md` e `outputs/offers-investment-continuity-20261114/qa-result.json`; RF-MN-003 e matriz final actualizados. Mantêm-se pendências jurídicas, visuais, de imagens e email real. Sem instalação, activação pública, migrações ou alteração de dados operacionais; aguarda nova autorização para instalar.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,6 +4,8 @@
|
||||
|
||||
**Decisão: aprovação técnica parcial com ressalvas; aceitação integral bloqueada.** As provas das cinco fases são válidas para os comportamentos que demonstram. Não demonstram uma publicação operacional nem a continuidade completa Oferta → dossier → viabilidade territorial. Foi reproduzida uma perda de reutilização da descrição nessa fronteira.
|
||||
|
||||
**Actualização posterior à auditoria `ed2fd86f`:** a correcção autorizada em QA está documentada em [Ofertas → Investimento](ofertas-correcao-contexto-qa-20261114.md). O saldo actualizado nas linhas abaixo substitui apenas o estado desses critérios; conserva-se a prova original da falha. O percurso controlado com hipóteses de localização e relatório foi demonstrado; publicação operacional e restantes dependências não foram activadas.
|
||||
|
||||
## Evidência e limites
|
||||
|
||||
- Confirmada a ancestralidade dos commits F1 `1f2990e4`, plano `6e302db6`, F2 `91c18573`, F3 `7691cab3`, F4 `e431de8b` e F5 `528a2597`.
|
||||
@@ -30,23 +32,23 @@
|
||||
| Denúncias e tratamento no Dashboard, auditoria e destaques administrativos | aprovado com ressalvas | F5 cobre isolamento, fundamento cifrado, tarefas existentes, versões e responsável. Destaque só rascunho/arquivo, sem promoção/pagamentos. Visual pendente. |
|
||||
| Isolamento de utilizadores, páginas, APIs, anexos, mapas e CSV | aprovado com ressalvas | Suites F1–F5 e regressão nativa; leitura pública limitada à projecção, comprovativos internos, sem originais externos. Não equivale a teste de intrusão independente nem validação visual. |
|
||||
| Preservação da versão pública durante edição | aprovado com ressalvas | Contrato sintético de Ofertas e nova regressão nativa aprovados; não existe ainda versão pública operacional de Ofertas para demonstrar o percurso real. |
|
||||
| Escrever uma vez: recuperar a descrição no contexto de investimento | bloqueado | Novo teste reproduz `project_summary` ausente embora `description` esteja guardada. `InvestorContext::reference` lê resumo canónico/`summary`, sem aproveitar a descrição da Oferta. |
|
||||
| Reutilização de valores financeiros autorizados na viabilidade, por localização e factores do negócio | não verificado | Os campos privados são recuperáveis, mas não foi localizado um contrato específico que os leve ao simulador, explicite pressupostos territoriais e preserve alterações deliberadas. Não inferir viabilidade a partir de receita/caixa/preço. Falta prova integrada e decisão de mapeamento quando necessária. |
|
||||
| Consulta da Oferta → acção no mesmo dossier → simulação → relatório → recuperação | bloqueado | Detalhe público apenas liga a regresso/denúncia e mapa; consulta normal fechada. O contexto nativo admite referências privadas autorizadas, mas não demonstra este percurso completo de aquisição/investimento. |
|
||||
| Escrever uma vez: recuperar a descrição no contexto de investimento | aprovado | Correcção QA específica da transformação; descrição recuperada, resumo existente e alterações deliberadas preservados, sem truncar a origem nem duplicar registos. Prova e recibo no relatório de correcção. |
|
||||
| Contexto territorial e hipóteses económicas autorizadas até à viabilidade | aprovado com ressalvas | Localização persistida até ao rascunho, custos de renda/transporte declarados pelo investidor, cálculo e comparação sensível aos custos. Valores empresariais confidenciais não são transferidos; não constitui conclusão automática sobre rentabilidade nem valida todos os cenários fiscais. |
|
||||
| Percurso controlado QA Oferta → mesmo dossier → simulação → relatório → recuperação | aprovado com ressalvas | Criação/submissão/revisão/aprovação reais no teste, projecção pública sintética isolada, referência privada autorizada, rascunho/resultados no dossier e exportadores HTML/Word existentes. Abertura pública operacional continua bloqueada e visual pendente. |
|
||||
| Restantes oportunidades, pesquisa e recursos afectados | aprovado com ressalvas | Nova regressão 14/227 passou. Não constitui declaração de ausência de defeitos em todo o portal ou em serviços externos não ensaiados. |
|
||||
| Desktop/móvel e acessibilidade funcional | não verificado | Última evidência do conector: `The browser access host is unavailable`; não foi repetido o ciclo de reinícios. HTML compilado não substitui conferência visual. |
|
||||
| Entrega real de notificações por email | não verificado | Serviço existente e permissões foram testados com transporte de teste; SMTP/fila/recepção real não ensaiados. Não foram enviados emails operacionais. |
|
||||
|
||||
## Bloqueantes e ressalvas para decisão
|
||||
|
||||
1. **Integração funcional:** corrigir e repetir o teste da descrição; demonstrar, sem exposição pública, o percurso de contexto/dossier/viabilidade/relatório/recuperação. Definir o mapeamento autorizado de dados económicos e pressupostos da localização antes de transportar informação confidencial; preservar proveniência, moeda e escolhas deliberadas. Não confundir preço de aquisição com investimento total nem dados históricos com previsão.
|
||||
1. **Integração funcional — corrigida em QA na passagem autorizada posterior:** o teste da descrição e a prova controlada contexto/dossier/viabilidade/relatório/recuperação passaram. A projecção limita-se a campos comuns autorizados; dados económicos confidenciais não são transferidos. Moeda e custos do investimento são hipóteses explícitas, sem confundir preço de aquisição com investimento total ou dados históricos com previsão. Qualquer novo contrato de transferência confidencial continua fora desta correcção.
|
||||
2. **Publicação:** implementar e testar um publicador controlado, se autorizado em nova instrução. A aceitação do consumidor sintético não autoriza remover as barreiras existentes. Instalação privada de avaliação e abertura pública são decisões separadas.
|
||||
3. **Jurídico:** faltam exemplar/versão/referência de aprovação do NDA, tipos admissíveis de comprovativo imobiliário, política final de validade e condições/avisos aplicáveis à divulgação. Não são motivos para invalidar as provas técnicas já aceites; bloqueiam os comportamentos dependentes.
|
||||
4. **Imagens:** comprovar processamento seguro e derivadas numa instalação compatível, incluindo scan, metadados, acesso e limite de seis. Não instalar extensões nem declarar galeria pronta nesta auditoria.
|
||||
5. **Visual e correio:** realizar conferência real desktop/móvel e entrega de email autorizada em ambiente de teste. Permanecem não verificados, sem solicitar que o utilizador repita autenticações nesta passagem.
|
||||
6. **Reversão:** um retorno cego à F4 é inseguro como plano: a versão F4 de `PrivateOfferConfidentiality::approvedHash` não verifica `OfferAvailability`, enquanto a F5 o faz. Uma retirada de ciclo zero pode conservar o mesmo hash editorial. A reversão deve preservar a recusa de leitura de ofertas retiradas/expiradas e os históricos; não assumir que repor código antigo mantém as novas protecções.
|
||||
|
||||
Não foram corrigidas estas lacunas porque a instrução autoriza exclusivamente auditoria e preparação do plano. O teste falhado fica explícito como critério de aceitação por satisfazer; não foi convertido numa aprovação por caracterização da falha.
|
||||
Na auditoria original não foram corrigidas lacunas por estar autorizada exclusivamente a auditoria. A falha histórica foi conservada; a autorização posterior permitiu a correcção delimitada agora comprovada, sem alterar os restantes bloqueios.
|
||||
|
||||
## Plano de instalação controlada — não executado
|
||||
|
||||
@@ -61,4 +63,4 @@ Não foram corrigidas estas lacunas porque a instrução autoriza exclusivamente
|
||||
|
||||
## Resultado e próxima acção
|
||||
|
||||
Auditoria documental, hashes e regressões delimitadas concluídos com achados. **O módulo não está integralmente aceite para o percurso de investimento solicitado.** Próxima acção recomendada: autorizar a correcção da continuidade Oferta → contexto/dossier e a sua prova de viabilidade, mantendo os bloqueios públicos; depois concluir as verificações dependentes e submeter o pacote à autorização de homologação. Nenhuma instalação efectuada.
|
||||
Auditoria original concluída com achados; a continuidade Oferta → contexto/dossier e a prova controlada de viabilidade/relatório foram corrigidas em QA por autorização posterior. **O módulo mantém ressalvas e bloqueios de publicação operacional, jurídicos e verificações pendentes.** Próxima acção: apreciar o recibo de correcção e as dependências restantes antes de qualquer autorização de homologação. Nenhuma instalação efectuada.
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Ofertas → Investimento — correcção da continuidade em QA
|
||||
|
||||
14/11/2026. Base: auditoria `ed2fd86f`, preservando F1–F5 até `528a2597`. Autorização exclusiva para investigar/corrigir a continuidade e demonstrá-la em QA. Sem instalação, publicação efectiva, alterações operacionais, migrações ou intervenção em `mne`.
|
||||
|
||||
## Causa e correcção
|
||||
|
||||
A reprodução antes da alteração confirmou o resultado anterior: dois testes, um aprovado e um falhado; o contexto não tinha `project_summary`. A descrição estava correctamente persistida em `Opportunity.description`, no mesmo identificador. A autorização já permitia ao dono consultar a Oferta. A perda ocorria na **transformação/recuperação**: `InvestorContext::reference('opportunity:…')` só projectava `canonical.executive_summary` ou `summary`, enquanto a preparação privada das Ofertas escreve `description`.
|
||||
|
||||
O adaptador existente passa a usar a descrição guardada como alternativa para `project_summary`, **apenas para Ofertas privadas já autorizadas**, conservando qualquer resumo existente não vazio. O título vem dos valores correntes da mesma preparação e a referência aponta para a rota específica da Oferta. Não se criam registos, fontes, relações ou contratos paralelos. Não se trunca a descrição, não se grava uma cópia na Oportunidade e não se altera a origem ao confirmar dados no dossier.
|
||||
|
||||
O reconciliador existente continua a apresentar prévia e confirmação, com versões, hash, proveniência e preservação de edições deliberadas. O teste integrado confirma que um resumo deliberadamente alterado no dossier não é substituído numa nova prévia da mesma Oferta.
|
||||
|
||||
## Campos e confidencialidade
|
||||
|
||||
| Informação | Continuidade autorizada |
|
||||
| --- | --- |
|
||||
| Identificador/chave da Oportunidade, versão, hash, título e referência | Contrato existente `opportunity:<id>`; mesmo cadastro e dossier, sem duplicação. |
|
||||
| Descrição | Alternativa para resumo apenas no contexto privado autorizado; resumo existente prevalece. Descrição longa preservada. Não é uma autorização de divulgação pública. |
|
||||
| Referências administrativas e sector comum já cadastrado | Contratos existentes preservados; a localização do ensaio mantém o mesmo identificador até ao contexto e ao rascunho financeiro. Não se deduz cobertura, disponibilidade ou benefício fiscal. |
|
||||
| Moeda, custos e pressupostos do investimento | Declarações explícitas do investidor no percurso existente. A moeda/preço da Oferta não se convertem automaticamente em moeda/investimento total do projecto. |
|
||||
| `private_offer.business`, receitas, fluxo de caixa, preço privado, contacto, razões e comprovativos | Não são copiados pelo adaptador para o contexto, relatórios ou exportações. Os campos de sector guardados dentro da informação empresarial confidencial também não são promovidos ao sector comum por inferência. |
|
||||
| Informação pública sintética | Apenas projecção pública separada e expressamente construída no teste. A descrição privada e os valores empresariais não passam para essa projecção. |
|
||||
| Leitura por NDA e autorização do vendedor | Mantém o âmbito próprio: não concede acesso à referência privada do contexto. Novo teste confirma recusa 404 mesmo depois das duas condições satisfeitas; revogação e demais testes F4 permanecem válidos. |
|
||||
|
||||
Não se introduziu um novo percurso de transferência de dados confidenciais. Qualquer futura reutilização desses dados precisa de autorização e contrato próprios; esta correcção não alarga direitos de consulta ou exportação.
|
||||
|
||||
## Prova integrada reproduzível
|
||||
|
||||
`OfferInvestmentAcceptanceAuditTest` usa exclusivamente `offers_phase1_20261114_test`, loopback:55441, com guardas e rollback existentes. Cria Oferta de empresa e referência provincial sintéticas, grava/recupera, associa contacto autorizado, submete, efectua revisão/aprovação por outra conta e conserva `status=draft`/`published_at=null`.
|
||||
|
||||
O mesmo teste comprova catálogo normal vazio e injecta `OfferPublicCatalogue(true)` exclusivamente no contentor do teste, sujeito à guarda real da base isolada. A projecção aprovada/publicada sintética tem resumo público próprio; não se altera qualquer interruptor de publicação operacional. A referência privada continua sujeita a `PortalProfileAccess`.
|
||||
|
||||
O dono selecciona a mesma Oportunidade num dossier, confere a prévia e confirma. Descrição e referência provincial chegam ao prefill existente do simulador. O investidor declara moeda e pressupostos de renda/transporte para esse local; guarda o rascunho financeiro, recupera-o e devolve os resultados com confirmação da revisão do contexto. Uma nova autenticação da mesma conta recupera o mesmo dossier e rascunho; outra conta é recusada. O teste confirma uma Oportunidade e um dossier, sem duplicações.
|
||||
|
||||
O teste frontend consome o JSON produzido pela prova PHP, recupera o snapshot e executa os calculadores existentes: receita mensal sintética 10.000, renda 2.000 e transporte 1.000 produzem saldo operacional estimado de 7.000 MZN; custos alternativos expressamente declarados de 5.000 e 3.000 produzem 2.000 MZN. Custos fixos 3.000, preço unitário 100 e custo variável 50 dão ponto de equilíbrio de 60 unidades. Estas hipóteses demonstram sensibilidade aos custos do local; **não atribuem uma tarifa provincial, uma margem real da empresa ou rentabilidade garantida**. Receita histórica confidencial da Oferta não é usada como previsão.
|
||||
|
||||
O mesmo teste gera o relatório privado HTML e Word pelo exportador existente, reabre o Word e verifica descrição/localização, recuperação do snapshot e ausência dos valores confidenciais e contacto. Os artefactos são sintéticos, não documentos de negócio nem uma aceitação visual.
|
||||
|
||||
Durante a preparação da prova, a referência provincial inicial não tinha a proveniência exigida pelo catálogo público; foi corrigida apenas a fixture isolada, reutilizando o contrato de `OpportunitySearchFixture`. O pedido do simulador foi ajustado à lista de campos já aceite e à confirmação da revisão de contexto. Nenhuma guarda foi desactivada para passar o ensaio.
|
||||
|
||||
## Ficheiros e verificação
|
||||
|
||||
- Produto: `app/Services/InvestorContext.php`, projecção delimitada de descrição/título/referência.
|
||||
- Testes PHP: `OfferInvestmentAcceptanceAuditTest.php`, persistência, resumo existente, descrição longa, dossier/rascunho/resultados e isolamento; `PrivateOfferConfidentialityTest.php`, acesso por NDA sem concessão de acesso ao contexto privado.
|
||||
- Teste frontend: `tests/frontend/offer-investment-continuity.test.mjs`, cálculo com pressupostos territoriais declarados e relatório real com reabertura do Word.
|
||||
- Documentação: matriz RF-MN-003, manual e matriz final de aceitação existentes; este recibo explica a correcção sem apagar a prova histórica da falha.
|
||||
|
||||
Resultados e comandos finais: `outputs/offers-investment-continuity-20261114/qa-result.json`. Não somar passagens sobrepostas nem apresentar os 59/780 históricos como executados novamente nesta correcção.
|
||||
|
||||
| Verificação final | Resultado |
|
||||
| --- | --- |
|
||||
| Continuidade Oferta/contexto e percurso integrado | 4 testes / 62 asserções aprovados |
|
||||
| Confidencialidade, incluindo NDA sem acesso ao contexto privado | 11 / 161 aprovados |
|
||||
| Percursos existentes de investimento/dossiers | 4 / 192 aprovados |
|
||||
| Gestão nativa de Oportunidades e versão pública | 3 / 46 aprovados |
|
||||
| **Passagem PHP integrada** | **22 testes / 461 asserções; zero erros/falhas** |
|
||||
| Regressão frontend de plano financeiro e relatórios | 41 testes aprovados |
|
||||
| Prova frontend ligada ao JSON da passagem PHP final | 1 teste aprovado; HTML e Word gerados/reabertos |
|
||||
| PHPStan completo, TypeScript e Pint | Aprovados; zero erros PHPStan |
|
||||
| Lint/formatação do novo teste frontend; diferenças Git | Aprovados |
|
||||
|
||||
Comando PHP final: `php vendor/bin/phpunit tests/Feature/OfferInvestmentAcceptanceAuditTest.php tests/Feature/PrivateOfferConfidentialityTest.php tests/Feature/InvestorPathTest.php tests/Feature/OpportunityManagementTest.php --log-junit outputs/offers-investment-continuity-20261114/regression.xml`, com a configuração sintética/guardada acima. Frontend: `node --test tests/frontend/investor-dossier-report.test.mjs tests/frontend/business-financial-plan.test.mjs`, seguido de `node --test tests/frontend/offer-investment-continuity.test.mjs`. Análise: `php vendor/bin/phpstan analyse --no-progress --memory-limit=1G`, `npm.cmd run types:check`, Pint nos três ficheiros PHP e `vp fmt --check`/`vp lint` no novo teste. A compilação QA anterior é reutilizada porque não mudou código frontend do produto; os testes PHP preservaram os assets existentes.
|
||||
|
||||
## Saldo para aceitação
|
||||
|
||||
**Corrigido em QA:** descrição na fronteira Oferta/contexto, preservação de resumo e edições deliberadas, recuperação no mesmo dossier, ensaio de localização/pressupostos → cálculo → relatórios HTML/Word → recuperação, sem passagem de dados financeiros confidenciais.
|
||||
|
||||
**Mantém-se bloqueado:** publicação efectiva operacional; NDA real enquanto faltar o exemplar aprovado; decisões jurídicas/prazo definitivos identificados na auditoria. A injecção de projecção sintética não substitui esse fecho.
|
||||
|
||||
**Por verificar:** conferência visual desktop/móvel, processamento de derivadas de imagens/galeria no ambiente compatível e entrega real de email. A simulação demonstrada é uma análise controlada com hipóteses declaradas, não uma prova de todos os cenários fiscais e factores de rentabilidade do portal.
|
||||
|
||||
Nenhuma instalação efectuada ou autorizada por este recibo. A eventual instalação aguarda nova autorização expressa, com o plano de cópia/restauro e reversão compatível já documentado.
|
||||
@@ -0,0 +1 @@
|
||||
{"tool":"phpunit","result":"passed","tests":4,"passed":4,"assertions":55,"duration_ms":25651}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites>
|
||||
<testsuite name="Tests\Feature\OfferInvestmentAcceptanceAuditTest" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" tests="4" assertions="55" errors="0" failures="0" skipped="0" time="25.647841">
|
||||
<testcase name="test_saved_offer_reuses_its_description_in_the_existing_investment_context" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="41" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="8" time="6.981317"/>
|
||||
<testcase name="test_native_context_options_do_not_disclose_another_owners_private_offer" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="55" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="7" time="2.323249"/>
|
||||
<testcase name="test_private_description_is_not_truncated_and_does_not_replace_an_existing_summary" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="67" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="5" time="2.097139"/>
|
||||
<testcase name="test_approved_offer_continues_in_same_dossier_location_analysis_saved_result_and_report_input" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="80" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="35" time="14.246136"/>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
@@ -0,0 +1,49 @@
|
||||
✔ financial forecast reconciles revenue, finance sources, working capital and monthly cash (25.013875ms)
|
||||
✔ opening payable installments reduce cash without changing profit and retain unpaid balances (4.962292ms)
|
||||
✔ opening payable schedule caps cent rounding at the remaining balance (0.817417ms)
|
||||
✔ monthly unit schedules drive revenue, costs, inventory and scenario volume (11.337458ms)
|
||||
✔ monthly unit schedules must match the horizon and contain valid quantities (2.604459ms)
|
||||
✔ monthly prices and unit costs affect revenue, consumption and scenarios (4.282666ms)
|
||||
✔ monthly money schedules cover the horizon and reject invalid amounts (2.09225ms)
|
||||
✔ monthly operating and payroll expense schedules remain separate from payments (3.289125ms)
|
||||
✔ monthly operating schedules require full horizon, exact cents and current snapshot version (18.074292ms)
|
||||
✔ losses and negative closing cash remain visible instead of being clamped to zero (3.032334ms)
|
||||
✔ principal repayments affect cash but are excluded from profit expenses (2.658625ms)
|
||||
✔ depreciation allocation reconciles exactly over useful life (1.653209ms)
|
||||
✔ missing currency, products, invalid numbers and invalid horizons are explicit errors (1.136708ms)
|
||||
✔ versioned plan snapshots restore assumptions and reject unsupported or oversized data (3.091333ms)
|
||||
✔ business plan serializer refuses snapshots above the server storage limit (0.349541ms)
|
||||
✔ sales invoicing and receipts follow an optional declared collection lag (0.986292ms)
|
||||
✔ scheduled collection percentage keeps unpaid invoices in receivables with exact rounding (0.648709ms)
|
||||
✔ sales recovery applies only to overdue invoices and never exceeds open balances (1.041541ms)
|
||||
✔ opening receivables affect cash only when a month and collection percentage are declared (0.967791ms)
|
||||
✔ opening receivable installments reconcile to the initial balance across months (1.622958ms)
|
||||
✔ opening receivable installments cap independently rounded cents at the available balance (2.369541ms)
|
||||
✔ inventory purchases, consumption, closing stock and stock shortfall reconcile independently of payments (0.385875ms)
|
||||
✔ inventory lead time separates monthly orders, receipts, shortages and goods still in transit (0.667ms)
|
||||
✔ monthly inventory purchase schedule reconciles variable orders, lead times, receipts and transit (1.319042ms)
|
||||
✔ monthly variable purchase payments affect cash independently from inventory orders and profit (0.722625ms)
|
||||
✔ product inventory schedules reconcile independently and do not transfer stock between products (6.993292ms)
|
||||
✔ supplier payment terms drive purchase cash flow and expose amounts due beyond the horizon (2.183292ms)
|
||||
✔ supplier order installments reconcile exact cents, cash dates and unallocated balances (0.841625ms)
|
||||
✔ business assumptions survive the existing encrypted-draft snapshot split and restore (0.779958ms)
|
||||
✔ presentation currency conversion requires and reports its rate, date, and declared source (1.636458ms)
|
||||
✔ CSV marks presentation currency conversion as an unverified user planning assumption (1.087958ms)
|
||||
✔ business plan export carries its assumptions, formulas, period, currency, and matching result (0.447959ms)
|
||||
✔ business plan CSV preserves declared monthly product schedules (0.446125ms)
|
||||
✔ CSV export exposes the sales collection assumption and outstanding receivable (0.320583ms)
|
||||
✔ business plan CSV neutralizes formula-like labels and preserves negative financial values (0.323792ms)
|
||||
✔ financial scenarios expose user-declared changes without creating tax values (5.431667ms)
|
||||
✔ one dossier groups stage evidence, SWOT, fiscal and all preserved APIEX versions without modifying sources (5.195167ms)
|
||||
✔ cross-dossier or cross-opportunity preparation is rejected and an ambiguous scope requires selection (1.404625ms)
|
||||
✔ financial results from a different opportunity do not enter the consolidated report or its index (0.736833ms)
|
||||
✔ the consolidated report reuses loan, break-even, financing and budget outputs with currency and historical state (14.746791ms)
|
||||
✔ the existing Word and print exporters retain dossier identity, pending stages, source and history, escaping active HTML (131.861583ms)
|
||||
ℹ tests 41
|
||||
ℹ suites 0
|
||||
ℹ pass 41
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 964.394334
|
||||
@@ -0,0 +1,663 @@
|
||||
{
|
||||
"path": {
|
||||
"funding_selections": [],
|
||||
"preparation_action": null,
|
||||
"private_investment": null,
|
||||
"case": {
|
||||
"id": "f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"title": "Synthetic acquisition acceptance",
|
||||
"version": 5,
|
||||
"closed": false
|
||||
},
|
||||
"journey_id": "a0851462-2b7f-47dc-bb0f-59c2a5004e36",
|
||||
"definition_id": "61000919-0000-4000-8000-000000000001",
|
||||
"definition_version": 1,
|
||||
"source_sha256": "aa13c53138ec15cd808de43d9dc2fc6808da86943d09d3352c9dd0944bdae364",
|
||||
"phases": {
|
||||
"1": "Ideia e viabilidade",
|
||||
"2": "Formaliza\u00e7\u00e3o e dossier",
|
||||
"3": "Licenciamento e terra",
|
||||
"4": "Financiamento",
|
||||
"5": "Execu\u00e7\u00e3o e acompanhamento"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"code": "S01",
|
||||
"phase": 1,
|
||||
"title": "Sistematizar a ideia",
|
||||
"branch": null,
|
||||
"sequence": 1,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 22,
|
||||
"id": "9f12bd89-308a-4e94-9cc3-df4822dea011",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S02",
|
||||
"phase": 1,
|
||||
"title": "An\u00e1lise preliminar",
|
||||
"branch": null,
|
||||
"sequence": 2,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 23,
|
||||
"id": "369a3135-119c-42d2-83d2-6b5e3ff9e659",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S03",
|
||||
"phase": 1,
|
||||
"title": "Decis\u00e3o sobre a viabilidade",
|
||||
"branch": null,
|
||||
"sequence": 3,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 24,
|
||||
"id": "0480e4d9-2265-4921-8338-83a0f728d49f",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S04",
|
||||
"phase": 1,
|
||||
"title": "Estruturar o investimento",
|
||||
"branch": null,
|
||||
"sequence": 4,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 25,
|
||||
"id": "882503f7-e30d-4c8d-b7ca-fb157bda2dc7",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S05",
|
||||
"phase": 2,
|
||||
"title": "Constitui\u00e7\u00e3o e registo",
|
||||
"branch": null,
|
||||
"sequence": 5,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 26,
|
||||
"id": "c3c87f28-f26a-42d0-80e9-74b412ae2ca5",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S06",
|
||||
"phase": 2,
|
||||
"title": "Preparar o dossier",
|
||||
"branch": null,
|
||||
"sequence": 6,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 27,
|
||||
"id": "57865d31-78f3-4961-a065-49efd4ff15e8",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S07",
|
||||
"phase": 2,
|
||||
"title": "Submeter quando aplic\u00e1vel",
|
||||
"branch": null,
|
||||
"sequence": 7,
|
||||
"dependencies": {
|
||||
"submit": [
|
||||
"S06"
|
||||
]
|
||||
},
|
||||
"source_paragraph": 28,
|
||||
"id": "18dd4fa9-c309-4031-88eb-f4c27ab69e47",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": {
|
||||
"submit": [
|
||||
"S06"
|
||||
]
|
||||
},
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S08",
|
||||
"phase": 3,
|
||||
"title": "Licenciamento pr\u00e9vio",
|
||||
"branch": null,
|
||||
"sequence": 8,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 29,
|
||||
"id": "65d81f1c-da8d-4f2b-af0d-a6a049cbe54b",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S09",
|
||||
"phase": 3,
|
||||
"title": "Necessidade de terra",
|
||||
"branch": null,
|
||||
"sequence": 9,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 30,
|
||||
"id": "b1a99d72-5ca6-4cb1-8a0a-7e35f1890392",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S10",
|
||||
"phase": 3,
|
||||
"title": "Regulariza\u00e7\u00e3o da terra",
|
||||
"branch": "land_needed",
|
||||
"sequence": 10,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 31,
|
||||
"id": "162637cf-d578-4a75-87b9-369996834843",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "undetermined",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S11",
|
||||
"phase": 3,
|
||||
"title": "Confirmar regulariza\u00e7\u00e3o aplic\u00e1vel",
|
||||
"branch": null,
|
||||
"sequence": 11,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 32,
|
||||
"id": "2c7d86f2-7426-4c6c-8ed4-cae8b6e68c99",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S12",
|
||||
"phase": 4,
|
||||
"title": "Necessidade e estrutura de financiamento",
|
||||
"branch": null,
|
||||
"sequence": 12,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 33,
|
||||
"id": "7bdb6b9b-324d-49e2-a133-5865b423c830",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S13",
|
||||
"phase": 4,
|
||||
"title": "Dilig\u00eancia e negocia\u00e7\u00e3o financeira",
|
||||
"branch": "external_funding",
|
||||
"sequence": 13,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 34,
|
||||
"id": "60bdbf70-8bbb-4771-abda-4f82b3f3e53e",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "undetermined",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S14",
|
||||
"phase": 4,
|
||||
"title": "Decis\u00e3o do financiador",
|
||||
"branch": "external_funding",
|
||||
"sequence": 14,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 35,
|
||||
"id": "69a00105-ed81-4679-a0de-536b995d6b3e",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "undetermined",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S15",
|
||||
"phase": 4,
|
||||
"title": "Fecho e desembolso",
|
||||
"branch": "external_funding",
|
||||
"sequence": 15,
|
||||
"dependencies": {
|
||||
"execute": [
|
||||
"S14"
|
||||
]
|
||||
},
|
||||
"source_paragraph": 36,
|
||||
"id": "4b70d527-bdfd-4792-9fd9-a3cc7c397f28",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "undetermined",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": {
|
||||
"execute": [
|
||||
"S14"
|
||||
]
|
||||
},
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S16",
|
||||
"phase": 5,
|
||||
"title": "Implementa\u00e7\u00e3o",
|
||||
"branch": null,
|
||||
"sequence": 16,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 37,
|
||||
"id": "9c838949-2537-4d81-b848-adf20be1b789",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S17",
|
||||
"phase": 5,
|
||||
"title": "Inspec\u00e7\u00f5es, testes e licen\u00e7a de opera\u00e7\u00e3o",
|
||||
"branch": null,
|
||||
"sequence": 17,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 38,
|
||||
"id": "8864abd3-fc9a-40f8-ad5f-22a84d87e10a",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": true
|
||||
},
|
||||
{
|
||||
"code": "S18",
|
||||
"phase": 5,
|
||||
"title": "In\u00edcio de opera\u00e7\u00e3o",
|
||||
"branch": null,
|
||||
"sequence": 18,
|
||||
"dependencies": {
|
||||
"operate": [
|
||||
"S17"
|
||||
]
|
||||
},
|
||||
"source_paragraph": 39,
|
||||
"id": "dc259b0e-2c14-422d-8309-dbd817f17ca4",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": {
|
||||
"operate": [
|
||||
"S17"
|
||||
]
|
||||
},
|
||||
"context_review_required": false
|
||||
},
|
||||
{
|
||||
"code": "S19",
|
||||
"phase": 5,
|
||||
"title": "Acompanhamento recorrente",
|
||||
"branch": null,
|
||||
"sequence": 19,
|
||||
"dependencies": [],
|
||||
"source_paragraph": 40,
|
||||
"id": "22afbc5a-daec-473b-bd69-d90605d6d604",
|
||||
"version": 1,
|
||||
"state": "not_started",
|
||||
"saved_state": "not_started",
|
||||
"applicability": "applicable",
|
||||
"reason": "",
|
||||
"detail": {
|
||||
"state": "not_started"
|
||||
},
|
||||
"preparation_allowed": true,
|
||||
"action_gaps": [],
|
||||
"context_review_required": false
|
||||
}
|
||||
],
|
||||
"facts": {
|
||||
"land_needed": "unknown",
|
||||
"external_funding": "unknown",
|
||||
"viability": "unknown"
|
||||
},
|
||||
"fact_reasons": [],
|
||||
"context_opportunity": null,
|
||||
"opportunity_options": [],
|
||||
"fintech": null,
|
||||
"canWrite": true,
|
||||
"canReview": false,
|
||||
"next": {
|
||||
"code": "S01",
|
||||
"title": "Sistematizar a ideia",
|
||||
"action": "Preparar",
|
||||
"href": "#S01"
|
||||
},
|
||||
"investment_context": {
|
||||
"opportunity_choices": [],
|
||||
"preparation_documents": [],
|
||||
"preparation_snapshot": null,
|
||||
"revision": 1,
|
||||
"selection": {
|
||||
"opportunity": {
|
||||
"key": "opportunity:4",
|
||||
"kind": "opportunity",
|
||||
"id": "4",
|
||||
"title": "Synthetic acquisition acceptance",
|
||||
"version": 5,
|
||||
"href": "\/minhas-oportunidades\/ofertas\/4",
|
||||
"fields": {
|
||||
"project_title": "Synthetic acquisition acceptance",
|
||||
"project_summary": "Existing company description to reuse once in investment preparation.",
|
||||
"province": "Synthetic province",
|
||||
"province_id": "22fd440c-f744-4647-86a6-348c01c194d7",
|
||||
"location": "Synthetic province"
|
||||
},
|
||||
"documents": [],
|
||||
"visibility": "private",
|
||||
"state": "approved",
|
||||
"sha256": "b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"project_title": "Synthetic acquisition acceptance",
|
||||
"investment_regime": "general",
|
||||
"special_zone_id": "",
|
||||
"currency": "MZN",
|
||||
"project_summary": "Existing company description to reuse once in investment preparation.",
|
||||
"province": "Synthetic province",
|
||||
"province_id": "22fd440c-f744-4647-86a6-348c01c194d7",
|
||||
"location": "Synthetic province"
|
||||
},
|
||||
"provenance": {
|
||||
"project_title": {
|
||||
"kind": "dossier",
|
||||
"id": "f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"version": 2
|
||||
},
|
||||
"currency": {
|
||||
"kind": "process_declaration",
|
||||
"actor_id": 8,
|
||||
"context_revision": 1
|
||||
},
|
||||
"project_summary": {
|
||||
"key": "opportunity:4",
|
||||
"kind": "opportunity",
|
||||
"id": "4",
|
||||
"title": "Synthetic acquisition acceptance",
|
||||
"version": 5,
|
||||
"href": "\/minhas-oportunidades\/ofertas\/4",
|
||||
"documents": [],
|
||||
"visibility": "private",
|
||||
"state": "approved",
|
||||
"sha256": "b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e"
|
||||
},
|
||||
"province": {
|
||||
"key": "opportunity:4",
|
||||
"kind": "opportunity",
|
||||
"id": "4",
|
||||
"title": "Synthetic acquisition acceptance",
|
||||
"version": 5,
|
||||
"href": "\/minhas-oportunidades\/ofertas\/4",
|
||||
"documents": [],
|
||||
"visibility": "private",
|
||||
"state": "approved",
|
||||
"sha256": "b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e"
|
||||
},
|
||||
"province_id": {
|
||||
"key": "opportunity:4",
|
||||
"kind": "opportunity",
|
||||
"id": "4",
|
||||
"title": "Synthetic acquisition acceptance",
|
||||
"version": 5,
|
||||
"href": "\/minhas-oportunidades\/ofertas\/4",
|
||||
"documents": [],
|
||||
"visibility": "private",
|
||||
"state": "approved",
|
||||
"sha256": "b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e"
|
||||
},
|
||||
"location": {
|
||||
"key": "opportunity:4",
|
||||
"kind": "opportunity",
|
||||
"id": "4",
|
||||
"title": "Synthetic acquisition acceptance",
|
||||
"version": 5,
|
||||
"href": "\/minhas-oportunidades\/ofertas\/4",
|
||||
"documents": [],
|
||||
"visibility": "private",
|
||||
"state": "approved",
|
||||
"sha256": "b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e"
|
||||
}
|
||||
},
|
||||
"review_steps": {
|
||||
"S02": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S03": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S04": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S08": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S12": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S13": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S14": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S15": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
},
|
||||
"S17": {
|
||||
"revision": 1,
|
||||
"reason": "O contexto mudou; reveja os pressupostos e a evid\u00eancia aplic\u00e1vel."
|
||||
}
|
||||
},
|
||||
"dependency_hash": "1311d8539ea6a08237003a36712145618834461dd63b510658b5419e95af46dd",
|
||||
"updated_at": "2026-11-14T16:35:56+00:00",
|
||||
"updated_by": 8,
|
||||
"requirements_assessment": {
|
||||
"version": "context-requirements-20261023",
|
||||
"requirements": [],
|
||||
"fiscal_notice": "Localiza\u00e7\u00e3o, corredor, cluster ou projecto \u00e2ncora n\u00e3o activam benef\u00edcios fiscais. O simulador mant\u00e9m as condi\u00e7\u00f5es, fonte, per\u00edodo e \u00e2mbito pr\u00f3prios; condi\u00e7\u00f5es ausentes permanecem indeterminadas."
|
||||
},
|
||||
"complements": []
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"id": "a18f1c7a-73bd-41d1-a28b-6c19b1bf3fb8",
|
||||
"source_key": "3b31db9e28c3d4b33f05812d6292408541b24e06f720861cbea1350cd9b0b1c2",
|
||||
"draft_id": "38f76a31-44ea-4815-b523-d99046475518",
|
||||
"draft_version": 1,
|
||||
"snapshot_sha256": "aa09f1d3ea78bbc8eccd5336548bd39466946799030e5459fe1b42e89587d87b",
|
||||
"financial_snapshot": "{\"version\":1,\"data\":{\"budgetPeriod\":\"Mensal\",\"loan\":{\"principal\":\"100000\",\"rate\":\"0\",\"months\":\"24\",\"rateType\":\"nominal\",\"periodMonths\":\"1\",\"gracePeriods\":\"0\",\"upfrontFee\":\"0\",\"recurringFee\":\"0\"},\"breakEven\":{\"fixed\":\"3000\",\"price\":\"100\",\"variable\":\"50\"},\"funding\":{\"total\":\"100000\",\"secured\":\"30000\"},\"incomes\":[{\"label\":\"Synthetic scenario revenue\",\"amount\":\"10000\"}],\"expenses\":[{\"label\":\"Declared rent at synthetic province\",\"amount\":\"2000\"},{\"label\":\"Declared transport at synthetic province\",\"amount\":\"1000\"}]}}",
|
||||
"context_opportunity": null,
|
||||
"currency": "MZN",
|
||||
"engine": "financial-calculators:v1",
|
||||
"kind": "local_calculation_inputs",
|
||||
"context_state": "current",
|
||||
"context_revision": 1,
|
||||
"context_dependency_hash": "1311d8539ea6a08237003a36712145618834461dd63b510658b5419e95af46dd",
|
||||
"recorded_at": "2026-11-14T16:35:59+00:00"
|
||||
}
|
||||
],
|
||||
"requests": [],
|
||||
"documents": [],
|
||||
"fiscal_reports": [],
|
||||
"land_report": null,
|
||||
"preparation_report": null,
|
||||
"company_reports": [],
|
||||
"zone_contexts": [],
|
||||
"sector_contexts": [],
|
||||
"sector_references": [],
|
||||
"content_sources": [],
|
||||
"tasks": [],
|
||||
"services": [],
|
||||
"tools": {
|
||||
"funding": "\/fundos?kind=institution&location_mode=physical&context=f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"simulator": "\/simulador-fiscal?context=f71a2833-c182-43ba-862d-234e4b64739b&draft=38f76a31-44ea-4815-b523-d99046475518",
|
||||
"preparation": null,
|
||||
"company": "\/investimentos\/f71a2833-c182-43ba-862d-234e4b64739b\/forma-juridica",
|
||||
"desk": "\/balcao-virtual\/dossiers\/f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"guidance": "\/balcao-virtual\/dossiers\/f71a2833-c182-43ba-862d-234e4b64739b\/orientacao",
|
||||
"library": "\/documentos?context=f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"opportunities": "\/oportunidades?context=f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"territory": "\/zonas?context=f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"partners": "\/parcerias\/painel?context=f71a2833-c182-43ba-862d-234e4b64739b",
|
||||
"land": "\/investidor\/terra-duat\/novo?context=f71a2833-c182-43ba-862d-234e4b64739b"
|
||||
}
|
||||
},
|
||||
"saved": {
|
||||
"project_title": "Synthetic acquisition acceptance",
|
||||
"currency": "MZN",
|
||||
"project_summary": "Existing company description to reuse once in investment preparation.",
|
||||
"province_id": "22fd440c-f744-4647-86a6-348c01c194d7",
|
||||
"journey_type": "investor",
|
||||
"purpose": "simulate_only",
|
||||
"context_applicability_snapshot": "{\"version\":1,\"context_revision\":1,\"consumer_opportunity\":null,\"portfolio_origin\":null,\"zone\":null,\"provenance\":{\"project_title\":{\"kind\":\"dossier\",\"id\":\"f71a2833-c182-43ba-862d-234e4b64739b\",\"version\":2},\"currency\":{\"kind\":\"process_declaration\",\"actor_id\":8,\"context_revision\":1},\"project_summary\":{\"key\":\"opportunity:4\",\"kind\":\"opportunity\",\"id\":\"4\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5,\"href\":\"\\\/minhas-oportunidades\\\/ofertas\\\/4\",\"documents\":[],\"visibility\":\"private\",\"state\":\"approved\",\"sha256\":\"b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e\"},\"province\":{\"key\":\"opportunity:4\",\"kind\":\"opportunity\",\"id\":\"4\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5,\"href\":\"\\\/minhas-oportunidades\\\/ofertas\\\/4\",\"documents\":[],\"visibility\":\"private\",\"state\":\"approved\",\"sha256\":\"b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e\"},\"province_id\":{\"key\":\"opportunity:4\",\"kind\":\"opportunity\",\"id\":\"4\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5,\"href\":\"\\\/minhas-oportunidades\\\/ofertas\\\/4\",\"documents\":[],\"visibility\":\"private\",\"state\":\"approved\",\"sha256\":\"b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e\"},\"location\":{\"key\":\"opportunity:4\",\"kind\":\"opportunity\",\"id\":\"4\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5,\"href\":\"\\\/minhas-oportunidades\\\/ofertas\\\/4\",\"documents\":[],\"visibility\":\"private\",\"state\":\"approved\",\"sha256\":\"b175039f4c49e40278436403a6aea19f218e03c3d9cb602583f0394caecd1f4e\"}},\"opportunity_choices\":[],\"complements\":[],\"requirements_assessment\":{\"version\":\"context-requirements-20261023\",\"requirements\":[],\"fiscal_notice\":\"Localiza\u00e7\u00e3o, corredor, cluster ou projecto \u00e2ncora n\u00e3o activam benef\u00edcios fiscais. O simulador mant\u00e9m as condi\u00e7\u00f5es, fonte, per\u00edodo e \u00e2mbito pr\u00f3prios; condi\u00e7\u00f5es ausentes permanecem indeterminadas.\"},\"path\":{\"definition_id\":\"61000919-0000-4000-8000-000000000001\",\"definition_version\":1,\"source_sha256\":\"aa13c53138ec15cd808de43d9dc2fc6808da86943d09d3352c9dd0944bdae364\",\"steps\":[{\"code\":\"S10\",\"title\":\"Regulariza\u00e7\u00e3o da terra\",\"state\":\"not_started\",\"applicability\":\"undetermined\",\"reason\":\"\",\"context_review_required\":false},{\"code\":\"S13\",\"title\":\"Dilig\u00eancia e negocia\u00e7\u00e3o financeira\",\"state\":\"not_started\",\"applicability\":\"undetermined\",\"reason\":\"\",\"context_review_required\":true},{\"code\":\"S14\",\"title\":\"Decis\u00e3o do financiador\",\"state\":\"not_started\",\"applicability\":\"undetermined\",\"reason\":\"\",\"context_review_required\":true},{\"code\":\"S15\",\"title\":\"Fecho e desembolso\",\"state\":\"not_started\",\"applicability\":\"undetermined\",\"reason\":\"\",\"context_review_required\":true}]},\"land_preparation\":{\"need\":\"unknown\",\"reason\":\"\"},\"field_provenance\":{\"project_title\":{\"kind\":\"dossier\",\"version\":2},\"currency\":{\"kind\":\"process_declaration\",\"context_revision\":1},\"project_summary\":{\"kind\":\"opportunity\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5},\"province\":{\"kind\":\"opportunity\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5},\"province_id\":{\"kind\":\"opportunity\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5},\"location\":{\"kind\":\"opportunity\",\"title\":\"Synthetic acquisition acceptance\",\"version\":5}},\"preparation\":null,\"fields\":{\"investment_regime\":\"general\",\"currency\":\"MZN\"},\"notice\":\"Refer\u00eancias e declara\u00e7\u00f5es para prepara\u00e7\u00e3o; sem reconhecimento autom\u00e1tico de elegibilidade ou benef\u00edcio.\"}",
|
||||
"financial_tools_snapshot": "{\"version\":1,\"data\":{\"budgetPeriod\":\"Mensal\",\"loan\":{\"principal\":\"100000\",\"rate\":\"0\",\"months\":\"24\",\"rateType\":\"nominal\",\"periodMonths\":\"1\",\"gracePeriods\":\"0\",\"upfrontFee\":\"0\",\"recurringFee\":\"0\"},\"breakEven\":{\"fixed\":\"3000\",\"price\":\"100\",\"variable\":\"50\"},\"funding\":{\"total\":\"100000\",\"secured\":\"30000\"},\"incomes\":[{\"label\":\"Synthetic scenario revenue\",\"amount\":\"10000\"}],\"expenses\":[{\"label\":\"Declared rent at synthetic province\",\"amount\":\"2000\"},{\"label\":\"Declared transport at synthetic province\",\"amount\":\"1000\"}]}}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"scope": "Synthetic investor-declared costs at the selected location; no automatic profitability or fiscal entitlement",
|
||||
"province_id": "22fd440c-f744-4647-86a6-348c01c194d7",
|
||||
"budget": {
|
||||
"incomeTotal": "10000.00",
|
||||
"expenseTotal": "3000.00",
|
||||
"operatingBalance": "7000.00",
|
||||
"status": "estimated"
|
||||
},
|
||||
"breakEven": {
|
||||
"contributionPerUnit": "50.00",
|
||||
"breakEvenUnits": "60",
|
||||
"breakEvenRevenue": "6000.00",
|
||||
"status": "estimated"
|
||||
},
|
||||
"alternative": {
|
||||
"incomeTotal": "10000.00",
|
||||
"expenseTotal": "8000.00",
|
||||
"operatingBalance": "2000.00",
|
||||
"status": "estimated"
|
||||
},
|
||||
"source_offer": "opportunity:4",
|
||||
"case_id": "f71a2833-c182-43ba-862d-234e4b64739b"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"tool":"phpstan","result":"passed","errors":0}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"base": "ed2fd86f",
|
||||
"phase5_preserved": "528a2597",
|
||||
"environment": {
|
||||
"database": "offers_phase1_20261114_test",
|
||||
"host": "127.0.0.1",
|
||||
"port": 55441,
|
||||
"fixtures": "synthetic only"
|
||||
},
|
||||
"cause": "Transformation: description persisted, project_summary read only canonical/summary. Existing identifiers and permissions preserved.",
|
||||
"php_suites": [
|
||||
{
|
||||
"name": "Tests\\Feature\\OfferInvestmentAcceptanceAuditTest",
|
||||
"tests": 4,
|
||||
"assertions": 62,
|
||||
"errors": 0,
|
||||
"failures": 0
|
||||
},
|
||||
{
|
||||
"name": "Tests\\Feature\\PrivateOfferConfidentialityTest",
|
||||
"tests": 11,
|
||||
"assertions": 161,
|
||||
"errors": 0,
|
||||
"failures": 0
|
||||
},
|
||||
{
|
||||
"name": "Tests\\Feature\\InvestorPathTest",
|
||||
"tests": 4,
|
||||
"assertions": 192,
|
||||
"errors": 0,
|
||||
"failures": 0
|
||||
},
|
||||
{
|
||||
"name": "Tests\\Feature\\OpportunityManagementTest",
|
||||
"tests": 3,
|
||||
"assertions": 46,
|
||||
"errors": 0,
|
||||
"failures": 0
|
||||
}
|
||||
],
|
||||
"php_totals": {
|
||||
"tests": 22,
|
||||
"assertions": 461,
|
||||
"errors": 0,
|
||||
"failures": 0
|
||||
},
|
||||
"frontend_regression": {
|
||||
"tests": 41,
|
||||
"passed": 41
|
||||
},
|
||||
"integrated_frontend_report": {
|
||||
"tests": 1,
|
||||
"passed": 1,
|
||||
"artifacts": [
|
||||
"private-integrated-report.html",
|
||||
"private-integrated-report.docx",
|
||||
"location-analysis.json"
|
||||
]
|
||||
},
|
||||
"static": {
|
||||
"phpstan": "full analysis passed, zero errors",
|
||||
"types": "tsc --noEmit passed",
|
||||
"pint": "affected PHP passed",
|
||||
"frontend_lint_format": "new mjs passed",
|
||||
"build": "unchanged frontend product sources: accepted QA build reused; no rebuild",
|
||||
"git_diff_check": "passed"
|
||||
},
|
||||
"field_policy": "Common authorised title/description/administrative references only; no private_offer financial/contact/price/proof transfer. NDA grant does not grant private context access.",
|
||||
"publication": "default consumer closed; injected synthetic consumer guarded by actual isolated database",
|
||||
"matrix": "RF-MN-003 only; other rows unchanged",
|
||||
"pending": [
|
||||
"real NDA and legal decisions",
|
||||
"effective public writer/activation",
|
||||
"desktop/mobile visual acceptance",
|
||||
"image processing derivatives",
|
||||
"real email delivery"
|
||||
],
|
||||
"installations": false,
|
||||
"operational_migrations": false,
|
||||
"mne_changes": false,
|
||||
"accepted_59_780_repeated": false,
|
||||
"source_sha256": {
|
||||
"app/Services/InvestorContext.php": "cdae33a63d796b7e9862c949711b2c83f2482990d3d77806591a594214313fff",
|
||||
"tests/Feature/OfferInvestmentAcceptanceAuditTest.php": "d91112cfccc08f45afb9a6c317ead1a5d03f97221a606ad123ca590a4b401b5b",
|
||||
"tests/Feature/PrivateOfferConfidentialityTest.php": "5beb77c609baf3c78768bfbe0906b5d5b8701cd3266ca9ead8c3198a30fb5bc6",
|
||||
"tests/frontend/offer-investment-continuity.test.mjs": "b37acc9d1dbf900aa8a5555666142bc088e73245b60c5aa8f6d0f7baa1a7655a"
|
||||
},
|
||||
"evidence_sha256": {
|
||||
"focused.log": "0943af2982f42d60848e480008f0e045ebda4da944d16408be0ae944b94428bc",
|
||||
"focused.xml": "e57322452c4e009e40dc0b3d5bbfa1da1c3add6afb74a8006143efbc904ee2cb",
|
||||
"frontend-regression.log": "ddbeb6fe3365cb70fd4c0e5eafaf4d7919ead5910415f6aed9167acfb43e315d",
|
||||
"integrated-report-input.json": "60d8d8f46821da56f47183f57e6869cb64160cf5f5c2fdd03eab2acdd24c5014",
|
||||
"location-analysis.json": "83729ad333ace4507e3827b97f84905327083682fb83eecbb05847e4f59a5dff",
|
||||
"phpstan.log": "c10f74fb840de989bba7e9c2dfee7f8419b5d2ac0cc0daf18a4a187f6530aa43",
|
||||
"private-integrated-report.docx": "df76a52f6f1d1ee990702aefa8b4c3d3604de2305be840be88c77fc3dbdf546e",
|
||||
"private-integrated-report.html": "3221f94d4df400f4edf5e9b262dbf4a13d6f80d6b6fd58b6f8a09ed384141367",
|
||||
"regression.log": "13480527b87e724e7c9687131ee0d0f611f1c9a6ac719612440dcfac8ccfd543",
|
||||
"regression.xml": "2785f34fb05e01b945fb1b7a61ba3e9d6c061d5c7d3cf09b189188b58a1b2e37",
|
||||
"report-test.log": "71d866136fca227770e0c73347750f6597224c9420672c6ab9dfcebd9f80c60d",
|
||||
"types.log": "b59cdb3ff539a29c8ae6c37772ecc1a93a5266c49959d408f16b17487b7ac755"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"tool":"phpunit","result":"passed","tests":22,"passed":22,"assertions":461,"duration_ms":135835}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites>
|
||||
<testsuite name="CLI Arguments" tests="22" assertions="461" errors="0" failures="0" skipped="0" time="135.825495">
|
||||
<testsuite name="Tests\Feature\OfferInvestmentAcceptanceAuditTest" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" tests="4" assertions="62" errors="0" failures="0" skipped="0" time="28.758459">
|
||||
<testcase name="test_saved_offer_reuses_its_description_in_the_existing_investment_context" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="41" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="8" time="6.650455"/>
|
||||
<testcase name="test_native_context_options_do_not_disclose_another_owners_private_offer" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="55" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="7" time="2.800221"/>
|
||||
<testcase name="test_private_description_is_not_truncated_and_does_not_replace_an_existing_summary" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="67" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="5" time="2.095158"/>
|
||||
<testcase name="test_approved_offer_continues_in_same_dossier_location_analysis_saved_result_and_report_input" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OfferInvestmentAcceptanceAuditTest.php" line="80" class="Tests\Feature\OfferInvestmentAcceptanceAuditTest" classname="Tests.Feature.OfferInvestmentAcceptanceAuditTest" assertions="42" time="17.212625"/>
|
||||
</testsuite>
|
||||
<testsuite name="Tests\Feature\PrivateOfferConfidentialityTest" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" tests="11" assertions="161" errors="0" failures="0" skipped="0" time="100.821485">
|
||||
<testcase name="test_confirmed_specific_fields_save_recover_and_bind_the_editorial_version" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="109" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="9" time="7.598624"/>
|
||||
<testcase name="test_property_fields_are_optional_and_categories_and_unknown_fields_are_enforced" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="120" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="7" time="5.093540"/>
|
||||
<testcase name="test_six_photos_limit_includes_restoration_and_alternate_routes_and_cleans_failed_uploads" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="131" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="15" time="10.676252"/>
|
||||
<testcase name="test_upload_validation_and_quarantine_do_not_grant_publication_or_read_access" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="173" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="9" time="7.387037"/>
|
||||
<testcase name="test_proof_bytes_are_encrypted_and_only_a_scoped_internal_validator_can_read_them" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="187" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="14" time="5.185126"/>
|
||||
<testcase name="test_nda_absence_keeps_access_closed_and_acceptance_alone_never_grants_access" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="209" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="13" time="8.880152"/>
|
||||
<testcase name="test_both_conditions_exact_versions_revocation_and_audit_are_required_on_every_read" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="227" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="18" time="9.641431"/>
|
||||
<testcase name="test_nda_version_changes_withdrawal_session_revocation_and_private_revision_close_access" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="259" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="35" time="21.375187"/>
|
||||
<testcase name="test_nda_integrity_and_authority_loss_are_rechecked_without_exposing_the_company" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="299" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="14" time="9.466717"/>
|
||||
<testcase name="test_compiled_confidential_page_has_no_company_data_before_both_conditions" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="323" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="11" time="6.025318"/>
|
||||
<testcase name="test_photo_metadata_and_original_are_bound_to_the_editorial_fingerprint" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\PrivateOfferConfidentialityTest.php" line="337" class="Tests\Feature\PrivateOfferConfidentialityTest" classname="Tests.Feature.PrivateOfferConfidentialityTest" assertions="16" time="9.492101"/>
|
||||
</testsuite>
|
||||
<testsuite name="Tests\Feature\InvestorPathTest" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\InvestorPathTest.php" tests="4" assertions="192" errors="0" failures="0" skipped="0" time="4.861569">
|
||||
<testcase name="test_existing_empty_desk_draft_can_explicitly_reuse_current_contextual_summary_without_overwriting_data" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\InvestorPathTest.php" line="53" class="Tests\Feature\InvestorPathTest" classname="Tests.Feature.InvestorPathTest" assertions="29" time="0.792451"/>
|
||||
<testcase name="test_two_independent_investments_recover_tool_evidence_support_and_internal_tasks_without_external_calls" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\InvestorPathTest.php" line="83" class="Tests\Feature\InvestorPathTest" classname="Tests.Feature.InvestorPathTest" assertions="107" time="2.252891"/>
|
||||
<testcase name="test_permissions_stale_versions_and_real_dependencies_do_not_block_independent_preparation" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\InvestorPathTest.php" line="131" class="Tests\Feature\InvestorPathTest" classname="Tests.Feature.InvestorPathTest" assertions="35" time="0.995810"/>
|
||||
<testcase name="test_applicability_changes_preserve_old_branch_evidence_and_legacy_workflow" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\InvestorPathTest.php" line="154" class="Tests\Feature\InvestorPathTest" classname="Tests.Feature.InvestorPathTest" assertions="21" time="0.820416"/>
|
||||
</testsuite>
|
||||
<testsuite name="Tests\Feature\OpportunityManagementTest" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OpportunityManagementTest.php" tests="3" assertions="46" errors="0" failures="0" skipped="0" time="1.383981">
|
||||
<testcase name="test_installation_requires_management_permission_and_current_proofs" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OpportunityManagementTest.php" line="20" class="Tests\Feature\OpportunityManagementTest" classname="Tests.Feature.OpportunityManagementTest" assertions="6" time="0.332180"/>
|
||||
<testcase name="test_incomplete_draft_recovers_and_soft_delete_restore_preserves_identity" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OpportunityManagementTest.php" line="32" class="Tests\Feature\OpportunityManagementTest" classname="Tests.Feature.OpportunityManagementTest" assertions="21" time="0.720887"/>
|
||||
<testcase name="test_closed_version_edits_do_not_change_public_values_classification_or_relations" file="C:\laragon\www\portal-investidor-qa-editorial-20261113\tests\Feature\OpportunityManagementTest.php" line="60" class="Tests\Feature\OpportunityManagementTest" classname="Tests.Feature.OpportunityManagementTest" assertions="19" time="0.330915"/>
|
||||
</testsuite>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
@@ -0,0 +1,9 @@
|
||||
✔ persisted synthetic offer crosses the dossier boundary into location assumptions, calculation and private report (130.377542ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 596.953708
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
> types:check
|
||||
> tsc --noEmit
|
||||
@@ -5,17 +5,21 @@ namespace Tests\Feature;
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\User;
|
||||
use App\Services\InvestorContext;
|
||||
use App\Services\OfferPublicCatalogue;
|
||||
use App\Services\OpportunityManagement;
|
||||
use App\Services\PortalProfileAdministration;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
/** Acceptance audit only: no change to the product's contracts or access rules. */
|
||||
/** Synthetic acceptance regression for the existing investment-context boundary. */
|
||||
final class OfferInvestmentAcceptanceAuditTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function prepare(): array
|
||||
private function prepare(array $location = []): array
|
||||
{
|
||||
$this->withoutVite();
|
||||
config(['portal.profile_access_enabled' => true]);
|
||||
@@ -28,7 +32,7 @@ final class OfferInvestmentAcceptanceAuditTest extends TestCase
|
||||
'title' => 'Synthetic acquisition acceptance', 'description' => 'Existing company description to reuse once in investment preparation.',
|
||||
'private_offer' => ['price_mode' => 'amount', 'amount' => '120000.00', 'currency' => 'MZN',
|
||||
'business' => ['revenue' => '60000.00', 'cash_flow' => '20000.00', 'currency' => 'MZN']],
|
||||
'reason' => 'Synthetic final acceptance integration audit only.'];
|
||||
'reason' => 'Synthetic final acceptance integration audit only.', ...$location];
|
||||
$record = $this->actingAs($owner)->postJson('/minhas-oportunidades/ofertas/rascunhos', $payload)->assertCreated()->json('record');
|
||||
|
||||
return [$owner, $record, $payload];
|
||||
@@ -42,6 +46,10 @@ final class OfferInvestmentAcceptanceAuditTest extends TestCase
|
||||
$this->assertSame($payload['title'], $reference['fields']['project_title']);
|
||||
$this->assertSame($payload['description'], $reference['fields']['project_summary'] ?? null,
|
||||
'Write once/read always: the existing description must not disappear at the investment-context boundary.');
|
||||
$this->assertSame('/minhas-oportunidades/ofertas/'.$record['id'], $reference['href']);
|
||||
$this->assertArrayNotHasKey('private_offer', $reference);
|
||||
$this->assertArrayNotHasKey('revenue', $reference['fields']);
|
||||
$this->assertArrayNotHasKey('currency', $reference['fields']);
|
||||
}
|
||||
|
||||
public function test_native_context_options_do_not_disclose_another_owners_private_offer(): void
|
||||
@@ -55,4 +63,113 @@ final class OfferInvestmentAcceptanceAuditTest extends TestCase
|
||||
$this->assertNotContains('opportunity:'.$record['id'], array_column(app(InvestorContext::class)->options(), 'key'));
|
||||
$this->getJson('/minhas-oportunidades/ofertas/'.$record['id'])->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_private_description_is_not_truncated_and_does_not_replace_an_existing_summary(): void
|
||||
{
|
||||
[$owner, $record, $payload] = $this->prepare();
|
||||
$description = str_repeat('Synthetic complete description. ', 400);
|
||||
$this->postJson('/minhas-oportunidades/ofertas/'.$record['id'].'/guardar', [
|
||||
'etag' => $record['etag'], 'description' => $description, 'reason' => $payload['reason'],
|
||||
])->assertOk();
|
||||
$this->assertSame(trim($description), app(InvestorContext::class)->reference('opportunity:'.$record['id'])['fields']['project_summary']);
|
||||
DB::table('opportunities')->where('id', $record['id'])->update(['summary' => 'Deliberate existing summary']);
|
||||
$this->assertSame('Deliberate existing summary', app(InvestorContext::class)->reference('opportunity:'.$record['id'])['fields']['project_summary']);
|
||||
$this->assertSame(trim($description), Opportunity::findOrFail($record['id'])->description);
|
||||
}
|
||||
|
||||
public function test_approved_offer_continues_in_same_dossier_location_analysis_saved_result_and_report_input(): void
|
||||
{
|
||||
Mail::fake();
|
||||
config(['editorial_workflow.enabled' => true, 'investor-path.enabled' => true, 'suitecrm.enabled' => false, 'investmoz_ai.enabled' => false]);
|
||||
$country = (string) Str::uuid();
|
||||
$province = (string) Str::uuid();
|
||||
foreach ([[$country, 'country', null], [$province, 'province', $country]] as [$id, $type, $parent]) {
|
||||
DB::table('geo_locations')->insert(['id' => $id, 'code' => 'SYNTHETIC-'.$id, 'name' => 'Synthetic '.$type,
|
||||
'location_type' => $type, 'parent_id' => $parent, 'data_classification' => 'public',
|
||||
// Same isolated reference contract as OpportunitySearchFixture; never an operational import.
|
||||
'source_system' => 'cartografia-workbook',
|
||||
'external_id' => '964a187ad32ea553b9e112cd159caa90b900050c42b7329594a75bcd798a96ff:'.$id]);
|
||||
}
|
||||
[$owner, $record, $payload] = $this->prepare(['province_id' => $province]);
|
||||
$contact = (string) Str::uuid();
|
||||
DB::table('contacts')->insert(['id' => $contact, 'full_name' => 'Synthetic private contact', 'source_system' => 'portal-user-contact', 'external_id' => 'user:'.$owner->id]);
|
||||
$url = '/minhas-oportunidades/ofertas/'.$record['id'];
|
||||
$this->postJson($url.'/guardar', ['etag' => $record['etag'], 'private_offer' => [...$payload['private_offer'], 'contact_id' => $contact], 'reason' => $payload['reason']])->assertOk();
|
||||
$this->postJson($url.'/submeter', ['etag' => app(OpportunityManagement::class)->etag(Opportunity::findOrFail($record['id'])), 'confirm' => true])->assertOk()->assertJsonPath('record.state', 'submitted');
|
||||
$reviewer = User::factory()->withTwoFactor()->create();
|
||||
app(PortalProfileAdministration::class)->insertGrant((string) Str::uuid(), User::factory()->create(), [
|
||||
'user_id' => $reviewer->id, 'role' => 'validator', 'responsibilities' => ['validation_coordinator'],
|
||||
'opportunity_types' => ['business_sale'], 'source' => 'synthetic-acceptance-audit',
|
||||
]);
|
||||
$this->actingAs($reviewer)->withSession(['portal.mfa_actor' => $reviewer->id, 'portal.mfa_at' => time(), 'portal.access_revision' => (int) $reviewer->fresh()->portal_access_revision]);
|
||||
foreach (['start_review', 'approve'] as $action) {
|
||||
$this->postJson($url.'/revisao', ['etag' => app(OpportunityManagement::class)->etag(Opportunity::findOrFail($record['id'])),
|
||||
'action' => $action, 'reason' => 'Synthetic independent review for investment continuity.'])->assertOk();
|
||||
}
|
||||
$offer = Opportunity::findOrFail($record['id']);
|
||||
$public = ['title' => 'Synthetic public acquisition', 'summary' => 'Approved synthetic public summary only.', 'category' => 'business_sale',
|
||||
'province_id' => $province, 'district_id' => null, 'price_mode' => 'negotiable', 'amount' => null, 'currency' => null];
|
||||
$hash = OfferPublicCatalogue::projectionHash($public);
|
||||
$context = $offer->workflow_context;
|
||||
$context['offer_publication'] = ['contract' => OfferPublicCatalogue::CONTRACT, 'qa_fixture' => true, 'revision' => 1, 'payload' => $public,
|
||||
'approval' => ['state' => 'approved', 'hash' => $hash, 'actor_id' => $reviewer->id, 'at' => now()->subMinutes(2)->toIso8601String()],
|
||||
'publication' => ['state' => 'published', 'hash' => $hash, 'actor_id' => User::factory()->create()->id, 'at' => now()->subMinute()->toIso8601String()]];
|
||||
$offer->forceFill(['workflow_context' => $context])->save();
|
||||
$this->getJson('/api/v1/offers')->assertOk()->assertJsonPath('results.total', 0);
|
||||
$this->app->instance(OfferPublicCatalogue::class, new OfferPublicCatalogue(true));
|
||||
$response = $this->getJson('/api/v1/offers/'.$offer->id)->assertOk()->assertJsonPath('data.summary', $public['summary']);
|
||||
$this->assertStringNotContainsString($payload['description'], $response->getContent());
|
||||
$this->assertStringNotContainsString('60000.00', $response->getContent());
|
||||
$this->assertNull($offer->fresh()->published_at);
|
||||
$this->actingAs($owner);
|
||||
$case = $this->postJson('/investimentos', ['title' => $payload['title'], 'client_key' => (string) Str::uuid(), 'reuse_consent' => true])->assertCreated()->json('id');
|
||||
$view = $this->getJson('/investimentos/'.$case.'/contexto')->assertOk()->json();
|
||||
$body = ['version' => $view['case']['version'], 'selection' => ['opportunity' => 'opportunity:'.$offer->id], 'declarations' => ['currency' => 'MZN']];
|
||||
$plan = $this->postJson('/investimentos/'.$case.'/contexto/previa', $body)->assertOk()->json();
|
||||
$this->assertSame($payload['description'], $plan['fields']['project_summary']);
|
||||
$this->postJson('/investimentos/'.$case.'/contexto/confirmar', [...$body, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
||||
$view = $this->getJson('/investimentos/'.$case.'/contexto')->assertOk()->json();
|
||||
$prefill = $view['prefill']['simulator'];
|
||||
$this->assertSame($province, $prefill['province_id']);
|
||||
$this->assertSame($payload['description'], $prefill['project_summary']);
|
||||
$this->assertStringNotContainsString('60000.00', json_encode($view, JSON_THROW_ON_ERROR));
|
||||
$financial = ['version' => 1, 'data' => ['budgetPeriod' => 'Mensal',
|
||||
'loan' => ['principal' => '100000', 'rate' => '0', 'months' => '24', 'rateType' => 'nominal', 'periodMonths' => '1', 'gracePeriods' => '0', 'upfrontFee' => '0', 'recurringFee' => '0'],
|
||||
'breakEven' => ['fixed' => '3000', 'price' => '100', 'variable' => '50'], 'funding' => ['total' => '100000', 'secured' => '30000'],
|
||||
'incomes' => [['label' => 'Synthetic scenario revenue', 'amount' => '10000']],
|
||||
'expenses' => [['label' => 'Declared rent at synthetic province', 'amount' => '2000'], ['label' => 'Declared transport at synthetic province', 'amount' => '1000']]]];
|
||||
$draft = $this->postJson('/api/v1/simulation-drafts', ['context' => $case, 'personal_snapshot' => [],
|
||||
'economic_snapshot' => [...array_intersect_key($prefill, array_flip(['purpose', 'journey_type', 'project_title', 'project_summary', 'currency', 'province_id', 'context_applicability_snapshot'])),
|
||||
'financial_tools_snapshot' => json_encode($financial, JSON_THROW_ON_ERROR)]],
|
||||
['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
|
||||
$saved = $this->getJson('/api/v1/simulation-drafts/'.$draft.'?context='.$case)->assertOk()->json('data.economic_snapshot');
|
||||
$this->assertSame($payload['description'], $saved['project_summary']);
|
||||
$path = $this->getJson('/investimentos/'.$case)->assertOk()->json();
|
||||
$this->postJson('/investimentos/'.$case.'/resultados', ['version' => $path['case']['version'], 'draft_id' => $draft, 'draft_version' => 1,
|
||||
'context_revision' => $path['investment_context']['revision'], 'context_reviewed' => true])->assertOk();
|
||||
auth()->logout();
|
||||
$this->actingAs(User::findOrFail($owner->id));
|
||||
$path = $this->getJson('/investimentos/'.$case)->assertOk()->json();
|
||||
$this->assertSame($draft, $path['results'][0]['draft_id']);
|
||||
$this->assertSame($payload['description'], $path['investment_context']['fields']['project_summary']);
|
||||
$this->assertSame(1, Opportunity::count());
|
||||
$this->assertSame(1, DB::table('desk_cases')->count());
|
||||
$this->assertStringNotContainsString('60000.00', json_encode($path, JSON_THROW_ON_ERROR));
|
||||
$output = base_path('outputs/offers-investment-continuity-20261114');
|
||||
if (! is_dir($output)) {
|
||||
mkdir($output, 0777, true);
|
||||
}
|
||||
file_put_contents($output.'/integrated-report-input.json', json_encode(['path' => $path, 'saved' => $saved], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
$body = ['version' => $path['case']['version'], 'selection' => ['opportunity' => 'opportunity:'.$offer->id],
|
||||
'declarations' => ['project_summary' => 'Deliberate investment-specific summary']];
|
||||
$plan = $this->postJson('/investimentos/'.$case.'/contexto/previa', $body)->assertOk()->json();
|
||||
$this->assertSame('Deliberate investment-specific summary', $plan['fields']['project_summary']);
|
||||
$this->postJson('/investimentos/'.$case.'/contexto/confirmar', [...$body, 'preview_hash' => $plan['preview_hash'], 'operation_key' => (string) Str::uuid(), 'confirm' => true])->assertOk();
|
||||
$view = $this->getJson('/investimentos/'.$case.'/contexto')->assertOk()->json();
|
||||
$this->postJson('/investimentos/'.$case.'/contexto/previa', ['version' => $view['case']['version'],
|
||||
'selection' => ['opportunity' => 'opportunity:'.$offer->id]])->assertOk()->assertJsonPath('fields.project_summary', 'Deliberate investment-specific summary');
|
||||
$this->assertSame($payload['description'], $offer->fresh()->description);
|
||||
$this->actingAs(User::factory()->create())->getJson('/investimentos/'.$case)->assertForbidden();
|
||||
$this->getJson('/api/v1/simulation-drafts/'.$draft.'?context='.$case)->assertNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Tests\Feature;
|
||||
use App\Jobs\ScanDocumentForMalware;
|
||||
use App\Models\Opportunity;
|
||||
use App\Models\User;
|
||||
use App\Services\InvestorContext;
|
||||
use App\Services\OpportunityManagement;
|
||||
use App\Services\PortalProfileAdministration;
|
||||
use App\Services\PrivateOfferConfidentiality;
|
||||
@@ -19,6 +20,7 @@ use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Tests\Support\CleanScannerFixture;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -238,6 +240,14 @@ final class PrivateOfferConfidentialityTest extends TestCase
|
||||
$data = $this->login($interested)->getJson($url.'/confidencial')->assertOk()->assertJsonPath('business.revenue', '789.01')->getContent();
|
||||
$this->assertStringNotContainsString('PRIVATE-CONTACT-CANARY', $data);
|
||||
$this->assertStringNotContainsString('PRIVATE-DESCRIPTION-CANARY', $data);
|
||||
// NDA permission is a bounded read, not permission to copy the private
|
||||
// Opportunity into another investment context or its downstream reports.
|
||||
try {
|
||||
app(InvestorContext::class)->reference('opportunity:'.$record->id);
|
||||
$this->fail('An NDA grant must not grant private investment-context access.');
|
||||
} catch (HttpException $error) {
|
||||
$this->assertSame(404, $error->getStatusCode());
|
||||
}
|
||||
$this->login($this->actor())->getJson($url.'/confidencial')->assertForbidden();
|
||||
$this->login($owner)->postJson($url.'/confidencialidade', $this->command($record, $nda, 'revoke', ['interested_user_id' => $interested->id]))->assertOk();
|
||||
$this->login($interested)->getJson($url.'/confidencial')->assertForbidden();
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import JSZip from 'jszip';
|
||||
import {
|
||||
calculateBudget,
|
||||
calculateBreakEven,
|
||||
restoreFinancialToolsSnapshot,
|
||||
} from '../../resources/js/lib/financial-calculators.ts';
|
||||
import { createInvestorDossierReport } from '../../resources/js/lib/investor-dossier-report.ts';
|
||||
import {
|
||||
createProjectDossierPrintHtml,
|
||||
generateProjectDossierDocx,
|
||||
} from '../../resources/js/lib/project-dossier-report.ts';
|
||||
|
||||
await test('persisted synthetic offer crosses the dossier boundary into location assumptions, calculation and private report', async () => {
|
||||
// Produced by the guarded PHP integration test, never by an operational export.
|
||||
const directory = new URL(
|
||||
'../../outputs/offers-investment-continuity-20261114/',
|
||||
import.meta.url,
|
||||
);
|
||||
const { path, saved } = JSON.parse(
|
||||
readFileSync(
|
||||
new URL('integrated-report-input.json', directory),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
assert.equal(path.case.title, 'Synthetic acquisition acceptance');
|
||||
assert.equal(
|
||||
path.investment_context.fields.project_summary,
|
||||
saved.project_summary,
|
||||
);
|
||||
assert.equal(path.investment_context.fields.province_id, saved.province_id);
|
||||
const data = restoreFinancialToolsSnapshot(
|
||||
path.results[0].financial_snapshot,
|
||||
);
|
||||
assert.ok(data);
|
||||
assert.deepEqual(
|
||||
data,
|
||||
restoreFinancialToolsSnapshot(saved.financial_tools_snapshot),
|
||||
);
|
||||
const budget = calculateBudget(
|
||||
data.incomes.map((row) => row.amount),
|
||||
data.expenses.map((row) => row.amount),
|
||||
);
|
||||
const breakEven = calculateBreakEven(
|
||||
data.breakEven.fixed,
|
||||
data.breakEven.price,
|
||||
data.breakEven.variable,
|
||||
);
|
||||
assert.equal(budget.operatingBalance, '7000.00');
|
||||
assert.equal(breakEven.breakEvenUnits, '60');
|
||||
// An explicit alternative location-cost assumption, not an inferred provincial tariff.
|
||||
const alternative = calculateBudget(
|
||||
data.incomes.map((row) => row.amount),
|
||||
['5000', '3000'],
|
||||
);
|
||||
assert.equal(alternative.operatingBalance, '2000.00');
|
||||
assert.deepEqual(
|
||||
data.expenses.map((row) => row.amount),
|
||||
['2000', '1000'],
|
||||
);
|
||||
const report = createInvestorDossierReport(path, '2026-11-14T12:00:00Z');
|
||||
const html = await createProjectDossierPrintHtml(report, [], false);
|
||||
assert.ok(html.includes(saved.project_summary));
|
||||
assert.ok(html.includes('Synthetic province'));
|
||||
assert.ok(!html.includes('60000.00'));
|
||||
assert.ok(!html.includes('Synthetic private contact'));
|
||||
const docx = await generateProjectDossierDocx(report, [], false);
|
||||
const bytes = Buffer.from(await docx.arrayBuffer());
|
||||
const zip = await JSZip.loadAsync(bytes);
|
||||
const xml = await zip.file('word/document.xml').async('string');
|
||||
assert.ok(xml.includes(saved.project_summary));
|
||||
assert.ok(xml.includes('Synthetic province'));
|
||||
assert.ok(!xml.includes('60000.00'));
|
||||
writeFileSync(new URL('private-integrated-report.html', directory), html);
|
||||
writeFileSync(new URL('private-integrated-report.docx', directory), bytes);
|
||||
writeFileSync(
|
||||
new URL('location-analysis.json', directory),
|
||||
JSON.stringify(
|
||||
{
|
||||
scope: 'Synthetic investor-declared costs at the selected location; no automatic profitability or fiscal entitlement',
|
||||
province_id: saved.province_id,
|
||||
budget,
|
||||
breakEven,
|
||||
alternative,
|
||||
source_offer: path.investment_context.selection.opportunity.key,
|
||||
case_id: path.case.id,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user