Allow multi-company carts to submit as one checkout creating one order per company.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export function useCartPage() {
|
|||||||
const items = computed(() => cartStore.activeCart?.items ?? [])
|
const items = computed(() => cartStore.activeCart?.items ?? [])
|
||||||
const isEmpty = computed(() => items.value.length === 0)
|
const isEmpty = computed(() => items.value.length === 0)
|
||||||
const companyName = computed(() => cartStore.companyNamesLabel || cartStore.activeCart?.companyName || '')
|
const companyName = computed(() => cartStore.companyNamesLabel || cartStore.activeCart?.companyName || '')
|
||||||
|
const companySummaries = computed(() => cartStore.companySummaries)
|
||||||
|
|
||||||
async function removeItem(itemId: string) {
|
async function removeItem(itemId: string) {
|
||||||
const item = items.value.find((entry) => entry.id === itemId)
|
const item = items.value.find((entry) => entry.id === itemId)
|
||||||
@@ -40,17 +41,25 @@ export function useCartPage() {
|
|||||||
submitting.value = true
|
submitting.value = true
|
||||||
submitError.value = ''
|
submitError.value = ''
|
||||||
try {
|
try {
|
||||||
// همگامسازی سبد با سرور قبل از ثبت تا productId/companyId کهنه نباشد
|
|
||||||
await cartStore.hydrate()
|
await cartStore.hydrate()
|
||||||
if (!cartStore.activeCart?.items.length) {
|
if (!cartStore.activeCart?.items.length) {
|
||||||
submitError.value = 'سبد خرید خالی است یا با سرور همگام نشد.'
|
submitError.value = 'سبد خرید خالی است یا با سرور همگام نشد.'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const order = await orderStore.submitOrder(cartStore.activeCart)
|
const created = await orderStore.submitOrder(cartStore.activeCart)
|
||||||
await cartStore.hydrate()
|
await cartStore.hydrate()
|
||||||
router.replace(`/orders/${order.id}/success`)
|
if (created.length === 1) {
|
||||||
|
router.replace(`/orders/${created[0]!.id}/success`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// چند شرکت → چند سفارش؛ به لیست سفارشها برو
|
||||||
|
router.replace({
|
||||||
|
path: '/orders',
|
||||||
|
query: { submitted: String(created.length) },
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
submitError.value = e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.'
|
submitError.value = e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.'
|
||||||
|
await cartStore.hydrate()
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
@@ -90,6 +99,7 @@ export function useCartPage() {
|
|||||||
items,
|
items,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
companyName,
|
companyName,
|
||||||
|
companySummaries,
|
||||||
showFullDialog,
|
showFullDialog,
|
||||||
submitting,
|
submitting,
|
||||||
submitError,
|
submitError,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { computed, ref, watch, onMounted } from 'vue'
|
import { computed, ref, watch, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useOrderStore } from '@/stores/order'
|
import { useOrderStore } from '@/stores/order'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useCatalogStore } from '@/stores/catalog'
|
import { useCatalogStore } from '@/stores/catalog'
|
||||||
@@ -12,22 +12,43 @@ import {
|
|||||||
matchesMobileOrderTab,
|
matchesMobileOrderTab,
|
||||||
type DesktopOrderFilterId,
|
type DesktopOrderFilterId,
|
||||||
} from '@/utils/orderStatusGroups'
|
} from '@/utils/orderStatusGroups'
|
||||||
|
import { toPersianDigits } from '@/utils/format'
|
||||||
|
|
||||||
const DESKTOP_PAGE_SIZE = 5
|
const DESKTOP_PAGE_SIZE = 5
|
||||||
const MOBILE_PAGE_SIZE = 5
|
const MOBILE_PAGE_SIZE = 5
|
||||||
|
|
||||||
export function useOrderList() {
|
export function useOrderList() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const orderStore = useOrderStore()
|
const orderStore = useOrderStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const catalogStore = useCatalogStore()
|
const catalogStore = useCatalogStore()
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const mobilePage = ref(1)
|
const mobilePage = ref(1)
|
||||||
|
const submitSuccessMessage = ref('')
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([orderStore.hydrate(), catalogStore.loadCompanies()])
|
await Promise.all([orderStore.hydrate(), catalogStore.loadCompanies()])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query.submitted,
|
||||||
|
(submitted) => {
|
||||||
|
const count = Number(submitted)
|
||||||
|
if (!Number.isFinite(count) || count < 1) {
|
||||||
|
submitSuccessMessage.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submitSuccessMessage.value =
|
||||||
|
count === 1
|
||||||
|
? 'پیشسفارش با موفقیت ثبت شد.'
|
||||||
|
: `${toPersianDigits(String(count))} پیشسفارش (برای شرکتهای مختلف) ثبت شد.`
|
||||||
|
const { submitted: _submitted, ...rest } = route.query
|
||||||
|
void router.replace({ query: rest })
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
const tab = computed({
|
const tab = computed({
|
||||||
get: () => (router.currentRoute.value.query.tab as string) ?? 'all',
|
get: () => (router.currentRoute.value.query.tab as string) ?? 'all',
|
||||||
set: (v: string) => router.replace({ query: { ...router.currentRoute.value.query, tab: v } }),
|
set: (v: string) => router.replace({ query: { ...router.currentRoute.value.query, tab: v } }),
|
||||||
@@ -202,6 +223,7 @@ export function useOrderList() {
|
|||||||
mobilePage,
|
mobilePage,
|
||||||
companySlug,
|
companySlug,
|
||||||
iconVariant,
|
iconVariant,
|
||||||
|
submitSuccessMessage,
|
||||||
goToOrder,
|
goToOrder,
|
||||||
setPage,
|
setPage,
|
||||||
setMobilePage,
|
setMobilePage,
|
||||||
|
|||||||
+53
-12
@@ -45,30 +45,70 @@ export const useCartStore = defineStore('cart', () => {
|
|||||||
|
|
||||||
const itemCount = computed(() => activeCart.value?.items.length ?? 0)
|
const itemCount = computed(() => activeCart.value?.items.length ?? 0)
|
||||||
|
|
||||||
|
const catalogStore = useCatalogStore()
|
||||||
|
|
||||||
const companyNamesLabel = computed(() => {
|
const companyNamesLabel = computed(() => {
|
||||||
if (!activeCart.value?.items.length) return ''
|
if (!activeCart.value?.items.length) return ''
|
||||||
return uniqueCompanyNames(activeCart.value.items)
|
return uniqueCompanyNames(activeCart.value.items)
|
||||||
})
|
})
|
||||||
|
|
||||||
const catalogStore = useCatalogStore()
|
const companySummaries = computed(() => {
|
||||||
|
const cart = activeCart.value
|
||||||
|
if (!cart?.items.length) return [] as {
|
||||||
|
companyId: string
|
||||||
|
companyName: string
|
||||||
|
meterage: number
|
||||||
|
maxMeterage: number
|
||||||
|
remaining: number
|
||||||
|
}[]
|
||||||
|
|
||||||
|
const byCompany = new Map<string, { companyName: string; meterage: number }>()
|
||||||
|
for (const item of cart.items) {
|
||||||
|
const current = byCompany.get(item.companyId)
|
||||||
|
if (current) {
|
||||||
|
current.meterage += item.meterage
|
||||||
|
} else {
|
||||||
|
byCompany.set(item.companyId, {
|
||||||
|
companyName: item.companyName || 'شرکت',
|
||||||
|
meterage: item.meterage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...byCompany.entries()].map(([companyId, info]) => {
|
||||||
|
const max =
|
||||||
|
catalogStore.getCompany(companyId)?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE
|
||||||
|
return {
|
||||||
|
companyId,
|
||||||
|
companyName: info.companyName,
|
||||||
|
meterage: info.meterage,
|
||||||
|
maxMeterage: max,
|
||||||
|
remaining: Math.max(0, max - info.meterage),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/** سقف نمایشی: مجموع سقف شرکتهای داخل سبد (هر شرکت جداگانه محدود میشود). */
|
||||||
const maxMeterage = computed(() => {
|
const maxMeterage = computed(() => {
|
||||||
if (!activeCart.value?.items.length) {
|
if (!companySummaries.value.length) {
|
||||||
const selected = selectedCompanyId.value
|
const selected = selectedCompanyId.value
|
||||||
? catalogStore.getCompany(selectedCompanyId.value)
|
? catalogStore.getCompany(selectedCompanyId.value)
|
||||||
: null
|
: null
|
||||||
return selected?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE
|
return selected?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE
|
||||||
}
|
}
|
||||||
|
return companySummaries.value.reduce((sum, row) => sum + row.maxMeterage, 0)
|
||||||
const companyIds = [...new Set(activeCart.value.items.map((item) => item.companyId))]
|
|
||||||
return companyIds.reduce((sum, id) => {
|
|
||||||
const company = catalogStore.getCompany(id)
|
|
||||||
return sum + (company?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE)
|
|
||||||
}, 0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const remainingCapacity = computed(() => Math.max(0, maxMeterage.value - totalMeterage.value))
|
const remainingCapacity = computed(() =>
|
||||||
const isFull = computed(() => totalMeterage.value >= maxMeterage.value)
|
companySummaries.value.length
|
||||||
|
? companySummaries.value.reduce((sum, row) => sum + row.remaining, 0)
|
||||||
|
: Math.max(0, maxMeterage.value - totalMeterage.value),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** وقتی حداقل یک شرکت به سقف خودش رسیده باشد. */
|
||||||
|
const isFull = computed(() =>
|
||||||
|
companySummaries.value.some((row) => row.meterage >= row.maxMeterage),
|
||||||
|
)
|
||||||
|
|
||||||
function applyServerCart(serverCart: ApiCart) {
|
function applyServerCart(serverCart: ApiCart) {
|
||||||
const cart: Cart = {
|
const cart: Cart = {
|
||||||
@@ -178,8 +218,8 @@ export const useCartStore = defineStore('cart', () => {
|
|||||||
|
|
||||||
replaceActiveCart({
|
replaceActiveCart({
|
||||||
id: cart.id || 'local',
|
id: cart.id || 'local',
|
||||||
companyId: company.id,
|
companyId: items[0]?.companyId || company.id,
|
||||||
companyName: company.name,
|
companyName: uniqueCompanyNames(items) || company.name,
|
||||||
items,
|
items,
|
||||||
notes: cart.notes,
|
notes: cart.notes,
|
||||||
})
|
})
|
||||||
@@ -349,6 +389,7 @@ export const useCartStore = defineStore('cart', () => {
|
|||||||
totalMeterage,
|
totalMeterage,
|
||||||
itemCount,
|
itemCount,
|
||||||
companyNamesLabel,
|
companyNamesLabel,
|
||||||
|
companySummaries,
|
||||||
maxMeterage,
|
maxMeterage,
|
||||||
remainingCapacity,
|
remainingCapacity,
|
||||||
isFull,
|
isFull,
|
||||||
|
|||||||
+35
-27
@@ -117,8 +117,9 @@ export const useOrderStore = defineStore('order', () => {
|
|||||||
/**
|
/**
|
||||||
* سفارش بدون روش تسویه ثبت میشود؛ مشتری بعد از قیمتگذاری مدیریت
|
* سفارش بدون روش تسویه ثبت میشود؛ مشتری بعد از قیمتگذاری مدیریت
|
||||||
* روش تسویه را انتخاب میکند.
|
* روش تسویه را انتخاب میکند.
|
||||||
|
* سبد چندشرکتی: برای هر شرکت یک سفارش جدا ساخته میشود.
|
||||||
*/
|
*/
|
||||||
async function submitOrder(cart: Cart): Promise<Order> {
|
async function submitOrder(cart: Cart): Promise<Order[]> {
|
||||||
if (!cart.items.length) {
|
if (!cart.items.length) {
|
||||||
throw new Error('سبد خرید خالی است.')
|
throw new Error('سبد خرید خالی است.')
|
||||||
}
|
}
|
||||||
@@ -126,43 +127,50 @@ export const useOrderStore = defineStore('order', () => {
|
|||||||
throw new Error('سبد خرید با سرور همگام نیست. لطفاً دوباره محصولات را به سبد اضافه کنید.')
|
throw new Error('سبد خرید با سرور همگام نیست. لطفاً دوباره محصولات را به سبد اضافه کنید.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// شرکت را از خود آیتمها بگیر؛ هدر سبد گاهی با شرکت واقعی آیتمها ناسازگار است.
|
|
||||||
const companyIds = [
|
const companyIds = [
|
||||||
...new Set(cart.items.map((item) => item.companyId).filter(Boolean)),
|
...new Set(cart.items.map((item) => item.companyId).filter(Boolean)),
|
||||||
]
|
]
|
||||||
if (!companyIds.length) {
|
if (!companyIds.length) {
|
||||||
throw new Error('شرکت سبد مشخص نیست. سبد را خالی کنید و دوباره اضافه کنید.')
|
throw new Error('شرکت سبد مشخص نیست. سبد را خالی کنید و دوباره اضافه کنید.')
|
||||||
}
|
}
|
||||||
if (companyIds.length > 1) {
|
|
||||||
throw new Error(
|
|
||||||
'سبد شامل چند شرکت است. فعلاً فقط اقلام یک شرکت را نگه دارید و ثبت کنید.',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const companyId = companyIds[0]!
|
|
||||||
const itemsForOrder = cart.items.filter((item) => item.companyId === companyId)
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
companyId,
|
|
||||||
notes: cart.notes,
|
|
||||||
items: itemsForOrder.map((item) => ({
|
|
||||||
productId: item.productId,
|
|
||||||
meterage: Number(item.meterage),
|
|
||||||
notes: item.notes,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const createdOrders: Order[] = []
|
||||||
try {
|
try {
|
||||||
const order = await apiRequest<Order>('/orders', {
|
for (const companyId of companyIds) {
|
||||||
method: 'POST',
|
const itemsForOrder = cart.items.filter((item) => item.companyId === companyId)
|
||||||
body,
|
if (!itemsForOrder.length) continue
|
||||||
})
|
|
||||||
orders.value.unshift(order)
|
const order = await apiRequest<Order>('/orders', {
|
||||||
clearCheckDraft()
|
method: 'POST',
|
||||||
return order
|
body: {
|
||||||
|
companyId,
|
||||||
|
notes: cart.notes,
|
||||||
|
items: itemsForOrder.map((item) => ({
|
||||||
|
productId: item.productId,
|
||||||
|
meterage: Number(item.meterage),
|
||||||
|
notes: item.notes,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
orders.value.unshift(order)
|
||||||
|
createdOrders.push(order)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (createdOrders.length) {
|
||||||
|
// بخشی ثبت شده؛ سفارشهای موفق را نگه میداریم و خطا را با توضیح برمیگردانیم
|
||||||
|
clearCheckDraft()
|
||||||
|
const names = createdOrders.map((o) => o.orderNumber).join('، ')
|
||||||
|
throw new Error(
|
||||||
|
e instanceof ApiError
|
||||||
|
? `${e.message} (سفارشهای ثبتشده: ${names})`
|
||||||
|
: `ثبت بخشی از سفارشها ناموفق بود. ثبتشده: ${names}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
throw new Error(e instanceof ApiError ? e.message : 'ثبت سفارش ناموفق بود.')
|
throw new Error(e instanceof ApiError ? e.message : 'ثبت سفارش ناموفق بود.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCheckDraft()
|
||||||
|
return createdOrders
|
||||||
}
|
}
|
||||||
|
|
||||||
/** انتخاب روش تسویه پس از صدور فاکتور. */
|
/** انتخاب روش تسویه پس از صدور فاکتور. */
|
||||||
|
|||||||
+62
-34
@@ -25,6 +25,7 @@ const {
|
|||||||
mobilePage,
|
mobilePage,
|
||||||
companySlug,
|
companySlug,
|
||||||
iconVariant,
|
iconVariant,
|
||||||
|
submitSuccessMessage,
|
||||||
goToOrder,
|
goToOrder,
|
||||||
setPage,
|
setPage,
|
||||||
setMobilePage,
|
setMobilePage,
|
||||||
@@ -49,39 +50,66 @@ const desktopFilterTabs: { id: DesktopOrderFilterId; label: string }[] = [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MobileOrdersPage
|
<div class="orders-view">
|
||||||
class="mobile-only"
|
<p v-if="submitSuccessMessage" class="orders-view__success" role="status">
|
||||||
:tab="tab"
|
{{ submitSuccessMessage }}
|
||||||
:tabs="mobileTabs"
|
</p>
|
||||||
:orders="paginatedOrdersMobile"
|
|
||||||
:total-count="filteredOrdersMobile.length"
|
|
||||||
:current-page="mobilePage"
|
|
||||||
:total-pages="mobileTotalPages"
|
|
||||||
:pagination-range="mobilePaginationRange"
|
|
||||||
:visible-pages="mobileVisiblePages"
|
|
||||||
:company-slug="companySlug"
|
|
||||||
:icon-variant="iconVariant"
|
|
||||||
@update:tab="tab = $event"
|
|
||||||
@select="goToOrder"
|
|
||||||
@back="router.push('/')"
|
|
||||||
@set-page="setMobilePage"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DesktopOrdersPage
|
<MobileOrdersPage
|
||||||
class="desktop-only"
|
class="mobile-only"
|
||||||
:stats="stats"
|
:tab="tab"
|
||||||
:desktop-filter="desktopFilter"
|
:tabs="mobileTabs"
|
||||||
:desktop-filter-tabs="desktopFilterTabs"
|
:orders="paginatedOrdersMobile"
|
||||||
:filtered-orders="filteredOrdersDesktop"
|
:total-count="filteredOrdersMobile.length"
|
||||||
:paginated-orders="paginatedOrders"
|
:current-page="mobilePage"
|
||||||
:pagination-range="paginationRange"
|
:total-pages="mobileTotalPages"
|
||||||
:visible-pages="visiblePages"
|
:pagination-range="mobilePaginationRange"
|
||||||
:current-page="currentPage"
|
:visible-pages="mobileVisiblePages"
|
||||||
:total-pages="totalPages"
|
:company-slug="companySlug"
|
||||||
@view-stat-filter="viewStatFilter"
|
:icon-variant="iconVariant"
|
||||||
@set-desktop-filter="setDesktopFilter"
|
@update:tab="tab = $event"
|
||||||
@select="goToOrder"
|
@select="goToOrder"
|
||||||
@set-page="setPage"
|
@back="router.push('/')"
|
||||||
@new-order="router.push('/')"
|
@set-page="setMobilePage"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DesktopOrdersPage
|
||||||
|
class="desktop-only"
|
||||||
|
:stats="stats"
|
||||||
|
:desktop-filter="desktopFilter"
|
||||||
|
:desktop-filter-tabs="desktopFilterTabs"
|
||||||
|
:filtered-orders="filteredOrdersDesktop"
|
||||||
|
:paginated-orders="paginatedOrders"
|
||||||
|
:pagination-range="paginationRange"
|
||||||
|
:visible-pages="visiblePages"
|
||||||
|
:current-page="currentPage"
|
||||||
|
:total-pages="totalPages"
|
||||||
|
@view-stat-filter="viewStatFilter"
|
||||||
|
@set-desktop-filter="setDesktopFilter"
|
||||||
|
@select="goToOrder"
|
||||||
|
@set-page="setPage"
|
||||||
|
@new-order="router.push('/')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.orders-view {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-view__success {
|
||||||
|
margin: 0 var(--page-padding) 12px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--color-success-soft);
|
||||||
|
border: 1px solid color-mix(in srgb, var(--color-success) 35%, transparent);
|
||||||
|
color: var(--color-success);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const {
|
|||||||
items,
|
items,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
companyName,
|
companyName,
|
||||||
|
companySummaries,
|
||||||
showFullDialog,
|
showFullDialog,
|
||||||
submitting,
|
submitting,
|
||||||
submitError,
|
submitError,
|
||||||
@@ -40,6 +41,7 @@ function goToOrdering() {
|
|||||||
class="mobile-only"
|
class="mobile-only"
|
||||||
:items="items"
|
:items="items"
|
||||||
:company-name="companyName"
|
:company-name="companyName"
|
||||||
|
:company-summaries="companySummaries"
|
||||||
:total-meterage="cartStore.totalMeterage"
|
:total-meterage="cartStore.totalMeterage"
|
||||||
:max-meterage="cartStore.maxMeterage"
|
:max-meterage="cartStore.maxMeterage"
|
||||||
:remaining-capacity="cartStore.remainingCapacity"
|
:remaining-capacity="cartStore.remainingCapacity"
|
||||||
@@ -55,6 +57,7 @@ function goToOrdering() {
|
|||||||
class="desktop-only"
|
class="desktop-only"
|
||||||
:items="items"
|
:items="items"
|
||||||
:company-name="companyName"
|
:company-name="companyName"
|
||||||
|
:company-summaries="companySummaries"
|
||||||
:total-meterage="cartStore.totalMeterage"
|
:total-meterage="cartStore.totalMeterage"
|
||||||
:max-meterage="cartStore.maxMeterage"
|
:max-meterage="cartStore.maxMeterage"
|
||||||
:remaining-capacity="cartStore.remainingCapacity"
|
:remaining-capacity="cartStore.remainingCapacity"
|
||||||
@@ -70,7 +73,7 @@ function goToOrdering() {
|
|||||||
<div class="dialog card">
|
<div class="dialog card">
|
||||||
<h3>سقف متراژ تکمیل شد</h3>
|
<h3>سقف متراژ تکمیل شد</h3>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
سقف متراژ این فاکتور تکمیل شده است. آیا مایلید ادامه محصولات را در فاکتور جدید ثبت کنید؟
|
سقف متراژ حداقل یک شرکت در سبد پر شده است. میتوانید همین پیشسفارشها را ثبت کنید یا سبد را خالی کرده و فاکتور جدید بسازید.
|
||||||
</p>
|
</p>
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<BaseButton variant="outline" :loading="submitting" @click="proceedAnyway">
|
<BaseButton variant="outline" :loading="submitting" @click="proceedAnyway">
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ import type { CartItem } from '@/types'
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
items: CartItem[]
|
items: CartItem[]
|
||||||
companyName: string
|
companyName: string
|
||||||
|
companySummaries?: {
|
||||||
|
companyId: string
|
||||||
|
companyName: string
|
||||||
|
meterage: number
|
||||||
|
maxMeterage: number
|
||||||
|
remaining: number
|
||||||
|
}[]
|
||||||
totalMeterage: number
|
totalMeterage: number
|
||||||
maxMeterage: number
|
maxMeterage: number
|
||||||
remainingCapacity: number
|
remainingCapacity: number
|
||||||
@@ -156,6 +163,17 @@ const quickLinks = [
|
|||||||
</span>
|
</span>
|
||||||
<strong>{{ formatMeterage(totalMeterage) }}</strong>
|
<strong>{{ formatMeterage(totalMeterage) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="row in companySummaries ?? []"
|
||||||
|
:key="row.companyId"
|
||||||
|
class="cart-summary-card__row cart-summary-card__row--sub"
|
||||||
|
>
|
||||||
|
<span class="cart-summary-card__row-label">{{ row.companyName }}</span>
|
||||||
|
<strong>
|
||||||
|
{{ formatMeterage(row.meterage) }}
|
||||||
|
<span class="cart-summary-card__cap">/ {{ formatMeterage(row.maxMeterage) }}</span>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
<div class="cart-summary-card__row cart-summary-card__row--hint">
|
<div class="cart-summary-card__row cart-summary-card__row--hint">
|
||||||
<span class="cart-summary-card__row-label">
|
<span class="cart-summary-card__row-label">
|
||||||
<IconOrders :size="16" />
|
<IconOrders :size="16" />
|
||||||
@@ -176,7 +194,7 @@ const quickLinks = [
|
|||||||
{{ submitting ? 'در حال ثبت…' : 'ثبت پیشسفارش' }}
|
{{ submitting ? 'در حال ثبت…' : 'ثبت پیشسفارش' }}
|
||||||
</button>
|
</button>
|
||||||
<p class="cart-summary-card__note">
|
<p class="cart-summary-card__note">
|
||||||
قیمتگذاری توسط مدیریت انجام میشود؛ پس از صدور فاکتور روش تسویه را انتخاب میکنید.
|
اقلام همه شرکتها در یک سبد هستند؛ با ثبت، برای هر شرکت یک پیشسفارش جدا ساخته میشود.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -483,6 +501,17 @@ const quickLinks = [
|
|||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cart-summary-card__row--sub {
|
||||||
|
padding-block: 6px;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-summary-card__cap {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.cart-summary-card__row:last-child {
|
.cart-summary-card__row:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ import type { CartItem } from '@/types'
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
items: CartItem[]
|
items: CartItem[]
|
||||||
companyName: string
|
companyName: string
|
||||||
|
companySummaries?: {
|
||||||
|
companyId: string
|
||||||
|
companyName: string
|
||||||
|
meterage: number
|
||||||
|
maxMeterage: number
|
||||||
|
remaining: number
|
||||||
|
}[]
|
||||||
totalMeterage: number
|
totalMeterage: number
|
||||||
maxMeterage: number
|
maxMeterage: number
|
||||||
remainingCapacity: number
|
remainingCapacity: number
|
||||||
@@ -67,10 +74,23 @@ const emit = defineEmits<{
|
|||||||
<div class="cart-mobile-page__footer">
|
<div class="cart-mobile-page__footer">
|
||||||
<div class="cart-mobile-summary">
|
<div class="cart-mobile-summary">
|
||||||
<div class="cart-mobile-summary__row">
|
<div class="cart-mobile-summary__row">
|
||||||
<span>جمع متراژ</span>
|
<span>جمع متراژ کل</span>
|
||||||
<strong>{{ formatMeterage(totalMeterage) }}</strong>
|
<strong>{{ formatMeterage(totalMeterage) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="cart-mobile-summary__row">
|
<template v-if="companySummaries?.length">
|
||||||
|
<div
|
||||||
|
v-for="row in companySummaries"
|
||||||
|
:key="row.companyId"
|
||||||
|
class="cart-mobile-summary__row cart-mobile-summary__row--sub"
|
||||||
|
>
|
||||||
|
<span>{{ row.companyName }}</span>
|
||||||
|
<strong>
|
||||||
|
{{ formatMeterage(row.meterage) }}
|
||||||
|
<span class="cart-mobile-summary__cap">/ {{ formatMeterage(row.maxMeterage) }}</span>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="cart-mobile-summary__row">
|
||||||
<span>حداکثر مجاز</span>
|
<span>حداکثر مجاز</span>
|
||||||
<strong>{{ formatMeterage(maxMeterage) }}</strong>
|
<strong>{{ formatMeterage(maxMeterage) }}</strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,8 +100,7 @@ const emit = defineEmits<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="cart-mobile-page__hint">
|
<p class="cart-mobile-page__hint">
|
||||||
پس از ثبت، مدیریت سفارش را قیمتگذاری میکند و فاکتور برای انتخاب روش تسویه به شما اعلام
|
پس از ثبت، برای هر شرکت یک پیشسفارش جداگانه ساخته میشود و مدیریت قیمتگذاری میکند.
|
||||||
میشود.
|
|
||||||
</p>
|
</p>
|
||||||
<BaseButton block :loading="submitting" @click="emit('checkout')">ثبت پیشسفارش</BaseButton>
|
<BaseButton block :loading="submitting" @click="emit('checkout')">ثبت پیشسفارش</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,10 +269,22 @@ const emit = defineEmits<{
|
|||||||
.cart-mobile-summary__row {
|
.cart-mobile-summary__row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cart-mobile-summary__row--sub {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-mobile-summary__cap {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.cart-mobile-summary__row:not(:last-child) {
|
.cart-mobile-summary__row:not(:last-child) {
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user