Преглед на файлове

feat: adiciona contratos, historico e edicao

ebagabee преди 1 месец
родител
ревизия
8108136e4e

+ 5 - 0
src/api/franchisee_contract.js

@@ -26,3 +26,8 @@ export const deleteFranchiseeContract = async (id) => {
   const { data } = await api.delete(`/franchisee-contract/${id}`);
   return data;
 };
+
+export const getFranchiseeContractTaxHistory = async (id) => {
+  const { data } = await api.get(`/franchisee-contract/${id}/tax-history`);
+  return data.payload;
+};

+ 44 - 13
src/pages/unit/components/EditContractDialog.vue

@@ -156,13 +156,14 @@
               icon="mdi-pencil"
               text-color="white"
               style="width: 40px; min-width: 40px; height: 40px"
+              @click="openTaxesDialog"
             />
           </div>
         </div>
       </q-card-section>
 
       <q-card-section>
-        <div class="text-body2 q-mb-sm">Histórico de TBR</div>
+        <div class="text-body2 q-mb-sm">Histórico de Reajuste de Valores e TBR</div>
 
         <q-table
           flat
@@ -200,7 +201,10 @@ import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 
 import { getUnit } from "src/api/unit";
 import { getInhabitantClassificationsForSelect } from "src/api/inhabitant_classification";
-import { updateFranchiseeContract } from "src/api/franchisee_contract";
+import {
+  updateFranchiseeContract,
+  getFranchiseeContractTaxHistory,
+} from "src/api/franchisee_contract";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
@@ -218,6 +222,10 @@ const props = defineProps({
 const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } =
   useDialogPluginComponent();
 
+function openTaxesDialog() {
+  onDialogOK({ openTaxes: true, contract: props.contract });
+}
+
 const saving = ref(false);
 const loadingHistory = ref(false);
 const inhabitantOptions = ref([]);
@@ -254,24 +262,36 @@ const contractForm = reactive({
 });
 
 const tbrColumns = [
-  { name: "year", label: "Ano", field: "year", align: "left" },
-  { name: "tbr_value", label: "TBR $", field: "tbr_value", align: "left" },
+  { name: "id", label: "Id", field: "id", align: "left" },
+  {
+    name: "year",
+    label: "Ano",
+    field: (row) => new Date(row.created_at).getFullYear(),
+    align: "left",
+  },
   {
-    name: "royalties_percentage",
-    label: "Royalties %",
-    field: "royalties_percentage",
+    name: "inhabitant_classification",
+    label: "Faixa de Habitantes",
+    field: "inhabitant_classification",
     align: "left",
   },
+  { name: "tbr_fixed_value", label: "TBR", field: "tbr_fixed_value", align: "left" },
   {
-    name: "fnm_percentage",
-    label: "FNM %",
-    field: "fnm_percentage",
+    name: "marketing_fund_percentage",
+    label: "FNM",
+    field: (row) =>
+      row.marketing_fund_percentage != null
+        ? `${(row.marketing_fund_percentage * 100).toFixed(0)}%`
+        : "-",
     align: "left",
   },
   {
-    name: "maintenance_percentage",
-    label: "Manutenção %",
-    field: "maintenance_percentage",
+    name: "maintance_tax_percentage",
+    label: "Manutenção",
+    field: (row) =>
+      row.maintance_tax_percentage != null
+        ? `${(row.maintance_tax_percentage * 100).toFixed(0)}%`
+        : "-",
     align: "left",
   },
 ];
@@ -295,6 +315,17 @@ async function loadData() {
     label: `${c.description} (${c.acronym})`,
     value: c.id,
   }));
+
+  await loadTaxHistory();
+}
+
+async function loadTaxHistory() {
+  loadingHistory.value = true;
+  try {
+    tbrHistory.value = await getFranchiseeContractTaxHistory(props.contract.id);
+  } finally {
+    loadingHistory.value = false;
+  }
 }
 
 async function save() {

+ 162 - 0
src/pages/unit/components/EditContractTaxesDialog.vue

@@ -0,0 +1,162 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card
+      class="q-dialog-plugin overflow-hidden"
+      style="width: 100%; max-width: 1100px"
+    >
+      <DefaultDialogHeader
+        :title="() => 'Editar Taxas'"
+        @close="onDialogCancel"
+      />
+
+      <q-card-section>
+        <div class="text-body2 q-mb-sm">Definir Valores</div>
+
+        <div class="row q-col-gutter-sm">
+          <DefaultSelect
+            v-model="form.inhabitant_classification_id"
+            label="Faixa de Habitantes"
+            color="secondary"
+            label-color="secondary"
+            :options="inhabitantOptions"
+            emit-value
+            map-options
+            use-input
+            fill-input
+            hide-selected
+            input-debounce="0"
+            class="col-md-3 col-12"
+          />
+
+          <DefaultCurrencyInput
+            v-model="form.tbr_fixed_value"
+            label="TBR $"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+          />
+
+          <DefaultInput
+            v-model="form.tax_base_fnm"
+            label="Fundo Nacional de Marketing"
+            color="secondary"
+            label-color="secondary"
+            type="number"
+            class="col-md-3 col-12"
+          >
+            <template #append>
+              <span class="text-secondary">%</span>
+            </template>
+          </DefaultInput>
+
+          <DefaultInput
+            v-model="form.tax_base_maintenance"
+            label="Taxa de Manutenção"
+            color="secondary"
+            label-color="secondary"
+            type="number"
+            class="col-md-3 col-12"
+          >
+            <template #append>
+              <span class="text-secondary">%</span>
+            </template>
+          </DefaultInput>
+        </div>
+      </q-card-section>
+
+      <q-card-actions align="right">
+        <q-btn
+          outline
+          color="primary"
+          label="Cancelar"
+          @click="onDialogCancel"
+        />
+        <q-btn color="primary" label="Salvar" :loading="saving" @click="confirmSave" />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
+
+import { getInhabitantClassificationsForSelect } from "src/api/inhabitant_classification";
+import { updateFranchiseeContract } from "src/api/franchisee_contract";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const props = defineProps({
+  contract: {
+    type: Object,
+    required: true,
+  },
+});
+
+const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } =
+  useDialogPluginComponent();
+
+const $q = useQuasar();
+const saving = ref(false);
+const inhabitantOptions = ref([]);
+
+const form = reactive({
+  inhabitant_classification_id:
+    props.contract.inhabitant_classification_id ?? null,
+  tbr_fixed_value: props.contract.tbr_fixed_value
+    ? parseFloat(props.contract.tbr_fixed_value)
+    : null,
+  tax_base_fnm:
+    props.contract.marketing_fund_percentage != null
+      ? parseFloat((props.contract.marketing_fund_percentage * 100).toFixed(4))
+      : null,
+  tax_base_maintenance:
+    props.contract.maintance_tax_percentage != null
+      ? parseFloat((props.contract.maintance_tax_percentage * 100).toFixed(4))
+      : null,
+});
+
+async function loadData() {
+  const classifications = await getInhabitantClassificationsForSelect();
+  inhabitantOptions.value = classifications.map((c) => ({
+    label: `${c.description} (${c.acronym})`,
+    value: c.id,
+  }));
+}
+
+function confirmSave() {
+  $q.dialog({
+    message: "Confirmar alteração nas Taxas?",
+    ok: { label: "Confirmar", color: "primary" },
+    cancel: { label: "Cancelar", color: "primary", outline: true },
+  }).onOk(save);
+}
+
+async function save() {
+  saving.value = true;
+  try {
+    await updateFranchiseeContract(props.contract.id, {
+      inhabitant_classification_id: form.inhabitant_classification_id,
+      tbr_fixed_value: form.tbr_fixed_value,
+      marketing_fund_percentage:
+        form.tax_base_fnm != null ? form.tax_base_fnm / 100 : null,
+      maintance_tax_percentage:
+        form.tax_base_maintenance != null
+          ? form.tax_base_maintenance / 100
+          : null,
+    });
+    onDialogOK();
+  } catch (error) {
+    console.error(error);
+  } finally {
+    saving.value = false;
+  }
+}
+
+onMounted(loadData);
+</script>

+ 308 - 0
src/pages/unit/components/ViewContractDialog.vue

@@ -0,0 +1,308 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card
+      class="q-dialog-plugin overflow-hidden"
+      style="width: 100%; max-width: 1100px"
+    >
+      <DefaultDialogHeader
+        :title="() => 'Visualizar Contrato'"
+        @close="onDialogCancel"
+      />
+
+      <q-card-section>
+        <div class="text-body2 q-mb-sm">Dados da Unidade</div>
+
+        <div class="row q-col-gutter-x-sm">
+          <DefaultInput
+            :model-value="unitData.id"
+            label="ID"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+            disable
+          />
+
+          <DefaultInput
+            :model-value="unitData.franchisee_name"
+            label="Nome do Franqueado"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+            disable
+          />
+
+          <DefaultInput
+            :model-value="unitData.franchisee_document"
+            label="CPF / CNH"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+            disable
+          />
+
+          <DefaultInput
+            :model-value="unitData.franchisee_birthday"
+            label="Data de Nascimento"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+            disable
+          />
+        </div>
+      </q-card-section>
+
+      <q-card-section>
+        <div class="row q-col-gutter-sm">
+          <DefaultInputDatePicker
+            v-model:untreated-date="contractForm.start_date"
+            label="Data de Início"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+            disable
+          />
+
+          <DefaultInputDatePicker
+            v-model:untreated-date="contractForm.end_date"
+            label="Data de Fim"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+            disable
+          />
+
+          <DefaultInput
+            v-model="contractForm.invoice_due_date"
+            label="Dia de Vencimento do Boleto"
+            color="secondary"
+            label-color="secondary"
+            type="number"
+            class="col-md-3 col-12"
+            disable
+          />
+
+          <DefaultSelect
+            v-model="contractForm.inhabitant_classification_id"
+            label="Faixa de Habitantes"
+            color="secondary"
+            label-color="secondary"
+            :options="inhabitantOptions"
+            emit-value
+            map-options
+            use-input
+            fill-input
+            hide-selected
+            input-debounce="0"
+            class="col-md-3 col-12"
+            disable
+          />
+        </div>
+
+        <div class="row q-col-gutter-sm q-mt-xs">
+          <DefaultCurrencyInput
+            v-model="contractForm.tbr_fixed_value"
+            label="TBR $"
+            color="secondary"
+            label-color="secondary"
+            class="col-md-3 col-12"
+            disable
+          />
+
+          <DefaultInput
+            v-model="contractForm.tax_base_royalts"
+            label="Taxa Base Royalties"
+            color="secondary"
+            label-color="secondary"
+            type="number"
+            class="col-md-3 col-12"
+            disable
+          >
+            <template #append>
+              <span class="text-secondary">%</span>
+            </template>
+          </DefaultInput>
+
+          <DefaultInput
+            v-model="contractForm.tax_base_fnm"
+            label="Fundo Nacional de Marketing"
+            color="secondary"
+            label-color="secondary"
+            type="number"
+            class="col-md-3 col-12"
+            disable
+          >
+            <template #append>
+              <span class="text-secondary">%</span>
+            </template>
+          </DefaultInput>
+
+          <DefaultInput
+            v-model="contractForm.tax_base_maintenance"
+            label="Taxa de Manutenção"
+            color="secondary"
+            label-color="secondary"
+            type="number"
+            class="col-md-3 col-12"
+            disable
+          >
+            <template #append>
+              <span class="text-secondary">%</span>
+            </template>
+          </DefaultInput>
+        </div>
+      </q-card-section>
+
+      <q-card-section>
+        <div class="text-body2 q-mb-sm">Histórico de Reajuste de Valores e TBR</div>
+
+        <q-table
+          flat
+          dense
+          :rows="tbrHistory"
+          :columns="historyColumns"
+          row-key="id"
+          :loading="loadingHistory"
+          :pagination="{ rowsPerPage: 5 }"
+          no-data-label="Nenhum histórico disponível"
+        />
+      </q-card-section>
+
+      <q-card-actions align="right">
+        <q-btn
+          color="primary"
+          label="Fechar"
+          @click="onDialogCancel"
+        />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { useDialogPluginComponent } from "quasar";
+
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
+import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
+
+import { getUnit } from "src/api/unit";
+import { getInhabitantClassificationsForSelect } from "src/api/inhabitant_classification";
+import { getFranchiseeContractTaxHistory } from "src/api/franchisee_contract";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const props = defineProps({
+  unitId: {
+    type: Number,
+    required: true,
+  },
+  contract: {
+    type: Object,
+    required: true,
+  },
+});
+
+const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
+
+const loadingHistory = ref(false);
+const inhabitantOptions = ref([]);
+const tbrHistory = ref([]);
+
+const unitData = reactive({
+  id: null,
+  franchisee_name: null,
+  franchisee_document: null,
+  franchisee_birthday: null,
+});
+
+const contractForm = reactive({
+  start_date: props.contract.start_date ?? null,
+  end_date: props.contract.end_date ?? null,
+  tbr_fixed_value: props.contract.tbr_fixed_value
+    ? parseFloat(props.contract.tbr_fixed_value)
+    : null,
+  invoice_due_date: props.contract.invoice_due_date ?? null,
+  inhabitant_classification_id:
+    props.contract.inhabitant_classification_id ?? null,
+  tax_base_royalts:
+    props.contract.tbr_fixed_value_percentage != null
+      ? parseFloat((props.contract.tbr_fixed_value_percentage * 100).toFixed(4))
+      : null,
+  tax_base_fnm:
+    props.contract.marketing_fund_percentage != null
+      ? parseFloat((props.contract.marketing_fund_percentage * 100).toFixed(4))
+      : null,
+  tax_base_maintenance:
+    props.contract.maintance_tax_percentage != null
+      ? parseFloat((props.contract.maintance_tax_percentage * 100).toFixed(4))
+      : null,
+});
+
+const historyColumns = [
+  { name: "id", label: "Id", field: "id", align: "left" },
+  {
+    name: "year",
+    label: "Ano",
+    field: (row) => new Date(row.created_at).getFullYear(),
+    align: "left",
+  },
+  {
+    name: "inhabitant_classification",
+    label: "Faixa de Habitantes",
+    field: "inhabitant_classification",
+    align: "left",
+  },
+  { name: "tbr_fixed_value", label: "TBR", field: "tbr_fixed_value", align: "left" },
+  {
+    name: "marketing_fund_percentage",
+    label: "FNM",
+    field: (row) =>
+      row.marketing_fund_percentage != null
+        ? `${(row.marketing_fund_percentage * 100).toFixed(0)}%`
+        : "-",
+    align: "left",
+  },
+  {
+    name: "maintance_tax_percentage",
+    label: "Manutenção",
+    field: (row) =>
+      row.maintance_tax_percentage != null
+        ? `${(row.maintance_tax_percentage * 100).toFixed(0)}%`
+        : "-",
+    align: "left",
+  },
+];
+
+async function loadData() {
+  const [unit, classifications] = await Promise.all([
+    getUnit(props.unitId),
+    getInhabitantClassificationsForSelect(),
+  ]);
+
+  unitData.id = unit.id;
+  unitData.franchisee_name = unit.name_responsible;
+
+  const firstPartner = unit.partners?.[0];
+  if (firstPartner) {
+    unitData.franchisee_document = firstPartner.cpf;
+    unitData.franchisee_birthday = firstPartner.birth_date;
+  }
+
+  inhabitantOptions.value = classifications.map((c) => ({
+    label: `${c.description} (${c.acronym})`,
+    value: c.id,
+  }));
+
+  loadingHistory.value = true;
+  try {
+    tbrHistory.value = await getFranchiseeContractTaxHistory(props.contract.id);
+  } finally {
+    loadingHistory.value = false;
+  }
+}
+
+onMounted(loadData);
+</script>

+ 34 - 2
src/pages/unit/tabs/ContractsTab.vue

@@ -25,12 +25,18 @@
       <template #body-cell-actions="{ row: contract }">
         <q-td align="center">
           <div class="row no-wrap" style="gap: 4px">
+            <q-btn
+              flat
+              round
+              dense
+              icon="mdi-eye-outline"
+              @click="openViewDialog(contract)"
+            />
             <q-btn
               flat
               round
               dense
               icon="mdi-file-edit-outline"
-              size="sm"
               @click="openEditDialog(contract)"
             />
             <q-btn
@@ -38,7 +44,6 @@
               round
               dense
               icon="mdi-trash-can-outline"
-              size="sm"
               color="negative"
               @click="confirmDelete(contract)"
             />
@@ -67,6 +72,14 @@ const EditContractDialog = defineAsyncComponent(
   () => import("src/pages/unit/components/EditContractDialog.vue"),
 );
 
+const EditContractTaxesDialog = defineAsyncComponent(
+  () => import("src/pages/unit/components/EditContractTaxesDialog.vue"),
+);
+
+const ViewContractDialog = defineAsyncComponent(
+  () => import("src/pages/unit/components/ViewContractDialog.vue"),
+);
+
 const props = defineProps({
   unitId: {
     type: Number,
@@ -79,6 +92,13 @@ const tableRef = ref(null);
 
 const loadContracts = () => getFranchiseeContractsByUnit(props.unitId);
 
+function openViewDialog(contract) {
+  $q.dialog({
+    component: ViewContractDialog,
+    componentProps: { unitId: props.unitId, contract },
+  });
+}
+
 function openCreateDialog() {
   $q.dialog({
     component: CreateContractDialog,
@@ -92,6 +112,18 @@ function openEditDialog(contract) {
   $q.dialog({
     component: EditContractDialog,
     componentProps: { unitId: props.unitId, contract },
+  }).onOk((result) => {
+    tableRef.value?.refresh();
+    if (result?.openTaxes) {
+      setTimeout(() => openTaxesDialog(result.contract), 300);
+    }
+  });
+}
+
+function openTaxesDialog(contract) {
+  $q.dialog({
+    component: EditContractTaxesDialog,
+    componentProps: { contract },
   }).onOk(() => {
     tableRef.value?.refresh();
   });