@@ -0,0 +1,503 @@
< template >
< section class = "dashboard-panel dashboard-profit-split" >
< div class = "mb-2 flex items-start justify-between gap-2" >
< DashboardSectionHeader
class = "!mb-0 min-w-0 flex-1"
title = "سود طلایی و ریالی"
help = "کارمزد معاملات بر اساس فیلدی که مشتری وارد کرده تفکیک میشود: وقتی مشتری مبلغ ریالی ثابت میزند کارمزد از طلا کسر میشود (سود طلایی)، و وقتی وزن ثابت میزند کارمزد از ریال کسر میشود (سود ریالی)."
/ >
< a-button type = "primary" size = "small" @click ="openSanad" >
ثبت سند تبدیل سود به طلا
< / a -button >
< / div >
<!-- دو کارت خلاصه -- >
< div class = "dashboard-profit-split__cards" >
< article class = "dashboard-profit-split__card dashboard-profit-split__card--gold" >
< span class = "dashboard-profit-split__card-title" > سود طلایی < / span >
< p class = "dashboard-profit-split__card-hint" > کارمزد کسرشده از طلا در این بازه < / p >
< p class = "dashboard-profit-split__value" >
< span class = "dashboard-profit-split__amount" > { { formatGram ( profitGoldGrams ) } } < / span >
< span class = "dashboard-profit-split__unit" > گرم < / span >
< / p >
< p class = "dashboard-profit-split__sub" >
≈ { { formatDashboardAmount ( profitGoldValueRial ) } } ریال
< / p >
< / article >
< article class = "dashboard-profit-split__card dashboard-profit-split__card--rial" >
< span class = "dashboard-profit-split__card-title" > سود ریالی < / span >
< p class = "dashboard-profit-split__card-hint" > کارمزد کسرشده از ریال در این بازه < / p >
< p class = "dashboard-profit-split__value" >
< span class = "dashboard-profit-split__amount" > { { formatDashboardAmount ( profitRial ) } } < / span >
< span class = "dashboard-profit-split__unit" > ریال < / span >
< / p >
< p class = "dashboard-profit-split__sub" >
{ { dailyRows . length } } روز دارای معامله
< / p >
< / article >
< / div >
<!-- جدول روزانه -- >
< div class = "mt-3 overflow-x-auto" >
< table class = "dashboard-table w-full min-w-[34rem] text-sm" >
< thead >
< tr class = "border-b text-xs" >
< th class = "px-2 py-2 text-right" > تاریخ < / th >
< th class = "px-2 py-2 text-right" > سود طلایی ( گرم ) < / th >
< th class = "px-2 py-2 text-right" > سود ریالی ( ریال ) < / th >
< th class = "px-2 py-2 text-right" > سود اختلاف قیمت ( ریال ) < / th >
< th class = "px-2 py-2 text-right" > تعداد سفارش < / th >
< / tr >
< / thead >
< tbody >
< tr v-if = "!dailyRows.length" >
< td colspan = "5" class = "dashboard-text-muted px-2 py-4 text-center text-xs" >
در این بازه معامله ای ثبت نشده
< / td >
< / tr >
< tr
v-for = "row in dailyRows"
:key = "row.date"
class = "border-b hover:bg-[var(--admin-bg-hover)]"
>
< td class = "whitespace-nowrap px-2 py-2 text-xs" > { { jalaliDate ( row . date ) } } < / td >
< td class = "px-2 py-2 font-semibold" : class = "row.profit_gold > 0 ? 'dashboard-profit' : ''" >
{ { formatGram ( row . profit _gold ) } }
< / td >
< td class = "px-2 py-2 font-semibold" : class = "row.profit_rial > 0 ? 'dashboard-profit' : ''" >
{ { formatDashboardAmount ( row . profit _rial ) } }
< / td >
< td class = "px-2 py-2" > { { formatDashboardAmount ( row . spread _rial ) } } < / td >
< td class = "px-2 py-2" > { { Number ( row . order _count ) . toLocaleString ( 'fa-IR' ) } } < / td >
< / tr >
< / tbody >
< / table >
< / div >
<!-- سندهای تبدیل سود به طلا -- >
< div class = "dashboard-profit-split__sanad mt-3" >
< div class = "mb-2 flex flex-wrap items-center justify-between gap-2" >
< span class = "text-sm font-bold" > سندهای تبدیل سود ریالی به طلا < / span >
< span v-if = "conversions.length" class="dashboard-text-muted text-xs" >
جمع تبدیلشده : {{ formatDashboardAmount ( conversionsTotalRial ) }} ریال →
{{ formatGram ( conversionsTotalGram ) }} گرم
< / span >
< / div >
< a -spin :spinning = "conversionsLoading" >
< div v-if = "!conversions.length" class="dashboard-empty py-3 text-center text-xs" >
هنوز سندی ثبت نشده
< / div >
< ul v-else class = "dashboard-profit-split__sanad-list" >
< li
v-for = "item in conversions"
:key = "item.id"
class = "dashboard-profit-split__sanad-item"
>
< div class = "min-w-0" >
< div class = "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-sm" >
< span class = "font-bold" > { { formatDashboardAmount ( item . rial _amount ) } } ریال < / span >
< span class = "dashboard-text-muted" > ← < / span >
< span class = "font-bold dashboard-profit" > { { formatGram ( item . gram _amount ) } } گرم < / span >
< span class = "dashboard-text-muted text-xs" >
( مظنه { { formatDashboardAmount ( item . unit _price ) } } )
< / span >
< / div >
< div class = "dashboard-text-muted mt-0.5 text-[11px]" >
{ { jalaliDate ( item . converted _at ) } }
< template v-if = "item.description" > — {{ item.description }} < / template >
< / div >
< / div >
< a -popconfirm
title = "این سند حذف شود؟"
ok -text = " حذف "
cancel -text = " انصراف "
@confirm ="removeConversion(item.id)"
>
< a-button type = "text" danger size = "small" > حذف < / a-button >
< / a-popconfirm >
< / li >
< / ul >
< / a-spin >
< / div >
<!-- مدال ثبت سند -- >
< BasicActionSheet
v -model :visible = "sanadVisible"
title = "ثبت سند تبدیل سود به طلا"
modal -width = " 24rem "
drawer -height = " auto "
compact
:show-button = "false"
@close ="resetForm"
>
< div class = "dashboard-profit-split__form" >
< div class = "dashboard-profit-split__field" >
< label > مبلغ ریالی سود < / label >
< a-input-number
v -model :value = "form.rial_amount"
:min = "0"
:step = "1000000"
class = "w-full"
:formatter = "numFormatter"
:parser = "numParser"
@change ="syncFromRial"
/ >
< / div >
< div class = "dashboard-profit-split__field" >
< label > قیمت هر گرم ( مظنه ) < / label >
< a-input-number
v -model :value = "form.unit_price"
:min = "0"
:step = "1000000"
class = "w-full"
:formatter = "numFormatter"
:parser = "numParser"
@change ="syncFromRial"
/ >
< / div >
< div class = "dashboard-profit-split__field" >
< label > وزن معادل ( گرم ) < / label >
< a-input-number
v -model :value = "form.gram_amount"
:min = "0"
:step = "0.1"
class = "w-full"
@change ="syncFromGram"
/ >
< / div >
< div class = "dashboard-profit-split__field" >
< label > تاریخ سند < / label >
< BaseDatePicker
v -model :value = "form.converted_at"
format = "YYYY-MM-DD HH:mm:ss"
class = "w-full"
/ >
< / div >
< div class = "dashboard-profit-split__field" >
< label > توضیحات < / label >
< a-textarea
v -model :value = "form.description"
:rows = "2"
placeholder = "مثلاً: از فلانجا ۲ گرم طلا خریده شد"
/ >
< / div >
< div class = "dashboard-profit-split__form-footer" >
< a-button danger block @click ="closeSanad" > بستن < / a -button >
< a-button type = "primary" block :loading = "submitting" @click ="submitSanad" > ثبت سند < / a -button >
< / div >
< / div >
< / BasicActionSheet >
< / section >
< / template >
< script setup lang = "ts" >
import { computed , ref , watch } from 'vue'
import moment from 'moment-jalaali'
import { message } from 'ant-design-vue'
import DashboardSectionHeader from '@/pages/dashboard/components/DashboardSectionHeader.vue'
import BasicActionSheet from '@/components/BasicActionSheet.vue'
import { formatDashboardAmount } from '@/pages/dashboard/dashboardFormat'
import {
deleteProfitConversion ,
getProfitConversions ,
storeProfitConversion ,
} from '@/api/dashboard'
import type { DashboardFinance , ProfitConversion } from '@/model/dashboard'
import { toJalali } from '@core/utils/useDataTime'
const props = defineProps < {
finance ? : DashboardFinance | null
startDate ? : string
endDate ? : string
/** قیمت هر گرم برای مقدار پیشفرض سند */
unitPrice ? : number
} > ( )
const profitGoldGrams = computed ( ( ) => Number ( props . finance ? . profitGoldGrams ? ? 0 ) )
const profitRial = computed ( ( ) => Number ( props . finance ? . profitRial ? ? 0 ) )
const profitGoldValueRial = computed ( ( ) => Number ( props . finance ? . profitGoldValueRial ? ? 0 ) )
const dailyRows = computed ( ( ) => props . finance ? . daily ? ? [ ] )
const conversions = ref < ProfitConversion [ ] > ( [ ] )
const conversionsLoading = ref ( false )
const conversionsTotalRial = ref ( 0 )
const conversionsTotalGram = ref ( 0 )
const sanadVisible = ref ( false )
const submitting = ref ( false )
type SanadForm = {
rial _amount : number | null
gram _amount : number | null
unit _price : number | null
converted _at : string | null
description : string | null
}
function emptyForm ( ) : SanadForm {
return {
rial _amount : null ,
gram _amount : null ,
unit _price : props . unitPrice ? Number ( props . unitPrice ) : null ,
converted _at : moment ( ) . format ( 'YYYY-MM-DD HH:mm:ss' ) ,
description : null ,
}
}
const form = ref < SanadForm > ( emptyForm ( ) )
function formatGram ( value ? : number | null ) {
return Number ( value ? ? 0 ) . toLocaleString ( 'fa-IR' , { maximumFractionDigits : 4 } )
}
function jalaliDate ( value ? : string | null ) {
if ( ! value ) return '—'
const normalized = String ( value ) . length <= 10 ? ` ${ value } 12:00:00 ` : value
return toJalali ( normalized , 'jYYYY/jMM/jDD' )
}
function numFormatter ( value : string | number ) {
return String ( value ? ? '' ) . replace ( /\B(?=(\d{3})+(?!\d))/g , ',' )
}
function numParser ( value : string ) {
return value . replace ( /[^\d.]/g , '' )
}
function syncFromRial ( ) {
const rial = Number ( form . value . rial _amount ? ? 0 )
const unit = Number ( form . value . unit _price ? ? 0 )
if ( rial > 0 && unit > 0 ) {
form . value . gram _amount = Number ( ( rial / unit ) . toFixed ( 4 ) )
}
}
function syncFromGram ( ) {
const gram = Number ( form . value . gram _amount ? ? 0 )
const unit = Number ( form . value . unit _price ? ? 0 )
if ( gram > 0 && unit > 0 ) {
form . value . rial _amount = Math . round ( gram * unit )
}
}
async function loadConversions ( ) {
conversionsLoading . value = true
try {
const res = await getProfitConversions ( props . startDate , props . endDate )
conversions . value = res ? . items ? ? [ ]
conversionsTotalRial . value = Number ( res ? . totalRial ? ? 0 )
conversionsTotalGram . value = Number ( res ? . totalGram ? ? 0 )
} catch ( e ) {
console . error ( e )
} finally {
conversionsLoading . value = false
}
}
function openSanad ( ) {
form . value = emptyForm ( )
sanadVisible . value = true
}
function closeSanad ( ) {
sanadVisible . value = false
resetForm ( )
}
function resetForm ( ) {
form . value = emptyForm ( )
}
async function submitSanad ( ) {
const rial = Number ( form . value . rial _amount ? ? 0 )
const gram = Number ( form . value . gram _amount ? ? 0 )
if ( rial <= 0 && gram <= 0 ) {
message . warning ( 'مبلغ ریالی یا وزن طلا را وارد کنید' )
return
}
submitting . value = true
try {
await storeProfitConversion ( {
rial _amount : form . value . rial _amount ,
gram _amount : form . value . gram _amount ,
unit _price : form . value . unit _price ,
converted _at : form . value . converted _at ,
description : form . value . description ,
} )
message . success ( 'سند تبدیل سود ثبت شد' )
closeSanad ( )
await loadConversions ( )
} catch ( e ) {
console . error ( e )
message . error ( 'خطا در ثبت سند' )
} finally {
submitting . value = false
}
}
async function removeConversion ( id : number ) {
try {
await deleteProfitConversion ( id )
message . success ( 'سند حذف شد' )
await loadConversions ( )
} catch ( e ) {
console . error ( e )
message . error ( 'خطا در حذف سند' )
}
}
watch (
( ) => [ props . startDate , props . endDate ] ,
( ) => {
void loadConversions ( )
} ,
{ immediate : true } ,
)
< / script >
< style scoped lang = "less" >
. dashboard - profit - split {
margin - top : 0.5 rem ;
}
. dashboard - profit - split _ _cards {
display : grid ;
grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ;
gap : 0.5 rem ;
}
@ media ( max - width : 640 px ) {
. dashboard - profit - split _ _cards {
grid - template - columns : 1 fr ;
}
}
. dashboard - profit - split _ _card {
display : flex ;
flex - direction : column ;
gap : 0.15 rem ;
min - height : 5 rem ;
padding : 0.6 rem 0.75 rem ;
border - radius : 0.5 rem ;
border : 1 px solid var ( -- admin - border ) ;
background : var ( -- admin - bg - muted ) ;
}
. dashboard - profit - split _ _card -- gold {
border - color : rgba ( 245 , 158 , 11 , 0.4 ) ;
background : rgba ( 245 , 158 , 11 , 0.1 ) ;
}
. dashboard - profit - split _ _card -- rial {
border - color : rgba ( 14 , 165 , 233 , 0.35 ) ;
background : rgba ( 14 , 165 , 233 , 0.1 ) ;
}
. dashboard - profit - split _ _card - title {
font - size : 0.8125 rem ;
font - weight : 700 ;
color : var ( -- admin - text ) ;
}
. dashboard - profit - split _ _card - hint {
margin : 0 ;
font - size : 0.625 rem ;
color : var ( -- admin - text - secondary ) ;
}
. dashboard - profit - split _ _value {
margin : 0.15 rem 0 0 ;
direction : ltr ;
text - align : right ;
unicode - bidi : isolate ;
}
. dashboard - profit - split _ _amount {
font - size : 1.05 rem ;
font - weight : 800 ;
font - variant - numeric : tabular - nums ;
}
. dashboard - profit - split _ _unit {
margin - right : 3 px ;
font - size : 0.625 rem ;
font - weight : 600 ;
color : var ( -- admin - text - secondary ) ;
}
. dashboard - profit - split _ _sub {
margin : 0 ;
font - size : 0.6875 rem ;
color : var ( -- admin - text - secondary ) ;
}
. dashboard - profit - split _ _sanad {
border - top : 1 px solid var ( -- admin - border ) ;
padding - top : 0.75 rem ;
}
. dashboard - profit - split _ _sanad - list {
display : flex ;
flex - direction : column ;
gap : 0.4 rem ;
margin : 0 ;
padding : 0 ;
list - style : none ;
}
. dashboard - profit - split _ _sanad - item {
display : flex ;
align - items : center ;
justify - content : space - between ;
gap : 0.5 rem ;
padding : 0.5 rem 0.65 rem ;
border - radius : 0.5 rem ;
background : var ( -- admin - bg - muted ) ;
border : 1 px solid var ( -- admin - border ) ;
}
. dashboard - profit - split _ _form {
@ apply flex flex - col gap - 3 px - 4 pb - 2 pt - 1 ;
}
. dashboard - profit - split _ _field {
@ apply flex flex - col gap - 1 ;
}
. dashboard - profit - split _ _field label {
@ apply text - xs font - semibold text - slate - 600 dark : text - slate - 300 ;
}
. dashboard - profit - split _ _form - footer {
@ apply sticky bottom - 0 grid grid - cols - 2 gap - 2 border - t border - slate - 200 bg - white pt - 3 dark : border - slate - 600 dark : bg - slate - 900 ;
}
. dashboard - text - muted {
color : var ( -- admin - text - secondary ) ;
}
. dashboard - empty {
color : var ( -- admin - text - secondary ) ;
}
. dashboard - profit {
color : # 059669 ;
}
html . is - dark {
. dashboard - profit {
color : # 6 ee7b7 ;
}
. dashboard - profit - split _ _card -- gold {
border - color : rgba ( 245 , 158 , 11 , 0.45 ) ;
background : rgba ( 245 , 158 , 11 , 0.14 ) ;
}
. dashboard - profit - split _ _card -- rial {
border - color : rgba ( 14 , 165 , 233 , 0.4 ) ;
background : rgba ( 14 , 165 , 233 , 0.14 ) ;
}
}
< / style >