diff --git a/src/components/ProductSkuSnapshotTags.vue b/src/components/ProductSkuSnapshotTags.vue
index 27f8f31..6c7aea5 100644
--- a/src/components/ProductSkuSnapshotTags.vue
+++ b/src/components/ProductSkuSnapshotTags.vue
@@ -8,7 +8,6 @@
variant="light-outline"
:content="tag.content"
/>
-
diff --git a/src/pages/fulfillment/OrderFulfillmentCreate.vue b/src/pages/fulfillment/OrderFulfillmentCreate.vue
index cd8fde7..d4cff57 100644
--- a/src/pages/fulfillment/OrderFulfillmentCreate.vue
+++ b/src/pages/fulfillment/OrderFulfillmentCreate.vue
@@ -26,10 +26,7 @@
+
-
全部履约
+ :disabled="getAvailableBaseQuantity(item) === '0' || (directVerify && isStockChecking)"
+ @click.stop="fillAllQuantity(item)"
+ >全部履约
@@ -100,10 +90,13 @@
/>
-
-
- 直接核销会立即扣减实物库存;提交前会校验库存,库存不足时不能提交。
-
+
+
+
+
@@ -135,18 +128,31 @@ import FulfillmentAPI from "@/api/FulfillmentAPI";
import ProductAPI from "@/api/ProductAPI";
import PhotoScroller from "@/components/PhotoScroller.vue";
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
+import ProductStockPreview from "@/components/ProductStockPreview.vue";
import { useNavBarStore } from "@/store/navBarStore";
+import type { Attachment } from "timi-web-api";
import { useUserStore } from "timi-web-api";
import type { GaoOrderFulfillmentItemRequest } from "@/types/Fulfillment";
import type { GaoSalesOrderItem, SalesOrder } from "@/types/SalesOrder";
import type { ProductSku, ProductSkuUnit } from "@/types/Product";
import type { PhotoItem } from "@/types/Photo";
-import type { Attachment } from "timi-web-api";
-import { comparePackageQuantity, convertPackageQuantity, createPackageRequestNo, getPackageQuantityText, normalizePackageQuantity, toPackageBaseQuantity } from "@/utils/package";
+import {
+ comparePackageQuantity,
+ convertPackageQuantity,
+ createPackageRequestNo,
+ getPackageQuantityText,
+ normalizePackageQuantity,
+ toPackageBaseQuantity
+} from "@/utils/package";
import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
-import { getProductStockShortageMessage, mergeProductStockRequirements } from "@/utils/productStock";
+import type { ProductStockPreviewItem } from "@/utils/productStock";
+import {
+ buildProductStockPreview,
+ getProductStockShortageMessage,
+ mergeProductStockRequirements
+} from "@/utils/productStock";
import { uploadPhotoList } from "@/utils/photo";
import { getProductPhotoURL } from "@/utils/product";
@@ -170,6 +176,9 @@ const unitPickerVisible = ref(false);
const unitPickerSourceItemId = ref("");
const unitPickerDraft = ref>([]);
const directVerify = ref(false);
+const isStockChecking = ref(false);
+const stockPreviewList = ref([]);
+let stockCheckToken = 0;
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");
@@ -185,6 +194,25 @@ const canDirectVerify = computed(() => userStore.hasRoles(gaoRoleCodes.storeMana
&& userStore.hasPermissions(permissionCodes.orderFulfillment.verify)
&& userStore.hasPermissions(permissionCodes.productStock.read));
+watch(directVerify, async value => {
+ stockCheckToken += 1;
+ const token = stockCheckToken;
+ if (!value) {
+ isStockChecking.value = false;
+ return;
+ }
+ isStockChecking.value = true;
+ try {
+ if (!await validateDirectStock() && token === stockCheckToken && directVerify.value) {
+ directVerify.value = false;
+ }
+ } finally {
+ if (token === stockCheckToken) {
+ isStockChecking.value = false;
+ }
+ }
+});
+
async function loadData(): Promise {
const salesOrderId = String(route.query.salesOrderId || "");
if (!salesOrderId) {
@@ -262,7 +290,7 @@ function getFulfillmentUnitForAvailableQuantity(item: GaoSalesOrderItem, unit?:
return getBaseUnit(item) || unit;
}
-function fillAllQuantity(item: GaoSalesOrderItem): void {
+async function fillAllQuantity(item: GaoSalesOrderItem): Promise {
const unit = getFulfillmentUnit(item);
if (!item.id || !unit) {
Message.warning("请先选择实际履约单位");
@@ -274,6 +302,9 @@ function fillAllQuantity(item: GaoSalesOrderItem): void {
return;
}
quantityMap[item.id] = quantity;
+ if (directVerify.value) {
+ await previewDirectStock();
+ }
}
function getFulfillmentUnitList(item: GaoSalesOrderItem): ProductSkuUnit[] {
@@ -356,6 +387,7 @@ function buildAttachments(): Partial[] {
}
async function validateDirectStock(): Promise {
+ stockPreviewList.value = [];
const selected = sourceItems.value.filter(item => !!item.id && quantityMap[item.id]);
const skuMap = new Map();
for (const item of selected) {
@@ -385,13 +417,14 @@ async function validateDirectStock(): Promise {
if (!item.skuId || !sku?.stockEnabled || !baseQuantity) {
return [];
}
- return [{ skuId: item.skuId, baseQuantity, productName: item.productNameSnapshot || "未命名产品", unitName: "基础单位" }];
+ return [{ skuId: item.skuId, baseQuantity, productName: item.productNameSnapshot || "未命名产品", unitName: "基础单位", stockEnabled: sku.stockEnabled }];
}));
if (!requirementList.length) {
return true;
}
try {
const stockList = await ProductAPI.stockBatchDetail({ skuIdList: requirementList.map(item => item.skuId) });
+ stockPreviewList.value = buildProductStockPreview(stockList, requirementList);
const shortageMessage = getProductStockShortageMessage(stockList, requirementList);
if (shortageMessage) {
Message.warning(`${shortageMessage},不能直接核销履约`);
@@ -399,11 +432,30 @@ async function validateDirectStock(): Promise {
}
return true;
} catch (error) {
+ stockPreviewList.value = [];
Message.error(getErrorMessage(error, "库存查询失败,暂不能直接核销履约"));
return false;
}
}
+async function previewDirectStock(): Promise {
+ if (!directVerify.value || isStockChecking.value) {
+ return;
+ }
+ stockCheckToken += 1;
+ const token = stockCheckToken;
+ isStockChecking.value = true;
+ try {
+ if (!await validateDirectStock() && token === stockCheckToken && directVerify.value) {
+ directVerify.value = false;
+ }
+ } finally {
+ if (token === stockCheckToken) {
+ isStockChecking.value = false;
+ }
+ }
+}
+
function validate(): boolean {
if (!order.value?.id) {
Message.warning("请从订单详情进入履约创建");
@@ -447,7 +499,12 @@ function validate(): boolean {
}
async function save(): Promise {
- if (isSaving.value || !validate()) return;
+ if (isSaving.value) return;
+ if (isStockChecking.value) {
+ Message.warning("库存正在预检,请稍候再提交");
+ return;
+ }
+ if (!validate()) return;
if (!requestNo.value) requestNo.value = createPackageRequestNo();
isSaving.value = true;
try {
@@ -552,10 +609,10 @@ onUnmounted(() => navBarStore.clearRight());
min-width: 0;
display: flex;
overflow: hidden;
+ align-items: center;
+ justify-content: start;
strong {
- flex: 1 1 0;
- min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@@ -568,6 +625,12 @@ onUnmounted(() => navBarStore.clearRight());
white-space: nowrap;
}
}
+ }
+
+ .input {
+ gap: .5rem;
+ display: flex;
+ margin-top: .5rem;
.sku-tags {
gap: .375rem;
@@ -575,12 +638,6 @@ onUnmounted(() => navBarStore.clearRight());
flex-wrap: wrap;
align-items: center;
}
- }
-
- .input {
- gap: .5rem;
- display: flex;
- margin-top: .5rem;
.unit {
font-size: .875rem;
@@ -591,10 +648,8 @@ onUnmounted(() => navBarStore.clearRight());
}
}
- .empty {
- color: var(--td-text-color-secondary);
- padding: 2rem 1rem;
- text-align: center;
+ .tips {
+ font-size: .875rem;
}
}