added charge for all products
This commit is contained in:
@@ -1,13 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="customer-wallet-balance space-y-3">
|
<div class="customer-wallet-balance" :class="compact ? 'space-y-1.5' : 'space-y-3'">
|
||||||
<div v-if="loading" class="rounded-xl border border-gray-200 bg-gray-50 px-4 py-6 text-center text-sm text-gray-500">
|
<div
|
||||||
|
v-if="loading"
|
||||||
|
class="rounded-xl border border-gray-200 bg-gray-50 text-center text-sm text-gray-500"
|
||||||
|
:class="compact ? 'px-3 py-3' : 'px-4 py-6'"
|
||||||
|
>
|
||||||
در حال بارگذاری موجودی…
|
در حال بارگذاری موجودی…
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="error" class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
<div
|
||||||
|
v-else-if="error"
|
||||||
|
class="rounded-xl border border-red-200 bg-red-50 text-sm text-red-700"
|
||||||
|
:class="compact ? 'px-3 py-2' : 'px-4 py-3'"
|
||||||
|
>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="summary && compact" class="customer-wallet-balance__minimal">
|
||||||
|
<template v-if="chargeTargets.length">
|
||||||
|
<button
|
||||||
|
v-for="target in chargeTargets"
|
||||||
|
:key="target.key"
|
||||||
|
type="button"
|
||||||
|
class="customer-wallet-balance__minimal-item"
|
||||||
|
:class="[
|
||||||
|
targetToneClass(target),
|
||||||
|
{ 'customer-wallet-balance__minimal-item--inactive': target.active === false },
|
||||||
|
{ 'customer-wallet-balance__minimal-item--selected': target.key === selectedChargeKey },
|
||||||
|
]"
|
||||||
|
:title="target.label"
|
||||||
|
@click="emit('selectChargeTarget', target.key)"
|
||||||
|
>
|
||||||
|
<span class="customer-wallet-balance__minimal-label">{{ minimalTargetLabel(target) }}</span>
|
||||||
|
<span class="customer-wallet-balance__minimal-value">{{ balanceForTarget(target) }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="metal in summary.metal_totals"
|
||||||
|
:key="metal.metal_id"
|
||||||
|
class="customer-wallet-balance__minimal-item"
|
||||||
|
:class="minimalMetalToneClass(metal)"
|
||||||
|
>
|
||||||
|
<span class="customer-wallet-balance__minimal-label">{{ minimalMetalLabel(metal) }}</span>
|
||||||
|
<span class="customer-wallet-balance__minimal-value">{{ formatMetalTotal(metal) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="customer-wallet-balance__minimal-item customer-wallet-balance__minimal-item--cash">
|
||||||
|
<span class="customer-wallet-balance__minimal-label">نقدی</span>
|
||||||
|
<span class="customer-wallet-balance__minimal-value">{{ usePrice(summary.cash_wallet ?? 0, true, '') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-else-if="summary">
|
<template v-else-if="summary">
|
||||||
<div
|
<div
|
||||||
v-for="metal in summary.metal_totals"
|
v-for="metal in summary.metal_totals"
|
||||||
@@ -68,14 +112,101 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { getCustomerAssetsSummary } from '@/api/customer'
|
import { getCustomerAssetsSummary } from '@/api/customer'
|
||||||
|
import type { ChargeTarget } from '@/model/wallet'
|
||||||
import type { WalletAssetsSummary, WalletInventoryCard, WalletMetalTotal } from '@/model/walletAssets'
|
import type { WalletAssetsSummary, WalletInventoryCard, WalletMetalTotal } from '@/model/walletAssets'
|
||||||
import { usePrice } from '@core/utils/usePrice'
|
import { usePrice } from '@core/utils/usePrice'
|
||||||
import { formatMetalDisplayValue } from '@/utils/metalWeight'
|
import { formatMetalDisplayValue } from '@/utils/metalWeight'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
customerId?: number | null
|
defineProps<{
|
||||||
|
customerId?: number | null
|
||||||
|
/** نمایش مینیمال برای مودالها */
|
||||||
|
compact?: boolean
|
||||||
|
/** گزینههای شارژ — برای نمایش موجودی هر محصول */
|
||||||
|
chargeTargets?: ChargeTarget[]
|
||||||
|
selectedChargeKey?: string
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
compact: false,
|
||||||
|
chargeTargets: () => [],
|
||||||
|
selectedChargeKey: '',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'selectChargeTarget', key: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
function minimalMetalLabel(metal: WalletMetalTotal) {
|
||||||
|
switch (Number(metal.metal_id)) {
|
||||||
|
case 1:
|
||||||
|
return 'طلا'
|
||||||
|
case 2:
|
||||||
|
return 'نقره'
|
||||||
|
case 3:
|
||||||
|
return 'مس'
|
||||||
|
default: {
|
||||||
|
const raw = (metal.metal_label || metal.label || '').trim()
|
||||||
|
return raw.replace(/^کل\s*دارایی\s*/u, '') || raw || 'فلز'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function minimalMetalToneClass(metal: WalletMetalTotal) {
|
||||||
|
switch (Number(metal.metal_id)) {
|
||||||
|
case 1:
|
||||||
|
return 'customer-wallet-balance__minimal-item--gold'
|
||||||
|
case 2:
|
||||||
|
return 'customer-wallet-balance__minimal-item--silver'
|
||||||
|
case 3:
|
||||||
|
return 'customer-wallet-balance__minimal-item--copper'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function minimalTargetLabel(target: ChargeTarget) {
|
||||||
|
if (target.kind === 'money') return 'نقدی'
|
||||||
|
const label = target.label.replace(/\s*\(کیف اصلی\)\s*$/u, '').trim()
|
||||||
|
if (label.length <= 14) return label
|
||||||
|
return `${label.slice(0, 13)}…`
|
||||||
|
}
|
||||||
|
|
||||||
|
function targetToneClass(target: ChargeTarget) {
|
||||||
|
if (target.kind === 'money') return 'customer-wallet-balance__minimal-item--cash'
|
||||||
|
switch (Number(target.metal_id)) {
|
||||||
|
case 1:
|
||||||
|
return 'customer-wallet-balance__minimal-item--gold'
|
||||||
|
case 2:
|
||||||
|
return 'customer-wallet-balance__minimal-item--silver'
|
||||||
|
case 3:
|
||||||
|
return 'customer-wallet-balance__minimal-item--copper'
|
||||||
|
default:
|
||||||
|
return 'customer-wallet-balance__minimal-item--product'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inventoryCards() {
|
||||||
|
if (!summary.value) return [] as WalletInventoryCard[]
|
||||||
|
return [...(summary.value.inventory_cards ?? []), ...(summary.value.count_products ?? [])]
|
||||||
|
}
|
||||||
|
|
||||||
|
function balanceForTarget(target: ChargeTarget) {
|
||||||
|
if (!summary.value) return '—'
|
||||||
|
if (target.kind === 'money') {
|
||||||
|
return usePrice(summary.value.cash_wallet ?? 0, true, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = inventoryCards().find((row) => Number(row.product_id) === Number(target.product_id))
|
||||||
|
if (card) return formatCard(card)
|
||||||
|
|
||||||
|
const unit = target.unit || (target.trade_by_quantity ? 'عدد' : 'گرم')
|
||||||
|
if (target.trade_by_quantity) {
|
||||||
|
return `۰ ${unit}`
|
||||||
|
}
|
||||||
|
return formatMetalDisplayValue(0, target.metal_id ?? 1)
|
||||||
|
}
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
const summary = ref<WalletAssetsSummary | null>(null)
|
const summary = ref<WalletAssetsSummary | null>(null)
|
||||||
@@ -154,3 +285,131 @@ function linesForMetal(metalId: number) {
|
|||||||
|
|
||||||
defineExpose({ reload: () => props.customerId && load(props.customerId) })
|
defineExpose({ reload: () => props.customerId && load(props.customerId) })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.customer-wallet-balance__minimal {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(4.75rem, 1fr));
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding: 0.45rem;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: #f8fafc;
|
||||||
|
max-height: 14rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.1rem;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0.35rem 0.25rem;
|
||||||
|
border-radius: 0.45rem;
|
||||||
|
background: #fff;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.customer-wallet-balance__minimal-item {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: box-shadow 0.15s ease, transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.customer-wallet-balance__minimal-item:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--selected {
|
||||||
|
box-shadow: 0 0 0 2px #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--inactive {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--gold {
|
||||||
|
background: #fffbeb;
|
||||||
|
border-color: #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--silver {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--copper {
|
||||||
|
background: #fff7ed;
|
||||||
|
border-color: #fdba74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--cash {
|
||||||
|
background: #eff6ff;
|
||||||
|
border-color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--product {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-label {
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #64748b;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-value {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--gold .customer-wallet-balance__minimal-label {
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--gold .customer-wallet-balance__minimal-value {
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--silver .customer-wallet-balance__minimal-label {
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--silver .customer-wallet-balance__minimal-value {
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--copper .customer-wallet-balance__minimal-label {
|
||||||
|
color: #c2410c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--copper .customer-wallet-balance__minimal-value {
|
||||||
|
color: #9a3412;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--cash .customer-wallet-balance__minimal-label {
|
||||||
|
color: #0369a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--cash .customer-wallet-balance__minimal-value {
|
||||||
|
color: #0284c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--product .customer-wallet-balance__minimal-label {
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-wallet-balance__minimal-item--product .customer-wallet-balance__minimal-value {
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export interface ChargeTarget {
|
|||||||
uses_main_wallet?: boolean
|
uses_main_wallet?: boolean
|
||||||
trade_by_quantity?: boolean
|
trade_by_quantity?: boolean
|
||||||
group_type?: string
|
group_type?: string
|
||||||
|
active?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChargeTargetsResponse {
|
export interface ChargeTargetsResponse {
|
||||||
|
|||||||
@@ -134,11 +134,15 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.wallet-charge-form :deep(.ant-form-item) {
|
||||||
|
margin-bottom: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
.wallet-charge-form__direction :deep(.ant-radio-button-wrapper) {
|
.wallet-charge-form__direction :deep(.ant-radio-button-wrapper) {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: 2.75rem;
|
height: 2.35rem;
|
||||||
line-height: 2.65rem;
|
line-height: 2.25rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,12 +156,12 @@ defineExpose({
|
|||||||
.wallet-charge-form__preview {
|
.wallet-charge-form__preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 0.5rem;
|
||||||
padding: 0.85rem 1rem;
|
padding: 0.55rem 0.75rem;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.6rem;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,10 +169,10 @@ defineExpose({
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 1.75rem;
|
width: 1.45rem;
|
||||||
height: 1.75rem;
|
height: 1.45rem;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
font-size: 1.15rem;
|
font-size: 1rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,12 +153,11 @@ async function submit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const target = selectedTarget.value
|
const target = selectedTarget.value
|
||||||
const productId = target?.kind === 'metal' ? target.product_id : undefined
|
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
await chargeWalletUnified({
|
await chargeWalletUnified({
|
||||||
...buildChargePayload({ ...formCharge.value, customer_id: customerId }, productId),
|
...buildChargePayload({ ...formCharge.value, customer_id: customerId }, target?.product_id ?? null),
|
||||||
})
|
})
|
||||||
message.success(walletChargeSuccessMessage(formCharge.value.direction))
|
message.success(walletChargeSuccessMessage(formCharge.value.direction))
|
||||||
emit('success')
|
emit('success')
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
allow-clear
|
allow-clear
|
||||||
placeholder="انتخاب کنید"
|
placeholder="انتخاب کنید"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
size="large"
|
size="middle"
|
||||||
:options="customerOptions"
|
:options="customerOptions"
|
||||||
:loading="customersLoading"
|
:loading="customersLoading"
|
||||||
:filter-option="filterCustomer"
|
:filter-option="filterCustomer"
|
||||||
@@ -16,9 +16,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomerWalletBalancePanel
|
<CustomerWalletBalancePanel
|
||||||
v-if="resolvedCustomerId"
|
v-if="resolvedCustomerId && !targetsLoading"
|
||||||
:customer-id="resolvedCustomerId"
|
:customer-id="resolvedCustomerId"
|
||||||
class="mb-3"
|
:charge-targets="targets"
|
||||||
|
:selected-charge-key="selectedTargetKey"
|
||||||
|
compact
|
||||||
|
@select-charge-target="selectedTargetKeyProxy = $event"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="wallet-charge-modal-body__section">
|
<div class="wallet-charge-modal-body__section">
|
||||||
@@ -26,10 +29,11 @@
|
|||||||
<a-spin :spinning="targetsLoading">
|
<a-spin :spinning="targetsLoading">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="selectedTargetKeyProxy"
|
v-model:value="selectedTargetKeyProxy"
|
||||||
|
show-search
|
||||||
class="w-full"
|
class="w-full"
|
||||||
size="large"
|
|
||||||
:options="targetOptions"
|
:options="targetOptions"
|
||||||
placeholder="انتخاب کنید"
|
placeholder="انتخاب کنید"
|
||||||
|
:filter-option="filterTarget"
|
||||||
/>
|
/>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,6 +114,10 @@ function filterCustomer(input: string, option?: { label?: string }) {
|
|||||||
return String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
return String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterTarget(input: string, option?: { label?: string }) {
|
||||||
|
return String(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
async function loadCustomers() {
|
async function loadCustomers() {
|
||||||
if (!props.showCustomerSelect) return
|
if (!props.showCustomerSelect) return
|
||||||
customersLoading.value = true
|
customersLoading.value = true
|
||||||
@@ -139,8 +147,8 @@ defineExpose({
|
|||||||
.wallet-charge-modal-body {
|
.wallet-charge-modal-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
padding: 0.25rem 1rem 0.5rem;
|
padding: 0 0.75rem 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-charge-modal-body__section {
|
.wallet-charge-modal-body__section {
|
||||||
|
|||||||
Reference in New Issue
Block a user