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:
@@ -4,6 +4,7 @@ import { useRoute } from 'vue-router';
|
||||
import { api } from '@/lib/api';
|
||||
import type { FinancialAccountDto, FinancialMovementDto } from '@mento/shared';
|
||||
import { UiButton, UiField, UiInput, UiPageHeader } from '@/components/ui';
|
||||
import MoneyInput from '@/components/MoneyInput.vue';
|
||||
import TreasuryNav from '@/components/treasury/TreasuryNav.vue';
|
||||
import { toman } from './treasury-format';
|
||||
import { formatJalaliNumeric } from '@/lib/jalali';
|
||||
@@ -70,7 +71,7 @@ onMounted(async () => {
|
||||
<article><span>خروجی</span><strong>{{ toman(account.periodOut) }}</strong></article>
|
||||
</div>
|
||||
<div v-if="account" class="reconcile">
|
||||
<UiField label="موجودی واقعی"><UiInput v-model="stated" /></UiField>
|
||||
<UiField label="موجودی واقعی"><MoneyInput v-model="stated" :decimals="0" /></UiField>
|
||||
<UiField label="علت اصلاح"><UiInput v-model="reason" /></UiField>
|
||||
<UiButton variant="secondary" @click="reconcile">ثبت مغایرت</UiButton>
|
||||
<UiButton variant="secondary" @click="adjust('in')">اصلاح ورود</UiButton>
|
||||
|
||||
@@ -4,6 +4,7 @@ import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import type { FinancialAccountDto, FinancialAccountType } 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';
|
||||
|
||||
@@ -171,7 +172,7 @@ onMounted(() => {
|
||||
<UiField v-if="form.type === 'bank'" label="نام بانک"><UiInput v-model="form.bankName" /></UiField>
|
||||
<UiField label="شماره یا شناسه"><UiInput v-model="form.accountRef" /></UiField>
|
||||
<UiField label="مسئول"><UiInput v-model="form.responsibleName" /></UiField>
|
||||
<UiField label="موجودی اولیه"><UiInput v-model="form.openingBalance" /></UiField>
|
||||
<UiField label="موجودی اولیه"><MoneyInput v-model="form.openingBalance" :decimals="0" /></UiField>
|
||||
<UiField label="تاریخ شروع"><UiInput v-model="form.openingDate" type="date" /></UiField>
|
||||
<UiField label="توضیحات"><UiInput v-model="form.notes" /></UiField>
|
||||
</div>
|
||||
@@ -192,7 +193,7 @@ onMounted(() => {
|
||||
<option v-for="account in active" :key="`to-${account.id}`" :value="account.id">{{ account.name }}</option>
|
||||
</select>
|
||||
</UiField>
|
||||
<UiField label="مبلغ"><UiInput v-model="transfer.amount" /></UiField>
|
||||
<UiField label="مبلغ"><MoneyInput v-model="transfer.amount" :decimals="0" /></UiField>
|
||||
<UiField label="شرح"><UiInput v-model="transfer.memo" /></UiField>
|
||||
</div>
|
||||
<p class="muted">این انتقال درآمد یا هزینه نیست. اگر موجودی مبدأ کافی نباشد ثبت نمیشود.</p>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -4,6 +4,7 @@ import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import type { FinancialAccountDto, OrgUserDto } 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,10 +91,10 @@ onMounted(() => {
|
||||
</select>
|
||||
</UiField>
|
||||
<UiField label="دوره"><UiInput v-model="form.period" placeholder="۱۴۰۵-۰۷" /></UiField>
|
||||
<UiField label="پایه"><UiInput v-model="form.baseAmount" /></UiField>
|
||||
<UiField label="مزایا"><UiInput v-model="form.benefits" /></UiField>
|
||||
<UiField label="پاداش"><UiInput v-model="form.bonus" /></UiField>
|
||||
<UiField label="کسورات"><UiInput v-model="form.deductions" /></UiField>
|
||||
<UiField label="پایه"><MoneyInput v-model="form.baseAmount" :decimals="0" /></UiField>
|
||||
<UiField label="مزایا"><MoneyInput v-model="form.benefits" :decimals="0" /></UiField>
|
||||
<UiField label="پاداش"><MoneyInput v-model="form.bonus" :decimals="0" /></UiField>
|
||||
<UiField label="کسورات"><MoneyInput v-model="form.deductions" :decimals="0" /></UiField>
|
||||
</div>
|
||||
<UiButton @click="createRow">ثبت حقوق</UiButton>
|
||||
</UiCard>
|
||||
@@ -109,7 +110,7 @@ onMounted(() => {
|
||||
</article>
|
||||
<UiCard v-if="pay.id" title="پرداخت حقوق">
|
||||
<div class="grid">
|
||||
<UiField label="مبلغ"><UiInput v-model="pay.amount" /></UiField>
|
||||
<UiField label="مبلغ"><MoneyInput v-model="pay.amount" :decimals="0" /></UiField>
|
||||
<UiField label="حساب">
|
||||
<select v-model="pay.accountId" class="select">
|
||||
<option v-for="account in accounts" :key="account.id" :value="account.id">{{ account.name }}</option>
|
||||
|
||||
@@ -4,6 +4,7 @@ import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import type { FinancialAccountDto, FinancialReceivableDto } 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';
|
||||
import { formatJalaliNumeric } from '@/lib/jalali';
|
||||
@@ -121,8 +122,8 @@ onMounted(() => {
|
||||
<div class="grid">
|
||||
<UiField label="عنوان"><UiInput v-model="form.title" /></UiField>
|
||||
<UiField label="طرف حساب"><UiInput v-model="form.partyName" /></UiField>
|
||||
<UiField label="مبلغ کل"><UiInput v-model="form.totalAmount" /></UiField>
|
||||
<UiField label="پیشپرداخت"><UiInput v-model="form.prepaidAmount" /></UiField>
|
||||
<UiField label="مبلغ کل"><MoneyInput v-model="form.totalAmount" :decimals="0" /></UiField>
|
||||
<UiField label="پیشپرداخت"><MoneyInput v-model="form.prepaidAmount" :decimals="0" /></UiField>
|
||||
<UiField label="حساب پیشپرداخت">
|
||||
<select v-model="form.accountId" class="select">
|
||||
<option value="">انتخاب</option>
|
||||
@@ -141,7 +142,7 @@ onMounted(() => {
|
||||
<label class="check"><input v-model="custom" type="checkbox" /> اقساط نامساوی</label>
|
||||
<div v-if="custom" class="custom">
|
||||
<div v-for="(line, index) in customLines" :key="index" class="grid">
|
||||
<UiField label="مبلغ قسط"><UiInput v-model="line.amount" /></UiField>
|
||||
<UiField label="مبلغ قسط"><MoneyInput v-model="line.amount" :decimals="0" /></UiField>
|
||||
<UiField label="سررسید"><UiInput v-model="line.dueDate" type="date" /></UiField>
|
||||
</div>
|
||||
<UiButton variant="secondary" @click="customLines.push({ amount: '', dueDate: '' })">قسط دیگر</UiButton>
|
||||
@@ -191,7 +192,7 @@ onMounted(() => {
|
||||
|
||||
<UiCard v-if="collect.installmentId" title="دریافت قسط">
|
||||
<div class="grid">
|
||||
<UiField label="مبلغ"><UiInput v-model="collect.amount" /></UiField>
|
||||
<UiField label="مبلغ"><MoneyInput v-model="collect.amount" :decimals="0" /></UiField>
|
||||
<UiField label="حساب مقصد">
|
||||
<select v-model="collect.accountId" class="select">
|
||||
<option v-for="account in accounts" :key="account.id" :value="account.id">{{ account.name }}</option>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { onMounted, ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
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';
|
||||
import { formatJalaliNumeric } from '@/lib/jalali';
|
||||
@@ -51,7 +52,7 @@ onMounted(() => {
|
||||
<option value="income">درآمد</option>
|
||||
</select>
|
||||
</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.interval" class="select">
|
||||
<option value="monthly">ماهانه</option>
|
||||
|
||||
Reference in New Issue
Block a user