30 lines
8.3 KiB
TypeScript
30 lines
8.3 KiB
TypeScript
import type {OfferPreparation,OfferRequest} from './iva-offer-preparations.ts';
|
|
export const offerWorkflowSource='Procedimento operacional fornecido pela Tempo em25/10/2026:pedido escrito ao Ministério das Finanças/AT → pedido apresentado → decisão recebida e despacho anexo → preparação do formulário aplicável com despacho. Origem distinta do CIVA14(2)a/b e14(3) e do cotejo documental do ModeloI.1;sem nova certificação ou aprovação criada pelo portal.';
|
|
export type OfferAttachment={id:string;draftId:string;name:string;sha256:string;scan:string;url:string};
|
|
export type OfferWorkflow={recipient:string;writtenRequest:string;requestAttachment?:OfferAttachment;decisionIssuer:string;decisionAttachment?:OfferAttachment;scopeConfirmed:''|'yes'|'no';scopeKey?:string;formName:string;formSource:string;formContent:string;formAttachment?:OfferAttachment;formDecisionId?:string;formDecisionHash?:string;formPreparedAt?:string;submission:'outside';submissionReference:string;records:Array<{at:string;stage:string;request:OfferRequest;workflow:Omit<OfferWorkflow,'records'>}>};
|
|
export function emptyOfferWorkflow():OfferWorkflow{return{recipient:'Ministério das Finanças / Autoridade Tributária',writtenRequest:'',decisionIssuer:'',scopeConfirmed:'',formName:'',formSource:'',formContent:'',submission:'outside',submissionReference:'',records:[]};}
|
|
export function offerScopeKey(row:OfferPreparation):string{return JSON.stringify({branch:row.branch,description:row.description,beneficiary:row.beneficiary,operation:row.operation,period:row.period,currency:row.currency,amount:row.amount,purpose:row.purpose,evidence:row.evidence,national:row.nationalInstitution,social:row.publicSocialInstitution,suitable:row.goodsSuitable,use:row.publicInterestUse,disaster:row.disaster,mitigation:row.mitigation,humanitarian:row.humanitarianPurpose,request:row.request,issuer:row.workflow?.decisionIssuer,document:row.workflow?.decisionAttachment?.id,hash:row.workflow?.decisionAttachment?.sha256});}
|
|
export function offerAttachmentValid(a:OfferAttachment|undefined):a is OfferAttachment{return!!a&&/^[0-9a-f-]{36}$/i.test(a.id)&&/^[0-9a-f-]{36}$/i.test(a.draftId)&&!!a.name&&/^[0-9a-f]{64}$/.test(a.sha256)&&a.url===`/api/v1/simulation-drafts/${a.draftId}/offer-documents/${a.id}`;}
|
|
export function offerWrittenRequest(row:OfferPreparation,w:OfferWorkflow):string{return`Ao ${w.recipient}\nAssunto:Pedido de isenção — ${row.branch}\nEntidade/destinatário:${row.beneficiary}\nFinalidade:${row.purpose}\n${row.branch==='P084'?'Justificação do interesse público/fins sociais:'+row.evidence:'Calamidade:'+row.disaster+';finalidade humanitária:'+row.humanitarianPurpose}\nBens:${row.description}\nOperação:${row.operation};período:${row.period};valor de referência:${row.amount} ${row.currency}\nDocumentos:${row.availableDocuments||row.documentRef}\n${w.writtenRequest}\nPreparação InvesteMoz;a apresentação ocorre fora do portal. Não constitui concessão.`;}
|
|
export function offerWorkflowApplied(row:OfferPreparation):{applied:boolean;reason:string}{const w=row.workflow,r=row.request;if(!w||!r||r.state!=='granted')return{applied:false,reason:'Sem despacho de deferimento registado;mantém-se hipótese condicionada.'};if(!r.decisionRef.trim()||!validDate(r.decisionOn)||!w.decisionIssuer.trim()||!offerAttachmentValid(w.decisionAttachment))return{applied:false,reason:'Despacho recebido incompleto:número,data,emitente e anexo por registar.'};if(w.scopeConfirmed!=='yes'||w.scopeKey!==offerScopeKey(row))return{applied:false,reason:'Correspondência desta linha ao âmbito do despacho não confirmada ou dados alterados;sem extensão automática.'};if((!r.operationScope?.trim()&&r.operation&&r.operation!==row.operation)||(!r.periodScope?.trim()&&/^\d{4}-\d{2}$/.test(row.period)&&(row.period<r.periodFrom.slice(0,7)||row.period>r.periodTo.slice(0,7))))return{applied:false,reason:'A operação ou o período desta linha está fora do âmbito expresso do despacho;sem extensão automática.'};const conditions=row.branch==='P084'?[row.nationalInstitution,row.publicSocialInstitution,row.goodsSuitable,row.publicInterestUse]:[row.mitigation];if(conditions.some(v=>v!=='yes')||!row.description||!row.beneficiary||!row.purpose||!row.operation||(row.branch==='P085'&&(row.operation!=='acquisition'||!row.disaster||!row.humanitarianPurpose)))return{applied:false,reason:'Condições próprias do tratamento por demonstrar;despacho não alarga o fundamento legal.'};return{applied:true,reason:`Tratamento deferido apenas nesta linha:${r.goods};${r.operationScope||r.operation};${r.periodScope||r.periodFrom+' a '+r.periodTo};condições:${r.conditions}. Despacho${r.decisionRef},${r.decisionOn},${w.decisionIssuer}. Sem extensão às restantes linhas,períodos ou oportunidades;sem dedução automática.`};}
|
|
function validDate(v:string){const d=new Date(v+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(v)&&Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===v;}
|
|
export function recordOfferWorkflow(row:OfferPreparation,stage:'request'|'presented'|'decision'|'form'):OfferPreparation{
|
|
const w=structuredClone(row.workflow??emptyOfferWorkflow()),r=structuredClone(row.request);if(!r)throw Error('Prepare primeiro o pedido.');
|
|
if(!w.recipient.trim()||!row.beneficiary.trim()||!row.description.trim()||!row.purpose.trim()||!row.operation)throw Error('Indique entidade,finalidade,bens,operação e destinatário do pedido;rascunho incompleto pode ser guardado.');
|
|
if(stage==='presented'&&(!r.reference.trim()||!validDate(r.presentedOn)||r.state!=='presented'))throw Error('Registe referência,data e estado apresentado;submissão fora do InvesteMoz.');
|
|
if(stage==='decision'||stage==='form'){
|
|
if(!['granted','denied'].includes(r.state)||!r.decisionRef.trim()||!validDate(r.decisionOn)||!w.decisionIssuer.trim()||!offerAttachmentValid(w.decisionAttachment))throw Error('Decisão recebida exige número,data,emitente e despacho anexo.');
|
|
const dates=validDate(r.periodFrom)&&validDate(r.periodTo)&&r.periodFrom<=r.periodTo;const months=/^\d{4}-(0[1-9]|1[0-2])$/.test(r.periodFrom)&&/^\d{4}-(0[1-9]|1[0-2])$/.test(r.periodTo)&&r.periodFrom<=r.periodTo;
|
|
if(!r.goods.trim()||(!r.operation&&!r.operationScope?.trim())||(!r.periodScope?.trim()&&!dates&&!months)||!r.conditions.trim())throw Error('Conserve o âmbito literal:bens,operação,período e condições da decisão.');
|
|
if(stage==='decision')w.scopeKey=w.scopeConfirmed==='yes'?offerScopeKey(row):undefined;
|
|
}
|
|
if(stage==='form'){
|
|
if(r.state!=='granted')throw Error('Não preparar formulário de isenção como deferido após indeferimento.');
|
|
if(!offerWorkflowApplied({...row,workflow:w}).applied)throw Error('Confira explicitamente a correspondência desta linha ao despacho antes de preparar o formulário;alterações não renovam a confirmação automaticamente.');
|
|
if(!w.formName.trim()||!w.formSource.trim()||(!w.formContent.trim()&&!offerAttachmentValid(w.formAttachment)))throw Error('Identifique formulário aplicável e referência da sua aplicabilidade;prepare conteúdo ou anexe o exemplar preenchido.Sem presumir que ModeloI.1 abrange todos os casos.');
|
|
w.formDecisionId=w.decisionAttachment!.id;w.formDecisionHash=w.decisionAttachment!.sha256;w.formPreparedAt=new Date().toISOString();
|
|
}
|
|
const {records,...snapshot}=w;w.records=[...records,{at:new Date().toISOString(),stage,request:r,workflow:snapshot}];return{...row,workflow:w};
|
|
}
|
|
export function offerWorkflowReport(row:OfferPreparation):string{const w=row.workflow;if(!w)return'';const state=offerWorkflowApplied(row);return`${offerWorkflowSource} Pedido:${row.request?.reference||'por indicar'};apresentado em:${row.request?.presentedOn||'por indicar'};estado:${row.request?.state||'preparation'};decisão:${row.request?.decisionRef||'por indicar'},${row.request?.decisionOn||'por indicar'};Pedido escrito:${offerWrittenRequest(row,w)};decisão emitente:${w.decisionIssuer||'por indicar'};anexo:${w.decisionAttachment?.name||'por anexar'},SHA256:${w.decisionAttachment?.sha256||'por indicar'};${state.reason};formulário:${w.formName||'por identificar'},fonte/aplicabilidade:${w.formSource||'por indicar'};conteúdo:${w.formContent||'por preparar'};despacho ligado ao formulário:${w.formDecisionId||'por preparar'}/${w.formDecisionHash||''};submissão fora do InvesteMoz:${w.submissionReference||'não registada'};histórico:${w.records.map(e=>e.stage+'@'+e.at+':'+e.request.state+':'+e.request.decisionRef).join(' | ')}`;}
|