@@ -13,24 +13,42 @@ import {
Input as AInput ,
Row as ARow ,
Select as ASelect ,
Space as ASpace ,
Table as ATable ,
Tooltip as ATooltip ,
Typography ,
message ,
theme ,
} from 'ant-design-vue' ;
import { EditOutlined , WalletOutlined } from '@ant-design/icons-vue' ;
import type { TableColumnsType } from 'ant-design-vue' ;
import { api } from '@/lib/api' ;
import type { FinancialAccountDto , OrgUserDto } from '@mento/shared' ;
import MoneyInput from '@/components/MoneyInput.vue' ;
import { formatJalaliNumeric } from '@/lib/jalali ';
import JalaliDateInput from '@/components/JalaliDateInput.vue ';
import {
addJalaliMonths ,
dateToJalali ,
formatLocalDateInput ,
jalaliMonthLength ,
jalaliMonthName ,
jalaliToDate ,
parseLocalInput ,
toFaDigits ,
formatJalaliNumeric ,
} from '@/lib/jalali' ;
import { toman } from './treasury-format' ;
import { parseMoneyNumber } from '@/lib/money' ;
const { Text : ATypographyText } = Typography ;
const { useToken } = theme ;
const { token } = useToken ( ) ;
type PayrollPayment = {
id : string ;
amount : string ;
occurredAt : string ;
accountId ? : string ;
accountName : string ;
} ;
@@ -41,8 +59,11 @@ type PayrollRow = {
period : string ;
baseAmount : string ;
benefits : string ;
benefitsReason ? : string | null ;
bonus : string ;
bonusReason ? : string | null ;
deductions : string ;
deductionsReason ? : string | null ;
payable : string ;
paidAmount : string ;
remaining : string ;
@@ -63,17 +84,24 @@ const accounts = ref<FinancialAccountDto[]>([]);
const error = ref ( '' ) ;
const ok = ref ( '' ) ;
const saving = ref ( false ) ;
const todayJalali = dateToJalali ( new Date ( ) ) ;
const periodDate = ref ( formatLocalDateInput ( jalaliToDate ( todayJalali . jy , todayJalali . jm , 1 ) ) ) ;
const form = ref ( {
userId : undefined as string | undefined ,
period : '' ,
baseAmount : '' ,
benefits : '' ,
benefitsReason : '' ,
bonus : '' ,
bonusReason : '' ,
deductions : '' ,
deductionsReason : '' ,
} ) ;
const pay = ref ( { id : '' , amount : '' , accountId : undefined as string | undefined } ) ;
const editingId = ref ( '' ) ;
const editingPaymentId = ref ( '' ) ;
const filterUserId = ref < string | undefined > ( undefined ) ;
const filterPeriod = ref ( '' ) ;
const filterYear = ref < number | undefined > ( undefined ) ;
const filterMonth = ref < number | undefined > ( undefined ) ;
const filterStatus = ref ( 'all' ) ;
const filterMin = ref ( '' ) ;
const filterMax = ref ( '' ) ;
@@ -90,6 +118,30 @@ const userOptions = computed(() =>
users . value . map ( ( user ) => ( { value : user . id , label : user . fullName } ) ) ,
) ;
const monthOptions = computed ( ( ) =>
Array . from ( { length : 12 } , ( _ , index ) => ( {
value : index + 1 ,
label : jalaliMonthName ( index + 1 ) ,
} ) ) ,
) ;
const yearOptions = computed ( ( ) => {
const year = todayJalali . jy ;
return [ year - 1 , year , year + 1 ] . map ( ( value ) => ( {
value ,
label : toFaDigits ( value ) ,
} ) ) ;
} ) ;
const periodParts = computed ( ( ) => {
const date = parseLocalInput ( periodDate . value ) ;
return date ? dateToJalali ( date ) : { jy : todayJalali . jy , jm : todayJalali . jm , jd : 1 } ;
} ) ;
const builtPeriod = computed ( ( ) =>
buildPeriod ( periodParts . value . jy , periodParts . value . jm , periodParts . value . jd ) ,
) ;
const payablePreview = computed ( ( ) => {
const base = parseMoneyNumber ( String ( form . value . baseAmount ? ? '' ) , 0 ) ? ? 0 ;
const benefits = parseMoneyNumber ( String ( form . value . benefits ? ? '' ) , 0 ) ? ? 0 ;
@@ -103,21 +155,23 @@ const openRows = computed(() =>
) ;
const payOptions = computed ( ( ) =>
openRows . value . map ( ( row ) => ( {
value : row . id ,
label : ` ${ row . fullName } · ${ row . period } · مانده ${ toman ( row . remaining ) } ` ,
} ) ) ,
rows . value
. filter ( ( row ) => row . status === 'open' || row . status === 'partial' || row . id === pay . value . id )
. filter ( ( row ) => row . status !== 'void' )
. map ( ( row ) => ( {
value : row . id ,
label : ` ${ row . fullName } · ${ formatPeriod ( row . period ) } · مانده ${ toman ( row . remaining ) } ` ,
} ) ) ,
) ;
const selectedPay = computed ( ( ) => rows . value . find ( ( row ) => row . id === pay . value . id ) ? ? null ) ;
const filtered = computed ( ( ) => {
const period = filterPeriod . value . trim ( ) ;
const min = parseMoneyNumber ( String ( filterMin . value ? ? '' ) , 0 ) ;
const max = parseMoneyNumber ( String ( filterMax . value ? ? '' ) , 0 ) ;
return rows . value . filter ( ( row ) => {
if ( filterUserId . value && row . userId !== filterUserId . value ) return false ;
if ( period && ! row . period . includes ( period ) ) return false ;
if ( ! periodMatches ( row . period , filterYear . value , filterMonth . value ) ) return false ;
if ( filterStatus . value !== 'all' && row . status !== filterStatus . value ) return false ;
const payable = Number ( row . payable ) ;
if ( min != null && filterMin . value !== '' && payable < min ) return false ;
@@ -143,15 +197,78 @@ const reportColumns: TableColumnsType<PayrollRow> = [
] ;
const paymentColumns : TableColumnsType < PayrollPayment > = [
{
title : 'تاریخ' ,
key : 'occurredAt' ,
customRender : ( { record } ) => formatJalaliNumeric ( new Date ( record . occurredAt ) ) ,
} ,
{ title : 'تاریخ' , key : 'occurredAt' } ,
{ title : 'حساب' , dataIndex : 'accountName' , key : 'accountName' } ,
{ title : 'مبلغ' , key : 'amount' , customRender : ( { record } ) => toman ( record . amount ) } ,
{ title : 'مبلغ' , key : 'amount' } ,
{ title : 'عملیات' , key : 'action' } ,
] ;
function moneyFilled ( value : string ) {
return ( parseMoneyNumber ( String ( value ? ? '' ) , 0 ) ? ? 0 ) > 0 ;
}
function reasonFor ( amount : string , reason : string ) {
if ( ! moneyFilled ( amount ) ) return null ;
const text = reason . trim ( ) ;
return text || null ;
}
function amountReason (
row : {
benefits ? : string ;
benefitsReason ? : string | null ;
bonus ? : string ;
bonusReason ? : string | null ;
deductions ? : string ;
deductionsReason ? : string | null ;
} ,
key : 'benefits' | 'bonus' | 'deductions' ,
) {
if ( ! Number ( row [ key ] ) ) return '' ;
if ( key === 'benefits' ) return row . benefitsReason ? . trim ( ) || '' ;
if ( key === 'bonus' ) return row . bonusReason ? . trim ( ) || '' ;
return row . deductionsReason ? . trim ( ) || '' ;
}
function buildPeriod ( year : number , month : number , day : number | null ) {
const startDay = Math . min ( Math . max ( 1 , Math . trunc ( Number ( day ) ) || 1 ) , jalaliMonthLength ( year , month ) ) ;
const next = addJalaliMonths ( year , month , 1 ) ;
const endDay = Math . min ( startDay , jalaliMonthLength ( next . jy , next . jm ) ) ;
const pad = ( value : number ) => String ( value ) . padStart ( 2 , '0' ) ;
return {
key : ` ${ year } / ${ pad ( month ) } / ${ pad ( startDay ) } - ${ next . jy } / ${ pad ( next . jm ) } / ${ pad ( endDay ) } ` ,
} ;
}
function formatPeriod ( period : string ) {
const range = period . match ( /^(\d{4})\/(\d{2})\/(\d{2})-(\d{4})\/(\d{2})\/(\d{2})$/ ) ;
if ( range ) {
const startYear = Number ( range [ 1 ] ) ;
const startMonth = Number ( range [ 2 ] ) ;
const startDay = Number ( range [ 3 ] ) ;
const endYear = Number ( range [ 4 ] ) ;
const endMonth = Number ( range [ 5 ] ) ;
const endDay = Number ( range [ 6 ] ) ;
const start = ` ${ toFaDigits ( startDay ) } ${ jalaliMonthName ( startMonth ) } ${ startYear === endYear ? '' : ` ${ toFaDigits ( startYear ) } ` } ` ;
return ` ${ start } تا ${ toFaDigits ( endDay ) } ${ jalaliMonthName ( endMonth ) } ${ toFaDigits ( endYear ) } ` ;
}
const legacy = period . match ( /^(\d{4})-(\d{2})$/ ) ;
if ( legacy ) return formatPeriod ( buildPeriod ( Number ( legacy [ 1 ] ) , Number ( legacy [ 2 ] ) , 1 ) . key ) ;
return period ;
}
function periodMatches ( period : string , year ? : number , month ? : number ) {
if ( year == null && month == null ) return true ;
const range = period . match ( /^(\d{4})\/(\d{2})\// ) ;
const legacy = period . match ( /^(\d{4})-(\d{2})$/ ) ;
const periodYearValue = Number ( range ? . [ 1 ] ? ? legacy ? . [ 1 ] ) ;
const periodMonthValue = Number ( range ? . [ 2 ] ? ? legacy ? . [ 2 ] ) ;
if ( ! periodYearValue || ! periodMonthValue ) return false ;
if ( year != null && periodYearValue !== year ) return false ;
if ( month != null && periodMonthValue !== month ) return false ;
return true ;
}
function apiMessage ( e : unknown ) {
if ( ! axios . isAxiosError ( e ) ) return 'عملیات ناموفق بود.' ;
const msg = ( e . response ? . data as { message ? : string | string [ ] } ) ? . message ;
@@ -173,30 +290,91 @@ async function load() {
accounts . value = acc . data . filter ( ( row ) => row . isActive ) ;
}
function moneyField ( value : string ) {
const amount = Number ( value ) ;
return amount ? String ( amount ) : '' ;
}
function periodStartDate ( period : string ) {
const range = period . match ( /^(\d{4})\/(\d{2})\/(\d{2})/ ) ;
if ( range ) {
return formatLocalDateInput ( jalaliToDate ( Number ( range [ 1 ] ) , Number ( range [ 2 ] ) , Number ( range [ 3 ] ) ) ) ;
}
const legacy = period . match ( /^(\d{4})-(\d{2})$/ ) ;
if ( legacy ) return formatLocalDateInput ( jalaliToDate ( Number ( legacy [ 1 ] ) , Number ( legacy [ 2 ] ) , 1 ) ) ;
return periodDate . value ;
}
function blankForm ( ) {
form . value = {
userId : undefined ,
baseAmount : '' ,
benefits : '' ,
benefitsReason : '' ,
bonus : '' ,
bonusReason : '' ,
deductions : '' ,
deductionsReason : '' ,
} ;
periodDate . value = formatLocalDateInput ( jalaliToDate ( todayJalali . jy , todayJalali . jm , 1 ) ) ;
}
function payrollPayload ( ) {
return {
userId : form . value . userId ,
period : builtPeriod . value . key ,
baseAmount : parseMoneyNumber ( String ( form . value . baseAmount ? ? '' ) , 0 ) ? ? 0 ,
benefits : parseMoneyNumber ( String ( form . value . benefits ? ? '' ) , 0 ) ? ? 0 ,
benefitsReason : reasonFor ( form . value . benefits , form . value . benefitsReason ) ,
bonus : parseMoneyNumber ( String ( form . value . bonus ? ? '' ) , 0 ) ? ? 0 ,
bonusReason : reasonFor ( form . value . bonus , form . value . bonusReason ) ,
deductions : parseMoneyNumber ( String ( form . value . deductions ? ? '' ) , 0 ) ? ? 0 ,
deductionsReason : reasonFor ( form . value . deductions , form . value . deductionsReason ) ,
} ;
}
function scrollToPayroll ( id : string ) {
document . getElementById ( id ) ? . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
}
function startEdit ( row : PayrollRow ) {
editingId . value = row . id ;
form . value = {
userId : row . userId ,
baseAmount : moneyField ( row . baseAmount ) ,
benefits : moneyField ( row . benefits ) ,
benefitsReason : row . benefitsReason ? ? '' ,
bonus : moneyField ( row . bonus ) ,
bonusReason : row . bonusReason ? ? '' ,
deductions : moneyField ( row . deductions ) ,
deductionsReason : row . deductionsReason ? ? '' ,
} ;
periodDate . value = periodStartDate ( row . period ) ;
editingPaymentId . value = '' ;
pay . value = { id : row . id , amount : '' , accountId : undefined } ;
scrollToPayroll ( 'payroll-setup' ) ;
}
function cancelEdit ( ) {
editingId . value = '' ;
blankForm ( ) ;
}
async function createRow ( ) {
error . value = '' ;
ok . value = '' ;
saving . value = true ;
const id = editingId . value ;
try {
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 = 'حقوق ثبت شد. تا وقتی پرداخت نکنید موجودی حساب عوض نمیشود.' ;
if ( id ) await api . patch ( ` /treasury/payroll/ ${ id } ` , payrollPayload ( ) ) ;
else await api . post ( '/treasury/payroll' , payrollPayload ( ) ) ;
editingId . value = '' ;
blankForm ( ) ;
ok . value = id
? 'حقوق بهروز شد.'
: 'حقوق ثبت شد. تا وقتی پرداخت نکنید موجودی حساب عوض نمیشود.' ;
await load ( ) ;
if ( ! id ) pay . value = { id : '' , amount : '' , accountId : undefined } ;
} catch ( e ) {
error . value = apiMessage ( e ) ;
} finally {
@@ -204,17 +382,38 @@ async function createRow() {
}
}
function selectPay ( id : string ) {
const row = rows . value . find ( ( item ) => item . id === id ) ;
if ( ! row ) {
pay . value = { id : '' , amount : '' , accountId : pay . value . accountId } ;
function onPayPick ( value : unknown ) {
editingPaymentId . value = '' ;
if ( value == null || value === '' ) {
pay . value = { id : '' , amount : '' , accountId : undefined } ;
return ;
}
const row = rows . value . find ( ( item ) => item . id === String ( value ) ) ;
if ( ! row ) {
pay . value = { id : '' , amount : '' , accountId : undefined } ;
return ;
}
pay . value = {
id : row . id ,
amount : row . remaining ,
accountId : accounts . value [ 0 ] ? . id ,
} ;
}
function startEditPayment ( payment : PayrollPayment ) {
if ( ! pay . value . id ) return ;
editingPaymentId . value = payment . id ;
pay . value = {
id : row . id ,
amount : row . remaining ,
accountId : pay . value . accountId ? ? accounts . value [ 0 ] ? . id ,
id : pay . value . id ,
amount : moneyField ( payment . amount ) ,
accountId : payment . accountId ,
} ;
scrollToPayroll ( 'payroll-pay' ) ;
}
function cancelPaymentEdit ( ) {
editingPaymentId . value = '' ;
pay . value = { id : pay . value . id , amount : '' , accountId : undefined } ;
}
async function payRow ( ) {
@@ -229,19 +428,25 @@ async function payRow() {
error . value = 'مبلغ پرداخت معتبر نیست.' ;
return ;
}
await api . post ( ` /treasury/payroll/ ${ keepId } /pay ` , {
amount ,
accountId ,
clientKey : crypto . randomUUID ( ) ,
} ) ;
ok . value = 'پرداخت حقوق از حساب ثبت شد. اگر مانده مانده باشد، مرحلهٔ بعد را همینجا بزنید.' ;
const paymentId = editingPaymentId . value ;
if ( paymentId ) {
await api . patch ( ` /treasury/payroll/ ${ keepId } /payments/ ${ paymentId } ` , { amount , accountId } ) ;
} else {
await api . post ( ` /treasury/payroll/ ${ keepId } /pay ` , {
amount ,
accountId ,
clientKey : crypto . randomUUID ( ) ,
} ) ;
}
editingPaymentId . value = '' ;
ok . value = paymentId
? 'پرداخت حقوق بهروز شد.'
: 'پرداخت حقوق از حساب ثبت شد. اگر مانده مانده باشد، مرحلهٔ بعد را همینجا بزنید.' ;
await load ( ) ;
const still = rows . value . find (
( row ) => row . id === keepId && row . status !== 'settled' && row . status !== 'void' ,
) ;
const still = rows . value . find ( ( row ) => row . id === keepId && row . status !== 'void' ) ;
pay . value = still
? { id : still . id , amount : still . remaining , accountId }
: { id : '' , amount : '' , accountId } ;
? { id : still . id , amount : paymentId ? '' : still . status === 'settled' ? '' : still . remaining , accountId : paymentId ? undefined : accountId }
: { id : '' , amount : '' , accountId : undefined } ;
} catch ( e ) {
error . value = apiMessage ( e ) ;
} finally {
@@ -261,13 +466,14 @@ onMounted(() => {
< a-alert v-if = "error" type="error" show-icon :message="error" / >
< a-alert v-if = "ok" type="success" show-icon :message="ok" / >
< a-card title = "تعیین حقوق" :bordered = "false" >
< a-card id = "payroll-setup" title = "تعیین حقوق" :bordered = "false" >
< a-form layout = "vertical" :model = "form" @finish ="createRow" @finishFailed ="onFinishFailed" >
< a -row : gutter = "[16 , 0]" >
< a-col :xs = "24" :md = "12" >
< a -row : gutter = "[8 , 0]" >
< a-col :xs = "24" :s m = "12" >
< a-form-item
label = "پرسنل"
name = "userId"
style = "margin-bottom: 8px"
: rules = "[{ required: true, message: 'پرسنل الزامی است' }]"
>
< a-select
@@ -281,55 +487,75 @@ onMounted(() => {
/ >
< / a-form-item >
< / a-col >
< a-col :xs = "24" :md = "12" >
< a-form-item
label = "دوره"
name = "period"
: rules = "[{ required: true, message: 'دوره الزامی است' }]"
>
< a-input v -model :value = "form.period" placeholder = "۱۴۰۵-۰۷" / >
< a-col :xs = "24" :s m = "12" >
< a-form-item label = "دوره" style = "margin-bottom: 8px" >
< JalaliDateInput
v-model = "periodDate"
start -view = " months "
:clearable = "false"
:display-text = "formatPeriod(builtPeriod.key)"
placeholder = "انتخاب دوره"
/ >
< / a-form-item >
< / a-col >
< a-col :xs = "24 " :md = "8 " >
< a-col :xs = "1 2" :md = "6 " >
< a-form-item
label = "پایه"
name = "baseAmount"
style = "margin-bottom: 8px"
: rules = "[{ required: true, message: 'مبلغ پایه الزامی است' }]"
>
< MoneyInput v-model = "form.baseAmount" :decimals="0" / >
< MoneyInput v-model = "form.baseAmount" addon-after="تومان" :decimals="0" / >
< / a-form-item >
< / a-col >
< a-col :xs = "24 " :md = "8 " >
< a-form-item label = "مزایا" name = "benefits" >
< MoneyInput v-model = "form.benefits" :decimals="0" / >
< a-col :xs = "1 2" :md = "6 " >
< a-form-item label = "مزایا" name = "benefits" style = "margin-bottom: 8px" >
< MoneyInput v-model = "form.benefits" addon-after="تومان" :decimals="0" / >
< / a-form-item >
< a-form-item v-if = "moneyFilled(form.benefits)" label="دلیل" style="margin-bottom: 8px" >
< a -input v -model :value = "form.benefitsReason" placeholder = "اختیاری" / >
< / a-form-item >
< / a-col >
< a-col :xs = "24 " :md = "8 " >
< a-form-item label = "پاداش" name = "bonus" >
< MoneyInput v-model = "form.bonus" :decimals="0" / >
< a-col :xs = "1 2" :md = "6 " >
< a-form-item label = "پاداش" name = "bonus" style = "margin-bottom: 8px" >
< MoneyInput v-model = "form.bonus" addon-after="تومان" :decimals="0" / >
< / a-form-item >
< a-form-item v-if = "moneyFilled(form.bonus)" label="دلیل" style="margin-bottom: 8px" >
< a -input v -model :value = "form.bonusReason" placeholder = "اختیاری" / >
< / a-form-item >
< / a-col >
< a-col :xs = "24 " :md = "8 " >
< a-form-item label = "کسورات" name = "deductions" >
< MoneyInput v-model = "form.deductions" :decimals="0" / >
< a-col :xs = "1 2" :md = "6 " >
< a-form-item label = "کسورات" name = "deductions" style = "margin-bottom: 8px" >
< MoneyInput v-model = "form.deductions" addon-after="تومان" :decimals="0" / >
< / a-form-item >
< a-form-item v-if = "moneyFilled(form.deductions)" label="دلیل" style="margin-bottom: 8px" >
< a -input v -model :value = "form.deductionsReason" placeholder = "اختیاری" / >
< / a-form-item >
< / a-col >
< a-col :x s = "24" :md = "16" >
< a-form-item label = "قابل پرداخت ">
< a-typography-text strong > { { toman ( payablePreview ) } } < / a-typography-text >
< / a-form-item >
< a-col :span = "24" >
< a-flex align = "center" justify = "space-between" wrap = "wrap" :gap = "8 ">
< a-typography-text >
قابل پرداخت
< a-typography-text strong > { { toman ( payablePreview ) } } < / a-typography-text >
< / a-typography-text >
< a-space >
< a-button v-if = "editingId" :disabled="saving" @click="cancelEdit" > انصراف < / a -button >
< a-button type = "primary" html -type = " submit " :loading = "saving" >
{ { editingId ? 'ذخیره تغییرات' : 'ثبت حقوق' } }
< / a-button >
< / a-space >
< / a-flex >
< / a-col >
< / a-row >
< a-button type = "primary" html -type = " submit " :loading = "saving" > ثبت حقوق < / a-button >
< / a-form >
< / a-card >
< a-card title = "پرداخت" :bordered = "false" >
< a-card id = "payroll-pay" title = "پرداخت" :bordered = "false" >
< a-flex vertical :gap = "16" >
< a-typography-text type = "secondary" >
می توانید کل مانده را یکجا بدهید ، یا هر بار بخشی را تا تمام شدن مانده ثبت کنید .
< / a-typography-text >
< a-empty v-if = "!openRows.length" description="حقوق باز برای پرداخت نیست." / >
< a-empty v-if = "!openRows.length && !pay.id " description="حقوق باز برای پرداخت نیست." / >
< a-form
v-else
layout = "vertical"
@@ -346,12 +572,13 @@ onMounted(() => {
>
< a-select
: value = "pay.id || undefined"
allow -clear
show -search
option -filter -prop = " label "
style = "width: 100%"
placeholder = "انتخاب پرسنل و دوره"
placeholder = "انتخاب دوره"
:options = "payOptions"
@ update : value = "selectPay(String($event ?? ''))"
@change ="onPayPick"
/ >
< / a-form-item >
< / a-col >
@@ -361,7 +588,7 @@ onMounted(() => {
name = "amount"
: rules = "[{ required: true, message: 'مبلغ الزامی است' }]"
>
< MoneyInput v-model = "pay.amount" :decimals="0" / >
< MoneyInput v-model = "pay.amount" addon-after="تومان" :decimals="0" / >
< / a-form-item >
< / a-col >
< a-col :xs = "24" :md = "6" >
@@ -387,9 +614,12 @@ onMounted(() => {
< a-typography-text > پرداخت شده { { toman ( selectedPay . paidAmount ) } } < / a-typography-text >
< a-typography-text strong > مانده { { toman ( selectedPay . remaining ) } } < / a-typography-text >
< / a-flex >
< a-button type = "primary" html -type = " submit " :loading = "saving" :disabled = "!pay.id" >
ثبت پرداخت
< / a-button >
< a-space >
< a-button v-if = "editingPaymentId" :disabled="saving" @click="cancelPaymentEdit" > انصراف < / a -button >
< a-button type = "primary" html -type = " submit " :loading = "saving" :disabled = "!pay.id" >
{ { editingPaymentId ? 'ذخیره پرداخت' : 'ثبت پرداخت' } }
< / a-button >
< / a-space >
< / a-form >
< a-table
@@ -398,7 +628,31 @@ onMounted(() => {
:data-source = "selectedPay.payments"
:pagination = "false"
row -key = " id "
/ >
>
< template # bodyCell = "{ column, record }" >
< template v-if = "column.key === 'occurredAt'" >
{{ formatJalaliNumeric ( new Date ( record.occurredAt ) ) }}
< / template >
< template v-else-if = "column.key === 'amount'" >
{{ toman ( record.amount ) }}
< / template >
< template v-else-if = "column.key === 'action'" >
< a -tooltip title = "ویرایش" >
< a-button
type = "text"
aria -label = " ویرایش "
: style = "{ color: token.colorInfo }"
@click ="startEditPayment(record as PayrollPayment)"
>
< template # icon > < EditOutlined / > < / template >
< / a-button >
< / a-tooltip >
< / template >
< template v-else >
{{ record.accountName }}
< / template >
< / template >
< / a -table >
< / a-flex >
< / a-card >
@@ -420,7 +674,26 @@ onMounted(() => {
< / a-col >
< a-col :xs = "24" :md = "8" >
< a-form-item label = "دوره" >
< a-input v -model :value = "filterPeriod" placeholder = "مثلاً ۱۴۰۵-۰۷" allow -clear / >
< a-row : gutter = "[8, 0]" >
< a-col :span = "12" >
< a-select
v -model :value = "filterYear"
allow -clear
placeholder = "سال"
style = "width: 100%"
:options = "yearOptions"
/ >
< / a-col >
< a-col :span = "12" >
< a-select
v -model :value = "filterMonth"
allow -clear
placeholder = "ماه"
style = "width: 100%"
:options = "monthOptions"
/ >
< / a-col >
< / a-row >
< / a-form-item >
< / a-col >
< a-col :xs = "24" :md = "8" >
@@ -430,12 +703,12 @@ onMounted(() => {
< / a-col >
< a-col :xs = "24" :md = "8" >
< a-form-item label = "حداقل قابل پرداخت" >
< MoneyInput v-model = "filterMin" :decimals="0" / >
< MoneyInput v-model = "filterMin" addon-after="تومان" :decimals="0" / >
< / a-form-item >
< / a-col >
< a-col :xs = "24" :md = "8" >
< a-form-item label = "حداکثر قابل پرداخت" >
< MoneyInput v-model = "filterMax" :decimals="0" / >
< MoneyInput v-model = "filterMax" addon-after="تومان" :decimals="0" / >
< / a-form-item >
< / a-col >
< / a-row >
@@ -451,14 +724,43 @@ onMounted(() => {
< template v-if = "column.key === 'status'" >
{{ statusLabel [ record.status ] ? ? record.status }}
< / template >
< template v-else-if = "column.key === 'period'" >
{{ formatPeriod ( record.period ) }}
< / template >
< template v-else-if = "column.key === 'action'" >
< a -button
v-if = "record.status = == 'open' || record.status === 'partial'"
type = "link"
@click ="selectPay(record.id)"
>
پرداخت
< / a-button >
< a -space :size = "4" >
< a-tooltip v-if = "record.status ! == 'void'" title="ویرایش" >
< a -button
type = "text"
aria -label = " ویرایش "
: style = "{ color: token.colorInfo }"
@click ="startEdit(record as PayrollRow)"
>
< template # icon > < EditOutlined / > < / template >
< / a-button >
< / a-tooltip >
< a-tooltip v-if = "record.status === 'open' || record.status === 'partial'" title="پرداخت" >
< a -button
type = "text"
aria -label = " پرداخت "
: style = "{ color: token.colorPrimary }"
@click ="onPayPick(record.id)"
>
< template # icon > < WalletOutlined / > < / template >
< / a-button >
< / a-tooltip >
< / a-space >
< / template >
< template v-else-if = "column.key === 'benefits' || column.key === 'bonus' || column.key === 'deductions'" >
< a -flex vertical >
< span > { { toman ( record [ column . key as 'benefits' | 'bonus' | 'deductions' ] ) } } < / span >
< a-typography-text
v-if = "amountReason(record, column.key as 'benefits' | 'bonus' | 'deductions')"
type = "secondary"
>
{ { amountReason ( record , column . key as 'benefits' | 'bonus' | 'deductions' ) } }
< / a-typography-text >
< / a-flex >
< / template >
< template v-else-if = "moneyKeys.includes(column.key as (typeof moneyKeys)[number])" >
{{ toman ( record [ column.key as ( typeof moneyKeys ) [ number ] ] ) }}