|
|
@@ -38,6 +38,14 @@
|
|
|
style="min-width: 200px"
|
|
|
@update:model-value="loadData"
|
|
|
/>
|
|
|
+ <q-btn
|
|
|
+ color="primary"
|
|
|
+ label="Nova conta a receber"
|
|
|
+ icon="mdi-plus"
|
|
|
+ unelevated
|
|
|
+ no-caps
|
|
|
+ @click="openCreate"
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
<div class="q-px-md">
|
|
|
@@ -81,7 +89,11 @@
|
|
|
@click="openInvoice(row)"
|
|
|
/>
|
|
|
<q-btn
|
|
|
- v-else-if="row.status !== 'paid' && row.status !== 'cancelled'"
|
|
|
+ v-else-if="
|
|
|
+ row.source !== 'manual' &&
|
|
|
+ row.status !== 'paid' &&
|
|
|
+ row.status !== 'cancelled'
|
|
|
+ "
|
|
|
color="deep-purple-5"
|
|
|
label="Gerar cobrança"
|
|
|
icon="mdi-cash-fast"
|
|
|
@@ -119,6 +131,79 @@
|
|
|
</DefaultTable>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- Diálogo de novo recebível avulso -->
|
|
|
+ <q-dialog v-model="createDialog">
|
|
|
+ <q-card style="min-width: 380px; max-width: 480px">
|
|
|
+ <DefaultDialogHeader
|
|
|
+ title="Nova conta a receber"
|
|
|
+ @close="createDialog = false"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-form ref="createFormRef">
|
|
|
+ <q-card-section class="q-gutter-sm">
|
|
|
+ <DefaultInput
|
|
|
+ v-model="createForm.history"
|
|
|
+ label="Descrição"
|
|
|
+ outlined
|
|
|
+ :rules="[(v) => !!v || 'Informe a descrição']"
|
|
|
+ />
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
+ <DefaultCurrencyInput
|
|
|
+ v-model="createForm.value"
|
|
|
+ label="Valor"
|
|
|
+ class="col-6"
|
|
|
+ outlined
|
|
|
+ :rules="[(v) => Number(v) > 0 || 'Informe o valor']"
|
|
|
+ />
|
|
|
+ <DefaultInputDatePicker
|
|
|
+ v-model="createForm.due_date"
|
|
|
+ label="Vencimento"
|
|
|
+ class="col-6"
|
|
|
+ :rules="[(v) => !!v || 'Informe o vencimento']"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <DefaultSelect
|
|
|
+ v-model="createForm.student_id"
|
|
|
+ label="Aluno (opcional)"
|
|
|
+ :options="studentOptions"
|
|
|
+ outlined
|
|
|
+ clearable
|
|
|
+ emit-value
|
|
|
+ map-options
|
|
|
+ :loading="loadingAux"
|
|
|
+ />
|
|
|
+ <DefaultSelect
|
|
|
+ v-model="createForm.financial_plan_account_id"
|
|
|
+ label="Plano de contas (opcional)"
|
|
|
+ :options="planAccountOptions"
|
|
|
+ outlined
|
|
|
+ clearable
|
|
|
+ emit-value
|
|
|
+ map-options
|
|
|
+ :loading="loadingAux"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="createForm.obs"
|
|
|
+ label="Observação (opcional)"
|
|
|
+ type="textarea"
|
|
|
+ autogrow
|
|
|
+ outlined
|
|
|
+ />
|
|
|
+ </q-card-section>
|
|
|
+
|
|
|
+ <q-card-actions align="right" class="q-pa-md">
|
|
|
+ <q-btn flat label="Cancelar" color="grey-7" @click="createDialog = false" />
|
|
|
+ <q-btn
|
|
|
+ color="primary"
|
|
|
+ label="Salvar"
|
|
|
+ :loading="creating"
|
|
|
+ @click="onCreate"
|
|
|
+ />
|
|
|
+ </q-card-actions>
|
|
|
+ </q-form>
|
|
|
+ </q-card>
|
|
|
+ </q-dialog>
|
|
|
+
|
|
|
<!-- Diálogo de baixa manual -->
|
|
|
<q-dialog v-model="settleDialog">
|
|
|
<q-card style="min-width: 360px; max-width: 460px">
|
|
|
@@ -179,11 +264,17 @@ import {
|
|
|
settleReceivableMe,
|
|
|
reopenReceivableMe,
|
|
|
chargeReceivableMe,
|
|
|
+ createManualReceivableMe,
|
|
|
+ settleManualReceivableMe,
|
|
|
+ reopenManualReceivableMe,
|
|
|
} from "src/api/unit_receivable";
|
|
|
+import { getStudents } from "src/api/student";
|
|
|
+import { getPlanAccountsMe } from "src/api/financial_plan_account";
|
|
|
|
|
|
import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
import DefaultTable from "src/components/defaults/DefaultTable.vue";
|
|
|
import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
|
|
|
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
|
|
|
import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
|
|
|
import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
|
|
|
import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
|
@@ -199,6 +290,23 @@ const chargingId = ref(null);
|
|
|
|
|
|
const settleForm = ref({ payment_date: null, discount: 0, fine: 0 });
|
|
|
|
|
|
+// Novo recebível avulso
|
|
|
+const createDialog = ref(false);
|
|
|
+const creating = ref(false);
|
|
|
+const createFormRef = ref(null);
|
|
|
+const loadingAux = ref(false);
|
|
|
+const studentOptions = ref([]);
|
|
|
+const planAccountOptions = ref([]);
|
|
|
+const defaultCreateForm = () => ({
|
|
|
+ history: "",
|
|
|
+ value: 0,
|
|
|
+ due_date: new Date().toISOString().slice(0, 10),
|
|
|
+ student_id: null,
|
|
|
+ financial_plan_account_id: null,
|
|
|
+ obs: "",
|
|
|
+});
|
|
|
+const createForm = ref(defaultCreateForm());
|
|
|
+
|
|
|
const statusFilterOptions = [
|
|
|
{ label: "Todos", value: null },
|
|
|
{ label: "Pendentes", value: "pending" },
|
|
|
@@ -267,12 +375,70 @@ const totals = computed(() => {
|
|
|
async function loadData() {
|
|
|
try {
|
|
|
const params = statusFilter.value ? { status: statusFilter.value } : {};
|
|
|
- rows.value = await getReceivablesMe(params);
|
|
|
+ const data = await getReceivablesMe(params);
|
|
|
+ // Chave composta: parcelas e avulsos podem ter o mesmo id numérico.
|
|
|
+ // realId é o id verdadeiro usado nas ações (baixa/reabrir/cobrança).
|
|
|
+ rows.value = (data ?? []).map((r) => ({
|
|
|
+ ...r,
|
|
|
+ realId: r.id,
|
|
|
+ id: `${r.source}-${r.id}`,
|
|
|
+ }));
|
|
|
} catch (e) {
|
|
|
console.error("Erro ao buscar contas a receber:", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+async function loadAux() {
|
|
|
+ if (studentOptions.value.length || planAccountOptions.value.length) return;
|
|
|
+ loadingAux.value = true;
|
|
|
+ try {
|
|
|
+ const [students, planAccounts] = await Promise.all([
|
|
|
+ getStudents(),
|
|
|
+ getPlanAccountsMe(),
|
|
|
+ ]);
|
|
|
+ studentOptions.value = (students ?? []).map((s) => ({
|
|
|
+ label: s.name,
|
|
|
+ value: s.id,
|
|
|
+ }));
|
|
|
+ planAccountOptions.value = (planAccounts ?? []).map((a) => ({
|
|
|
+ label: `${a.code} — ${a.description}`,
|
|
|
+ value: a.id,
|
|
|
+ }));
|
|
|
+ } catch (e) {
|
|
|
+ console.error("Erro ao carregar dados auxiliares:", e);
|
|
|
+ } finally {
|
|
|
+ loadingAux.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function openCreate() {
|
|
|
+ createForm.value = defaultCreateForm();
|
|
|
+ createDialog.value = true;
|
|
|
+ loadAux();
|
|
|
+}
|
|
|
+
|
|
|
+async function onCreate() {
|
|
|
+ const valid = await createFormRef.value?.validate();
|
|
|
+ if (!valid) return;
|
|
|
+ creating.value = true;
|
|
|
+ try {
|
|
|
+ await createManualReceivableMe({
|
|
|
+ history: createForm.value.history,
|
|
|
+ value: createForm.value.value,
|
|
|
+ due_date: createForm.value.due_date,
|
|
|
+ student_id: createForm.value.student_id || null,
|
|
|
+ financial_plan_account_id: createForm.value.financial_plan_account_id || null,
|
|
|
+ obs: createForm.value.obs || null,
|
|
|
+ });
|
|
|
+ createDialog.value = false;
|
|
|
+ await loadData();
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ } finally {
|
|
|
+ creating.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function openSettle(row) {
|
|
|
selected.value = row;
|
|
|
settleForm.value = {
|
|
|
@@ -286,12 +452,17 @@ function openSettle(row) {
|
|
|
async function onSettle() {
|
|
|
if (!selected.value) return;
|
|
|
settling.value = true;
|
|
|
+ const payload = {
|
|
|
+ payment_date: settleForm.value.payment_date,
|
|
|
+ discount: settleForm.value.discount,
|
|
|
+ fine: settleForm.value.fine,
|
|
|
+ };
|
|
|
try {
|
|
|
- await settleReceivableMe(selected.value.id, {
|
|
|
- payment_date: settleForm.value.payment_date,
|
|
|
- discount: settleForm.value.discount,
|
|
|
- fine: settleForm.value.fine,
|
|
|
- });
|
|
|
+ if (selected.value.source === "manual") {
|
|
|
+ await settleManualReceivableMe(selected.value.realId, payload);
|
|
|
+ } else {
|
|
|
+ await settleReceivableMe(selected.value.realId, payload);
|
|
|
+ }
|
|
|
settleDialog.value = false;
|
|
|
await loadData();
|
|
|
} catch (e) {
|
|
|
@@ -304,7 +475,11 @@ async function onSettle() {
|
|
|
async function onReopen(row) {
|
|
|
reopeningId.value = row.id;
|
|
|
try {
|
|
|
- await reopenReceivableMe(row.id);
|
|
|
+ if (row.source === "manual") {
|
|
|
+ await reopenManualReceivableMe(row.realId);
|
|
|
+ } else {
|
|
|
+ await reopenReceivableMe(row.realId);
|
|
|
+ }
|
|
|
await loadData();
|
|
|
} catch (e) {
|
|
|
console.error(e);
|
|
|
@@ -316,7 +491,7 @@ async function onReopen(row) {
|
|
|
async function onCharge(row) {
|
|
|
chargingId.value = row.id;
|
|
|
try {
|
|
|
- await chargeReceivableMe(row.id);
|
|
|
+ await chargeReceivableMe(row.realId);
|
|
|
await loadData();
|
|
|
} catch (e) {
|
|
|
console.error(e);
|