hidden sku code
This commit is contained in:
@@ -8,7 +8,6 @@
|
|||||||
variant="light-outline"
|
variant="light-outline"
|
||||||
:content="tag.content"
|
:content="tag.content"
|
||||||
/>
|
/>
|
||||||
<t-tag v-if="unitText" size="small" :content="unitText" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,7 @@
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<div class="name-gift">
|
<div class="name-gift">
|
||||||
<strong
|
<strong v-text="item.productNameSnapshot || '未命名产品'"/>
|
||||||
class="clip-text"
|
|
||||||
v-text="item.productNameSnapshot || '未命名产品'"
|
|
||||||
/>
|
|
||||||
<t-tag
|
<t-tag
|
||||||
v-if="item.gifted"
|
v-if="item.gifted"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -39,17 +36,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="can-fulfill" v-text="getItemDescription(item)" />
|
<span class="can-fulfill" v-text="getItemDescription(item)" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
<div
|
<div
|
||||||
v-if="item.skuCodeSnapshot || item.skuSpecSnapshot?.length || item.skuNameSnapshot || item.specSummarySnapshot || item.unitSnapshot"
|
v-if="item.skuCodeSnapshot || item.skuSpecSnapshot?.length || item.skuNameSnapshot || item.specSummarySnapshot || item.unitSnapshot"
|
||||||
class="sku-tags"
|
class="sku-tags"
|
||||||
>
|
>
|
||||||
<t-tag
|
|
||||||
v-if="item.skuCodeSnapshot"
|
|
||||||
size="small"
|
|
||||||
theme="primary"
|
|
||||||
variant="light"
|
|
||||||
:content="item.skuCodeSnapshot"
|
|
||||||
/>
|
|
||||||
<product-sku-snapshot-tags
|
<product-sku-snapshot-tags
|
||||||
:product-name="item.productNameSnapshot"
|
:product-name="item.productNameSnapshot"
|
||||||
:snapshot="item.skuSpecSnapshot"
|
:snapshot="item.skuSpecSnapshot"
|
||||||
@@ -57,8 +49,6 @@
|
|||||||
:unit="item.unitSnapshot"
|
:unit="item.unitSnapshot"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="input">
|
|
||||||
<t-input
|
<t-input
|
||||||
v-model.trim="quantityMap[item.id || '']"
|
v-model.trim="quantityMap[item.id || '']"
|
||||||
class="quantity"
|
class="quantity"
|
||||||
@@ -80,7 +70,7 @@
|
|||||||
<t-button
|
<t-button
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:disabled="getAvailableBaseQuantity(item) === '0'"
|
:disabled="getAvailableBaseQuantity(item) === '0' || (directVerify && isStockChecking)"
|
||||||
@click.stop="fillAllQuantity(item)"
|
@click.stop="fillAllQuantity(item)"
|
||||||
>全部履约</t-button>
|
>全部履约</t-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,10 +90,13 @@
|
|||||||
/>
|
/>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item v-if="canDirectVerify" label="直接核销履约" content-align="right">
|
<t-form-item v-if="canDirectVerify" label="直接核销履约" content-align="right">
|
||||||
<t-switch v-model="directVerify" size="small" :disabled="isSaving" />
|
<t-switch v-model="directVerify" size="small" :disabled="isSaving || isStockChecking" />
|
||||||
<template #help v-if="directVerify">
|
</t-form-item>
|
||||||
<span class="red">直接核销会立即扣减实物库存;提交前会校验库存,库存不足时不能提交。</span>
|
<t-form-item v-if="directVerify" label-width="0">
|
||||||
</template>
|
<div class="stock-tips">
|
||||||
|
<span class="tips red">直接核销会立即扣减实物库存</span>
|
||||||
|
<product-stock-preview v-if="stockPreviewList.length" :items="stockPreviewList" />
|
||||||
|
</div>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="备注">
|
<t-form-item label="备注">
|
||||||
<t-textarea v-model="remark" borderless autosize maxlength="500" />
|
<t-textarea v-model="remark" borderless autosize maxlength="500" />
|
||||||
@@ -135,18 +128,31 @@ import FulfillmentAPI from "@/api/FulfillmentAPI";
|
|||||||
import ProductAPI from "@/api/ProductAPI";
|
import ProductAPI from "@/api/ProductAPI";
|
||||||
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 ProductStockPreview from "@/components/ProductStockPreview.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
|
import type { Attachment } from "timi-web-api";
|
||||||
import { useUserStore } from "timi-web-api";
|
import { useUserStore } from "timi-web-api";
|
||||||
import type { GaoOrderFulfillmentItemRequest } from "@/types/Fulfillment";
|
import type { GaoOrderFulfillmentItemRequest } from "@/types/Fulfillment";
|
||||||
import type { GaoSalesOrderItem, SalesOrder } from "@/types/SalesOrder";
|
import type { GaoSalesOrderItem, SalesOrder } from "@/types/SalesOrder";
|
||||||
import type { ProductSku, ProductSkuUnit } from "@/types/Product";
|
import type { ProductSku, ProductSkuUnit } from "@/types/Product";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import type { Attachment } from "timi-web-api";
|
import {
|
||||||
import { comparePackageQuantity, convertPackageQuantity, createPackageRequestNo, getPackageQuantityText, normalizePackageQuantity, toPackageBaseQuantity } from "@/utils/package";
|
comparePackageQuantity,
|
||||||
|
convertPackageQuantity,
|
||||||
|
createPackageRequestNo,
|
||||||
|
getPackageQuantityText,
|
||||||
|
normalizePackageQuantity,
|
||||||
|
toPackageBaseQuantity
|
||||||
|
} from "@/utils/package";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
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 { uploadPhotoList } from "@/utils/photo";
|
||||||
import { getProductPhotoURL } from "@/utils/product";
|
import { getProductPhotoURL } from "@/utils/product";
|
||||||
|
|
||||||
@@ -170,6 +176,9 @@ const unitPickerVisible = ref(false);
|
|||||||
const unitPickerSourceItemId = ref("");
|
const unitPickerSourceItemId = ref("");
|
||||||
const unitPickerDraft = ref<Array<string | number>>([]);
|
const unitPickerDraft = ref<Array<string | number>>([]);
|
||||||
const directVerify = ref(false);
|
const directVerify = ref(false);
|
||||||
|
const isStockChecking = ref(false);
|
||||||
|
const stockPreviewList = ref<ProductStockPreviewItem[]>([]);
|
||||||
|
let stockCheckToken = 0;
|
||||||
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
const photoUrlList = computed(() => photoItems.value.map(item => item.url));
|
||||||
const isPackage = computed(() => order.value?.orderType === "PACKAGE");
|
const isPackage = computed(() => order.value?.orderType === "PACKAGE");
|
||||||
const expectedItemType = computed(() => isPackage.value ? "PACKAGE_ENTITLEMENT" : "DIRECT_PRODUCT");
|
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.orderFulfillment.verify)
|
||||||
&& userStore.hasPermissions(permissionCodes.productStock.read));
|
&& 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<void> {
|
async function loadData(): Promise<void> {
|
||||||
const salesOrderId = String(route.query.salesOrderId || "");
|
const salesOrderId = String(route.query.salesOrderId || "");
|
||||||
if (!salesOrderId) {
|
if (!salesOrderId) {
|
||||||
@@ -262,7 +290,7 @@ function getFulfillmentUnitForAvailableQuantity(item: GaoSalesOrderItem, unit?:
|
|||||||
return getBaseUnit(item) || unit;
|
return getBaseUnit(item) || unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillAllQuantity(item: GaoSalesOrderItem): void {
|
async function fillAllQuantity(item: GaoSalesOrderItem): Promise<void> {
|
||||||
const unit = getFulfillmentUnit(item);
|
const unit = getFulfillmentUnit(item);
|
||||||
if (!item.id || !unit) {
|
if (!item.id || !unit) {
|
||||||
Message.warning("请先选择实际履约单位");
|
Message.warning("请先选择实际履约单位");
|
||||||
@@ -274,6 +302,9 @@ function fillAllQuantity(item: GaoSalesOrderItem): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
quantityMap[item.id] = quantity;
|
quantityMap[item.id] = quantity;
|
||||||
|
if (directVerify.value) {
|
||||||
|
await previewDirectStock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFulfillmentUnitList(item: GaoSalesOrderItem): ProductSkuUnit[] {
|
function getFulfillmentUnitList(item: GaoSalesOrderItem): ProductSkuUnit[] {
|
||||||
@@ -356,6 +387,7 @@ function buildAttachments(): Partial<Attachment>[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function validateDirectStock(): Promise<boolean> {
|
async function validateDirectStock(): Promise<boolean> {
|
||||||
|
stockPreviewList.value = [];
|
||||||
const selected = sourceItems.value.filter(item => !!item.id && quantityMap[item.id]);
|
const selected = sourceItems.value.filter(item => !!item.id && quantityMap[item.id]);
|
||||||
const skuMap = new Map<string, ProductSku>();
|
const skuMap = new Map<string, ProductSku>();
|
||||||
for (const item of selected) {
|
for (const item of selected) {
|
||||||
@@ -385,13 +417,14 @@ async function validateDirectStock(): Promise<boolean> {
|
|||||||
if (!item.skuId || !sku?.stockEnabled || !baseQuantity) {
|
if (!item.skuId || !sku?.stockEnabled || !baseQuantity) {
|
||||||
return [];
|
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) {
|
if (!requirementList.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const stockList = await ProductAPI.stockBatchDetail({ skuIdList: requirementList.map(item => item.skuId) });
|
const stockList = await ProductAPI.stockBatchDetail({ skuIdList: requirementList.map(item => item.skuId) });
|
||||||
|
stockPreviewList.value = buildProductStockPreview(stockList, requirementList);
|
||||||
const shortageMessage = getProductStockShortageMessage(stockList, requirementList);
|
const shortageMessage = getProductStockShortageMessage(stockList, requirementList);
|
||||||
if (shortageMessage) {
|
if (shortageMessage) {
|
||||||
Message.warning(`${shortageMessage},不能直接核销履约`);
|
Message.warning(`${shortageMessage},不能直接核销履约`);
|
||||||
@@ -399,11 +432,30 @@ async function validateDirectStock(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
stockPreviewList.value = [];
|
||||||
Message.error(getErrorMessage(error, "库存查询失败,暂不能直接核销履约"));
|
Message.error(getErrorMessage(error, "库存查询失败,暂不能直接核销履约"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function previewDirectStock(): Promise<void> {
|
||||||
|
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 {
|
function validate(): boolean {
|
||||||
if (!order.value?.id) {
|
if (!order.value?.id) {
|
||||||
Message.warning("请从订单详情进入履约创建");
|
Message.warning("请从订单详情进入履约创建");
|
||||||
@@ -447,7 +499,12 @@ function validate(): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save(): Promise<void> {
|
async function save(): Promise<void> {
|
||||||
if (isSaving.value || !validate()) return;
|
if (isSaving.value) return;
|
||||||
|
if (isStockChecking.value) {
|
||||||
|
Message.warning("库存正在预检,请稍候再提交");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!validate()) return;
|
||||||
if (!requestNo.value) requestNo.value = createPackageRequestNo();
|
if (!requestNo.value) requestNo.value = createPackageRequestNo();
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -552,10 +609,10 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: start;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
flex: 1 1 0;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -568,6 +625,12 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
gap: .5rem;
|
||||||
|
display: flex;
|
||||||
|
margin-top: .5rem;
|
||||||
|
|
||||||
.sku-tags {
|
.sku-tags {
|
||||||
gap: .375rem;
|
gap: .375rem;
|
||||||
@@ -575,12 +638,6 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
gap: .5rem;
|
|
||||||
display: flex;
|
|
||||||
margin-top: .5rem;
|
|
||||||
|
|
||||||
.unit {
|
.unit {
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
@@ -591,10 +648,8 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.tips {
|
||||||
color: var(--td-text-color-secondary);
|
font-size: .875rem;
|
||||||
padding: 2rem 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user