diff --git a/src/components/orders/PreorderPaymentFields.vue b/src/components/orders/PreorderPaymentFields.vue new file mode 100644 index 0000000..500840c --- /dev/null +++ b/src/components/orders/PreorderPaymentFields.vue @@ -0,0 +1,127 @@ + + + + + روش پرداخت + قیمت پیشسفارش به روش پرداخت بستگی دارد. قبل از اعلام قیمت باید مشخص شود. + + + {{ method.title }} + {{ method.hint }} + + + + مدت چک + + + {{ month }} ماهه + + + + + + + diff --git a/src/composables/useCartPage.ts b/src/composables/useCartPage.ts index 65bbd51..6f40010 100644 --- a/src/composables/useCartPage.ts +++ b/src/composables/useCartPage.ts @@ -3,6 +3,7 @@ import { useRouter } from 'vue-router' import { useCartStore } from '@/stores/cart' import { useOrderStore } from '@/stores/order' import { confirm } from '@/composables/useConfirm' +import type { SettlementType } from '@/types' export function useCartPage() { const router = useRouter() @@ -11,6 +12,8 @@ export function useCartPage() { const showFullDialog = ref(false) const submitting = ref(false) const submitError = ref('') + const settlementType = ref(null) + const checkTermMonths = ref(null) const items = computed(() => cartStore.activeCart?.items ?? []) const isEmpty = computed(() => items.value.length === 0) @@ -33,11 +36,22 @@ export function useCartPage() { } /** - * ثبت سفارش بدون آدرس و بدون انتخاب تسویه. - * سفارش هنوز نهایی نیست؛ قیمتگذاری و روش تسویه بعد از بررسی مدیریت است. + * پیشسفارش با روش پرداخت (و مدت چک در صورت نیاز) ثبت میشود. + * قیمتگذاری مدیریت بعد از این انتخاب انجام میشود. */ async function submitOrder() { if (!cartStore.activeCart || items.value.length === 0 || submitting.value) return + if (!settlementType.value) { + submitError.value = 'قبل از ثبت پیشسفارش روش پرداخت را انتخاب کنید.' + return + } + if ( + (settlementType.value === 'check' || settlementType.value === 'cash_check') && + !checkTermMonths.value + ) { + submitError.value = 'مدت چک را از ۱ تا ۴ ماه انتخاب کنید.' + return + } submitting.value = true submitError.value = '' try { @@ -46,7 +60,10 @@ export function useCartPage() { submitError.value = 'سبد خرید خالی است یا با سرور همگام نشد.' return } - const order = await orderStore.submitOrder(cartStore.activeCart) + const order = await orderStore.submitOrder(cartStore.activeCart, { + settlementType: settlementType.value, + checkTermMonths: checkTermMonths.value, + }) await cartStore.hydrate() router.replace(`/orders/${order.id}/success`) } catch (e) { @@ -90,6 +107,8 @@ export function useCartPage() { showFullDialog, submitting, submitError, + settlementType, + checkTermMonths, removeItem, proceedToCheckout, proceedAnyway, diff --git a/src/stores/order.ts b/src/stores/order.ts index 39db6fd..aec472c 100644 --- a/src/stores/order.ts +++ b/src/stores/order.ts @@ -117,11 +117,13 @@ export const useOrderStore = defineStore('order', () => { } /** - * سفارش بدون روش تسویه ثبت میشود؛ مشتری بعد از قیمتگذاری مدیریت - * روش تسویه را انتخاب میکند. + * پیشسفارش همراه روش پرداخت ثبت میشود تا قیمتگذاری بر همان اساس انجام شود. * اقلام همه شرکتها در یک فاکتور واحد ثبت میشوند. */ - async function submitOrder(cart: Cart): Promise { + async function submitOrder( + cart: Cart, + payment: { settlementType: SettlementType; checkTermMonths?: number | null }, + ): Promise { if (!cart.items.length) { throw new Error('سبد خرید خالی است.') } @@ -143,6 +145,9 @@ export const useOrderStore = defineStore('order', () => { method: 'POST', body: { companyId: companyIds[0], + settlementType: payment.settlementType, + checkTermMonths: + payment.settlementType === 'cash' ? undefined : payment.checkTermMonths ?? undefined, notes: notes || undefined, items: cart.items.map((item) => ({ productId: item.productId, @@ -159,7 +164,28 @@ export const useOrderStore = defineStore('order', () => { } } - /** انتخاب روش تسویه پس از صدور فاکتور. */ + async function setPaymentPlan( + orderId: string, + settlementType: SettlementType, + checkTermMonths?: number | null, + ): Promise { + try { + const order = await apiRequest(`/orders/${orderId}/payment-plan`, { + method: 'POST', + body: { + settlementType, + checkTermMonths: + settlementType === 'cash' ? undefined : checkTermMonths ?? undefined, + }, + }) + upsertOrder(order) + return order + } catch (e) { + throw new Error(e instanceof ApiError ? e.message : 'ثبت روش پرداخت ناموفق بود.') + } + } + + /** انتخاب روش تسویه پس از صدور فاکتور؛ فقط برای سفارشهایی که روش پرداخت ندارند. */ async function chooseSettlement( orderId: string, settlementType: SettlementType, @@ -273,6 +299,7 @@ export const useOrderStore = defineStore('order', () => { removeDraftCheck, getDraftRass, submitOrder, + setPaymentPlan, chooseSettlement, submitCashReceipt, updateOrderChecks, diff --git a/src/types/index.ts b/src/types/index.ts index b20b6ab..ce3c18c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -187,8 +187,10 @@ export interface Order { companyName: string items: OrderItem[] totalMeterage: number - /** تا وقتی مشتری بعد از قیمتگذاری روش تسویه را انتخاب نکرده، خالی است. */ + /** هنگام ثبت پیشسفارش انتخاب میشود. سفارشهای قدیمی یا ثبتشده توسط ادمین ممکن است خالی باشند. */ settlementType: SettlementType | null + /** مدت چک به ماه (۱ تا ۴) برای روشهای چکی */ + checkTermMonths?: number | null checkMode?: CheckRegistrationMode checks?: CheckData[] checkRassDays?: number diff --git a/src/utils/orderTracking.ts b/src/utils/orderTracking.ts index 4d874da..c971ca6 100644 --- a/src/utils/orderTracking.ts +++ b/src/utils/orderTracking.ts @@ -48,13 +48,13 @@ const STATUS_HERO: Partial> = { variant: 'waiting', title: 'پیشسفارش ثبت شد', description: - 'سفارش شما برای بررسی و قیمتگذاری به مدیریت ارسال شد. پس از صدور فاکتور میتوانید روش تسویه (نقد یا چک) را انتخاب کنید.', + 'سفارش شما برای بررسی و قیمتگذاری به مدیریت ارسال شد. روش پرداخت هنگام ثبت پیشسفارش مشخص شده است.', }, awaiting_review: { variant: 'waiting', title: 'در انتظار بررسی مدیریت', description: - 'پیشسفارش شما در صف بررسی است. پس از قیمتگذاری و صدور فاکتور، از طریق همین صفحه برای انتخاب تسویه مطلع میشوید.', + 'پیشسفارش شما در صف بررسی است. قیمت بر اساس روش پرداختی که انتخاب کردهاید اعلام میشود.', }, pricing: { variant: 'waiting', diff --git a/src/utils/settlement.ts b/src/utils/settlement.ts index 7eeac0f..d607d07 100644 --- a/src/utils/settlement.ts +++ b/src/utils/settlement.ts @@ -1,15 +1,15 @@ import type { OrderStatus, SettlementType } from '@/types' export const SETTLEMENT_TYPE_LABELS: Record = { - cash: 'تسویه نقدی', - check: 'تسویه چکی', - cash_check: 'تسویه نقدی-چکی', + cash: 'نقدی', + check: 'چکی', + cash_check: 'نصف نقد، نصف چکی', } export function settlementTypeLabel( type: SettlementType | null | undefined, ): string { - if (!type) return 'در انتظار انتخاب مشتری' + if (!type) return 'هنوز انتخاب نشده' return SETTLEMENT_TYPE_LABELS[type] ?? type } diff --git a/src/views/cart/CartView.vue b/src/views/cart/CartView.vue index 30e61b0..cdc3a32 100644 --- a/src/views/cart/CartView.vue +++ b/src/views/cart/CartView.vue @@ -18,6 +18,8 @@ const { showFullDialog, submitting, submitError, + settlementType, + checkTermMonths, removeItem, proceedToCheckout, proceedAnyway, @@ -43,6 +45,10 @@ function goToOrdering() { :remaining-capacity="cartStore.remainingCapacity" :is-empty="isEmpty" :submitting="submitting" + :settlement-type="settlementType" + :check-term-months="checkTermMonths" + @update:settlement-type="settlementType = $event" + @update:check-term-months="checkTermMonths = $event" @back="router.push('/')" @remove="removeItem" @checkout="proceedToCheckout" @@ -59,6 +65,10 @@ function goToOrdering() { :remaining-capacity="cartStore.remainingCapacity" :is-empty="isEmpty" :submitting="submitting" + :settlement-type="settlementType" + :check-term-months="checkTermMonths" + @update:settlement-type="settlementType = $event" + @update:check-term-months="checkTermMonths = $event" @remove="removeItem" @checkout="proceedToCheckout" @select-company="goToOrdering()" diff --git a/src/views/cart/components/DesktopCartPage.vue b/src/views/cart/components/DesktopCartPage.vue index fe758d4..8d2b186 100644 --- a/src/views/cart/components/DesktopCartPage.vue +++ b/src/views/cart/components/DesktopCartPage.vue @@ -10,7 +10,8 @@ import IconLightbulb from '@/components/icons/IconLightbulb.vue' import IconLayoutGrid from '@/components/icons/IconLayoutGrid.vue' import IconDiamond from '@/components/icons/IconDiamond.vue' import { formatMeterage, formatNumber } from '@/utils/format' -import type { CartItem } from '@/types' +import type { CartItem, SettlementType } from '@/types' +import PreorderPaymentFields from '@/components/orders/PreorderPaymentFields.vue' defineProps<{ items: CartItem[] @@ -27,6 +28,8 @@ defineProps<{ remainingCapacity: number isEmpty: boolean submitting?: boolean + settlementType: SettlementType | null + checkTermMonths: number | null }>() const emit = defineEmits<{ @@ -34,6 +37,8 @@ const emit = defineEmits<{ checkout: [] selectCompany: [] back: [] + 'update:settlementType': [value: SettlementType] + 'update:checkTermMonths': [value: number] }>() const router = useRouter() @@ -180,6 +185,12 @@ const quickLinks = [ + - پس از ثبت سفارش، مدیریت قیمتگذاری میکند و فاکتور صادر میشود؛ سپس روش تسویه را انتخاب میکنید. + روش پرداخت الان ثبت میشود. مدیریت بعد از آن قیمت و متراژ را اعلام میکند. diff --git a/src/views/cart/components/MobileCartPage.vue b/src/views/cart/components/MobileCartPage.vue index 60f76d1..499a038 100644 --- a/src/views/cart/components/MobileCartPage.vue +++ b/src/views/cart/components/MobileCartPage.vue @@ -4,7 +4,8 @@ import EmptyState from '@/components/ui/EmptyState.vue' import IconTrash from '@/components/icons/IconTrash.vue' import IconChevronBack from '@/components/icons/IconChevronBack.vue' import { formatMeterage, formatNumber } from '@/utils/format' -import type { CartItem } from '@/types' +import type { CartItem, SettlementType } from '@/types' +import PreorderPaymentFields from '@/components/orders/PreorderPaymentFields.vue' defineProps<{ items: CartItem[] @@ -21,6 +22,8 @@ defineProps<{ remainingCapacity: number isEmpty: boolean submitting?: boolean + settlementType: SettlementType | null + checkTermMonths: number | null }>() const emit = defineEmits<{ @@ -28,6 +31,8 @@ const emit = defineEmits<{ remove: [id: string] checkout: [] selectCompany: [] + 'update:settlementType': [value: SettlementType] + 'update:checkTermMonths': [value: number] }>() @@ -98,8 +103,14 @@ const emit = defineEmits<{ {{ formatMeterage(remainingCapacity) }} + - پس از ثبت سفارش، کارشناسان قیمتگذاری میکنند و فاکتور برای شما صادر میشود. + پس از ثبت، مدیریت بر اساس همین روش پرداخت قیمت و متراژ را اعلام میکند. ثبت پیشسفارش diff --git a/src/views/orders/OrderDetailView.vue b/src/views/orders/OrderDetailView.vue index 65bc32c..8c71625 100644 --- a/src/views/orders/OrderDetailView.vue +++ b/src/views/orders/OrderDetailView.vue @@ -3,7 +3,8 @@ import { ref, computed, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useMediaQuery } from '@vueuse/core' import { useOrderStore } from '@/stores/order' -import type { Order } from '@/types' +import type { Order, SettlementType } from '@/types' +import PreorderPaymentFields from '@/components/orders/PreorderPaymentFields.vue' import BaseButton from '@/components/ui/BaseButton.vue' import CurrencyInput from '@/components/ui/CurrencyInput.vue' import PersianDateInput from '@/components/ui/PersianDateInput.vue' @@ -155,7 +156,37 @@ const trackingFooterLabel = computed(() => ) function settlementLabel(type: Order['settlementType']) { - return settlementTypeLabel(type) + const base = settlementTypeLabel(type) + const months = order.value?.checkTermMonths + if ((type === 'check' || type === 'cash_check') && months) { + return `${base} · چک ${months} ماهه` + } + return base +} + +const planType = ref(null) +const planMonths = ref(null) +const planSaving = ref(false) + +async function savePaymentPlan() { + const current = order.value + if (!current || !planType.value) { + formError.value = 'روش پرداخت را انتخاب کنید.' + return + } + if ((planType.value === 'check' || planType.value === 'cash_check') && !planMonths.value) { + formError.value = 'مدت چک را از ۱ تا ۴ ماه انتخاب کنید.' + return + } + planSaving.value = true + formError.value = '' + try { + await orderStore.setPaymentPlan(current.id, planType.value, planMonths.value) + } catch (e) { + formError.value = e instanceof Error ? e.message : 'ثبت روش پرداخت ناموفق بود.' + } finally { + planSaving.value = false + } } function showCashTracking(order: Order) { @@ -314,6 +345,19 @@ async function submitReceipt() { {{ order.orderNumber }} · {{ order.companyName }} + + + ثبت روش پرداخت + + @@ -340,6 +384,19 @@ async function submitReceipt() { + + + ثبت روش پرداخت + + مبلغ فاکتور
قیمت پیشسفارش به روش پرداخت بستگی دارد. قبل از اعلام قیمت باید مشخص شود.
- پس از ثبت سفارش، مدیریت قیمتگذاری میکند و فاکتور صادر میشود؛ سپس روش تسویه را انتخاب میکنید. + روش پرداخت الان ثبت میشود. مدیریت بعد از آن قیمت و متراژ را اعلام میکند.
- پس از ثبت سفارش، کارشناسان قیمتگذاری میکنند و فاکتور برای شما صادر میشود. + پس از ثبت، مدیریت بر اساس همین روش پرداخت قیمت و متراژ را اعلام میکند.
{{ order.orderNumber }} · {{ order.companyName }}