feat: gate treasury as an org module and group money inputs

Link optional invoice and cheque settlements to cash accounts, and let superadmin enable the finance module per organization.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-18 19:33:09 +03:30
co-authored by Cursor
parent ab538b8304
commit 8981a552fc
25 changed files with 509 additions and 33 deletions
@@ -4,6 +4,7 @@ import axios from 'axios';
import { api } from '@/lib/api';
import type { FinancialAccountDto, FinancialCategoryDto, FinancialDocumentDto } from '@mento/shared';
import { UiButton, UiCard, UiField, UiInput, UiPageHeader } from '@/components/ui';
import MoneyInput from '@/components/MoneyInput.vue';
import TreasuryNav from '@/components/treasury/TreasuryNav.vue';
import { toman } from './treasury-format';
@@ -90,6 +91,19 @@ async function createDoc() {
}
}
async function upload(id: string, event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
const body = new FormData();
body.append('file', file);
try {
await api.post(`/treasury/documents/${id}/attachments`, body);
ok.value = 'پیوست ذخیره شد.';
} catch (e) {
error.value = apiMessage(e);
}
}
async function voidDoc(id: string) {
if (!window.confirm('این سند و گردش متصل به آن باطل شود؟')) return;
try {
@@ -120,7 +134,7 @@ onMounted(() => {
<UiCard title="سند جدید">
<div class="grid">
<UiField label="عنوان"><UiInput v-model="form.title" /></UiField>
<UiField label="مبلغ کل"><UiInput v-model="form.amount" /></UiField>
<UiField label="مبلغ کل"><MoneyInput v-model="form.amount" :decimals="0" /></UiField>
<UiField label="دسته‌بندی">
<select v-model="form.categoryId" class="select">
<option value="">بدون دسته</option>
@@ -128,7 +142,7 @@ onMounted(() => {
</select>
</UiField>
<UiField label="طرف حساب"><UiInput v-model="form.partyName" /></UiField>
<UiField :label="settleLabel"><UiInput v-model="form.settleAmount" /></UiField>
<UiField :label="settleLabel"><MoneyInput v-model="form.settleAmount" :decimals="0" /></UiField>
<UiField label="حساب">
<select v-model="form.accountId" class="select">
<option value="">انتخاب حساب</option>
@@ -162,6 +176,10 @@ onMounted(() => {
<div class="side">
<b>{{ toman(row.amount) }}</b>
<button v-if="row.status !== 'void'" type="button" @click="voidDoc(row.id)">ابطال</button>
<label v-if="row.status !== 'void'" class="file">
پیوست
<input type="file" @change="upload(row.id, $event)" />
</label>
</div>
</article>
<p v-if="!rows.length" class="muted">سندی نیست.</p>
@@ -177,7 +195,9 @@ onMounted(() => {
.list article { display: flex; justify-content: space-between; gap: 0.75rem; background: #fff; border: 1px solid #e7eef2; border-radius: 14px; padding: 0.8rem 1rem; }
.list small, .muted { color: #64748b; }
.side { display: grid; justify-items: end; gap: 0.3rem; }
.side button { border: 0; background: transparent; color: #b91c1c; cursor: pointer; font: inherit; }
.side button, .file { border: 0; background: transparent; color: #b91c1c; cursor: pointer; font: inherit; }
.file { color: #0f766e; font-weight: 700; }
.file input { display: none; }
.err { color: #b91c1c; font-weight: 700; }
.ok { color: #0f766e; font-weight: 700; }
</style>