auto verify for order fulfillment and payment
This commit is contained in:
@@ -89,7 +89,7 @@
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<div v-else class="empty" v-text="'暂无可履约明细,请返回订单详情检查'" />
|
||||
<t-form class="form" label-align="left" label-width="6rem">
|
||||
<t-form class="form" label-width="7rem">
|
||||
<t-form-item label="履约时间" content-align="right">
|
||||
<t-button
|
||||
theme="primary"
|
||||
@@ -99,6 +99,12 @@
|
||||
@click="pickerVisible = true"
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item v-if="canDirectVerify" label="直接核销履约" content-align="right">
|
||||
<t-switch v-model="directVerify" size="small" :disabled="isSaving" />
|
||||
<template #help v-if="directVerify">
|
||||
<span class="red">直接核销会立即扣减实物库存;提交前会校验库存,库存不足时不能提交。</span>
|
||||
</template>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-textarea v-model="remark" borderless autosize maxlength="500" />
|
||||
</t-form-item>
|
||||
@@ -140,6 +146,7 @@ import { comparePackageQuantity, convertPackageQuantity, createPackageRequestNo,
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
import { getProductStockShortageMessage, mergeProductStockRequirements } from "@/utils/productStock";
|
||||
import { uploadPhotoList } from "@/utils/photo";
|
||||
import { getProductPhotoURL } from "@/utils/product";
|
||||
|
||||
@@ -162,6 +169,7 @@ const photoItems = ref<PhotoItem[]>([]);
|
||||
const unitPickerVisible = ref(false);
|
||||
const unitPickerSourceItemId = ref("");
|
||||
const unitPickerDraft = ref<Array<string | number>>([]);
|
||||
const directVerify = ref(false);
|
||||
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
||||
const isPackage = computed(() => order.value?.orderType === "PACKAGE");
|
||||
const expectedItemType = computed(() => isPackage.value ? "PACKAGE_ENTITLEMENT" : "DIRECT_PRODUCT");
|
||||
@@ -172,6 +180,10 @@ const unitPickerOptions = computed(() => {
|
||||
return item ? getFulfillmentUnitList(item).map(unit => ({ label: unit.name, value: unit.id || "" })) : [];
|
||||
});
|
||||
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || userStore.hasRoles(gaoRoleCodes.storeManager));
|
||||
const canDirectVerify = computed(() => userStore.hasRoles(gaoRoleCodes.storeManager)
|
||||
&& userStore.hasPermissions(permissionCodes.orderFulfillment.create)
|
||||
&& userStore.hasPermissions(permissionCodes.orderFulfillment.verify)
|
||||
&& userStore.hasPermissions(permissionCodes.productStock.read));
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const salesOrderId = String(route.query.salesOrderId || "");
|
||||
@@ -343,6 +355,55 @@ function buildAttachments(): Partial<Attachment>[] {
|
||||
}] : []);
|
||||
}
|
||||
|
||||
async function validateDirectStock(): Promise<boolean> {
|
||||
const selected = sourceItems.value.filter(item => !!item.id && quantityMap[item.id]);
|
||||
const skuMap = new Map<string, ProductSku>();
|
||||
for (const item of selected) {
|
||||
if (!item.skuId) {
|
||||
continue;
|
||||
}
|
||||
let sku = fulfillmentSkuMap[item.skuId];
|
||||
if (!sku) {
|
||||
try {
|
||||
sku = await ProductAPI.skuDetail(item.skuId);
|
||||
} catch {
|
||||
// 统一在下方提示,避免部分 SKU 查询失败时静默跳过库存校验
|
||||
}
|
||||
}
|
||||
if (!sku) {
|
||||
Message.warning(`${item.productNameSnapshot || "产品"} 的库存信息加载失败,请刷新后重试`);
|
||||
return false;
|
||||
}
|
||||
fulfillmentSkuMap[item.skuId] = sku;
|
||||
skuMap.set(item.skuId, sku);
|
||||
}
|
||||
const requirementList = mergeProductStockRequirements(selected.flatMap(item => {
|
||||
const sku = item.skuId ? skuMap.get(item.skuId) : undefined;
|
||||
const unit = getFulfillmentUnit(item);
|
||||
const quantity = normalizePackageQuantity(quantityMap[item.id || ""] || "");
|
||||
const baseQuantity = quantity && unit ? toPackageBaseQuantity(quantity, unit.baseQuantityPerUnit) : undefined;
|
||||
if (!item.skuId || !sku?.stockEnabled || !baseQuantity) {
|
||||
return [];
|
||||
}
|
||||
return [{ skuId: item.skuId, baseQuantity, productName: item.productNameSnapshot || "未命名产品", unitName: "基础单位" }];
|
||||
}));
|
||||
if (!requirementList.length) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const stockList = await ProductAPI.stockBatchDetail({ skuIdList: requirementList.map(item => item.skuId) });
|
||||
const shortageMessage = getProductStockShortageMessage(stockList, requirementList);
|
||||
if (shortageMessage) {
|
||||
Message.warning(`${shortageMessage},不能直接核销履约`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "库存查询失败,暂不能直接核销履约"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function validate(): boolean {
|
||||
if (!order.value?.id) {
|
||||
Message.warning("请从订单详情进入履约创建");
|
||||
@@ -397,15 +458,19 @@ async function save(): Promise<void> {
|
||||
skuUnitId: fulfillmentUnitMap[item.id!]!,
|
||||
quantity: normalizePackageQuantity(quantityMap[item.id || ""])!
|
||||
}));
|
||||
const result = await FulfillmentAPI.create({
|
||||
const request = {
|
||||
salesOrderId: order.value!.id!,
|
||||
requestNo: requestNo.value,
|
||||
deliveredAt: resolveTimestamp(deliveredAtText.value),
|
||||
itemList,
|
||||
attachmentList: buildAttachments(),
|
||||
remark: remark.value.trim() || null
|
||||
});
|
||||
Message.success("履约单已提交");
|
||||
};
|
||||
if (directVerify.value && !await validateDirectStock()) return;
|
||||
const result = directVerify.value
|
||||
? await FulfillmentAPI.createAndVerify(request)
|
||||
: await FulfillmentAPI.create(request);
|
||||
Message.success(directVerify.value ? "履约单创建并核销成功" : "履约单已提交");
|
||||
await router.replace(`/management/order-fulfillments/${result.id}`);
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "履约单创建失败,重试会复用同一请求号"));
|
||||
|
||||
Reference in New Issue
Block a user