Просмотр исходного кода

feat(plan-account): conta pai/filho + plano da matriz somente leitura

- Dialog com radio Conta Pai / Conta Filho (filho herda o tipo do pai)
  e coluna "Conta Pai".
- Contas do plano da matriz aparecem como somente leitura (chip "Matriz",
  sem editar/excluir); apenas o plano da própria unidade é editável.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ebagabee 1 месяц назад
Родитель
Сommit
ed3f1d9769
1 измененных файлов с 101 добавлено и 5 удалено
  1. 101 5
      src/pages/financial/ChartOfAccountsPage.vue

+ 101 - 5
src/pages/financial/ChartOfAccountsPage.vue

@@ -24,6 +24,12 @@
         :columns="columns"
         :delete-function="onDelete"
       >
+        <template #body-cell-parent="{ row }">
+          <q-td class="text-left">
+            {{ row.parent ? `${row.parent.code} — ${row.parent.description}` : "—" }}
+          </q-td>
+        </template>
+
         <template #body-cell-chart_type="{ row }">
           <q-td class="text-left">
             <q-chip
@@ -39,6 +45,7 @@
         <template #body-cell-actions="{ row }">
           <q-td auto-width>
             <q-btn
+              v-if="row.unit_id"
               color="primary-2"
               label="Editar"
               dense
@@ -47,6 +54,15 @@
               class="q-px-sm"
               @click="openEdit(row)"
             />
+            <q-chip
+              v-else
+              dense
+              square
+              color="blue-grey-1"
+              text-color="blue-grey-7"
+              icon="mdi-lock-outline"
+              label="Matriz"
+            />
           </q-td>
         </template>
       </DefaultTable>
@@ -61,15 +77,23 @@
 
         <q-form ref="formRef">
           <q-card-section class="q-gutter-sm">
+            <q-option-group
+              v-model="accountKind"
+              :options="kindOptions"
+              color="primary"
+              inline
+              dense
+            />
             <div class="row q-col-gutter-sm">
               <DefaultInput
                 v-model="form.code"
                 label="Código (ex.: 1.1.01)"
-                class="col-5"
+                :class="accountKind === 'parent' ? 'col-5' : 'col-12'"
                 outlined
                 :rules="[(v) => !!v || 'Informe o código']"
               />
               <DefaultSelect
+                v-if="accountKind === 'parent'"
                 v-model="form.chart_type"
                 label="Tipo"
                 :options="chartTypeOptions"
@@ -80,12 +104,32 @@
                 :rules="[(v) => !!v || 'Selecione o tipo']"
               />
             </div>
+            <DefaultSelect
+              v-if="accountKind === 'child'"
+              v-model="form.parent_id"
+              label="Conta Pai"
+              :options="parentOptions"
+              option-value="id"
+              option-label="label"
+              outlined
+              emit-value
+              map-options
+              :loading="loadingParents"
+              :rules="[(v) => !!v || 'Selecione a conta pai']"
+            />
             <DefaultInput
               v-model="form.description"
               label="Descrição"
               outlined
               :rules="[(v) => !!v || 'Informe a descrição']"
             />
+            <div
+              v-if="accountKind === 'child' && selectedParent"
+              class="text-caption text-grey-7"
+            >
+              Tipo herdado do pai:
+              <b>{{ chartTypeLabel(selectedParent.chart_type) }}</b>
+            </div>
           </q-card-section>
 
           <q-card-actions align="right" class="q-pa-md">
@@ -104,7 +148,7 @@
 </template>
 
 <script setup>
-import { onMounted, ref } from "vue";
+import { onMounted, ref, computed } from "vue";
 import { useQuasar } from "quasar";
 import {
   getPlanAccountsMe,
@@ -128,9 +172,20 @@ const editing = ref(false);
 const editingId = ref(null);
 const formRef = ref(null);
 
-const defaultForm = () => ({ code: "", description: "", chart_type: "despesa" });
+const defaultForm = () => ({
+  code: "",
+  description: "",
+  chart_type: "despesa",
+  parent_id: null,
+});
 const form = ref(defaultForm());
 
+const accountKind = ref("parent");
+const kindOptions = [
+  { label: "Conta Pai", value: "parent" },
+  { label: "Conta Filho", value: "child" },
+];
+
 const chartTypeOptions = [
   { label: "Receita", value: "receita" },
   { label: "Despesa", value: "despesa" },
@@ -140,24 +195,43 @@ function chartTypeLabel(type) {
   return chartTypeOptions.find((o) => o.value === type)?.label ?? type;
 }
 
+const loadingParents = ref(false);
+const parentOptions = computed(() =>
+  rows.value
+    .filter((a) => a.id !== editingId.value)
+    .map((a) => ({
+      id: a.id,
+      label: `${a.code} — ${a.description}`,
+      chart_type: a.chart_type,
+    })),
+);
+const selectedParent = computed(() =>
+  parentOptions.value.find((o) => o.id === form.value.parent_id),
+);
+
 const columns = [
   { name: "code", label: "Código", field: "code", align: "left", sortable: true },
   { name: "description", label: "Descrição", field: "description", align: "left" },
+  { name: "parent", label: "Conta Pai", field: "parent", align: "left" },
   { name: "chart_type", label: "Tipo", field: "chart_type", align: "left" },
   { name: "actions", label: "Ações", field: "actions", align: "right" },
 ];
 
 async function loadData() {
+  loadingParents.value = true;
   try {
     rows.value = await getPlanAccountsMe();
   } catch (e) {
     console.error("Erro ao carregar plano de contas:", e);
+  } finally {
+    loadingParents.value = false;
   }
 }
 
 function openCreate() {
   editing.value = false;
   editingId.value = null;
+  accountKind.value = "parent";
   form.value = defaultForm();
   dialog.value = true;
 }
@@ -165,10 +239,12 @@ function openCreate() {
 function openEdit(row) {
   editing.value = true;
   editingId.value = row.id;
+  accountKind.value = row.parent_id ? "child" : "parent";
   form.value = {
     code: row.code,
     description: row.description,
     chart_type: row.chart_type,
+    parent_id: row.parent_id ?? null,
   };
   dialog.value = true;
 }
@@ -176,12 +252,24 @@ function openEdit(row) {
 async function onSave() {
   const valid = await formRef.value?.validate();
   if (!valid) return;
+
+  const payload = {
+    code: form.value.code,
+    description: form.value.description,
+  };
+  if (accountKind.value === "child") {
+    payload.parent_id = form.value.parent_id;
+  } else {
+    payload.chart_type = form.value.chart_type;
+    payload.parent_id = null;
+  }
+
   saving.value = true;
   try {
     if (editing.value) {
-      await updatePlanAccountMe(editingId.value, form.value);
+      await updatePlanAccountMe(editingId.value, payload);
     } else {
-      await createPlanAccountMe(form.value);
+      await createPlanAccountMe(payload);
     }
     dialog.value = false;
     await loadData();
@@ -193,6 +281,14 @@ async function onSave() {
 }
 
 function onDelete(id) {
+  const row = rows.value.find((r) => r.id === id);
+  if (row && !row.unit_id) {
+    $q.notify({
+      type: "negative",
+      message: "O plano de contas da Matriz não pode ser excluído pela unidade.",
+    });
+    return;
+  }
   $q.dialog({
     title: "Excluir conta",
     message: "Tem certeza que deseja excluir esta conta do plano?",