fix(finance): shared treasury layout so tab switches stop jumping
Keep title and nav mounted in TreasuryLayout with stable header height, and finish Ant form chrome (MoneyInput, Jalali) on finance pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,13 +16,15 @@ import {
|
||||
Row as ARow,
|
||||
Select as ASelect,
|
||||
Typography,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
import { api } from '@/lib/api';
|
||||
import type { FinancialAccountDto, OrgUserDto } from '@mento/shared';
|
||||
import TreasuryNav from '@/components/treasury/TreasuryNav.vue';
|
||||
import MoneyInput from '@/components/MoneyInput.vue';
|
||||
import { toman } from './treasury-format';
|
||||
import { parseMoneyNumber } from '@/lib/money';
|
||||
|
||||
const { Title: ATypographyTitle, Text: ATypographyText } = Typography;
|
||||
const { Text: ATypographyText } = Typography;
|
||||
const AListItem = AList.Item;
|
||||
const AListItemMeta = AList.Item.Meta;
|
||||
const ASelectOption = ASelect.Option;
|
||||
@@ -41,8 +43,16 @@ const users = ref<OrgUserDto[]>([]);
|
||||
const accounts = ref<FinancialAccountDto[]>([]);
|
||||
const error = ref('');
|
||||
const ok = ref('');
|
||||
const form = ref({ userId: '', period: '', baseAmount: '', benefits: '', bonus: '', deductions: '' });
|
||||
const pay = ref({ id: '', amount: '', accountId: '' });
|
||||
const saving = ref(false);
|
||||
const form = ref({
|
||||
userId: undefined as string | undefined,
|
||||
period: '',
|
||||
baseAmount: '',
|
||||
benefits: '',
|
||||
bonus: '',
|
||||
deductions: '',
|
||||
});
|
||||
const pay = ref({ id: '', amount: '', accountId: undefined as string | undefined });
|
||||
|
||||
function apiMessage(e: unknown) {
|
||||
if (!axios.isAxiosError(e)) return 'عملیات ناموفق بود.';
|
||||
@@ -50,6 +60,10 @@ function apiMessage(e: unknown) {
|
||||
return Array.isArray(msg) ? msg.join('، ') : msg || 'عملیات ناموفق بود.';
|
||||
}
|
||||
|
||||
function onFinishFailed() {
|
||||
message.warning('لطفاً فیلدهای الزامی را کامل کنید.');
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const [payroll, people, acc] = await Promise.all([
|
||||
api.get<PayrollRow[]>('/treasury/payroll'),
|
||||
@@ -63,33 +77,66 @@ async function load() {
|
||||
|
||||
async function createRow() {
|
||||
error.value = '';
|
||||
ok.value = '';
|
||||
saving.value = true;
|
||||
try {
|
||||
await api.post('/treasury/payroll', form.value);
|
||||
const baseAmount = parseMoneyNumber(String(form.value.baseAmount ?? ''), 0) ?? 0;
|
||||
await api.post('/treasury/payroll', {
|
||||
userId: form.value.userId,
|
||||
period: form.value.period,
|
||||
baseAmount,
|
||||
benefits: parseMoneyNumber(String(form.value.benefits ?? ''), 0) ?? 0,
|
||||
bonus: parseMoneyNumber(String(form.value.bonus ?? ''), 0) ?? 0,
|
||||
deductions: parseMoneyNumber(String(form.value.deductions ?? ''), 0) ?? 0,
|
||||
});
|
||||
form.value = {
|
||||
userId: undefined,
|
||||
period: '',
|
||||
baseAmount: '',
|
||||
benefits: '',
|
||||
bonus: '',
|
||||
deductions: '',
|
||||
};
|
||||
ok.value = 'حقوق ثبت شد و تا پرداخت، موجودی حساب تغییر نمیکند.';
|
||||
await load();
|
||||
} catch (e) {
|
||||
error.value = apiMessage(e);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function payRow() {
|
||||
error.value = '';
|
||||
ok.value = '';
|
||||
saving.value = true;
|
||||
try {
|
||||
const amount = parseMoneyNumber(String(pay.value.amount ?? ''), 0);
|
||||
if (amount == null || amount <= 0) {
|
||||
error.value = 'مبلغ پرداخت معتبر نیست.';
|
||||
return;
|
||||
}
|
||||
await api.post(`/treasury/payroll/${pay.value.id}/pay`, {
|
||||
amount: pay.value.amount,
|
||||
amount,
|
||||
accountId: pay.value.accountId,
|
||||
clientKey: crypto.randomUUID(),
|
||||
});
|
||||
ok.value = 'پرداخت حقوق از حساب ثبت شد.';
|
||||
pay.value = { id: '', amount: '', accountId: '' };
|
||||
pay.value = { id: '', amount: '', accountId: undefined };
|
||||
await load();
|
||||
} catch (e) {
|
||||
error.value = apiMessage(e);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function startPay(row: PayrollRow) {
|
||||
pay.value = { id: row.id, amount: row.remaining, accountId: accounts.value[0]?.id || '' };
|
||||
pay.value = {
|
||||
id: row.id,
|
||||
amount: row.remaining,
|
||||
accountId: accounts.value[0]?.id,
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -100,26 +147,31 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-flex vertical :gap="16">
|
||||
<div>
|
||||
<a-typography-title :level="3" :style="{ margin: 0 }">حقوق و دستمزد</a-typography-title>
|
||||
<a-typography-text type="secondary">فقط مالک و مدیر این صفحه را میبینند.</a-typography-text>
|
||||
</div>
|
||||
|
||||
<TreasuryNav />
|
||||
|
||||
<a-flex vertical :gap="16">
|
||||
<a-alert v-if="error" type="error" show-icon :message="error" />
|
||||
<a-alert v-if="ok" type="success" show-icon :message="ok" />
|
||||
|
||||
<a-card :bordered="false">
|
||||
<a-divider orientation="right">ثبت دوره</a-divider>
|
||||
<a-form layout="vertical" @finish="createRow">
|
||||
<a-form
|
||||
layout="vertical"
|
||||
:model="form"
|
||||
@finish="createRow"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
<a-row :gutter="[16, 0]">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="پرسنل" name="userId">
|
||||
<a-select v-model:value="form.userId" style="width: 100%" placeholder="انتخاب">
|
||||
<a-select-option value="">انتخاب</a-select-option>
|
||||
<a-form-item
|
||||
label="پرسنل"
|
||||
name="userId"
|
||||
:rules="[{ required: true, message: 'پرسنل الزامی است' }]"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="form.userId"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
placeholder="انتخاب"
|
||||
>
|
||||
<a-select-option v-for="user in users" :key="user.id" :value="user.id">
|
||||
{{ user.fullName }}
|
||||
</a-select-option>
|
||||
@@ -127,54 +179,84 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="8">
|
||||
<a-form-item label="دوره" name="period">
|
||||
<a-form-item
|
||||
label="دوره"
|
||||
name="period"
|
||||
:rules="[{ required: true, message: 'دوره الزامی است' }]"
|
||||
>
|
||||
<a-input v-model:value="form.period" placeholder="۱۴۰۵-۰۷" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="8">
|
||||
<a-form-item label="پایه" name="baseAmount">
|
||||
<a-input v-model:value="form.baseAmount" />
|
||||
<a-form-item
|
||||
label="پایه"
|
||||
name="baseAmount"
|
||||
:rules="[{ required: true, message: 'مبلغ پایه الزامی است' }]"
|
||||
>
|
||||
<MoneyInput v-model="form.baseAmount" :decimals="0" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="8">
|
||||
<a-form-item label="مزایا" name="benefits">
|
||||
<a-input v-model:value="form.benefits" />
|
||||
<MoneyInput v-model="form.benefits" :decimals="0" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="8">
|
||||
<a-form-item label="پاداش" name="bonus">
|
||||
<a-input v-model:value="form.bonus" />
|
||||
<MoneyInput v-model="form.bonus" :decimals="0" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="8">
|
||||
<a-form-item label="کسورات" name="deductions">
|
||||
<a-input v-model:value="form.deductions" />
|
||||
<MoneyInput v-model="form.deductions" :decimals="0" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" html-type="submit">ثبت حقوق</a-button>
|
||||
<a-button type="primary" html-type="submit" :loading="saving">ثبت حقوق</a-button>
|
||||
</a-form>
|
||||
|
||||
<template v-if="pay.id">
|
||||
<a-divider orientation="right">پرداخت حقوق</a-divider>
|
||||
<a-form layout="vertical" @finish="payRow">
|
||||
<a-form
|
||||
layout="vertical"
|
||||
:model="pay"
|
||||
@finish="payRow"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
<a-row :gutter="[16, 0]">
|
||||
<a-col :xs="24" :md="8">
|
||||
<a-form-item label="مبلغ" name="amount">
|
||||
<a-input v-model:value="pay.amount" />
|
||||
<a-form-item
|
||||
label="مبلغ"
|
||||
name="amount"
|
||||
:rules="[{ required: true, message: 'مبلغ الزامی است' }]"
|
||||
>
|
||||
<MoneyInput v-model="pay.amount" :decimals="0" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="8">
|
||||
<a-form-item label="حساب" name="accountId">
|
||||
<a-select v-model:value="pay.accountId" style="width: 100%">
|
||||
<a-select-option v-for="account in accounts" :key="account.id" :value="account.id">
|
||||
<a-form-item
|
||||
label="حساب"
|
||||
name="accountId"
|
||||
:rules="[{ required: true, message: 'حساب الزامی است' }]"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="pay.accountId"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
placeholder="انتخاب حساب"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="account in accounts"
|
||||
:key="account.id"
|
||||
:value="account.id"
|
||||
>
|
||||
{{ account.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" html-type="submit">ثبت پرداخت</a-button>
|
||||
<a-button type="primary" html-type="submit" :loading="saving">ثبت پرداخت</a-button>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
@@ -203,6 +285,5 @@ onMounted(() => {
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-flex>
|
||||
</div>
|
||||
</a-flex>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user