2026-09-11 01:09:59 +03:30
|
|
|
|
<script setup lang="ts">
|
2026-09-11 11:34:49 +03:30
|
|
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
2026-10-05 11:56:49 +03:30
|
|
|
|
import TableColumnsMenu from '@/components/TableColumnsMenu.vue';
|
2026-09-11 01:09:59 +03:30
|
|
|
|
import { RouterLink } from 'vue-router';
|
|
|
|
|
|
import axios from 'axios';
|
|
|
|
|
|
import { api } from '@/lib/api';
|
2026-09-11 11:34:49 +03:30
|
|
|
|
import { CURRENCY_OPTIONS, formatMoney, parseMoneyNumber } from '@/lib/money';
|
|
|
|
|
|
import { formatJalaliNumeric, formatLocalDateInput } from '@/lib/jalali';
|
|
|
|
|
|
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
|
|
|
|
|
import MoneyInput from '@/components/MoneyInput.vue';
|
2026-09-11 01:09:59 +03:30
|
|
|
|
import UiButton from '@/components/ui/UiButton.vue';
|
2026-09-11 11:34:49 +03:30
|
|
|
|
import UiInput from '@/components/ui/UiInput.vue';
|
|
|
|
|
|
import UiModal from '@/components/ui/UiModal.vue';
|
|
|
|
|
|
import type {
|
|
|
|
|
|
ChequeDirection,
|
|
|
|
|
|
ChequeDto,
|
|
|
|
|
|
ChequeStatus,
|
|
|
|
|
|
CreateChequeRequest,
|
|
|
|
|
|
OrganizationDto,
|
|
|
|
|
|
PaginatedResponse,
|
|
|
|
|
|
PersonDto,
|
|
|
|
|
|
} from '@mento/shared';
|
2026-09-11 01:09:59 +03:30
|
|
|
|
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const error = ref('');
|
|
|
|
|
|
const items = ref<ChequeDto[]>([]);
|
|
|
|
|
|
const status = ref('');
|
|
|
|
|
|
const q = ref('');
|
|
|
|
|
|
const savingId = ref('');
|
2026-10-18 19:59:24 +03:30
|
|
|
|
const clearOpen = ref(false);
|
|
|
|
|
|
const clearRow = ref<ChequeDto | null>(null);
|
|
|
|
|
|
const clearAccountId = ref('');
|
|
|
|
|
|
const clearAccounts = ref<Array<{ id: string; name: string }>>([]);
|
2026-09-11 11:34:49 +03:30
|
|
|
|
const tab = ref<ChequeDirection>('in');
|
2026-09-11 01:09:59 +03:30
|
|
|
|
|
2026-09-11 11:34:49 +03:30
|
|
|
|
const formOpen = ref(false);
|
|
|
|
|
|
const formDirection = ref<ChequeDirection>('in');
|
|
|
|
|
|
const saving = ref(false);
|
|
|
|
|
|
const formError = ref('');
|
|
|
|
|
|
const people = ref<PersonDto[]>([]);
|
|
|
|
|
|
const enabledCurrencies = ref<string[]>(['IRR']);
|
|
|
|
|
|
const personId = ref('');
|
|
|
|
|
|
const amount = ref('');
|
|
|
|
|
|
const currency = ref('IRR');
|
|
|
|
|
|
const chequeNumber = ref('');
|
|
|
|
|
|
const chequeBank = ref('');
|
|
|
|
|
|
const dueDate = ref(formatLocalDateInput(new Date()));
|
|
|
|
|
|
const formStatus = ref<ChequeStatus>('pending');
|
|
|
|
|
|
const notes = ref('');
|
2026-09-11 01:09:59 +03:30
|
|
|
|
|
2026-09-11 11:34:49 +03:30
|
|
|
|
function statusLabelFor(direction: string): Record<ChequeStatus, string> {
|
|
|
|
|
|
return {
|
|
|
|
|
|
pending: 'در جریان',
|
|
|
|
|
|
cleared: direction === 'out' ? 'پرداخت شد' : 'وصول شد',
|
|
|
|
|
|
bounced: 'برگشت',
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const formStatusLabel = computed(() => statusLabelFor(formDirection.value));
|
|
|
|
|
|
const isIncomingForm = computed(() => formDirection.value !== 'out');
|
|
|
|
|
|
|
|
|
|
|
|
const currencyOptions = computed(() => {
|
|
|
|
|
|
const codes = enabledCurrencies.value.length ? [...enabledCurrencies.value] : [currency.value];
|
|
|
|
|
|
if (currency.value && !codes.includes(currency.value)) codes.push(currency.value);
|
|
|
|
|
|
return codes.map((code) => {
|
|
|
|
|
|
const opt = CURRENCY_OPTIONS.find((c) => c.code === code);
|
|
|
|
|
|
return { code, label: opt ? `${opt.suffix} - ${opt.code}` : code };
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function axiosMessage(e: unknown, fallback: string) {
|
|
|
|
|
|
if (axios.isAxiosError(e)) {
|
|
|
|
|
|
const msg = (e.response?.data as { message?: string | string[] })?.message;
|
|
|
|
|
|
return Array.isArray(msg) ? msg.join('، ') : (msg ?? fallback);
|
|
|
|
|
|
}
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function personLabel(p: PersonDto) {
|
|
|
|
|
|
return p.company?.name ? `${p.fullName} — ${p.company.name}` : p.fullName;
|
|
|
|
|
|
}
|
2026-09-11 01:09:59 +03:30
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<ChequeDto[]>('/cheques', {
|
2026-09-11 11:34:49 +03:30
|
|
|
|
params: {
|
|
|
|
|
|
status: status.value || undefined,
|
|
|
|
|
|
q: q.value || undefined,
|
|
|
|
|
|
direction: tab.value,
|
|
|
|
|
|
},
|
2026-09-11 01:09:59 +03:30
|
|
|
|
});
|
|
|
|
|
|
items.value = data;
|
|
|
|
|
|
} catch (e) {
|
2026-09-11 11:34:49 +03:30
|
|
|
|
error.value = axiosMessage(e, 'بارگذاری چکها ناموفق بود.');
|
2026-09-11 01:09:59 +03:30
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-18 19:59:24 +03:30
|
|
|
|
async function confirmClear() {
|
|
|
|
|
|
if (!clearRow.value || !clearAccountId.value) return;
|
|
|
|
|
|
savingId.value = clearRow.value.id;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.patch<ChequeDto>(`/cheques/${clearRow.value.id}`, {
|
|
|
|
|
|
status: 'cleared',
|
|
|
|
|
|
financialAccountId: clearAccountId.value,
|
|
|
|
|
|
});
|
|
|
|
|
|
items.value = items.value.map((item) => (item.id === data.id ? data : item));
|
|
|
|
|
|
clearOpen.value = false;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'وصول چک ناموفق بود.';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
savingId.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-11 01:09:59 +03:30
|
|
|
|
async function setStatus(row: ChequeDto, next: ChequeStatus) {
|
|
|
|
|
|
if (row.status === next) return;
|
2026-10-18 19:33:09 +03:30
|
|
|
|
if (next === 'cleared' && row.status !== 'cleared') {
|
|
|
|
|
|
const accountsRes = await api
|
|
|
|
|
|
.get<Array<{ id: string; name: string; isActive: boolean }>>('/treasury/accounts')
|
|
|
|
|
|
.catch(() => null);
|
|
|
|
|
|
if (!accountsRes) {
|
|
|
|
|
|
savingId.value = row.id;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.patch<ChequeDto>(`/cheques/${row.id}`, { status: next });
|
|
|
|
|
|
items.value = items.value.map((item) => (item.id === row.id ? data : item));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'تغییر وضعیت چک ناموفق بود.';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
savingId.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-10-18 19:59:24 +03:30
|
|
|
|
const active = accountsRes.data.filter((item) => item.isActive);
|
2026-10-18 19:33:09 +03:30
|
|
|
|
if (!active.length) {
|
|
|
|
|
|
error.value = 'برای وصول چک ابتدا یک حساب صندوق بسازید.';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-10-18 19:59:24 +03:30
|
|
|
|
clearAccounts.value = active.map((item) => ({ id: item.id, name: item.name }));
|
|
|
|
|
|
clearAccountId.value = active[0]!.id;
|
|
|
|
|
|
clearRow.value = row;
|
|
|
|
|
|
clearOpen.value = true;
|
|
|
|
|
|
return;
|
2026-10-18 19:33:09 +03:30
|
|
|
|
}
|
2026-09-11 01:09:59 +03:30
|
|
|
|
savingId.value = row.id;
|
|
|
|
|
|
try {
|
2026-10-18 19:59:24 +03:30
|
|
|
|
const { data } = await api.patch<ChequeDto>(`/cheques/${row.id}`, { status: next });
|
2026-09-11 01:09:59 +03:30
|
|
|
|
items.value = items.value.map((item) => (item.id === row.id ? data : item));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'تغییر وضعیت چک ناموفق بود.';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
savingId.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-11 11:34:49 +03:30
|
|
|
|
async function loadFormOptions() {
|
|
|
|
|
|
const [peopleRes, orgRes] = await Promise.all([
|
|
|
|
|
|
api.get<PaginatedResponse<PersonDto>>('/people', { params: { limit: 100 } }),
|
|
|
|
|
|
api.get<OrganizationDto>('/organizations/current'),
|
|
|
|
|
|
]);
|
|
|
|
|
|
people.value = peopleRes.data.items;
|
|
|
|
|
|
enabledCurrencies.value = orgRes.data.enabledCurrencies?.length
|
|
|
|
|
|
? orgRes.data.enabledCurrencies
|
|
|
|
|
|
: [orgRes.data.baseCurrency || 'IRR'];
|
|
|
|
|
|
if (!currency.value || !enabledCurrencies.value.includes(currency.value)) {
|
|
|
|
|
|
currency.value = orgRes.data.baseCurrency || 'IRR';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function openForm(direction: ChequeDirection) {
|
|
|
|
|
|
formDirection.value = direction;
|
|
|
|
|
|
formError.value = '';
|
|
|
|
|
|
personId.value = '';
|
|
|
|
|
|
amount.value = '';
|
|
|
|
|
|
chequeNumber.value = '';
|
|
|
|
|
|
chequeBank.value = '';
|
|
|
|
|
|
dueDate.value = formatLocalDateInput(new Date());
|
|
|
|
|
|
formStatus.value = 'pending';
|
|
|
|
|
|
notes.value = '';
|
|
|
|
|
|
formOpen.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await loadFormOptions();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
formError.value = 'بارگذاری اشخاص ناموفق بود.';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitForm() {
|
|
|
|
|
|
const parsedAmount = parseMoneyNumber(String(amount.value ?? ''), 0);
|
|
|
|
|
|
if (!personId.value) {
|
|
|
|
|
|
formError.value = 'شخص را انتخاب کنید.';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (parsedAmount == null || parsedAmount <= 0) {
|
|
|
|
|
|
formError.value = 'مبلغ چک را وارد کنید.';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!chequeNumber.value.trim() || !chequeBank.value.trim() || !dueDate.value) {
|
|
|
|
|
|
formError.value = 'شماره، بانک و سررسید چک لازم است.';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
saving.value = true;
|
|
|
|
|
|
formError.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const body: CreateChequeRequest = {
|
|
|
|
|
|
personId: personId.value,
|
|
|
|
|
|
amount: parsedAmount,
|
|
|
|
|
|
currency: currency.value,
|
|
|
|
|
|
number: chequeNumber.value.trim(),
|
|
|
|
|
|
bank: chequeBank.value.trim(),
|
|
|
|
|
|
dueDate: new Date(dueDate.value).toISOString(),
|
|
|
|
|
|
status: formStatus.value,
|
|
|
|
|
|
direction: formDirection.value,
|
|
|
|
|
|
notes: notes.value.trim() || undefined,
|
|
|
|
|
|
};
|
|
|
|
|
|
await api.post<ChequeDto>('/cheques', body);
|
|
|
|
|
|
formOpen.value = false;
|
|
|
|
|
|
tab.value = formDirection.value;
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
formError.value = axiosMessage(e, 'ثبت چک ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saving.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
watch(tab, () => {
|
|
|
|
|
|
void load();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-11 01:09:59 +03:30
|
|
|
|
onMounted(load);
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<section class="page" dir="rtl">
|
|
|
|
|
|
<header class="head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1>چکها</h1>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<p class="sub">دفتر چکهای دریافتی و پرداختی. وضعیت فقط برای پیگیری است</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="head-actions">
|
|
|
|
|
|
<UiButton type="button" @click="openForm('in')">دریافت چک</UiButton>
|
|
|
|
|
|
<UiButton variant="ghost" type="button" @click="openForm('out')">پرداخت چک</UiButton>
|
|
|
|
|
|
<UiButton variant="ghost" type="button" :disabled="loading" @click="load">
|
|
|
|
|
|
بروزرسانی
|
|
|
|
|
|
</UiButton>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<div class="tabs">
|
|
|
|
|
|
<button type="button" :class="{ on: tab === 'in' }" @click="tab = 'in'">دریافتی</button>
|
|
|
|
|
|
<button type="button" :class="{ on: tab === 'out' }" @click="tab = 'out'">پرداختی</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-11 01:09:59 +03:30
|
|
|
|
<div class="toolbar">
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<input
|
|
|
|
|
|
v-model="q"
|
|
|
|
|
|
type="search"
|
|
|
|
|
|
placeholder="شماره چک، بانک، فاکتور یا شخص"
|
|
|
|
|
|
@keyup.enter="load"
|
|
|
|
|
|
/>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
<select v-model="status" @change="load">
|
|
|
|
|
|
<option value="">همه وضعیتها</option>
|
|
|
|
|
|
<option value="pending">در جریان</option>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<option value="cleared">{{ tab === 'out' ? 'پرداخت شد' : 'وصول شد' }}</option>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
<option value="bounced">برگشت</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<UiButton type="button" @click="load">اعمال</UiButton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="error" class="error">{{ error }}</p>
|
|
|
|
|
|
<p v-else-if="loading && !items.length" class="muted">در حال بارگذاری…</p>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<p v-else-if="!items.length" class="muted">
|
|
|
|
|
|
{{
|
|
|
|
|
|
tab === 'out'
|
|
|
|
|
|
? 'چک پرداختی ثبت نشده. از «پرداخت چک» میتوانید به شخص دیگری چک بدهید.'
|
|
|
|
|
|
: 'چک دریافتی ثبت نشده. از «دریافت چک» میتوانید بدون فاکتور ثبت کنید.'
|
|
|
|
|
|
}}
|
|
|
|
|
|
</p>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
<div v-else class="table-wrap">
|
2026-10-05 11:56:49 +03:30
|
|
|
|
<div class="tc-row">
|
|
|
|
|
|
<TableColumnsMenu table-key="cheques" align="end" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<table v-table-cols="'cheques'">
|
2026-09-11 01:09:59 +03:30
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>شماره</th>
|
|
|
|
|
|
<th>بانک</th>
|
|
|
|
|
|
<th>سررسید</th>
|
|
|
|
|
|
<th>مبلغ</th>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<th v-if="tab === 'in'">فاکتور</th>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
<th>طرفحساب</th>
|
|
|
|
|
|
<th>وضعیت</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<tr v-for="row in items" :key="row.id">
|
2026-09-11 01:09:59 +03:30
|
|
|
|
<td>{{ row.number }}</td>
|
|
|
|
|
|
<td>{{ row.bank }}</td>
|
|
|
|
|
|
<td>{{ formatJalaliNumeric(new Date(row.dueDate)) }}</td>
|
|
|
|
|
|
<td>{{ formatMoney(row.amount, row.currency) }}</td>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<td v-if="tab === 'in'">
|
|
|
|
|
|
<RouterLink
|
|
|
|
|
|
v-if="row.invoiceId"
|
|
|
|
|
|
:to="{ name: 'invoice-detail', params: { id: row.invoiceId } }"
|
|
|
|
|
|
>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
{{ row.invoiceNumber }}
|
|
|
|
|
|
</RouterLink>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<span v-else class="muted">بدون فاکتور</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<RouterLink
|
|
|
|
|
|
v-if="row.personId"
|
|
|
|
|
|
:to="{ name: 'person-detail', params: { id: row.personId } }"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ row.personName || '—' }}
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
<span v-else>{{ row.personName || '—' }}</span>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
</td>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<select
|
|
|
|
|
|
:value="row.status"
|
|
|
|
|
|
:disabled="savingId === row.id"
|
|
|
|
|
|
@change="setStatus(row, ($event.target as HTMLSelectElement).value as ChequeStatus)"
|
|
|
|
|
|
>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<option value="pending">{{ statusLabelFor(row.direction).pending }}</option>
|
|
|
|
|
|
<option value="cleared">{{ statusLabelFor(row.direction).cleared }}</option>
|
|
|
|
|
|
<option value="bounced">{{ statusLabelFor(row.direction).bounced }}</option>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
</select>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
2026-09-11 11:34:49 +03:30
|
|
|
|
|
2026-10-18 19:59:24 +03:30
|
|
|
|
<UiModal :open="clearOpen" title="حساب وصول چک" @close="clearOpen = false">
|
|
|
|
|
|
<p class="hint">چک {{ clearRow?.number }} در کدام حساب صندوق بنشیند؟</p>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>حساب</span>
|
|
|
|
|
|
<select v-model="clearAccountId">
|
|
|
|
|
|
<option v-for="account in clearAccounts" :key="account.id" :value="account.id">
|
|
|
|
|
|
{{ account.name }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<UiButton
|
|
|
|
|
|
:loading="savingId === clearRow?.id"
|
|
|
|
|
|
@click="confirmClear"
|
|
|
|
|
|
>
|
|
|
|
|
|
ثبت وصول
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</UiModal>
|
|
|
|
|
|
|
2026-09-11 11:34:49 +03:30
|
|
|
|
<UiModal
|
|
|
|
|
|
:open="formOpen"
|
|
|
|
|
|
:title="isIncomingForm ? 'دریافت چک' : 'پرداخت چک'"
|
|
|
|
|
|
@close="formOpen = false"
|
|
|
|
|
|
>
|
|
|
|
|
|
<form class="receive-form" @submit.prevent="submitForm">
|
|
|
|
|
|
<p class="hint">
|
|
|
|
|
|
{{
|
|
|
|
|
|
isIncomingForm
|
|
|
|
|
|
? 'چک را از یک شخص بگیرید؛ لازم نیست به فاکتور وصل باشد.'
|
|
|
|
|
|
: 'چکی که به شخص دیگری میپردازید را ثبت کنید.'
|
|
|
|
|
|
}}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<p v-if="formError" class="error">{{ formError }}</p>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>{{ isIncomingForm ? 'شخص' : 'گیرنده' }} <em>*</em></span>
|
|
|
|
|
|
<select v-model="personId" required>
|
|
|
|
|
|
<option value="">انتخاب شخص</option>
|
|
|
|
|
|
<option v-for="p in people" :key="p.id" :value="p.id">
|
|
|
|
|
|
{{ personLabel(p) }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div class="row-2">
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>مبلغ <em>*</em></span>
|
|
|
|
|
|
<MoneyInput v-model="amount" :decimals="0" placeholder="مبلغ چک" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label v-if="currencyOptions.length > 1" class="field">
|
|
|
|
|
|
<span>ارز</span>
|
|
|
|
|
|
<select v-model="currency">
|
|
|
|
|
|
<option v-for="opt in currencyOptions" :key="opt.code" :value="opt.code">
|
|
|
|
|
|
{{ opt.label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row-2">
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>شماره چک <em>*</em></span>
|
2026-09-11 12:40:38 +03:30
|
|
|
|
<UiInput v-model="chequeNumber" placeholder="شماره چک" :maxlength="80" required />
|
2026-09-11 11:34:49 +03:30
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>بانک <em>*</em></span>
|
2026-09-11 12:40:38 +03:30
|
|
|
|
<UiInput v-model="chequeBank" placeholder="نام بانک" :maxlength="120" required />
|
2026-09-11 11:34:49 +03:30
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row-2">
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>سررسید <em>*</em></span>
|
|
|
|
|
|
<JalaliDateInput v-model="dueDate" required placeholder="سررسید چک" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>وضعیت</span>
|
|
|
|
|
|
<select v-model="formStatus">
|
|
|
|
|
|
<option value="pending">{{ formStatusLabel.pending }}</option>
|
|
|
|
|
|
<option value="cleared">{{ formStatusLabel.cleared }}</option>
|
|
|
|
|
|
<option value="bounced">{{ formStatusLabel.bounced }}</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>یادداشت</span>
|
|
|
|
|
|
<textarea v-model="notes" rows="3" maxlength="2000" placeholder="اختیاری" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<UiButton type="button" :disabled="saving" @click="submitForm">
|
|
|
|
|
|
{{ saving ? 'در حال ثبت…' : isIncomingForm ? 'ثبت دریافت' : 'ثبت پرداخت' }}
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
<UiButton variant="ghost" type="button" :disabled="saving" @click="formOpen = false">
|
|
|
|
|
|
انصراف
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</UiModal>
|
2026-09-11 01:09:59 +03:30
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.page { display: grid; gap: 1rem; }
|
2026-09-11 11:34:49 +03:30
|
|
|
|
.head { display: flex; justify-content: space-between; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
|
|
|
|
|
|
.head-actions { display: flex; gap: 0.45rem; flex-wrap: wrap; }
|
2026-09-11 01:09:59 +03:30
|
|
|
|
h1 { margin: 0; font-size: 1.25rem; }
|
|
|
|
|
|
.sub, .muted { margin: 0.25rem 0 0; color: var(--muted); font-size: 0.88rem; }
|
|
|
|
|
|
.error { color: var(--danger, #dc2626); font-weight: 600; }
|
2026-09-11 11:34:49 +03:30
|
|
|
|
.hint { margin: 0 0 0.75rem; color: var(--muted); font-size: 0.88rem; }
|
|
|
|
|
|
.tabs {
|
|
|
|
|
|
display: flex; gap: 0.35rem; overflow-x: auto; border-bottom: 1px solid var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.tabs button {
|
|
|
|
|
|
border: 0; background: transparent; padding: 0.65rem 0.85rem; cursor: pointer;
|
|
|
|
|
|
color: #64748b; font-weight: 700; border-bottom: 2px solid transparent; white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tabs button.on { color: var(--teal); border-bottom-color: var(--teal); }
|
2026-09-11 01:09:59 +03:30
|
|
|
|
.toolbar {
|
|
|
|
|
|
display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.toolbar input, .toolbar select {
|
|
|
|
|
|
min-height: var(--control-h); border: 1px solid var(--line); border-radius: var(--radius);
|
|
|
|
|
|
padding: 0 0.75rem; background: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
.table-wrap { overflow: auto; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); }
|
|
|
|
|
|
table { width: 100%; border-collapse: collapse; min-width: 720px; }
|
|
|
|
|
|
th, td { padding: 0.7rem 0.65rem; border-bottom: 1px solid #eef2f7; text-align: right; }
|
|
|
|
|
|
th { background: #f8fafc; color: #64748b; font-size: 0.82rem; }
|
|
|
|
|
|
select {
|
|
|
|
|
|
min-height: var(--control-h); border: 1px solid var(--line); border-radius: var(--radius);
|
|
|
|
|
|
padding: 0 0.55rem; background: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
a { color: #0f766e; font-weight: 700; }
|
2026-09-11 11:34:49 +03:30
|
|
|
|
.receive-form { display: grid; gap: 0.85rem; }
|
|
|
|
|
|
.row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
|
|
|
|
|
|
.field { display: grid; gap: 0.35rem; }
|
|
|
|
|
|
.field > span { color: var(--muted); font-size: 0.86rem; font-weight: 600; }
|
|
|
|
|
|
.field em { color: #ef4444; font-style: normal; }
|
|
|
|
|
|
.field select,
|
|
|
|
|
|
.field textarea,
|
|
|
|
|
|
.field :deep(.money-input) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: var(--control-h);
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 0.55rem 0.75rem;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field textarea { min-height: 5rem; resize: vertical; }
|
|
|
|
|
|
@media (max-width: 640px) {
|
|
|
|
|
|
.row-2 { grid-template-columns: 1fr; }
|
|
|
|
|
|
}
|
2026-09-11 01:09:59 +03:30
|
|
|
|
</style>
|