feat(convert): send integer ring requests for admin approval
Leftover asset splits into whole 1.5 and 2.5 rings, and the customer submits that split as a request. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -50,4 +50,6 @@
|
||||
- 2026-10-21 | نمودار خانه = کارت لندینگ | `home/components/LandingLiveChartMain.vue` + `useLandingLiveChart.ts` + `styles/landing-live-chart-main.less` | موبایل PriceChartSection و دسکتاپ DesktopChartSection/Panel همین کارت؛ استایل از main.less
|
||||
- 2026-10-21 | چیدمان کارت نمودار خانه | همان دو فایل vue/less | هدر: نام+وضعیت راست، تب محصول چپ؛ قیمت راست و تغییر چپ؛ تب بازه کنار عنوان نمودار
|
||||
- 2026-10-21 | تب نمودار = محصول فعال | `useLandingLiveChart.ts` + `LandingLiveChartMain.vue` | تب از کاتالوگ `active && show`؛ داده با `price_offset_percent` همان محصول
|
||||
- 2026-10-31 | مقدار تبدیل کاتد/مفتول/شمش | `convert/components/CopperConvertCalculator.vue` + `useCopperConvertPage.ts` | زبانه مقدار تا موجودی؛ مانده با زبانه ۱.۵ (تا کل مانده) و ۲.۵ (تا باقیمانده بعد از ۱.۵)
|
||||
- 2026-11-01 | درخواست تبدیل سیم | `convert/CopperConvertCalculator.vue` + `useCopperConvertPage.ts` + سرور `CopperConvertRequestService` + ادمین `hesabKetab.ts` فرزند `copperConvertMain` | حلقه صحیح؛ منوی ادمین زیر حساب کتاب نه منوی اصلی
|
||||
- 2026-11-01 | ۵۰۰ لیست تبدیل | `2026_11_01_000001_create_copper_convert_requests_table.php` | git pull کافی نیست؛ بدون `php artisan migrate` جدول نیست و GET ادمین ۵۰۰ میدهد
|
||||
- 2026-11-01 | جدول درخواست تبدیل | ادمین `copperConvert/components/table.vue` | دسکتاپ BaseTable؛ موبایل `CopperConvertMobileCardList` مثل بقیه صفحات
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import useAxios from '@/utils/useAxios'
|
||||
import type { CopperConvertSource } from '@/utils/copperWireConvert'
|
||||
|
||||
export function createCopperConvertRequest(payload: {
|
||||
source: CopperConvertSource
|
||||
rings15: number
|
||||
rings25: number
|
||||
}) {
|
||||
return useAxios.post('/customer/copper-convert-request', payload)
|
||||
}
|
||||
@@ -127,16 +127,15 @@
|
||||
<span class="copper-convert__result-title">تقسیم بین دو سیم</span>
|
||||
</div>
|
||||
<p class="copper-convert__asset-hint">
|
||||
مانده {{ formatAssetAmount(remainderAmount) }} {{ assetUnit }} است. زبانهٔ ۱.۵ تا کل مانده میرود؛ زبانهٔ ۲.۵ فقط تا آنچه بعد از ۱.۵ میماند.
|
||||
مانده {{ formatAssetAmount(remainderAmount) }} {{ assetUnit }} است. هر زبانه فقط عدد صحیح حلقه است؛ اعشاری که به یک حلقه نرسد در دارایی میماند.
|
||||
</p>
|
||||
|
||||
<div class="copper-convert__weight">
|
||||
<label class="copper-convert__field-label">سیم ۱.۵ mm²</label>
|
||||
<div class="copper-convert__weight-wrap">
|
||||
<span class="copper-convert__weight-value" dir="ltr">{{ formatAssetAmount(wire15Amount) }}</span>
|
||||
<span class="copper-convert__weight-unit">{{ assetUnit }}</span>
|
||||
<span class="copper-convert__weight-value" dir="ltr">{{ wire15Rings }}</span>
|
||||
<span class="copper-convert__weight-unit">عدد حلقه</span>
|
||||
</div>
|
||||
<p class="copper-convert__asset-hint">معادل {{ formatRings(wire15Rings) }} حلقه</p>
|
||||
<div
|
||||
class="app-touch-slider copper-convert__slider"
|
||||
@touchstart.stop
|
||||
@@ -144,26 +143,26 @@
|
||||
@pointerdown.stop
|
||||
>
|
||||
<a-slider
|
||||
v-model:value="wire15Amount"
|
||||
v-model:value="wire15Rings"
|
||||
:min="0"
|
||||
:max="remainderAmount"
|
||||
:step="sliderStep"
|
||||
:tip-formatter="sliderTip"
|
||||
:max="maxRings15 > 0 ? maxRings15 : 1"
|
||||
:step="1"
|
||||
:disabled="maxRings15 <= 0"
|
||||
:tip-formatter="ringTip"
|
||||
/>
|
||||
</div>
|
||||
<div class="copper-convert__slider-scale">
|
||||
<span>۰</span>
|
||||
<span dir="ltr">{{ formatAssetAmount(remainderAmount) }} {{ assetUnit }}</span>
|
||||
<span dir="ltr">{{ maxRings15 }} عدد حلقه</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="copper-convert__weight">
|
||||
<label class="copper-convert__field-label">سیم ۲.۵ mm²</label>
|
||||
<div class="copper-convert__weight-wrap">
|
||||
<span class="copper-convert__weight-value" dir="ltr">{{ formatAssetAmount(wire25Amount) }}</span>
|
||||
<span class="copper-convert__weight-unit">{{ assetUnit }}</span>
|
||||
<span class="copper-convert__weight-value" dir="ltr">{{ wire25Rings }}</span>
|
||||
<span class="copper-convert__weight-unit">عدد حلقه</span>
|
||||
</div>
|
||||
<p class="copper-convert__asset-hint">معادل {{ formatRings(wire25Rings) }} حلقه</p>
|
||||
<div
|
||||
class="app-touch-slider copper-convert__slider"
|
||||
@touchstart.stop
|
||||
@@ -171,19 +170,35 @@
|
||||
@pointerdown.stop
|
||||
>
|
||||
<a-slider
|
||||
v-model:value="wire25Amount"
|
||||
v-model:value="wire25Rings"
|
||||
:min="0"
|
||||
:max="wire25Max > 0 ? wire25Max : 1"
|
||||
:step="sliderStep"
|
||||
:disabled="wire25Max <= 0"
|
||||
:tip-formatter="sliderTip"
|
||||
:max="maxRings25 > 0 ? maxRings25 : 1"
|
||||
:step="1"
|
||||
:disabled="maxRings25 <= 0"
|
||||
:tip-formatter="ringTip"
|
||||
/>
|
||||
</div>
|
||||
<div class="copper-convert__slider-scale">
|
||||
<span>۰</span>
|
||||
<span dir="ltr">{{ formatAssetAmount(wire25Max) }} {{ assetUnit }}</span>
|
||||
<span dir="ltr">{{ maxRings25 }} عدد حلقه</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="copper-convert__asset-hint">
|
||||
اعشار مانده: {{ formatAssetAmount(leftoverAmount) }} {{ assetUnit }} نگه داشته میشود.
|
||||
</p>
|
||||
<BasicButton
|
||||
type="primary"
|
||||
class="!w-full"
|
||||
:loading="submitting"
|
||||
:disabledB="wire15Rings + wire25Rings < 1"
|
||||
@click="submitRequest"
|
||||
>
|
||||
ثبت درخواست تبدیل
|
||||
</BasicButton>
|
||||
<p class="copper-convert__asset-hint">
|
||||
درخواست برای ادمین میرود. بعد از تأیید، همان مقدار دارایی فروخته و حلقههای انتخابشده خریده میشود.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<p v-if="!result && amountAttempted && !loading" class="copper-convert__warn">
|
||||
@@ -195,6 +210,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import { useCopperConvertPageContext } from '../composables/useCopperConvertPage'
|
||||
import type { CopperConvertSource } from '@/utils/copperWireConvert'
|
||||
|
||||
@@ -206,11 +222,13 @@ const {
|
||||
sliderAmount,
|
||||
sliderStep,
|
||||
remainderAmount,
|
||||
wire15Amount,
|
||||
wire25Amount,
|
||||
wire25Max,
|
||||
maxRings15,
|
||||
maxRings25,
|
||||
wire15Rings,
|
||||
wire25Rings,
|
||||
leftoverAmount,
|
||||
submitting,
|
||||
submitRequest,
|
||||
result,
|
||||
priceRows,
|
||||
} = useCopperConvertPageContext()
|
||||
@@ -237,6 +255,10 @@ function sliderTip(value?: number) {
|
||||
return `${formatAssetAmount(Number(value ?? 0))} ${assetUnit.value}`
|
||||
}
|
||||
|
||||
function ringTip(value?: number) {
|
||||
return `${Math.floor(Number(value ?? 0))} عدد حلقه`
|
||||
}
|
||||
|
||||
function formatRial(value: number) {
|
||||
return Math.round(value).toLocaleString('en-US')
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import {
|
||||
type InjectionKey,
|
||||
type Ref,
|
||||
} from 'vue'
|
||||
import { createCopperConvertRequest } from '@/api/copperConvert'
|
||||
import { getProducts } from '@/api/products'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import type { Product } from '@/models/products'
|
||||
import type { WalletInventoryCard } from '@/models/walletAssets'
|
||||
@@ -38,11 +40,13 @@ export interface CopperConvertPageContext {
|
||||
sliderAmount: Ref<number>
|
||||
sliderStep: ComputedRef<number>
|
||||
remainderAmount: ComputedRef<number>
|
||||
wire15Amount: Ref<number>
|
||||
wire25Amount: Ref<number>
|
||||
wire25Max: ComputedRef<number>
|
||||
wire15Rings: ComputedRef<number>
|
||||
wire25Rings: ComputedRef<number>
|
||||
maxRings15: ComputedRef<number>
|
||||
maxRings25: ComputedRef<number>
|
||||
wire15Rings: Ref<number>
|
||||
wire25Rings: Ref<number>
|
||||
leftoverAmount: ComputedRef<number>
|
||||
submitting: Ref<boolean>
|
||||
submitRequest: () => Promise<void>
|
||||
prices: ComputedRef<CopperConvertPrices>
|
||||
result: ComputedRef<CopperConvertResult | null>
|
||||
priceRows: ComputedRef<{ label: string; value: string; missing?: boolean }[]>
|
||||
@@ -74,6 +78,18 @@ function quantityToKg(quantity: number, unit: string, product?: Product): number
|
||||
return quantity
|
||||
}
|
||||
|
||||
function kgToNative(kg: number, unit: string, product?: Product): number {
|
||||
if (kg <= 0) return 0
|
||||
if (unit.includes('کیلو')) return kg
|
||||
if (unit.includes('گرم')) return kg * 1000
|
||||
const perPiece = Number(product?.amount ?? 0)
|
||||
if (perPiece > 0) {
|
||||
const gramsPerPiece = weightToGrams(perPiece, getProductWeightUnit(product))
|
||||
if (gramsPerPiece > 0) return (kg * 1000) / gramsPerPiece
|
||||
}
|
||||
return kg
|
||||
}
|
||||
|
||||
function titleIncludes(product: Product, part: string) {
|
||||
return (product.title ?? '').includes(part)
|
||||
}
|
||||
@@ -122,8 +138,9 @@ export function useCopperConvertPage() {
|
||||
const loading = ref(true)
|
||||
const source = ref<CopperConvertSource>('katod')
|
||||
const sliderAmount = ref(0)
|
||||
const wire15Amount = ref(0)
|
||||
const wire25Amount = ref(0)
|
||||
const wire15Rings = ref(0)
|
||||
const wire25Rings = ref(0)
|
||||
const submitting = ref(false)
|
||||
const catalog = ref<Product[]>([])
|
||||
const { assetsSummary, loadWalletAssets } = useWalletAssets()
|
||||
|
||||
@@ -163,19 +180,10 @@ export function useCopperConvertPage() {
|
||||
roundAmount(Math.max(0, assetAmount.value - sliderAmount.value)),
|
||||
)
|
||||
|
||||
const wire25Max = computed(() =>
|
||||
roundAmount(Math.max(0, remainderAmount.value - wire15Amount.value)),
|
||||
)
|
||||
|
||||
watch(source, () => {
|
||||
sliderAmount.value = assetAmount.value
|
||||
wire15Amount.value = 0
|
||||
wire25Amount.value = 0
|
||||
})
|
||||
|
||||
watch([remainderAmount, wire15Amount], () => {
|
||||
if (wire15Amount.value > remainderAmount.value) wire15Amount.value = remainderAmount.value
|
||||
if (wire25Amount.value > wire25Max.value) wire25Amount.value = wire25Max.value
|
||||
wire15Rings.value = 0
|
||||
wire25Rings.value = 0
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -221,24 +229,62 @@ export function useCopperConvertPage() {
|
||||
calcCopperWireConvert(selectedKg.value, source.value, prices.value),
|
||||
)
|
||||
|
||||
const wire15Rings = computed(() => {
|
||||
const converted = calcCopperWireConvert(
|
||||
quantityToKg(wire15Amount.value, assetUnit.value, sourceProduct.value),
|
||||
source.value,
|
||||
prices.value,
|
||||
)
|
||||
return converted?.rings15ByValue ?? 0
|
||||
const sourceUnitPrice = computed(() => {
|
||||
if (source.value === 'katod') return prices.value.katodPerKg
|
||||
if (source.value === 'maftoul') return prices.value.maftoulPerKg
|
||||
return prices.value.shamshPerKg
|
||||
})
|
||||
|
||||
const wire25Rings = computed(() => {
|
||||
const converted = calcCopperWireConvert(
|
||||
quantityToKg(wire25Amount.value, assetUnit.value, sourceProduct.value),
|
||||
source.value,
|
||||
prices.value,
|
||||
)
|
||||
return converted?.rings25ByValue ?? 0
|
||||
const remainderValue = computed(() =>
|
||||
quantityToKg(remainderAmount.value, assetUnit.value, sourceProduct.value) * sourceUnitPrice.value,
|
||||
)
|
||||
|
||||
const maxRings15 = computed(() => {
|
||||
const price = prices.value.wireRing15
|
||||
if (price <= 0 || remainderValue.value <= 0) return 0
|
||||
return Math.floor(remainderValue.value / price)
|
||||
})
|
||||
|
||||
const maxRings25 = computed(() => {
|
||||
const price = prices.value.wireRing25
|
||||
const left = remainderValue.value - wire15Rings.value * prices.value.wireRing15
|
||||
if (price <= 0 || left <= 0) return 0
|
||||
return Math.floor(left / price)
|
||||
})
|
||||
|
||||
const leftoverAmount = computed(() => {
|
||||
const spent = wire15Rings.value * prices.value.wireRing15 + wire25Rings.value * prices.value.wireRing25
|
||||
const leftValue = Math.max(0, remainderValue.value - spent)
|
||||
const perUnit = sourceUnitPrice.value
|
||||
if (perUnit <= 0) return remainderAmount.value
|
||||
return roundAmount(kgToNative(leftValue / perUnit, assetUnit.value, sourceProduct.value))
|
||||
})
|
||||
|
||||
watch([wire15Rings, maxRings15, maxRings25], () => {
|
||||
const next15 = Math.max(0, Math.min(maxRings15.value, Math.floor(wire15Rings.value)))
|
||||
if (next15 !== wire15Rings.value) wire15Rings.value = next15
|
||||
const next25 = Math.max(0, Math.min(maxRings25.value, Math.floor(wire25Rings.value)))
|
||||
if (next25 !== wire25Rings.value) wire25Rings.value = next25
|
||||
})
|
||||
|
||||
async function submitRequest() {
|
||||
if (submitting.value) return
|
||||
if (wire15Rings.value + wire25Rings.value < 1) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await createCopperConvertRequest({
|
||||
source: source.value,
|
||||
rings15: wire15Rings.value,
|
||||
rings25: wire25Rings.value,
|
||||
})
|
||||
wire15Rings.value = 0
|
||||
wire25Rings.value = 0
|
||||
message.success('درخواست تبدیل برای تأیید ادمین ثبت شد')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const priceRows = computed(() => {
|
||||
const p = prices.value
|
||||
const row = (label: string, amount: number) => ({
|
||||
@@ -288,11 +334,13 @@ export function useCopperConvertPage() {
|
||||
sliderAmount,
|
||||
sliderStep,
|
||||
remainderAmount,
|
||||
wire15Amount,
|
||||
wire25Amount,
|
||||
wire25Max,
|
||||
maxRings15,
|
||||
maxRings25,
|
||||
wire15Rings,
|
||||
wire25Rings,
|
||||
leftoverAmount,
|
||||
submitting,
|
||||
submitRequest,
|
||||
prices,
|
||||
result,
|
||||
priceRows,
|
||||
|
||||
Reference in New Issue
Block a user