fix order/fulfillment unit

This commit is contained in:
Timi
2026-09-09 14:23:35 +08:00
parent e154fd6186
commit ce4a45fb65
6 changed files with 99 additions and 23 deletions
@@ -191,7 +191,7 @@ async function loadFulfillmentSkuList(): Promise<void> {
continue; continue;
} }
const originalUnit = getSellableUnitList(item).find(unit => unit.id === item.skuUnitId); const originalUnit = getSellableUnitList(item).find(unit => unit.id === item.skuUnitId);
const defaultUnit = originalUnit || getSellableUnitList(item)[0]; const defaultUnit = getFulfillmentUnitForAvailableQuantity(item, originalUnit || getSellableUnitList(item)[0]);
if (defaultUnit?.id) { if (defaultUnit?.id) {
fulfillmentUnitMap[item.id] = defaultUnit.id; fulfillmentUnitMap[item.id] = defaultUnit.id;
} }
@@ -217,7 +217,26 @@ function getItemDescription(item: GaoSalesOrderItem): string {
} }
function getBaseUnitName(item: GaoSalesOrderItem): string { function getBaseUnitName(item: GaoSalesOrderItem): string {
return item.skuId ? fulfillmentSkuMap[item.skuId]?.unitList?.find(unit => unit.isBase)?.name || "基础单位" : "基础单位"; return getBaseUnit(item)?.name || "基础单位";
}
function getBaseUnit(item: GaoSalesOrderItem): ProductSkuUnit | undefined {
return getSellableUnitList(item).find(unit => unit.isBase);
}
function shouldDowngradeUnit(item: GaoSalesOrderItem, unit?: ProductSkuUnit): boolean {
const sku = item.skuId ? fulfillmentSkuMap[item.skuId] : undefined;
if (!unit || unit.isBase || sku?.allowDecimalQuantity !== false) {
return false;
}
return Number(getAvailableBaseQuantity(item)) < Number(unit.baseQuantityPerUnit);
}
function getFulfillmentUnitForAvailableQuantity(item: GaoSalesOrderItem, unit?: ProductSkuUnit): ProductSkuUnit | undefined {
if (!shouldDowngradeUnit(item, unit)) {
return unit;
}
return getBaseUnit(item) || unit;
} }
function fillAllQuantity(item: GaoSalesOrderItem): void { function fillAllQuantity(item: GaoSalesOrderItem): void {
@@ -242,6 +261,22 @@ function getFulfillmentUnit(item: GaoSalesOrderItem): ProductSkuUnit | undefined
return getSellableUnitList(item).find(unit => unit.id === fulfillmentUnitMap[item.id || ""]); return getSellableUnitList(item).find(unit => unit.id === fulfillmentUnitMap[item.id || ""]);
} }
function setFulfillmentUnit(item: GaoSalesOrderItem, unit: ProductSkuUnit): void {
if (!item.id) {
return;
}
const currentUnit = getFulfillmentUnit(item);
const currentQuantity = quantityMap[item.id];
if (currentQuantity && currentUnit?.id !== unit.id) {
quantityMap[item.id] = convertPackageQuantity(
currentQuantity,
currentUnit?.baseQuantityPerUnit ?? "1",
unit.baseQuantityPerUnit
) || "";
}
fulfillmentUnitMap[item.id] = unit.id!;
}
function openUnitPicker(item: GaoSalesOrderItem): void { function openUnitPicker(item: GaoSalesOrderItem): void {
if (!item.id) { if (!item.id) {
return; return;
@@ -257,8 +292,11 @@ function openUnitPicker(item: GaoSalesOrderItem): void {
function confirmUnitPicker(value: Array<string | number>): void { function confirmUnitPicker(value: Array<string | number>): void {
const skuUnitId = String(value[0] || ""); const skuUnitId = String(value[0] || "");
if (unitPickerSourceItemId.value && skuUnitId) { const item = sourceItems.value.find(sourceItem => sourceItem.id === unitPickerSourceItemId.value);
fulfillmentUnitMap[unitPickerSourceItemId.value] = skuUnitId; const selectedUnit = item ? getSellableUnitList(item).find(unit => unit.id === skuUnitId) : undefined;
const fulfillmentUnit = item ? getFulfillmentUnitForAvailableQuantity(item, selectedUnit) : selectedUnit;
if (item?.id && fulfillmentUnit?.id) {
setFulfillmentUnit(item, fulfillmentUnit);
} }
unitPickerVisible.value = false; unitPickerVisible.value = false;
} }
@@ -31,7 +31,6 @@
v-for="item in fulfillment.itemList || []" v-for="item in fulfillment.itemList || []"
:key="item.id" :key="item.id"
:title="item.productNameSnapshot || '未命名产品'" :title="item.productNameSnapshot || '未命名产品'"
:note="`待履约(基础单位)${item.reservedBaseBefore || 0} → ${item.reservedBaseAfter || 0}`"
> >
<template #description> <template #description>
<div class="item-description"> <div class="item-description">
@@ -41,7 +40,7 @@
:fallback="item.specSummarySnapshot || item.skuNameSnapshot || '默认 SKU'" :fallback="item.specSummarySnapshot || item.skuNameSnapshot || '默认 SKU'"
:unit="item.unitSnapshot" :unit="item.unitSnapshot"
/> />
<span v-text="`履约数量 ${item.quantity || 0}`" /> <span v-text="getFulfillmentQuantityText(item)" />
</div> </div>
</template> </template>
<template #leftIcon> <template #leftIcon>
@@ -94,7 +93,9 @@
<t-dialog <t-dialog
v-model:visible="reasonVisible" v-model:visible="reasonVisible"
:title="reasonAction === 'reject' ? '拒绝履约' : '撤销履约'" :title="reasonAction === 'reject' ? '拒绝履约' : '撤销履约'"
cancel-btn="取消"
:confirm-btn="reasonAction === 'reject' ? '拒绝' : '撤销'" :confirm-btn="reasonAction === 'reject' ? '拒绝' : '撤销'"
@cancel="reasonVisible = false"
@confirm="submitReason" @confirm="submitReason"
> >
<t-textarea v-model.trim="reason" borderless autosize placeholder="请输入原因" /> <t-textarea v-model.trim="reason" borderless autosize placeholder="请输入原因" />
@@ -102,16 +103,23 @@
<bottom-popup <bottom-popup
v-model="revokeVisible" v-model="revokeVisible"
title="履约冲正" title="履约冲正"
height="min(55vh, 32rem)"
left-button-text="取消" left-button-text="取消"
right-button-text="提交" right-button-text="提交"
@left-button-click="revokeVisible = false" @left-button-click="revokeVisible = false"
@right-button-click="submitRevoke" @right-button-click="submitRevoke"
> >
<t-form label-align="top"> <t-form class="revoke-form">
<t-form-item label="冲正原因" required-mark><t-textarea v-model.trim="revokeReason" borderless autosize /></t-form-item> <t-form-item label="冲正原因" required-mark>
<t-form-item label="库存是否退回门店"><t-switch v-model="returnedToStore" /></t-form-item> <t-textarea
</t-form> v-model="revokeReason"
borderless
autosize
/>
</t-form-item>
<t-form-item label="库存退回门店" content-align="right">
<t-switch v-model="returnedToStore" />
</t-form-item>
<t-form-item label-width="0">
<photo-scroller <photo-scroller
:model-value="photoUrlList" :model-value="photoUrlList"
v-model:photo-items="photoItems" v-model:photo-items="photoItems"
@@ -120,6 +128,8 @@
required required
title="冲正凭证照片" title="冲正凭证照片"
/> />
</t-form-item>
</t-form>
</bottom-popup> </bottom-popup>
</div> </div>
</template> </template>
@@ -133,11 +143,11 @@ import SalesOrderAPI from "@/api/SalesOrderAPI";
import PhotoScroller from "@/components/PhotoScroller.vue"; import PhotoScroller from "@/components/PhotoScroller.vue";
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue"; import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
import { useNavBarStore } from "@/store/navBarStore"; import { useNavBarStore } from "@/store/navBarStore";
import type { OrderFulfillment } from "@/types/Fulfillment"; import type { OrderFulfillment, OrderFulfillmentItem } from "@/types/Fulfillment";
import type { GaoSalesOrderItemType, SalesOrder } from "@/types/SalesOrder"; import type { GaoSalesOrderItemType, SalesOrder } from "@/types/SalesOrder";
import type { PhotoItem } from "@/types/Photo"; import type { PhotoItem } from "@/types/Photo";
import type { Attachment } from "timi-web-api"; import type { Attachment } from "timi-web-api";
import { createPackageRequestNo } from "@/utils/package"; import { createPackageRequestNo, getPackageQuantityText } from "@/utils/package";
import { getAttachmentReadURL } from "@/utils/attachment"; import { getAttachmentReadURL } from "@/utils/attachment";
import { getErrorMessage } from "@/utils/error"; import { getErrorMessage } from "@/utils/error";
import { getProductPhotoURL } from "@/utils/product"; import { getProductPhotoURL } from "@/utils/product";
@@ -186,6 +196,10 @@ const sourceItemType = computed<GaoSalesOrderItemType | undefined>(() => {
return order.value?.itemList?.find(item => item.id === sourceOrderItemId)?.itemType; return order.value?.itemList?.find(item => item.id === sourceOrderItemId)?.itemType;
}); });
function getFulfillmentQuantityText(item: OrderFulfillmentItem): string {
return `履约数量 ${getPackageQuantityText(item.quantity)} ${item.unitSnapshot || "单位"}`;
}
async function loadData(): Promise<void> { async function loadData(): Promise<void> {
const id = String(route.params.id || ""); const id = String(route.params.id || "");
if (!id) return; if (!id) return;
@@ -339,4 +353,8 @@ onUnmounted(() => navBarStore.clearRight());
} }
} }
} }
.revoke-form {
margin: 0;
}
</style> </style>
@@ -70,7 +70,6 @@
import { type DropdownValue } from "tdesign-mobile-vue"; import { type DropdownValue } from "tdesign-mobile-vue";
import { PassTimeLabel } from "timi-web"; import { PassTimeLabel } from "timi-web";
import ProductAPI from "@/api/ProductAPI"; import ProductAPI from "@/api/ProductAPI";
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
import { useNavBarStore } from "@/store/navBarStore"; import { useNavBarStore } from "@/store/navBarStore";
import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product"; import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product";
import { getErrorMessage } from "@/utils/error"; import { getErrorMessage } from "@/utils/error";
+19 -1
View File
@@ -310,13 +310,31 @@ function getRemainingAmountClass(amount?: number | null): "green" | "red" | "gra
return amount === 0 ? "green" : "red"; return amount === 0 ? "green" : "red";
} }
function getItemUnitName(item: GaoSalesOrderItem): string {
return item.unitSnapshot || "单位";
}
function getBaseUnitName(item: GaoSalesOrderItem): string {
return item.baseUnitName || "基础单位";
}
function shouldDisplayBaseQuantity(item: GaoSalesOrderItem, baseQuantity?: number | string): boolean {
const isBaseUnit = Number(item.baseQuantityPerUnitSnapshot ?? 1) === 1;
return item.allowDecimalQuantity === false
&& !isBaseUnit
&& baseQuantity !== undefined
&& Number(baseQuantity) < Number(item.baseQuantityPerUnitSnapshot ?? 1);
}
function getItemQuantityText(item: GaoSalesOrderItem, baseQuantity?: number | string): string { function getItemQuantityText(item: GaoSalesOrderItem, baseQuantity?: number | string): string {
const quantity = convertPackageQuantity( const quantity = convertPackageQuantity(
String(baseQuantity ?? "0"), String(baseQuantity ?? "0"),
"1", "1",
item.baseQuantityPerUnitSnapshot ?? "1" item.baseQuantityPerUnitSnapshot ?? "1"
); );
return getPackageQuantityText(quantity ?? baseQuantity); const useBaseUnit = shouldDisplayBaseQuantity(item, baseQuantity);
const displayQuantity = useBaseUnit ? getPackageQuantityText(baseQuantity) : getPackageQuantityText(quantity ?? baseQuantity);
return `${displayQuantity} ${useBaseUnit ? getBaseUnitName(item) : getItemUnitName(item)}`;
} }
function getAvailableQuantityClass(item: GaoSalesOrderItem): "green" | "orange" { function getAvailableQuantityClass(item: GaoSalesOrderItem): "green" | "orange" {
@@ -144,8 +144,9 @@ function sumProductQuantity(field: "quantity" | "fulfilledBaseQuantity"): string
} }
function getProductText(item: NonNullable<SalesOrder["itemList"]>[number]): string { function getProductText(item: NonNullable<SalesOrder["itemList"]>[number]): string {
const quantity = getPackageQuantityText(item.quantity);
const skuText = getProductSkuSnapshotText(item.productNameSnapshot, item.skuNameSnapshot, item.specSummarySnapshot); const skuText = getProductSkuSnapshotText(item.productNameSnapshot, item.skuNameSnapshot, item.specSummarySnapshot);
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}${item.unitSnapshot ? ` / ${item.unitSnapshot}` : ""}`; return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""} × ${quantity}${item.unitSnapshot ? ` ${item.unitSnapshot}` : ""}`;
} }
</script> </script>
+2
View File
@@ -26,6 +26,8 @@ export type GaoSalesOrderItem = {
skuSpecSnapshot?: ProductSkuSpecSnapshot[]; skuSpecSnapshot?: ProductSkuSpecSnapshot[];
unitSnapshot?: string; unitSnapshot?: string;
baseQuantityPerUnitSnapshot?: number | string; baseQuantityPerUnitSnapshot?: number | string;
allowDecimalQuantity?: boolean;
baseUnitName?: string;
quantity?: number | string; quantity?: number | string;
baseQuantity?: number | string; baseQuantity?: number | string;
reservedBaseQuantity?: number | string; reservedBaseQuantity?: number | string;