ChartOfAccountsPage.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <template>
  2. <div>
  3. <DefaultHeaderPage title="Plano de Contas" :show-filter-icon="false" />
  4. <div class="row justify-end items-center q-px-md q-mb-sm">
  5. <q-btn
  6. v-if="canAdd"
  7. color="primary"
  8. label="Nova conta"
  9. icon="mdi-plus"
  10. unelevated
  11. no-caps
  12. @click="openCreate"
  13. />
  14. </div>
  15. <div class="q-px-md">
  16. <DefaultTable
  17. v-model:rows="rows"
  18. no-api-call
  19. :add-item="false"
  20. title="Plano de Contas"
  21. descricao="contas"
  22. :feminino="true"
  23. :columns="columns"
  24. :delete-function="canDelete ? onDelete : null"
  25. >
  26. <template #body-cell-parent="{ row }">
  27. <q-td class="text-left">
  28. {{ row.parent ? `${row.parent.code} — ${row.parent.description}` : "—" }}
  29. </q-td>
  30. </template>
  31. <template #body-cell-chart_type="{ row }">
  32. <q-td class="text-left">
  33. <q-chip
  34. :color="row.chart_type === 'receita' ? 'positive' : 'blue-grey-5'"
  35. text-color="white"
  36. dense
  37. square
  38. :label="chartTypeLabel(row.chart_type)"
  39. />
  40. </q-td>
  41. </template>
  42. <template #body-cell-actions="{ row }">
  43. <q-td auto-width>
  44. <q-btn
  45. v-if="row.unit_id && canEdit"
  46. color="primary-2"
  47. label="Editar"
  48. dense
  49. no-caps
  50. outline
  51. class="q-px-sm"
  52. @click="openEdit(row)"
  53. />
  54. <q-chip
  55. v-else
  56. dense
  57. square
  58. color="blue-grey-1"
  59. text-color="blue-grey-7"
  60. icon="mdi-lock-outline"
  61. label="Matriz"
  62. />
  63. </q-td>
  64. </template>
  65. </DefaultTable>
  66. </div>
  67. <q-dialog v-model="dialog">
  68. <q-card style="min-width: 360px; max-width: 460px">
  69. <DefaultDialogHeader
  70. :title="editing ? 'Editar conta' : 'Nova conta'"
  71. @close="dialog = false"
  72. />
  73. <q-form ref="formRef">
  74. <q-card-section class="q-gutter-sm">
  75. <q-option-group
  76. v-model="accountKind"
  77. :options="kindOptions"
  78. color="primary"
  79. inline
  80. dense
  81. />
  82. <div class="row q-col-gutter-sm">
  83. <DefaultInput
  84. v-model="form.code"
  85. label="Código (ex.: 1.1.01)"
  86. :class="accountKind === 'parent' ? 'col-5' : 'col-12'"
  87. outlined
  88. :rules="[(v) => !!v || 'Informe o código']"
  89. />
  90. <DefaultSelect
  91. v-if="accountKind === 'parent'"
  92. v-model="form.chart_type"
  93. label="Tipo"
  94. :options="chartTypeOptions"
  95. class="col-7"
  96. outlined
  97. emit-value
  98. map-options
  99. :rules="[(v) => !!v || 'Selecione o tipo']"
  100. />
  101. </div>
  102. <DefaultSelect
  103. v-if="accountKind === 'child'"
  104. v-model="form.parent_id"
  105. label="Conta Pai"
  106. :options="parentOptions"
  107. option-value="id"
  108. option-label="label"
  109. outlined
  110. emit-value
  111. map-options
  112. :loading="loadingParents"
  113. :rules="[(v) => !!v || 'Selecione a conta pai']"
  114. />
  115. <DefaultInput
  116. v-model="form.description"
  117. label="Descrição"
  118. outlined
  119. :rules="[(v) => !!v || 'Informe a descrição']"
  120. />
  121. <div
  122. v-if="accountKind === 'child' && selectedParent"
  123. class="text-caption text-grey-7"
  124. >
  125. Tipo herdado do pai:
  126. <b>{{ chartTypeLabel(selectedParent.chart_type) }}</b>
  127. </div>
  128. </q-card-section>
  129. <q-card-actions align="right" class="q-pa-md">
  130. <q-btn flat label="Cancelar" color="grey-7" @click="dialog = false" />
  131. <q-btn
  132. color="primary"
  133. label="Salvar"
  134. :loading="saving"
  135. @click="onSave"
  136. />
  137. </q-card-actions>
  138. </q-form>
  139. </q-card>
  140. </q-dialog>
  141. </div>
  142. </template>
  143. <script setup>
  144. import { onMounted, ref, computed } from "vue";
  145. import { useQuasar } from "quasar";
  146. import {
  147. getPlanAccountsMe,
  148. createPlanAccountMe,
  149. updatePlanAccountMe,
  150. deletePlanAccountMe,
  151. } from "src/api/financial_plan_account";
  152. import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
  153. import DefaultTable from "src/components/defaults/DefaultTable.vue";
  154. import DefaultInput from "src/components/defaults/DefaultInput.vue";
  155. import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
  156. import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
  157. import { permissionStore } from "src/stores/permission";
  158. const $q = useQuasar();
  159. const permissions = permissionStore();
  160. const canAdd = computed(() =>
  161. permissions.getAccess("franchisee_financial", "add"),
  162. );
  163. const canEdit = computed(() =>
  164. permissions.getAccess("franchisee_financial", "edit"),
  165. );
  166. const canDelete = computed(() =>
  167. permissions.getAccess("franchisee_financial", "delete"),
  168. );
  169. const rows = ref([]);
  170. const dialog = ref(false);
  171. const saving = ref(false);
  172. const editing = ref(false);
  173. const editingId = ref(null);
  174. const formRef = ref(null);
  175. const defaultForm = () => ({
  176. code: "",
  177. description: "",
  178. chart_type: "despesa",
  179. parent_id: null,
  180. });
  181. const form = ref(defaultForm());
  182. const accountKind = ref("parent");
  183. const kindOptions = [
  184. { label: "Conta Pai", value: "parent" },
  185. { label: "Conta Filho", value: "child" },
  186. ];
  187. const chartTypeOptions = [
  188. { label: "Receita", value: "receita" },
  189. { label: "Despesa", value: "despesa" },
  190. ];
  191. function chartTypeLabel(type) {
  192. return chartTypeOptions.find((o) => o.value === type)?.label ?? type;
  193. }
  194. const loadingParents = ref(false);
  195. const parentOptions = computed(() =>
  196. rows.value
  197. .filter((a) => a.id !== editingId.value)
  198. .map((a) => ({
  199. id: a.id,
  200. label: `${a.code} — ${a.description}`,
  201. chart_type: a.chart_type,
  202. })),
  203. );
  204. const selectedParent = computed(() =>
  205. parentOptions.value.find((o) => o.id === form.value.parent_id),
  206. );
  207. const columns = [
  208. { name: "code", label: "Código", field: "code", align: "left", sortable: true },
  209. { name: "description", label: "Descrição", field: "description", align: "left" },
  210. { name: "parent", label: "Conta Pai", field: "parent", align: "left" },
  211. { name: "chart_type", label: "Tipo", field: "chart_type", align: "left" },
  212. { name: "actions", label: "Ações", field: "actions", align: "right" },
  213. ];
  214. async function loadData() {
  215. loadingParents.value = true;
  216. try {
  217. rows.value = await getPlanAccountsMe();
  218. } catch (e) {
  219. console.error("Erro ao carregar plano de contas:", e);
  220. } finally {
  221. loadingParents.value = false;
  222. }
  223. }
  224. function openCreate() {
  225. editing.value = false;
  226. editingId.value = null;
  227. accountKind.value = "parent";
  228. form.value = defaultForm();
  229. dialog.value = true;
  230. }
  231. function openEdit(row) {
  232. editing.value = true;
  233. editingId.value = row.id;
  234. accountKind.value = row.parent_id ? "child" : "parent";
  235. form.value = {
  236. code: row.code,
  237. description: row.description,
  238. chart_type: row.chart_type,
  239. parent_id: row.parent_id ?? null,
  240. };
  241. dialog.value = true;
  242. }
  243. async function onSave() {
  244. const valid = await formRef.value?.validate();
  245. if (!valid) return;
  246. const payload = {
  247. code: form.value.code,
  248. description: form.value.description,
  249. };
  250. if (accountKind.value === "child") {
  251. payload.parent_id = form.value.parent_id;
  252. } else {
  253. payload.chart_type = form.value.chart_type;
  254. payload.parent_id = null;
  255. }
  256. saving.value = true;
  257. try {
  258. if (editing.value) {
  259. await updatePlanAccountMe(editingId.value, payload);
  260. } else {
  261. await createPlanAccountMe(payload);
  262. }
  263. dialog.value = false;
  264. await loadData();
  265. } catch (e) {
  266. console.error(e);
  267. } finally {
  268. saving.value = false;
  269. }
  270. }
  271. function onDelete(id) {
  272. const row = rows.value.find((r) => r.id === id);
  273. if (row && !row.unit_id) {
  274. $q.notify({
  275. type: "negative",
  276. message: "O plano de contas da Matriz não pode ser excluído pela unidade.",
  277. });
  278. return;
  279. }
  280. $q.dialog({
  281. title: "Excluir conta",
  282. message: "Tem certeza que deseja excluir esta conta do plano?",
  283. cancel: true,
  284. persistent: true,
  285. }).onOk(async () => {
  286. try {
  287. await deletePlanAccountMe(id);
  288. await loadData();
  289. } catch (e) {
  290. console.error(e);
  291. }
  292. });
  293. }
  294. onMounted(loadData);
  295. </script>