auto complete order
This commit is contained in:
@@ -292,7 +292,6 @@ function selectUnit(sku: ProductSku, skuUnitId: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sku-card {
|
.sku-card {
|
||||||
margin-top: 1.25rem;
|
|
||||||
padding: .75rem;
|
padding: .75rem;
|
||||||
background: var(--td-bg-color-secondarycontainer);
|
background: var(--td-bg-color-secondarycontainer);
|
||||||
border-radius: .875rem;
|
border-radius: .875rem;
|
||||||
@@ -349,7 +348,7 @@ function selectUnit(sku: ProductSku, skuUnitId: string): void {
|
|||||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||||
|
|
||||||
.collapse {
|
.collapse {
|
||||||
margin-bottom: 1rem;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-header {
|
.product-header {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</t-cell>
|
</t-cell>
|
||||||
<t-cell title="识别成功自动登记">
|
<t-cell title="识别成功自动登记">
|
||||||
<template #left-icon>
|
<template #left-icon>
|
||||||
<t-icon name="arrow-right-up" />
|
<t-icon name="task-checked" />
|
||||||
</template>
|
</template>
|
||||||
<template #right-icon>
|
<template #right-icon>
|
||||||
<t-switch v-model="autoRegisterEnabled" />
|
<t-switch v-model="autoRegisterEnabled" />
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<div class="description" v-text="config.description" />
|
<div class="description" v-text="config.description" />
|
||||||
</div>
|
</div>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="成交价(元)" label-width="6rem" required-mark>
|
<t-form-item label="成交价" label-width="6rem" required-mark>
|
||||||
<t-input
|
<t-input
|
||||||
v-model.trim="saleAmountText"
|
v-model.trim="saleAmountText"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -170,6 +170,41 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
<t-form-item label="已全额收款和核销" content-align="right">
|
||||||
|
<t-switch
|
||||||
|
v-model="receiveFullPayment"
|
||||||
|
size="small"
|
||||||
|
:disabled="!canReceiveImmediately"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="已全部产品履约和核销" content-align="right">
|
||||||
|
<t-switch
|
||||||
|
v-model="fulfillAll"
|
||||||
|
size="small"
|
||||||
|
:disabled="!canFulfillImmediately"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label-width="0">
|
||||||
|
<div class="red">全部履约会同步启用全额收款;请先准备全部产品库存,任意一步失败,整张订单都会回滚。</div>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item v-if="receiveFullPayment && parsedSaleAmount" label-width="0">
|
||||||
|
<photo-scroller
|
||||||
|
:model-value="paymentPhotoUrlList"
|
||||||
|
v-model:photo-items="paymentPhotoItems"
|
||||||
|
:upload="uploadPhotoList"
|
||||||
|
:edit-mode="true"
|
||||||
|
title="收款凭证照片(必要)"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item v-if="fulfillAll" label-width="0">
|
||||||
|
<photo-scroller
|
||||||
|
:model-value="fulfillmentPhotoUrlList"
|
||||||
|
v-model:photo-items="fulfillmentPhotoItems"
|
||||||
|
:upload="uploadPhotoList"
|
||||||
|
:edit-mode="true"
|
||||||
|
title="履约凭证照片(必要)"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
<t-form-item label="购买备注">
|
<t-form-item label="购买备注">
|
||||||
<t-textarea
|
<t-textarea
|
||||||
v-model.trim="note"
|
v-model.trim="note"
|
||||||
@@ -195,26 +230,41 @@
|
|||||||
:enable-scan="false"
|
:enable-scan="false"
|
||||||
@change="handleCustomerChange"
|
@change="handleCustomerChange"
|
||||||
/>
|
/>
|
||||||
<t-popup v-model="configSelectorVisible" placement="bottom">
|
<bottom-popup
|
||||||
<div class="config-selector bg-white">
|
v-model="configSelectorVisible"
|
||||||
<div class="popup-header">
|
title="选择套餐"
|
||||||
<t-button variant="text" size="small" @click="configSelectorVisible = false">关闭</t-button>
|
height="70vh"
|
||||||
<h3>选择套餐</h3>
|
right-button-text=""
|
||||||
<span />
|
@left-button-click="configSelectorVisible = false"
|
||||||
</div>
|
>
|
||||||
|
<div class="config-selector">
|
||||||
<t-cell-group v-if="configList.length">
|
<t-cell-group v-if="configList.length">
|
||||||
<t-cell
|
<t-cell
|
||||||
v-for="item in configList"
|
v-for="item in configList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
|
class="config-option"
|
||||||
:title="item.name || '未命名套餐'"
|
:title="item.name || '未命名套餐'"
|
||||||
:note="`标准价:${formatPackageAmount(item.standardAmount)}`"
|
:note="formatPackageAmount(item.standardAmount)"
|
||||||
arrow
|
arrow
|
||||||
@click="selectConfig(item)"
|
@click="selectConfig(item)"
|
||||||
/>
|
>
|
||||||
|
<template #description>
|
||||||
|
<div class="products">
|
||||||
|
<t-tag
|
||||||
|
v-for="productItem in item.itemList || []"
|
||||||
|
:key="productItem.id || `${productItem.skuId}-${productItem.skuUnitId}`"
|
||||||
|
:content="getConfigProductText(productItem)"
|
||||||
|
theme="primary"
|
||||||
|
variant="light"
|
||||||
|
/>
|
||||||
|
<t-tag v-if="!item.itemList?.length" content="暂无产品权益" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<div v-else class="center-tips" v-text="'暂无可购买套餐'" />
|
<div v-else class="center-tips" v-text="'暂无可购买套餐'" />
|
||||||
</div>
|
</div>
|
||||||
</t-popup>
|
</bottom-popup>
|
||||||
<t-popup v-model="unitPickerVisible" placement="bottom">
|
<t-popup v-model="unitPickerVisible" placement="bottom">
|
||||||
<t-picker v-model="unitPickerDraft" :columns="unitPickerOptions" title="调整权益单位" @cancel="unitPickerVisible = false" @confirm="confirmItemUnitPicker" />
|
<t-picker v-model="unitPickerDraft" :columns="unitPickerOptions" title="调整权益单位" @cancel="unitPickerVisible = false" @confirm="confirmItemUnitPicker" />
|
||||||
</t-popup>
|
</t-popup>
|
||||||
@@ -252,6 +302,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Time } from "timi-web";
|
import { Time } from "timi-web";
|
||||||
import { useUserStore } from "timi-web-api";
|
import { useUserStore } from "timi-web-api";
|
||||||
|
import { BottomPopup } from "timi-tdesign-mobile";
|
||||||
|
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||||
import CustomerAPI from "@/api/CustomerAPI";
|
import CustomerAPI from "@/api/CustomerAPI";
|
||||||
import PackageAPI from "@/api/PackageAPI";
|
import PackageAPI from "@/api/PackageAPI";
|
||||||
import ProductAPI from "@/api/ProductAPI";
|
import ProductAPI from "@/api/ProductAPI";
|
||||||
@@ -265,14 +317,16 @@ import { useNavBarStore } from "@/store/navBarStore";
|
|||||||
import { useSettingStore } from "@/store/settingStore";
|
import { useSettingStore } from "@/store/settingStore";
|
||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import type { GaoPackageConfig, PackageAttachment, PackagePurchaseItem } from "@/types/Package";
|
import type { GaoPackageConfig, PackageAttachment, PackageConfigItem, PackagePurchaseItem } from "@/types/Package";
|
||||||
import type { Product, ProductSku, ProductSkuUnit } from "@/types/Product";
|
import type { Product, ProductSku, ProductSkuUnit } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import {
|
import {
|
||||||
addPackageQuantity,
|
addPackageQuantity,
|
||||||
convertPackageQuantity,
|
convertPackageQuantity,
|
||||||
formatPackageAmount,
|
formatPackageAmount,
|
||||||
normalizePackageQuantity
|
getPackageQuantityText,
|
||||||
|
normalizePackageQuantity,
|
||||||
|
parsePackageAmount
|
||||||
} from "@/utils/package";
|
} from "@/utils/package";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
@@ -423,15 +477,22 @@ function parseDateTime(value: string): Date {
|
|||||||
const note = ref("");
|
const note = ref("");
|
||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
const photoItems = ref<PhotoItem[]>([]);
|
const photoItems = ref<PhotoItem[]>([]);
|
||||||
|
const paymentPhotoItems = ref<PhotoItem[]>([]);
|
||||||
|
const fulfillmentPhotoItems = ref<PhotoItem[]>([]);
|
||||||
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
||||||
|
const paymentPhotoUrlList = computed(() => paymentPhotoItems.value.map(item => item.url));
|
||||||
|
const fulfillmentPhotoUrlList = computed(() => fulfillmentPhotoItems.value.map(item => item.url));
|
||||||
|
|
||||||
function buildAttachments(): PackageAttachment[] {
|
function buildAttachments(source: PhotoItem[] = photoItems.value): PackageAttachment[] {
|
||||||
return photoItems.value.flatMap(item => item.tempFileId ? [{
|
return source.flatMap(item => item.tempFileId ? [{
|
||||||
tempFileId: item.tempFileId,
|
tempFileId: item.tempFileId,
|
||||||
attachType: item.attachType || "PHOTO"
|
attachType: item.attachType || "PHOTO"
|
||||||
}] : []);
|
}] : []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const receiveFullPayment = ref(false);
|
||||||
|
const fulfillAll = ref(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isSaleable,
|
isSaleable,
|
||||||
canPriceOverride,
|
canPriceOverride,
|
||||||
@@ -446,9 +507,32 @@ const {
|
|||||||
config,
|
config,
|
||||||
selectedCustomerList,
|
selectedCustomerList,
|
||||||
note,
|
note,
|
||||||
|
receiveFullPayment,
|
||||||
|
fulfillAll,
|
||||||
hasPermission: permissionCode => userStore.hasPermissions(permissionCode),
|
hasPermission: permissionCode => userStore.hasPermissions(permissionCode),
|
||||||
buildRegisteredAt: buildPurchasedAt,
|
buildRegisteredAt: buildPurchasedAt,
|
||||||
buildAttachments
|
buildAttachments,
|
||||||
|
buildPaymentAttachments: () => buildAttachments(paymentPhotoItems.value),
|
||||||
|
buildFulfillmentAttachments: () => buildAttachments(fulfillmentPhotoItems.value)
|
||||||
|
});
|
||||||
|
|
||||||
|
const parsedSaleAmount = computed(() => parsePackageAmount(saleAmountText.value));
|
||||||
|
const canReceiveImmediately = computed(() => userStore.hasPermissions(permissionCodes.fundReceive.create)
|
||||||
|
&& userStore.hasPermissions(permissionCodes.fundReceive.verify));
|
||||||
|
const canFulfillImmediately = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create)
|
||||||
|
&& userStore.hasPermissions(permissionCodes.orderFulfillment.verify)
|
||||||
|
&& (parsedSaleAmount.value === 0 || canReceiveImmediately.value));
|
||||||
|
|
||||||
|
watch(fulfillAll, value => {
|
||||||
|
if (value && parsedSaleAmount.value !== 0) {
|
||||||
|
receiveFullPayment.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(receiveFullPayment, value => {
|
||||||
|
if (!value && parsedSaleAmount.value !== 0) {
|
||||||
|
fulfillAll.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const unitPickerOptions = computed(() => {
|
const unitPickerOptions = computed(() => {
|
||||||
@@ -456,6 +540,12 @@ const unitPickerOptions = computed(() => {
|
|||||||
return item ? getSelectableUnitList(item).map(unit => ({ label: unit.name, value: unit.id || "" })) : [];
|
return item ? getSelectableUnitList(item).map(unit => ({ label: unit.name, value: unit.id || "" })) : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getConfigProductText(item: PackageConfigItem): string {
|
||||||
|
const quantity = getPackageQuantityText(item.defaultQuantity);
|
||||||
|
const skuText = getProductSkuSnapshotText(item.productNameSnapshot, item.skuNameSnapshot, item.specSummarySnapshot);
|
||||||
|
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""} × ${quantity}${item.unitSnapshot ? ` ${item.unitSnapshot}` : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
function getItemLabel(item: PackagePurchaseItem): string {
|
function getItemLabel(item: PackagePurchaseItem): string {
|
||||||
const configItem = config.value?.itemList?.find(configItem => configItem.skuId === item.skuId);
|
const configItem = config.value?.itemList?.find(configItem => configItem.skuId === item.skuId);
|
||||||
const product = item.gifted ? giftProductMap[item.skuId] : undefined;
|
const product = item.gifted ? giftProductMap[item.skuId] : undefined;
|
||||||
@@ -647,6 +737,28 @@ async function submitRecord(): Promise<void> {
|
|||||||
if (!validatePurchaseForm() || !validateItemDecimalQuantity()) {
|
if (!validatePurchaseForm() || !validateItemDecimalQuantity()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (receiveFullPayment.value || fulfillAll.value) {
|
||||||
|
const dialog = DialogPlugin.confirm({
|
||||||
|
title: "确认直通完成交易",
|
||||||
|
content: fulfillAll.value
|
||||||
|
? "将一次性购买套餐、核销全额收款,并完成全部履约和库存扣减。提交后如需更正,只能走撤销或冲正流程。"
|
||||||
|
: "将一次性购买套餐并核销全额收款。提交后如需更正,只能走资金冲正流程。",
|
||||||
|
cancelBtn: "取消",
|
||||||
|
confirmBtn: "确认并提交",
|
||||||
|
onCancel() {
|
||||||
|
dialog.destroy();
|
||||||
|
},
|
||||||
|
async onConfirm() {
|
||||||
|
dialog.destroy();
|
||||||
|
await purchasePackage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await purchasePackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function purchasePackage(): Promise<void> {
|
||||||
|
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -656,7 +768,7 @@ async function submitRecord(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await settingStore.playRegisterSuccessSound();
|
await settingStore.playRegisterSuccessSound();
|
||||||
Message.success("套餐购买已提交");
|
Message.success(fulfillAll.value ? "套餐、收款及履约已完成" : receiveFullPayment.value ? "套餐及全额收款已完成" : "套餐购买已提交");
|
||||||
await router.replace({
|
await router.replace({
|
||||||
name: "SalesOrderDetail",
|
name: "SalesOrderDetail",
|
||||||
params: { id: result.salesOrderId },
|
params: { id: result.salesOrderId },
|
||||||
@@ -749,6 +861,25 @@ watch(config, value => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.straight-through {
|
||||||
|
width: 100%;
|
||||||
|
padding: .75rem;
|
||||||
|
background: var(--td-bg-color-secondarycontainer);
|
||||||
|
border-radius: .875rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: .9375rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
color: var(--td-text-color-secondary);
|
||||||
|
margin: .5rem .5rem 0;
|
||||||
|
font-size: .75rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.customer-profile {
|
.customer-profile {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
@@ -881,7 +1012,21 @@ watch(config, value => {
|
|||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-selector,
|
.config-selector {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.config-option {
|
||||||
|
.products {
|
||||||
|
gap: .35rem;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.selected-customer-popup {
|
.selected-customer-popup {
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ interface UsePackagePurchaseOptions {
|
|||||||
config: Ref<GaoPackageConfig | undefined>;
|
config: Ref<GaoPackageConfig | undefined>;
|
||||||
selectedCustomerList: Ref<Customer[]>;
|
selectedCustomerList: Ref<Customer[]>;
|
||||||
note: Ref<string>;
|
note: Ref<string>;
|
||||||
|
receiveFullPayment: Ref<boolean>;
|
||||||
|
fulfillAll: Ref<boolean>;
|
||||||
hasPermission: (permissionCode: string) => boolean;
|
hasPermission: (permissionCode: string) => boolean;
|
||||||
buildRegisteredAt: () => number;
|
buildRegisteredAt: () => number;
|
||||||
buildAttachments: () => PackageAttachment[];
|
buildAttachments: () => PackageAttachment[];
|
||||||
|
buildPaymentAttachments: () => PackageAttachment[];
|
||||||
|
buildFulfillmentAttachments: () => PackageAttachment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 套餐配置直接购买流程,购买请求只依赖套餐配置 ID,不再关联购买事件。 */
|
/** 套餐配置直接购买流程,购买请求只依赖套餐配置 ID,不再关联购买事件。 */
|
||||||
@@ -91,12 +95,19 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
|||||||
saleAmount,
|
saleAmount,
|
||||||
purchasedAt: options.buildRegisteredAt(),
|
purchasedAt: options.buildRegisteredAt(),
|
||||||
attachmentList: options.buildAttachments(),
|
attachmentList: options.buildAttachments(),
|
||||||
|
straightThrough: options.receiveFullPayment.value || options.fulfillAll.value ? {
|
||||||
|
receiveFullPayment: options.receiveFullPayment.value,
|
||||||
|
paymentAttachmentList: options.buildPaymentAttachments(),
|
||||||
|
fulfillAll: options.fulfillAll.value,
|
||||||
|
fulfillmentAttachmentList: options.buildFulfillmentAttachments()
|
||||||
|
} : undefined,
|
||||||
itemList,
|
itemList,
|
||||||
remark: options.note.value.trim() || null
|
remark: options.note.value.trim() || null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePurchaseForm(): boolean {
|
function validatePurchaseForm(): boolean {
|
||||||
|
const saleAmount = parsePackageAmount(saleAmountText.value);
|
||||||
if (!options.hasPermission(permissionCodes.customerPackage.create)) {
|
if (!options.hasPermission(permissionCodes.customerPackage.create)) {
|
||||||
Message.warning("当前账号没有套餐购买权限");
|
Message.warning("当前账号没有套餐购买权限");
|
||||||
return false;
|
return false;
|
||||||
@@ -113,7 +124,7 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
|||||||
Message.warning("套餐当前不可购买,请检查套餐状态和有效期");
|
Message.warning("套餐当前不可购买,请检查套餐状态和有效期");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (parsePackageAmount(saleAmountText.value) === undefined) {
|
if (saleAmount === undefined) {
|
||||||
Message.warning("成交价必须是合法的元金额,最多两位小数");
|
Message.warning("成交价必须是合法的元金额,最多两位小数");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -139,6 +150,31 @@ export function usePackagePurchase(options: UsePackagePurchaseOptions) {
|
|||||||
Message.warning("套餐至少需要保留一项正数量权益");
|
Message.warning("套餐至少需要保留一项正数量权益");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const canReceiveImmediately = options.hasPermission(permissionCodes.fundReceive.create)
|
||||||
|
&& options.hasPermission(permissionCodes.fundReceive.verify);
|
||||||
|
const canFulfillImmediately = options.hasPermission(permissionCodes.orderFulfillment.create)
|
||||||
|
&& options.hasPermission(permissionCodes.orderFulfillment.verify)
|
||||||
|
&& (saleAmount === 0 || canReceiveImmediately);
|
||||||
|
if (options.receiveFullPayment.value && !canReceiveImmediately) {
|
||||||
|
Message.warning("当前账号没有收款登记和核销权限");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (options.fulfillAll.value && !canFulfillImmediately) {
|
||||||
|
Message.warning("当前账号没有交易直通所需权限");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (options.fulfillAll.value && saleAmount !== 0 && !options.receiveFullPayment.value) {
|
||||||
|
Message.warning("全部履约直通必须同时直接全额收款");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (options.receiveFullPayment.value && saleAmount !== 0 && !options.buildPaymentAttachments().length) {
|
||||||
|
Message.warning("直接全额收款必须上传收款凭证照片");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (options.fulfillAll.value && !options.buildFulfillmentAttachments().length) {
|
||||||
|
Message.warning("全部履约直通必须上传履约凭证照片");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,41 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
<t-form-item label="已全额收款和核销" content-align="right">
|
||||||
|
<t-switch
|
||||||
|
v-model="receiveFullPayment"
|
||||||
|
size="small"
|
||||||
|
:disabled="!canReceiveImmediately"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="已全部产品履约和核销" content-align="right">
|
||||||
|
<t-switch
|
||||||
|
v-model="fulfillAll"
|
||||||
|
size="small"
|
||||||
|
:disabled="!canFulfillImmediately"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label-width="0">
|
||||||
|
<div class="red">全部履约会同步启用全额收款;请先准备全部产品库存,任意一步失败,整张订单都会回滚。</div>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item v-if="receiveFullPayment && totalAmount" label-width="0">
|
||||||
|
<photo-scroller
|
||||||
|
:model-value="paymentPhotoUrlList"
|
||||||
|
v-model:photo-items="paymentPhotoItems"
|
||||||
|
:upload="uploadPhotoList"
|
||||||
|
:edit-mode="true"
|
||||||
|
title="收款凭证照片(必要)"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item v-if="fulfillAll" label-width="0">
|
||||||
|
<photo-scroller
|
||||||
|
:model-value="fulfillmentPhotoUrlList"
|
||||||
|
v-model:photo-items="fulfillmentPhotoItems"
|
||||||
|
:upload="uploadPhotoList"
|
||||||
|
:edit-mode="true"
|
||||||
|
title="履约凭证照片(必要)"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
<t-form-item label="订单备注">
|
<t-form-item label="订单备注">
|
||||||
<t-textarea v-model="remark" class="textarea" borderless autosize maxlength="500" placeholder="记录本单需要注意的内容" />
|
<t-textarea v-model="remark" class="textarea" borderless autosize maxlength="500" placeholder="记录本单需要注意的内容" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
@@ -169,6 +204,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Attachment, useUserStore } from "timi-web-api";
|
import { type Attachment, useUserStore } from "timi-web-api";
|
||||||
import { Time } from "timi-web";
|
import { Time } from "timi-web";
|
||||||
|
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||||
import CustomerAPI from "@/api/CustomerAPI";
|
import CustomerAPI from "@/api/CustomerAPI";
|
||||||
import ProductAPI from "@/api/ProductAPI";
|
import ProductAPI from "@/api/ProductAPI";
|
||||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||||
@@ -206,7 +242,11 @@ const draftTime = ref("");
|
|||||||
const pickerVisible = ref(false);
|
const pickerVisible = ref(false);
|
||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
const photoItems = ref<PhotoItem[]>([]);
|
const photoItems = ref<PhotoItem[]>([]);
|
||||||
|
const paymentPhotoItems = ref<PhotoItem[]>([]);
|
||||||
|
const fulfillmentPhotoItems = ref<PhotoItem[]>([]);
|
||||||
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
||||||
|
const paymentPhotoUrlList = computed(() => paymentPhotoItems.value.map(item => item.url));
|
||||||
|
const fulfillmentPhotoUrlList = computed(() => fulfillmentPhotoItems.value.map(item => item.url));
|
||||||
const maxQuantity = Number.MAX_SAFE_INTEGER;
|
const maxQuantity = Number.MAX_SAFE_INTEGER;
|
||||||
const productCache = new Map<string, Product>();
|
const productCache = new Map<string, Product>();
|
||||||
const skuCache = new Map<string, ProductSku>();
|
const skuCache = new Map<string, ProductSku>();
|
||||||
@@ -218,6 +258,28 @@ const scannerVisible = ref(false);
|
|||||||
const scanStatus = ref("点击扫码开始识别。");
|
const scanStatus = ref("点击扫码开始识别。");
|
||||||
let latestDecodedText = "";
|
let latestDecodedText = "";
|
||||||
|
|
||||||
|
// ---------- 交易直通 ----------
|
||||||
|
|
||||||
|
const receiveFullPayment = ref(false);
|
||||||
|
const fulfillAll = ref(false);
|
||||||
|
const canReceiveImmediately = computed(() => userStore.hasPermissions(permissionCodes.fundReceive.create)
|
||||||
|
&& userStore.hasPermissions(permissionCodes.fundReceive.verify));
|
||||||
|
const canFulfillImmediately = computed(() => userStore.hasPermissions(permissionCodes.orderFulfillment.create)
|
||||||
|
&& userStore.hasPermissions(permissionCodes.orderFulfillment.verify)
|
||||||
|
&& (totalAmount.value === 0 || canReceiveImmediately.value));
|
||||||
|
|
||||||
|
watch(fulfillAll, value => {
|
||||||
|
if (value && totalAmount.value !== 0) {
|
||||||
|
receiveFullPayment.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(receiveFullPayment, value => {
|
||||||
|
if (!value && totalAmount.value !== 0) {
|
||||||
|
fulfillAll.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function getSku(item: FormItem): ProductSku | undefined {
|
function getSku(item: FormItem): ProductSku | undefined {
|
||||||
return skuCache.get(item.skuId || "");
|
return skuCache.get(item.skuId || "");
|
||||||
}
|
}
|
||||||
@@ -376,8 +438,8 @@ function resolveTimestamp(value: string): number | undefined {
|
|||||||
const timestamp = new Date(value.replace(" ", "T")).getTime();
|
const timestamp = new Date(value.replace(" ", "T")).getTime();
|
||||||
return Number.isFinite(timestamp) ? timestamp : undefined;
|
return Number.isFinite(timestamp) ? timestamp : undefined;
|
||||||
}
|
}
|
||||||
function buildAttachments(): Partial<Attachment>[] {
|
function buildAttachments(source: PhotoItem[] = photoItems.value): Partial<Attachment>[] {
|
||||||
return photoItems.value.flatMap(item => item.tempFileId ? [{ tempFileId: item.tempFileId, attachType: "PHOTO" }] : []);
|
return source.flatMap(item => item.tempFileId ? [{ tempFileId: item.tempFileId, attachType: "PHOTO" }] : []);
|
||||||
}
|
}
|
||||||
function validate(): boolean {
|
function validate(): boolean {
|
||||||
if (!customer.value?.id) { Message.warning("请选择客户"); return false; }
|
if (!customer.value?.id) { Message.warning("请选择客户"); return false; }
|
||||||
@@ -392,10 +454,36 @@ function validate(): boolean {
|
|||||||
keys.add(key);
|
keys.add(key);
|
||||||
if (canPriceOverride.value && item.unitPriceText && parsePackageAmount(item.unitPriceText) === undefined) { Message.warning("成交单价必须是合法金额"); return false; }
|
if (canPriceOverride.value && item.unitPriceText && parsePackageAmount(item.unitPriceText) === undefined) { Message.warning("成交单价必须是合法金额"); return false; }
|
||||||
}
|
}
|
||||||
|
if (receiveFullPayment.value && !canReceiveImmediately.value) { Message.warning("当前账号没有收款登记和核销权限"); return false; }
|
||||||
|
if (fulfillAll.value && !canFulfillImmediately.value) { Message.warning("当前账号没有交易直通所需权限"); return false; }
|
||||||
|
if (fulfillAll.value && totalAmount.value !== 0 && !receiveFullPayment.value) { Message.warning("全部履约直通必须同时直接全额收款"); return false; }
|
||||||
|
if (receiveFullPayment.value && totalAmount.value && !buildAttachments(paymentPhotoItems.value).length) { Message.warning("直接全额收款必须上传收款凭证照片"); return false; }
|
||||||
|
if (fulfillAll.value && !buildAttachments(fulfillmentPhotoItems.value).length) { Message.warning("全部履约直通必须上传履约凭证照片"); return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async function save(): Promise<void> {
|
async function save(): Promise<void> {
|
||||||
if (isSaving.value || !validate()) return;
|
if (isSaving.value || !validate()) return;
|
||||||
|
if (receiveFullPayment.value || fulfillAll.value) {
|
||||||
|
const dialog = DialogPlugin.confirm({
|
||||||
|
title: "确认直通完成交易",
|
||||||
|
content: fulfillAll.value
|
||||||
|
? "将一次性创建订单、核销全额收款,并完成全部履约和库存扣减。提交后如需更正,只能走撤销或冲正流程。"
|
||||||
|
: "将一次性创建订单并核销全额收款。提交后如需更正,只能走资金冲正流程。",
|
||||||
|
cancelBtn: "取消",
|
||||||
|
confirmBtn: "确认并提交",
|
||||||
|
onCancel() {
|
||||||
|
dialog.destroy();
|
||||||
|
},
|
||||||
|
async onConfirm() {
|
||||||
|
dialog.destroy();
|
||||||
|
await submitOrder();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await submitOrder();
|
||||||
|
}
|
||||||
|
async function submitOrder(): Promise<void> {
|
||||||
if (!requestNo.value) requestNo.value = createPackageRequestNo();
|
if (!requestNo.value) requestNo.value = createPackageRequestNo();
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -413,9 +501,15 @@ async function save(): Promise<void> {
|
|||||||
purchasedAt: resolveTimestamp(purchasedAtText.value),
|
purchasedAt: resolveTimestamp(purchasedAtText.value),
|
||||||
itemList,
|
itemList,
|
||||||
attachmentList: buildAttachments(),
|
attachmentList: buildAttachments(),
|
||||||
|
straightThrough: receiveFullPayment.value || fulfillAll.value ? {
|
||||||
|
receiveFullPayment: receiveFullPayment.value,
|
||||||
|
paymentAttachmentList: buildAttachments(paymentPhotoItems.value),
|
||||||
|
fulfillAll: fulfillAll.value,
|
||||||
|
fulfillmentAttachmentList: buildAttachments(fulfillmentPhotoItems.value)
|
||||||
|
} : undefined,
|
||||||
remark: remark.value.trim() || null
|
remark: remark.value.trim() || null
|
||||||
});
|
});
|
||||||
Message.success("销售订单已创建");
|
Message.success(fulfillAll.value ? "订单、收款及履约已完成" : receiveFullPayment.value ? "订单及全额收款已完成" : "销售订单已创建");
|
||||||
await router.replace(`/management/sales-orders/${result.id}`);
|
await router.replace(`/management/sales-orders/${result.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = getErrorMessage(error, "销售订单创建失败,重试会复用同一请求号");
|
const message = getErrorMessage(error, "销售订单创建失败,重试会复用同一请求号");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<h1 v-text="appName" />
|
<h1 v-text="appName" />
|
||||||
<p v-text="appDescription" />
|
<p v-text="appDescription" />
|
||||||
</section>
|
</section>
|
||||||
<t-cell-group title="协议" theme="card" v-entrance-transition="70">
|
<t-cell-group theme="card" v-entrance-transition="70">
|
||||||
<t-cell
|
<t-cell
|
||||||
v-for="item in agreementItemList"
|
v-for="item in agreementItemList"
|
||||||
:key="item.code"
|
:key="item.code"
|
||||||
@@ -21,6 +21,8 @@
|
|||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<footer class="footer" v-entrance-transition="140">
|
<footer class="footer" v-entrance-transition="140">
|
||||||
<p>技术支持:夜雨</p>
|
<p>技术支持:夜雨</p>
|
||||||
|
<p>联系技术:imyeyu@qq.com</p>
|
||||||
|
<p>短信服务支持:广州易保网络科技有限公司</p>
|
||||||
<p>Copyright © 2026 imyeyu.com</p>
|
<p>Copyright © 2026 imyeyu.com</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const entryGroupList: HomeEntryGroup[] = [
|
|||||||
{
|
{
|
||||||
path: "/management/customers/create?redirect=/customers",
|
path: "/management/customers/create?redirect=/customers",
|
||||||
permission: permissionCodes.customer.create,
|
permission: permissionCodes.customer.create,
|
||||||
icon: "edit-1",
|
icon: "user-add",
|
||||||
title: "录入"
|
title: "录入"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
|||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
import type { OrderFulfillment } from "@/types/Fulfillment";
|
import type { OrderFulfillment } from "@/types/Fulfillment";
|
||||||
import type { ProductSku } from "@/types/Product";
|
import type { ProductSku } from "@/types/Product";
|
||||||
import type { GaoSalesOrderItem, GaoSalesOrderStatus, SalesOrder } from "@/types/SalesOrder";
|
import type { GaoSalesOrderItem, GaoSalesOrderStatus, SalesOrder, SalesOrderStraightThroughRequest } from "@/types/SalesOrder";
|
||||||
|
|
||||||
export type PackageConfigStatus = "DRAFT" | "ACTIVE" | "INACTIVE";
|
export type PackageConfigStatus = "DRAFT" | "ACTIVE" | "INACTIVE";
|
||||||
|
|
||||||
@@ -79,6 +79,7 @@ export type PackagePurchaseRequest = {
|
|||||||
purchasedAt?: number;
|
purchasedAt?: number;
|
||||||
itemList?: PackagePurchaseItem[];
|
itemList?: PackagePurchaseItem[];
|
||||||
attachmentList?: PackageAttachment[];
|
attachmentList?: PackageAttachment[];
|
||||||
|
straightThrough?: SalesOrderStraightThroughRequest;
|
||||||
remark?: string | null;
|
remark?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,17 @@ export type DirectProductOrderItemRequest = {
|
|||||||
remark?: string | null;
|
remark?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SalesOrderStraightThroughRequest = {
|
||||||
|
receiveFullPayment?: boolean;
|
||||||
|
paymentOccurredAt?: number;
|
||||||
|
paymentAttachmentList?: Partial<Attachment>[];
|
||||||
|
paymentRemark?: string | null;
|
||||||
|
fulfillAll?: boolean;
|
||||||
|
deliveredAt?: number;
|
||||||
|
fulfillmentAttachmentList?: Partial<Attachment>[];
|
||||||
|
fulfillmentRemark?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type DirectProductOrderCreateRequest = {
|
export type DirectProductOrderCreateRequest = {
|
||||||
requestNo: string;
|
requestNo: string;
|
||||||
storeId?: string;
|
storeId?: string;
|
||||||
@@ -105,6 +116,7 @@ export type DirectProductOrderCreateRequest = {
|
|||||||
purchasedAt?: number;
|
purchasedAt?: number;
|
||||||
itemList: DirectProductOrderItemRequest[];
|
itemList: DirectProductOrderItemRequest[];
|
||||||
attachmentList?: Partial<Attachment>[];
|
attachmentList?: Partial<Attachment>[];
|
||||||
|
straightThrough?: SalesOrderStraightThroughRequest;
|
||||||
remark?: string | null;
|
remark?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user