|
|
@@ -0,0 +1,302 @@
|
|
|
+<template>
|
|
|
+ <q-dialog ref="dialogRef" persistent @hide="onDialogHide">
|
|
|
+ <q-card class="q-dialog-plugin overflow-hidden" style="width: 100%; max-width: 1100px">
|
|
|
+ <DefaultDialogHeader title="Gerar Contas a Receber" @close="onDialogCancel" />
|
|
|
+
|
|
|
+ <q-card-section class="q-pt-none">
|
|
|
+ <div class="row q-col-gutter-sm items-end">
|
|
|
+ <DefaultSelect
|
|
|
+ v-model="referenceMonth"
|
|
|
+ label="Mês de Referência"
|
|
|
+ class="col-12 col-md-4"
|
|
|
+ :options="monthOptions"
|
|
|
+ option-value="value"
|
|
|
+ option-label="label"
|
|
|
+ emit-value
|
|
|
+ map-options
|
|
|
+ />
|
|
|
+
|
|
|
+ <div class="col-12 col-md-3 text-body2 q-pb-sm">
|
|
|
+ Ano: <b>{{ referenceYear }}</b>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="col-12 col-md-5 text-right">
|
|
|
+ <q-btn
|
|
|
+ color="primary"
|
|
|
+ outline
|
|
|
+ icon="mdi-calculator"
|
|
|
+ label="Calcular Preview"
|
|
|
+ :loading="loadingPreview"
|
|
|
+ @click="loadPreview"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-banner v-if="error" class="bg-negative text-white q-mt-md" dense>
|
|
|
+ {{ error }}
|
|
|
+ </q-banner>
|
|
|
+
|
|
|
+ <q-banner v-else-if="loaded && previews.length === 0" class="bg-warning text-white q-mt-md" dense>
|
|
|
+ Nenhum contrato vigente encontrado para o mês informado.
|
|
|
+ </q-banner>
|
|
|
+
|
|
|
+ <q-table
|
|
|
+ v-if="previews.length > 0"
|
|
|
+ flat
|
|
|
+ dense
|
|
|
+ :rows="previews"
|
|
|
+ :columns="columns"
|
|
|
+ row-key="unit_id"
|
|
|
+ class="q-mt-md"
|
|
|
+ :pagination="{ rowsPerPage: 0 }"
|
|
|
+ hide-pagination
|
|
|
+ >
|
|
|
+ <template #body-cell-tbr_value="{ row }">
|
|
|
+ <q-td>{{ formatToBRLCurrency(row.tbr_value) }}</q-td>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #body-cell-royalties_effective_value="{ row }">
|
|
|
+ <q-td>
|
|
|
+ {{ formatToBRLCurrency(row.royalties_effective_value) }}
|
|
|
+ <div class="text-caption text-grey-7">
|
|
|
+ {{ formatPercentage(row.royalties_effective_percentage) }} ({{ row.royalties_applied_criteria }})
|
|
|
+ </div>
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #body-cell-fnm_effective_value="{ row }">
|
|
|
+ <q-td>
|
|
|
+ {{ formatToBRLCurrency(row.fnm_effective_value) }}
|
|
|
+ <div class="text-caption text-grey-7">
|
|
|
+ {{ formatPercentage(row.fnm_effective_percentage) }}
|
|
|
+ </div>
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #body-cell-maintenance_effective_value="{ row }">
|
|
|
+ <q-td>
|
|
|
+ {{ formatToBRLCurrency(row.maintenance_effective_value) }}
|
|
|
+ <div class="text-caption text-grey-7">
|
|
|
+ {{ formatPercentage(row.maintenance_effective_percentage) }}
|
|
|
+ </div>
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #body-cell-final_value="{ row }">
|
|
|
+ <q-td>
|
|
|
+ <b>{{ formatToBRLCurrency(row.final_value) }}</b>
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #body-cell-status="{ row }">
|
|
|
+ <q-td>
|
|
|
+ <q-chip
|
|
|
+ v-if="row.error"
|
|
|
+ color="negative"
|
|
|
+ text-color="white"
|
|
|
+ dense
|
|
|
+ :label="row.error"
|
|
|
+ />
|
|
|
+ <q-chip
|
|
|
+ v-else-if="row.receivable_already_generated"
|
|
|
+ color="warning"
|
|
|
+ text-color="white"
|
|
|
+ dense
|
|
|
+ label="Já gerado"
|
|
|
+ />
|
|
|
+ <q-chip v-else color="positive" text-color="white" dense label="Pronto" />
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
+ </q-table>
|
|
|
+
|
|
|
+ <div v-if="previews.length > 0" class="row q-mt-md q-gutter-sm items-center">
|
|
|
+ <div class="col">
|
|
|
+ <div class="text-body2">
|
|
|
+ Total geral a cobrar:
|
|
|
+ <b>{{ formatToBRLCurrency(totalToBill) }}</b>
|
|
|
+ em <b>{{ readyCount }}</b> unidade(s) pronta(s) para geração.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </q-card-section>
|
|
|
+
|
|
|
+ <q-card-actions>
|
|
|
+ <q-space />
|
|
|
+ <q-btn outline label="Fechar" @click="onDialogCancel" />
|
|
|
+ <q-btn
|
|
|
+ color="primary-2"
|
|
|
+ label="Gerar para Todas Pendentes"
|
|
|
+ icon="mdi-content-save-outline"
|
|
|
+ :loading="loadingGenerate"
|
|
|
+ :disable="readyCount === 0"
|
|
|
+ @click="onGenerateAll"
|
|
|
+ />
|
|
|
+ </q-card-actions>
|
|
|
+ </q-card>
|
|
|
+ </q-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, ref } from "vue";
|
|
|
+import { useDialogPluginComponent, useQuasar } from "quasar";
|
|
|
+
|
|
|
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
|
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
|
|
|
+
|
|
|
+import {
|
|
|
+ previewBatchTbrCalculation,
|
|
|
+ generateBatchReceivables,
|
|
|
+} from "src/api/tbr_calculation";
|
|
|
+import { formatToBRLCurrency, formatPercentage } from "src/helpers/utils";
|
|
|
+
|
|
|
+defineEmits([...useDialogPluginComponent.emits]);
|
|
|
+
|
|
|
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
|
|
+ useDialogPluginComponent();
|
|
|
+
|
|
|
+const $q = useQuasar();
|
|
|
+
|
|
|
+const today = new Date();
|
|
|
+const referenceMonth = ref(today.getMonth() + 1);
|
|
|
+const referenceYear = ref(today.getFullYear());
|
|
|
+
|
|
|
+const previews = ref([]);
|
|
|
+const loadingPreview = ref(false);
|
|
|
+const loadingGenerate = ref(false);
|
|
|
+const loaded = ref(false);
|
|
|
+const error = ref(null);
|
|
|
+
|
|
|
+const monthOptions = [
|
|
|
+ { value: 1, label: "Janeiro" },
|
|
|
+ { value: 2, label: "Fevereiro" },
|
|
|
+ { value: 3, label: "Março" },
|
|
|
+ { value: 4, label: "Abril" },
|
|
|
+ { value: 5, label: "Maio" },
|
|
|
+ { value: 6, label: "Junho" },
|
|
|
+ { value: 7, label: "Julho" },
|
|
|
+ { value: 8, label: "Agosto" },
|
|
|
+ { value: 9, label: "Setembro" },
|
|
|
+ { value: 10, label: "Outubro" },
|
|
|
+ { value: 11, label: "Novembro" },
|
|
|
+ { value: 12, label: "Dezembro" },
|
|
|
+];
|
|
|
+
|
|
|
+const columns = [
|
|
|
+ {
|
|
|
+ name: "unit_name",
|
|
|
+ label: "Unidade",
|
|
|
+ field: (row) => row.unit_name ?? `#${row.unit_id}`,
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "municipality_size_name",
|
|
|
+ label: "Porte",
|
|
|
+ field: "municipality_size_name",
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "contract_month_reference",
|
|
|
+ label: "Mês Contrato",
|
|
|
+ field: "contract_month_reference",
|
|
|
+ align: "center",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "tbr_value",
|
|
|
+ label: "TBR",
|
|
|
+ field: "tbr_value",
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "royalties_effective_value",
|
|
|
+ label: "Royalties",
|
|
|
+ field: "royalties_effective_value",
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "fnm_effective_value",
|
|
|
+ label: "FNM",
|
|
|
+ field: "fnm_effective_value",
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "maintenance_effective_value",
|
|
|
+ label: "Manutenção",
|
|
|
+ field: "maintenance_effective_value",
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "final_value",
|
|
|
+ label: "Total",
|
|
|
+ field: "final_value",
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "status",
|
|
|
+ label: "Status",
|
|
|
+ field: "status",
|
|
|
+ align: "left",
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
+const readyCount = computed(
|
|
|
+ () =>
|
|
|
+ previews.value.filter((p) => !p.error && !p.receivable_already_generated)
|
|
|
+ .length,
|
|
|
+);
|
|
|
+
|
|
|
+const totalToBill = computed(() =>
|
|
|
+ previews.value
|
|
|
+ .filter((p) => !p.error && !p.receivable_already_generated)
|
|
|
+ .reduce((sum, p) => sum + Number(p.final_value || 0), 0),
|
|
|
+);
|
|
|
+
|
|
|
+async function loadPreview() {
|
|
|
+ loadingPreview.value = true;
|
|
|
+ error.value = null;
|
|
|
+ try {
|
|
|
+ previews.value = await previewBatchTbrCalculation({
|
|
|
+ reference_year: referenceYear.value,
|
|
|
+ reference_month: referenceMonth.value,
|
|
|
+ });
|
|
|
+ loaded.value = true;
|
|
|
+ } catch (err) {
|
|
|
+ error.value =
|
|
|
+ err?.response?.data?.message || "Erro ao calcular o preview em lote.";
|
|
|
+ previews.value = [];
|
|
|
+ } finally {
|
|
|
+ loadingPreview.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function onGenerateAll() {
|
|
|
+ $q.dialog({
|
|
|
+ title: "Confirmar geração",
|
|
|
+ message: `Gerar Contas a Receber para ${readyCount.value} unidade(s)?`,
|
|
|
+ ok: { label: "Gerar", color: "primary" },
|
|
|
+ cancel: { label: "Cancelar", color: "primary", outline: true },
|
|
|
+ }).onOk(async () => {
|
|
|
+ loadingGenerate.value = true;
|
|
|
+ try {
|
|
|
+ const result = await generateBatchReceivables({
|
|
|
+ reference_year: referenceYear.value,
|
|
|
+ reference_month: referenceMonth.value,
|
|
|
+ });
|
|
|
+ $q.notify({
|
|
|
+ type: "positive",
|
|
|
+ message:
|
|
|
+ result.message ||
|
|
|
+ `${result.payload?.generated_count ?? 0} título(s) gerado(s).`,
|
|
|
+ });
|
|
|
+ onDialogOK(result.payload ?? null);
|
|
|
+ } catch (err) {
|
|
|
+ $q.notify({
|
|
|
+ type: "negative",
|
|
|
+ message:
|
|
|
+ err?.response?.data?.message || "Erro ao gerar Contas a Receber.",
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ loadingGenerate.value = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+</script>
|