All checks were successful
Dev Build & Deploy Portal / build-deploy (push) Successful in 2m42s
- Páginas taxas/index (listagem com status e ações) e taxas/emitir (wizard) - CamposEmissaoTaxa: campos dinâmicos por tipo (data, numérico, inputmask, texto) - useEmissaoTaxaPortal: composable com fluxo completo — busca catálogo, calcula vencimento/multa-juros, valida e emite taxa via API - taxaService: client HTTP para os endpoints /api/v1/contribuinte/taxas - atributoMascara, formatacao, formulaCalculo: utilitários de suporte - portal.vue: item "Taxas" no menu de navegação - painel.vue: atalho rápido "Emitir Taxa" com ícone pi-file-export Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
234 lines
10 KiB
Vue
234 lines
10 KiB
Vue
<script setup>
|
|
import { onMounted, ref } from 'vue'
|
|
import CamposEmissaoTaxa from '@/components/taxas/CamposEmissaoTaxa.vue'
|
|
import { useEmissaoTaxaPortal } from '@/composables/useEmissaoTaxaPortal'
|
|
import { formatarDocumento } from '@/utils/formatacao'
|
|
|
|
definePageMeta({
|
|
layout: 'portal',
|
|
middleware: 'auth',
|
|
})
|
|
|
|
const router = useRouter()
|
|
const etapa = ref('formulario')
|
|
|
|
const {
|
|
contribuinte,
|
|
catalogo,
|
|
tributoSelecionadoId,
|
|
itensTributo,
|
|
itensInformativosDoc,
|
|
formulaSelecionada,
|
|
periodoReferencia,
|
|
vencimentoDebito,
|
|
vencimentoGuia,
|
|
valorTaxa,
|
|
valoresItens,
|
|
observacao,
|
|
totalizadores,
|
|
resultadoEmissao,
|
|
carregando,
|
|
carregandoTributo,
|
|
carregandoCalculo,
|
|
carregandoEmissao,
|
|
mensagemErro,
|
|
erros,
|
|
labelCatalogo,
|
|
carregarDadosIniciais,
|
|
onSelecionarTributo,
|
|
onPeriodoChange,
|
|
recalcularTotais,
|
|
emitirTaxa,
|
|
imprimirGuia,
|
|
reiniciar,
|
|
} = useEmissaoTaxaPortal()
|
|
|
|
onMounted(carregarDadosIniciais)
|
|
|
|
async function onTributoChange(id) {
|
|
await onSelecionarTributo(id)
|
|
}
|
|
|
|
async function onSubmit() {
|
|
const ok = await emitirTaxa()
|
|
if (ok) etapa.value = 'resultado'
|
|
}
|
|
|
|
function novaEmissao() {
|
|
reiniciar()
|
|
etapa.value = 'formulario'
|
|
carregarDadosIniciais()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-6 max-w-3xl">
|
|
<div class="flex items-center gap-4">
|
|
<button
|
|
class="inline-flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 transition-colors"
|
|
@click="router.push('/portal/taxas')"
|
|
>
|
|
<i class="pi pi-arrow-left text-xs" aria-hidden="true" />
|
|
Voltar
|
|
</button>
|
|
</div>
|
|
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-slate-800 dark:text-slate-100">Emitir Taxa</h1>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-0.5">Emissão simplificada de taxas disponíveis para o seu cadastro.</p>
|
|
</div>
|
|
|
|
<div v-if="carregando" class="space-y-4">
|
|
<div v-for="i in 3" :key="i" class="h-20 bg-slate-200 dark:bg-slate-700 rounded-xl animate-pulse" />
|
|
</div>
|
|
|
|
<template v-else-if="etapa === 'formulario'">
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 space-y-4">
|
|
<h2 class="text-sm font-semibold text-slate-700 dark:text-slate-200 uppercase tracking-wide">Contribuinte</h2>
|
|
<div class="grid sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1.5">CPF/CNPJ</label>
|
|
<InputText
|
|
:model-value="formatarDocumento(contribuinte?.documento)"
|
|
class="w-full"
|
|
size="small"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1.5">Nome</label>
|
|
<InputText
|
|
:model-value="contribuinte?.nomeCompleto"
|
|
class="w-full"
|
|
size="small"
|
|
disabled
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 space-y-4">
|
|
<h2 class="text-sm font-semibold text-slate-700 dark:text-slate-200 uppercase tracking-wide">Dados da taxa</h2>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1.5">Taxa *</label>
|
|
<Select
|
|
:model-value="tributoSelecionadoId"
|
|
:options="catalogo"
|
|
:option-label="labelCatalogo"
|
|
option-value="id"
|
|
placeholder="Selecione a taxa"
|
|
show-clear
|
|
class="w-full"
|
|
size="small"
|
|
:loading="carregandoTributo"
|
|
@update:model-value="onTributoChange"
|
|
/>
|
|
<p v-if="erros.tributo" class="text-xs text-red-500 mt-1">{{ erros.tributo }}</p>
|
|
<p v-if="catalogo.length === 0" class="text-xs text-amber-600 dark:text-amber-400 mt-2">
|
|
Nenhuma taxa disponível para emissão no portal. Verifique com a prefeitura.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1.5">Período referência *</label>
|
|
<InputText
|
|
v-model="periodoReferencia"
|
|
type="month"
|
|
class="w-full"
|
|
size="small"
|
|
@change="onPeriodoChange"
|
|
/>
|
|
<p v-if="erros.periodoReferencia" class="text-xs text-red-500 mt-1">{{ erros.periodoReferencia }}</p>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1.5">Vencimento débito</label>
|
|
<InputText
|
|
:model-value="vencimentoDebito"
|
|
class="w-full"
|
|
size="small"
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1.5">Vencimento guia *</label>
|
|
<InputText
|
|
v-model="vencimentoGuia"
|
|
type="date"
|
|
class="w-full"
|
|
size="small"
|
|
/>
|
|
<p v-if="erros.vencimentoGuia" class="text-xs text-red-500 mt-1">{{ erros.vencimentoGuia }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<CamposEmissaoTaxa
|
|
v-if="tributoSelecionadoId"
|
|
:itens-tributo="itensTributo"
|
|
:itens-informativos-doc="itensInformativosDoc"
|
|
:formula-selecionada="formulaSelecionada"
|
|
:valores-itens="valoresItens"
|
|
:valor-taxa="valorTaxa"
|
|
:erros="erros"
|
|
@update:valores-itens="valoresItens = $event"
|
|
@update:valor-taxa="valorTaxa = $event"
|
|
/>
|
|
|
|
<div v-if="tributoSelecionadoId" class="flex gap-2">
|
|
<Button
|
|
label="Calcular valores"
|
|
icon="pi pi-calculator"
|
|
size="small"
|
|
severity="secondary"
|
|
outlined
|
|
:loading="carregandoCalculo"
|
|
@click="recalcularTotais"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="totalizadores" class="rounded-lg border border-slate-200 dark:border-slate-600 divide-y divide-slate-100 dark:divide-slate-700 text-sm">
|
|
<div class="flex justify-between px-4 py-2"><span class="text-slate-500">Principal</span><span>R$ {{ totalizadores.principal }}</span></div>
|
|
<div class="flex justify-between px-4 py-2"><span class="text-slate-500">Multa</span><span>R$ {{ totalizadores.multa }}</span></div>
|
|
<div class="flex justify-between px-4 py-2"><span class="text-slate-500">Juros</span><span>R$ {{ totalizadores.juros }}</span></div>
|
|
<div class="flex justify-between px-4 py-3 font-bold text-slate-800 dark:text-slate-100"><span>Total</span><span>R$ {{ totalizadores.total }}</span></div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-1.5">Observação</label>
|
|
<InputText v-model="observacao" class="w-full" size="small" />
|
|
</div>
|
|
</div>
|
|
|
|
<p v-if="mensagemErro" class="text-sm text-red-600 dark:text-red-400">{{ mensagemErro }}</p>
|
|
|
|
<Button
|
|
label="Emitir taxa"
|
|
icon="pi pi-check"
|
|
:loading="carregandoEmissao"
|
|
:disabled="!tributoSelecionadoId"
|
|
@click="onSubmit"
|
|
/>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl border border-emerald-200 dark:border-emerald-800 p-8 text-center space-y-4">
|
|
<div class="w-14 h-14 bg-emerald-100 dark:bg-emerald-900/30 rounded-full flex items-center justify-center mx-auto">
|
|
<i class="pi pi-check text-emerald-600 dark:text-emerald-400 text-2xl" aria-hidden="true" />
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-slate-800 dark:text-slate-100">Taxa emitida com sucesso</h2>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">
|
|
Protocolo: <strong>{{ resultadoEmissao?.numeroProtocolo || '—' }}</strong>
|
|
</p>
|
|
</div>
|
|
<div class="flex flex-wrap justify-center gap-3">
|
|
<Button label="Imprimir guia" icon="pi pi-file-pdf" @click="imprimirGuia" />
|
|
<Button label="Ver taxas emitidas" severity="secondary" outlined @click="router.push('/portal/taxas')" />
|
|
<Button label="Nova emissão" severity="secondary" text @click="novaEmissao" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|