Ver Fonte

feat: tela Contas a Receber do franchisor com dados reais (TBR)

Consome /franchisee-account-receive, lista os títulos por unidade e
calcula os cards de recebidos/pendentes. Remove o DevBanner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ebagabee há 2 dias atrás
pai
commit
7bc8eea3ce

+ 6 - 0
src/api/franchisee_account_receive.js

@@ -0,0 +1,6 @@
+import api from "src/api";
+
+export const getFranchiseeReceivables = async (params = {}) => {
+  const { data } = await api.get("/franchisee-account-receive", { params });
+  return data.payload;
+};

+ 71 - 42
src/pages/financial/AccountsReceivablePage.vue

@@ -2,79 +2,57 @@
   <div>
     <DefaultHeaderPage title="Contas a Receber" :show-filter-icon="false" />
 
-    <div class="q-px-md"><DevBanner /></div>
     <div class="row q-pa-md q-gutter-md">
       <FinancialCard
         title="Saldo Tesouraria"
         icon="mdi-bank-outline"
         :financial-value="0"
-        :integer="0"
-        integer-label="pagamentos pendentes"
       />
       <FinancialCard
         title="Contas Recebidas"
         icon="mdi-check-circle-outline"
-        :financial-value="0"
-        :integer="0"
-        integer-label="pagamentos pendentes"
+        :financial-value="receivedTotal"
+        :integer="receivedCount"
+        integer-label="títulos recebidos"
       />
       <FinancialCard
         title="Contas a Receber"
         icon="mdi-cash-plus"
-        :financial-value="0"
-        :integer="0"
-        integer-label="pagamentos pendentes"
+        :financial-value="pendingTotal"
+        :integer="pendingCount"
+        integer-label="títulos pendentes"
       />
     </div>
 
     <div class="row justify-end items-center q-px-md q-mb-sm q-gutter-sm">
-      <q-btn
-        :color="showMovimentacoes ? 'secondary' : 'primary'"
-        label="Últimas Movimentações"
-        unelevated
-        no-caps
-        @click="showMovimentacoes = !showMovimentacoes"
-      />
       <q-btn color="primary" label="Exportar Relatório" icon="mdi-download" unelevated no-caps />
     </div>
 
     <div class="q-px-md">
       <DefaultTable
-        v-if="!showMovimentacoes"
         v-model:rows="rows"
         no-api-call
-        add-item
+        :add-item="false"
+        :loading="loading"
         title="Contas a Receber"
         description="contas"
         :female="true"
         :columns="columns"
-        @on-add-item="handleAddItem"
-      />
-      <DefaultTable
-        v-else
-        v-model:rows="movimentacoesRows"
-        no-api-call
-        :add-item="false"
-        title="Últimas Movimentações"
-        description="movimentações"
-        :female="true"
-        :columns="movimentacoesColumns"
-        @on-add-item="handleAddItem"
       />
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref } from "vue";
+import { ref, computed, onMounted } from "vue";
+import { Notify } from "quasar";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
-import DevBanner from "src/components/shared/DevBanner.vue";
 import DefaultTable from "src/components/defaults/DefaultTable.vue";
 import FinancialCard from "src/components/financial/FinancialCard.vue";
+import { getFranchiseeReceivables } from "src/api/franchisee_account_receive";
 
-const showMovimentacoes = ref(false);
-const rows = ref([]);
-const movimentacoesRows = ref([]);
+const loading = ref(false);
+const items = ref([]);
 
 const columns = [
   { name: "unit", label: "Unidade", field: "unit", align: "left" },
@@ -85,12 +63,63 @@ const columns = [
   { name: "status", label: "Status", field: "status", align: "left" },
 ];
 
-const movimentacoesColumns = [
-  { name: "description", label: "Descrição", field: "description", align: "left" },
-  { name: "date", label: "Data", field: "date", align: "left" },
-  { name: "value", label: "Valor", field: "value", align: "left" },
-  { name: "status", label: "Status", field: "status", align: "left" },
-];
+const STATUS_LABEL = {
+  pending: "Pendente",
+  awaiting_payment: "Aguardando",
+  paid: "Pago",
+  overdue: "Atrasado",
+  cancelled: "Cancelado",
+};
+
+const formatCurrency = (value) =>
+  new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(value ?? 0);
+
+const formatDate = (value) => {
+  if (!value) return "—";
+  const [y, m, d] = value.split("-");
+  return `${d}/${m}/${y}`;
+};
+
+const rows = computed(() =>
+  items.value.map((i) => ({
+    id: i.id,
+    unit: i.unit_name ?? `Unidade ${i.unit_id}`,
+    description: i.history,
+    category: "TBR",
+    value: formatCurrency(Number(i.value)),
+    due_date: formatDate(i.due_date),
+    status: STATUS_LABEL[i.status] ?? i.status,
+  })),
+);
+
+const isSettled = (status) => status === "paid";
+
+const receivedTotal = computed(() =>
+  items.value
+    .filter((i) => isSettled(i.status))
+    .reduce((acc, i) => acc + Number(i.paid_value || 0), 0),
+);
+const receivedCount = computed(() => items.value.filter((i) => isSettled(i.status)).length);
+
+const pendingTotal = computed(() =>
+  items.value
+    .filter((i) => !isSettled(i.status) && i.status !== "cancelled")
+    .reduce((acc, i) => acc + (Number(i.value || 0) - Number(i.paid_value || 0)), 0),
+);
+const pendingCount = computed(
+  () => items.value.filter((i) => !isSettled(i.status) && i.status !== "cancelled").length,
+);
+
+const fetchReceivables = async () => {
+  loading.value = true;
+  try {
+    items.value = await getFranchiseeReceivables();
+  } catch {
+    Notify.create({ message: "Não foi possível carregar as contas a receber.", type: "negative" });
+  } finally {
+    loading.value = false;
+  }
+};
 
-const handleAddItem = () => {};
+onMounted(fetchReceivables);
 </script>