From a6baad20258a61d9ce9a786029ca4cb63a7c992e Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期二, 21 四月 2026 17:31:33 +0800
Subject: [PATCH] 优化出入库功能,新增对未完全入库和出库商品的检查,更新相关API接口以支持审批人字段的统一命名
---
src/pages/inventoryManagement/scanIn/index.vue | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 309 insertions(+), 14 deletions(-)
diff --git a/src/pages/inventoryManagement/scanIn/index.vue b/src/pages/inventoryManagement/scanIn/index.vue
index 8479acd..f68ebd5 100644
--- a/src/pages/inventoryManagement/scanIn/index.vue
+++ b/src/pages/inventoryManagement/scanIn/index.vue
@@ -99,7 +99,7 @@
<text class="kv-label">鍏ュ簱鏁伴噺</text>
<view class="kv-value stocked-qty-input-wrap">
<up-input :key="'stocked-' + idx"
- v-model="item.stockedQuantity"
+ v-model="item.operateQuantity"
type="number"
placeholder="璇疯緭鍏ュ叆搴撴暟閲�"
clearable
@@ -109,6 +109,51 @@
</view>
</view>
</view>
+ <view class="approval-process">
+ <view class="approval-header">
+ <text class="approval-title">瀹℃牳娴佺▼</text>
+ <text class="approval-desc">姣忎釜姝ラ鍙兘閫夋嫨涓�涓鎵逛汉</text>
+ </view>
+ <view class="approval-steps">
+ <view v-for="(step, stepIndex) in stockApproverNodes"
+ :key="step.id"
+ class="approval-step">
+ <view class="step-title">
+ <text>瀹℃壒浜�</text>
+ </view>
+ <view class="approver-container">
+ <view v-if="step.userName"
+ class="approver-item">
+ <view class="approver-avatar">
+ <text class="avatar-text">{{ step.userName.charAt(0) }}</text>
+ </view>
+ <view class="approver-info">
+ <text class="approver-name">{{ step.userName }}</text>
+ </view>
+ <view class="delete-approver-btn"
+ @click="removeApprover(stepIndex)">脳</view>
+ </view>
+ <view v-else
+ class="add-approver-btn"
+ @click="openApproverPicker(stepIndex)">
+ <view class="add-circle">+</view>
+ <text class="add-label">閫夋嫨瀹℃壒浜�</text>
+ </view>
+ </view>
+ <view class="delete-step-btn"
+ v-if="stockApproverNodes.length > 1"
+ @click="removeStockApproverNode(stepIndex)">鍒犻櫎鑺傜偣</view>
+ </view>
+ </view>
+ <view class="add-step-btn">
+ <u-button icon="plus"
+ plain
+ type="primary"
+ style="width: 100%"
+ @click="addStockApproverNode">鏂板鑺傜偣</u-button>
+ </view>
+ </view>
+
<view class="footer-btns">
<u-button class="footer-cancel-btn"
@click="cancelForm">杩斿洖</u-button>
@@ -117,11 +162,12 @@
@click="confirmInbound">纭鍏ュ簱</u-button>
</view>
</scroll-view>
+
</view>
</template>
<script setup>
- import { ref, computed } from "vue";
+ import { ref, computed, onMounted, onUnmounted } from "vue";
import PageHeader from "@/components/PageHeader.vue";
import { productList as salesProductList } from "@/api/salesManagement/salesLedger";
import modal from "@/plugins/modal";
@@ -146,6 +192,8 @@
const contractKind = ref(CONTRACT_KIND.sales);
const scanLedgerId = ref(null);
const submitLoading = ref(false);
+ const stockApproverNodes = ref([{ id: 1, userId: null, userName: "" }]);
+ let nextApproverNodeId = 2;
const submitConfigByScene = createSubmitConfig(scanLedgerId);
const cardTitleMain = computed(() => {
@@ -166,7 +214,22 @@
};
};
- const { detailFieldRows, summaryFieldRows } = useScanOutFieldRows(contractKind);
+ const { detailFieldRows: rawDetailFieldRows, summaryFieldRows: rawSummaryFieldRows } = useScanOutFieldRows(
+ contractKind,
+ "inbound"
+ );
+ const shouldShowInboundQuantityField = key => {
+ if (type.value === QUALITY_TYPE.qualified) return key !== "unqualifiedStockedQuantity";
+ if (type.value === QUALITY_TYPE.unqualified)
+ return key !== "stockedQuantity" && key !== "remainingQuantity";
+ return true;
+ };
+ const detailFieldRows = computed(() =>
+ rawDetailFieldRows.value.filter(row => shouldShowInboundQuantityField(row.key))
+ );
+ const summaryFieldRows = computed(() =>
+ rawSummaryFieldRows.value.filter(row => shouldShowInboundQuantityField(row.key))
+ );
const emptyDash = v => {
if (v === null || v === undefined || v === "") return "-";
@@ -211,25 +274,39 @@
return emptyDash(v);
};
+ const shouldValidateStockStatus = computed(() => {
+ return (
+ contractKind.value === CONTRACT_KIND.sales &&
+ type.value === QUALITY_TYPE.qualified
+ );
+ });
+
const isFullyStocked = item => {
+ if (!shouldValidateStockStatus.value) return false;
const s = item?.productStockStatus;
return s == 2 || s === "2";
};
const onStockedQtyBlur = item => {
if (isFullyStocked(item)) return;
- const raw = item.stockedQuantity;
+ const raw = item.operateQuantity;
if (raw === null || raw === undefined || String(raw).trim() === "") {
- item.stockedQuantity = "0";
+ item.operateQuantity = "0";
return;
}
const n = Number(String(raw).trim());
if (Number.isNaN(n)) {
- item.stockedQuantity = defaultStockedQuantityFromRow(item);
+ item.operateQuantity =
+ type.value === QUALITY_TYPE.unqualified ? "0" : defaultStockedQuantityFromRow(item, "inbound");
return;
}
- item.stockedQuantity = String(Math.max(0, n));
+ item.operateQuantity = String(Math.max(0, n));
};
+
+ const hasEditableInboundItems = computed(() => {
+ if (!recordList.value?.length) return false;
+ return recordList.value.some(item => !isFullyStocked(item));
+ });
const formatCell = (item, row, idx) => {
if (row.key === "index") {
@@ -242,11 +319,35 @@
return formatProductStockStatus(item.productStockStatus);
if (row.key === "heavyBox") return formatHeavyBox(item.heavyBox);
if (row.key === "remainingQuantity") {
+ const v = item.remainingQuantity;
+ return emptyDash(v);
+ }
+ if (row.key === "remainingShippedQuantity") {
+ const v = item.remainingShippedQuantity;
+ return emptyDash(v);
+ }
+ if (row.key === "shippedQuantity") {
+ const v = item.shippedQuantity;
+ return emptyDash(v);
+ }
+ if (row.key === "unqualifiedShippedQuantity") {
const v =
- item.remainingQuantity ??
- item.remaining_quantity ??
- item.remainQuantity ??
- item.remain_quantity;
+ item.unqualifiedShippedQuantity ??
+ item.unQualifiedShippedQuantity ??
+ item.unqualifiedShippedQty ??
+ item.unqualifiedOutboundQuantity;
+ return emptyDash(v);
+ }
+ if (row.key === "stockedQuantity") {
+ const v = item.stockedQuantity;
+ return emptyDash(v);
+ }
+ if (row.key === "unqualifiedStockedQuantity") {
+ const v =
+ item.unqualifiedStockedQuantity ??
+ item.unQualifiedStockedQuantity ??
+ item.unqualifiedStockedQty ??
+ item.unqualifiedInboundQuantity;
return emptyDash(v);
}
if (row.key === "availableQuality") {
@@ -267,11 +368,71 @@
scanLedgerId.value = null;
expandedByIndex.value = {};
recordList.value = [];
+ stockApproverNodes.value = [{ id: 1, userId: null, userName: "" }];
};
- const confirmInbound = async () => {
+ onMounted(() => {
+ uni.$on("selectContact", handleSelectContact);
+ });
+
+ onUnmounted(() => {
+ uni.$off("selectContact", handleSelectContact);
+ });
+
+ const addStockApproverNode = () => {
+ stockApproverNodes.value.push({
+ id: nextApproverNodeId++,
+ userId: null,
+ userName: "",
+ });
+ };
+
+ const removeStockApproverNode = index => {
+ if (stockApproverNodes.value.length <= 1) {
+ modal.msgError("鑷冲皯淇濈暀涓�涓鎵硅妭鐐�");
+ return;
+ }
+ stockApproverNodes.value.splice(index, 1);
+ };
+
+ const removeApprover = stepIndex => {
+ if (!stockApproverNodes.value[stepIndex]) return;
+ stockApproverNodes.value[stepIndex].userId = null;
+ stockApproverNodes.value[stepIndex].userName = "";
+ };
+
+ const openApproverPicker = index => {
+ uni.setStorageSync("stepIndex", index);
+ uni.navigateTo({
+ url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect?approveType=9",
+ });
+ };
+
+ const handleSelectContact = data => {
+ const { stepIndex, contact } = data || {};
+ if (stepIndex === null || stepIndex === undefined) return;
+ const idx = Number(stepIndex);
+ if (Number.isNaN(idx) || !stockApproverNodes.value[idx]) return;
+ stockApproverNodes.value[idx].userId = contact?.userId ?? null;
+ stockApproverNodes.value[idx].userName = contact?.nickName || contact?.userName || "";
+ };
+
+ const validateApproverNodes = () => {
+ const hasEmptyNode = stockApproverNodes.value.some(node => !node.userId);
+ if (hasEmptyNode) {
+ modal.msgError("璇蜂负姣忎釜瀹℃壒鑺傜偣閫夋嫨瀹℃壒浜�");
+ return false;
+ }
+ return true;
+ };
+
+ const submitInbound = async () => {
if (scanLedgerId.value == null || scanLedgerId.value === "") {
modal.msgError("缂哄皯璁㈠崟淇℃伅锛岃閲嶆柊鎵爜");
+ return;
+ }
+ if (!hasEditableInboundItems.value) {
+ modal.msgError("璇ヤ骇鍝佸凡缁忓叏閮ㄥ叆搴�");
return;
}
const salesLedgerProductList = buildSalesLedgerProductList(recordList.value);
@@ -286,7 +447,11 @@
return;
}
const runApi = currentSubmitConfig.runApi;
- const payload = currentSubmitConfig.payloadBuilder(salesLedgerProductList);
+ const approveUserIds = stockApproverNodes.value.map(node => node.userId).join(",");
+ const payload = currentSubmitConfig.payloadBuilder(
+ salesLedgerProductList,
+ approveUserIds
+ );
try {
submitLoading.value = true;
modal.loading("鎻愪氦涓�...");
@@ -304,6 +469,11 @@
} finally {
submitLoading.value = false;
}
+ };
+
+ const confirmInbound = () => {
+ if (!validateApproverNodes()) return;
+ submitInbound();
};
const goBack = () => {
@@ -351,7 +521,18 @@
if (res.code === 200 && res.data && res.data.length > 0) {
recordList.value = res.data.map(row => ({
...row,
- stockedQuantity: defaultStockedQuantityFromRow(row),
+ unqualifiedShippedQuantity:
+ row.unqualifiedShippedQuantity ??
+ row.unQualifiedShippedQuantity ??
+ row.unqualifiedShippedQty ??
+ row.unqualifiedOutboundQuantity,
+ unqualifiedStockedQuantity:
+ row.unqualifiedStockedQuantity ??
+ row.unQualifiedStockedQuantity ??
+ row.unqualifiedStockedQty ??
+ row.unqualifiedInboundQuantity,
+ operateQuantity:
+ type.value === QUALITY_TYPE.unqualified ? "0" : defaultStockedQuantityFromRow(row, "inbound"),
}));
expandedByIndex.value = {};
showForm.value = true;
@@ -579,4 +760,118 @@
color: #fff;
border: none;
}
+
+ .approval-process {
+ background: #fff;
+ margin: 20rpx;
+ border-radius: 16rpx;
+ padding: 24rpx;
+ }
+
+ .approval-header {
+ margin-bottom: 16rpx;
+ }
+
+ .approval-title {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: #333;
+ display: block;
+ }
+
+ .approval-desc {
+ font-size: 24rpx;
+ color: #999;
+ margin-top: 6rpx;
+ }
+
+ .approval-step {
+ margin-bottom: 18rpx;
+ }
+
+ .step-title text {
+ font-size: 24rpx;
+ color: #666;
+ }
+
+ .approver-container {
+ display: flex;
+ align-items: center;
+ margin-top: 10rpx;
+ }
+
+ .approver-item {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ padding: 12rpx 0;
+ }
+
+ .approver-avatar {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 50%;
+ background: #f3f4f6;
+ border: 2rpx solid #e5e7eb;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .avatar-text {
+ font-size: 24rpx;
+ color: #374151;
+ font-weight: 600;
+ }
+
+ .approver-info {
+ flex: 1;
+ }
+
+ .approver-name {
+ font-size: 28rpx;
+ color: #333;
+ }
+
+ .delete-approver-btn {
+ font-size: 32rpx;
+ color: #ff4d4f;
+ padding: 0 8rpx;
+ }
+
+ .add-approver-btn {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ color: #3b82f6;
+ padding: 10rpx 0;
+ }
+
+ .add-circle {
+ width: 52rpx;
+ height: 52rpx;
+ border: 2rpx dashed #a0aec0;
+ border-radius: 50%;
+ color: #6b7280;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 34rpx;
+ line-height: 1;
+ }
+
+ .add-label {
+ font-size: 26rpx;
+ }
+
+ .delete-step-btn {
+ color: #ff4d4f;
+ font-size: 24rpx;
+ margin-top: 8rpx;
+ }
+
+ .add-step-btn {
+ margin-top: 8rpx;
+ }
</style>
--
Gitblit v1.9.3