Commit 818c00db9fac9bcb3f4b435551d043ffeedf8f88
1 parent
614b30c6
feat(douyinLogistics): 抖音物流功能优化
- 移除商家实际收入的可编辑输入框,改为只读显示 - 在商品信息中增加ERP商品名称显示字段 - 添加异常订单筛选选项和抖音SKU名称搜索功能 - 实现卖家备注回传抖音功能,包括弹窗表单和API调用 - 优化订单状态本地即时更新逻辑,避免页面闪烁 - 增加批量创建运单后的本地状态同步机制 - 完善订单列表的数据过滤和搜索条件支持
Showing
67 changed files
with
3453 additions
and
1791 deletions
Antis.Erp.Plat/antis-ncc-admin/src/views/douyinLogistics/CreateWaybill.vue
| ... | ... | @@ -144,20 +144,11 @@ |
| 144 | 144 | <span class="order-kv-label">优惠金额</span> |
| 145 | 145 | <span class="order-kv-val amount-text amount-discount">{{ formatDiscountFen(form.orderAmountFen, form.payAmountFen) }}</span> |
| 146 | 146 | </div> |
| 147 | - <div class="order-kv-row order-kv-row--editor"> | |
| 147 | + <div class="order-kv-row"> | |
| 148 | 148 | <span class="order-kv-label">商家实际收入</span> |
| 149 | - <div class="order-kv-editor"> | |
| 150 | - <el-input-number | |
| 151 | - v-model="form.merchantIncome" | |
| 152 | - :min="0" | |
| 153 | - :precision="2" | |
| 154 | - :step="1" | |
| 155 | - :controls="false" | |
| 156 | - size="small" | |
| 157 | - class="order-kv-income-input" | |
| 158 | - :disabled="waybillSubmitBlocked" | |
| 159 | - /> | |
| 160 | - </div> | |
| 149 | + <span class="order-kv-val amount-text amount-readonly"> | |
| 150 | + {{ form.merchantIncome !== null && form.merchantIncome !== undefined ? `¥${Number(form.merchantIncome).toFixed(2)}` : '' }} | |
| 151 | + </span> | |
| 161 | 152 | </div> |
| 162 | 153 | </div> |
| 163 | 154 | </el-col> |
| ... | ... | @@ -184,6 +175,9 @@ |
| 184 | 175 | <div class="product-thumb-placeholder" v-else>暂无图片</div> |
| 185 | 176 | <div class="product-info"> |
| 186 | 177 | <div class="product-name">{{ item.product_name }}</div> |
| 178 | + <div class="product-name-erp"> | |
| 179 | + ERP:{{ item.erp_spmc || '-' }} | |
| 180 | + </div> | |
| 187 | 181 | <div v-if="getSkuCode(item)" class="product-sku"> |
| 188 | 182 | <span class="sku-label">SKUID:</span>{{ getSkuCode(item) }} |
| 189 | 183 | <span v-if="formatSpec(item.spec)" class="sku-spec">({{ formatSpec(item.spec) }})</span> |
| ... | ... | @@ -318,8 +312,8 @@ |
| 318 | 312 | <template slot-scope="scope"> |
| 319 | 313 | <div class="product-name-cell"> |
| 320 | 314 | <div class="product-name-main">{{ scope.row.product_name || '无' }}</div> |
| 321 | - <div v-if="scope.row.erp_spmc && String(scope.row.erp_spmc) !== String(scope.row.product_name)" class="product-name-erp"> | |
| 322 | - ERP:{{ scope.row.erp_spmc }} | |
| 315 | + <div class="product-name-erp"> | |
| 316 | + ERP:{{ scope.row.erp_spmc || '-' }} | |
| 323 | 317 | </div> |
| 324 | 318 | </div> |
| 325 | 319 | </template> |
| ... | ... | @@ -1185,6 +1179,7 @@ const loadOrderDetail = async () => { |
| 1185 | 1179 | selectedSerialNumbers: item.selectedSerialNumbers || [], // 序列号列表 |
| 1186 | 1180 | spxlhLoaded: false, // 序列号类型是否已加载 |
| 1187 | 1181 | spxlhType: item.spxlhType || '', // 序列号类型 |
| 1182 | + erp_spmc: item.erp_spmc || item.ErpSpmc || item.erpProductName || item.spmc || item.Spmc || '', // ERP 商品名称 | |
| 1188 | 1183 | erpSpbh: '' // ERP 商品主键 Id,用于 UpdateSerialNumberStatus 手动录入序列号 |
| 1189 | 1184 | } |
| 1190 | 1185 | |
| ... | ... | @@ -1275,6 +1270,7 @@ const addProduct = async (product) => { |
| 1275 | 1270 | // 添加商品到清单 |
| 1276 | 1271 | const newProduct = { |
| 1277 | 1272 | product_name: product.spmc || '', |
| 1273 | + erp_spmc: product.spmc || '', | |
| 1278 | 1274 | product_pic: resolveProductPicUrl(productPic || product.imageUrl || ''), |
| 1279 | 1275 | // ERP 添加的商品没有抖音图,ERP 图直接填 erp_pic,用于"商品清单"展示 |
| 1280 | 1276 | douyin_pic: '', |
| ... | ... | @@ -1850,7 +1846,6 @@ const handleSubmit = async () => { |
| 1850 | 1846 | remark: form.value.remark, |
| 1851 | 1847 | douyinOrderIds: form.value.orderId_Douyin || '', |
| 1852 | 1848 | mergedOrderInternalIds: mergedInternalIds.value.length > 1 ? mergedInternalIds.value : undefined, |
| 1853 | - merchantIncome: form.value.merchantIncome, | |
| 1854 | 1849 | fhr: form.value.fhr |
| 1855 | 1850 | }) |
| 1856 | 1851 | |
| ... | ... | @@ -2430,6 +2425,10 @@ onMounted(() => { |
| 2430 | 2425 | color: #e6a23c; |
| 2431 | 2426 | } |
| 2432 | 2427 | |
| 2428 | +.amount-readonly { | |
| 2429 | + color: #606266; | |
| 2430 | +} | |
| 2431 | + | |
| 2433 | 2432 | /* ── 商品明细(订单信息右列):多件时纵向滚动,避免把整页撑过长 ── */ |
| 2434 | 2433 | .order-product-list { |
| 2435 | 2434 | max-height: min(380px, 42vh); | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/douyinLogistics/OrderList.vue
| ... | ... | @@ -89,6 +89,7 @@ |
| 89 | 89 | <el-option label="已发货(全部)" :value="1" /> |
| 90 | 90 | <el-option label="已发货-待提交发货单" value="shipped_pending_form" /> |
| 91 | 91 | <el-option label="已发货-已提交发货单" value="shipped_submitted_form" /> |
| 92 | + <el-option label="异常订单(取消/退款)" value="abnormal" /> | |
| 92 | 93 | <el-option label="已取消" :value="2" /> |
| 93 | 94 | <el-option label="已退款" :value="3" /> |
| 94 | 95 | <el-option label="退款中" :value="4" /> |
| ... | ... | @@ -109,6 +110,9 @@ |
| 109 | 110 | <el-form-item label="商品名称"> |
| 110 | 111 | <el-input v-model="filterForm.productName" placeholder="商品关键词" clearable class="filter-w--product" /> |
| 111 | 112 | </el-form-item> |
| 113 | + <el-form-item label="抖音SKU名称"> | |
| 114 | + <el-input v-model="filterForm.skuName" placeholder="SKU名称关键词" clearable class="filter-w--sku" /> | |
| 115 | + </el-form-item> | |
| 112 | 116 | <el-form-item label="同步时间"> |
| 113 | 117 | <el-date-picker |
| 114 | 118 | v-model="filterForm.createTimeRange" |
| ... | ... | @@ -375,6 +379,9 @@ |
| 375 | 379 | <div v-if="row.sellerWords" style="color: #67c23a; white-space: pre-line;" :title="row.sellerWords"> |
| 376 | 380 | <span style="color: #909399; font-size: 10px;">卖家:</span>{{ row.sellerWords }} |
| 377 | 381 | </div> |
| 382 | + <div v-if="row.status === 1" style="margin-top: 4px;"> | |
| 383 | + <el-button type="text" size="mini" @click="openRemarkDialog(row)">编辑并回传抖音</el-button> | |
| 384 | + </div> | |
| 378 | 385 | <div v-if="!row.buyerWords && !row.sellerWords" style="color: #909399;"> |
| 379 | 386 | - |
| 380 | 387 | </div> |
| ... | ... | @@ -474,13 +481,38 @@ |
| 474 | 481 | <el-button type="primary" :loading="manualShipLoading" @click="handleManualShip">确认发货</el-button> |
| 475 | 482 | </div> |
| 476 | 483 | </el-dialog> |
| 484 | + | |
| 485 | + <el-dialog :visible.sync="remarkDialogVisible" title="备注回传抖音" width="560px" :close-on-click-modal="false"> | |
| 486 | + <el-form label-width="90px" @submit.native.prevent> | |
| 487 | + <el-form-item label="订单号"> | |
| 488 | + <span>{{ remarkForm.orderDisplay }}</span> | |
| 489 | + </el-form-item> | |
| 490 | + <el-form-item label="买家备注"> | |
| 491 | + <el-input v-model="remarkForm.buyerWords" type="textarea" :rows="3" disabled /> | |
| 492 | + </el-form-item> | |
| 493 | + <el-form-item label="卖家备注"> | |
| 494 | + <el-input | |
| 495 | + v-model="remarkForm.sellerWords" | |
| 496 | + type="textarea" | |
| 497 | + :rows="4" | |
| 498 | + maxlength="500" | |
| 499 | + show-word-limit | |
| 500 | + placeholder="请输入卖家备注,保存后会同步到抖音" | |
| 501 | + /> | |
| 502 | + </el-form-item> | |
| 503 | + </el-form> | |
| 504 | + <div slot="footer"> | |
| 505 | + <el-button @click="remarkDialogVisible = false">取消</el-button> | |
| 506 | + <el-button type="primary" :loading="remarkSaving" @click="handleSaveRemark">保存并回传抖音</el-button> | |
| 507 | + </div> | |
| 508 | + </el-dialog> | |
| 477 | 509 | </div> |
| 478 | 510 | </template> |
| 479 | 511 | |
| 480 | 512 | <script> |
| 481 | 513 | import { ref, onMounted } from 'vue' |
| 482 | 514 | import { Message as ElMessage, MessageBox as ElMessageBox } from 'element-ui' |
| 483 | -import { getOrders, syncOrders, createWaybill, initDatabase, shipToDouyin, manualShip, createSalesOrder, clearAndResync, getShops, splitMergedOrders, unsplitOrders, getTokenByCredentials, getAuthorizeUrl } from '@/api/douyinLogistics' | |
| 515 | +import { getOrders, syncOrders, createWaybill, initDatabase, shipToDouyin, manualShip, createSalesOrder, clearAndResync, getShops, splitMergedOrders, unsplitOrders, getTokenByCredentials, getAuthorizeUrl, updateSellerRemark } from '@/api/douyinLogistics' | |
| 484 | 516 | import { getDouyinLogisticsBaseOrigin } from '@/utils/douyinLogisticsAxios' |
| 485 | 517 | import router from '@/router' |
| 486 | 518 | |
| ... | ... | @@ -497,12 +529,13 @@ const selectedOrders = ref([]) |
| 497 | 529 | const shopList = ref([]) |
| 498 | 530 | const filterForm = ref({ |
| 499 | 531 | shopId: undefined, |
| 500 | - status: 0, | |
| 532 | + status: undefined, | |
| 501 | 533 | orderId: '', |
| 502 | 534 | receiverName: '', |
| 503 | 535 | receiverPhone: '', |
| 504 | 536 | trackingNumber: '', |
| 505 | 537 | productName: '', |
| 538 | + skuName: '', | |
| 506 | 539 | createTimeRange: null, |
| 507 | 540 | payTimeRange: null |
| 508 | 541 | }) |
| ... | ... | @@ -537,6 +570,8 @@ const buildListFilters = (overrideStatus, includeSort = true) => { |
| 537 | 570 | } else if (effectiveStatus === 'shipped_submitted_form') { |
| 538 | 571 | filters.status = 1 |
| 539 | 572 | filters.shipmentFormSubmitted = true |
| 573 | + } else if (effectiveStatus === 'abnormal') { | |
| 574 | + filters.abnormalOnly = true | |
| 540 | 575 | } else if (effectiveStatus !== undefined && effectiveStatus !== null) { |
| 541 | 576 | filters.status = effectiveStatus |
| 542 | 577 | } |
| ... | ... | @@ -565,6 +600,10 @@ const buildListFilters = (overrideStatus, includeSort = true) => { |
| 565 | 600 | if (productName) { |
| 566 | 601 | filters.productName = productName |
| 567 | 602 | } |
| 603 | + const skuName = (filterForm.value.skuName || '').trim() | |
| 604 | + if (skuName) { | |
| 605 | + filters.skuName = skuName | |
| 606 | + } | |
| 568 | 607 | |
| 569 | 608 | if (filterForm.value.createTimeRange && Array.isArray(filterForm.value.createTimeRange) && filterForm.value.createTimeRange.length === 2) { |
| 570 | 609 | filters.createTimeStart = filterForm.value.createTimeRange[0] + ' 00:00:00' |
| ... | ... | @@ -601,7 +640,7 @@ const isStatActive = (key) => { |
| 601 | 640 | if (key === 'shipped') { |
| 602 | 641 | return s === 1 || s === 'shipped_pending_form' || s === 'shipped_submitted_form' |
| 603 | 642 | } |
| 604 | - if (key === 'abnormal') return s === 2 || s === 3 || s === 4 | |
| 643 | + if (key === 'abnormal') return s === 'abnormal' || s === 2 || s === 3 || s === 4 | |
| 605 | 644 | return false |
| 606 | 645 | } |
| 607 | 646 | |
| ... | ... | @@ -613,8 +652,7 @@ const applyStatFilter = (key) => { |
| 613 | 652 | } else if (key === 'shipped') { |
| 614 | 653 | filterForm.value.status = 1 |
| 615 | 654 | } else { |
| 616 | - filterForm.value.status = 2 | |
| 617 | - ElMessage.info('已切换为「已取消」。可在「订单状态」中选择已退款、退款中等类型。') | |
| 655 | + filterForm.value.status = 'abnormal' | |
| 618 | 656 | } |
| 619 | 657 | handleSearch() |
| 620 | 658 | } |
| ... | ... | @@ -679,6 +717,39 @@ const refreshListAndStats = async () => { |
| 679 | 717 | await Promise.all([fetchOrders(), fetchStats()]) |
| 680 | 718 | } |
| 681 | 719 | |
| 720 | +/** 当前是否在“待发货”视图(创建运单成功后应立即移出) */ | |
| 721 | +const isPendingListView = () => filterForm.value.status === 0 | |
| 722 | + | |
| 723 | +/** 本地即时迁移订单状态,避免等待手动刷新 */ | |
| 724 | +const applyWaybillTransitionLocal = (orderIds) => { | |
| 725 | + if (!Array.isArray(orderIds) || orderIds.length === 0) return | |
| 726 | + const idSet = new Set(orderIds.map(id => Number(id))) | |
| 727 | + let movedCount = 0 | |
| 728 | + | |
| 729 | + // 本地先把订单标记为“已发货-未提交发货单” | |
| 730 | + orders.value.forEach((row) => { | |
| 731 | + if (idSet.has(Number(row.id))) { | |
| 732 | + if (row.status === 0) movedCount++ | |
| 733 | + row.status = 1 | |
| 734 | + row.isPendingShipmentForm = true | |
| 735 | + } | |
| 736 | + }) | |
| 737 | + | |
| 738 | + // 在“待发货”视图下,立即移出列表,避免用户误判 | |
| 739 | + if (isPendingListView()) { | |
| 740 | + orders.value = orders.value.filter(row => !idSet.has(Number(row.id))) | |
| 741 | + pagination.value.total = Math.max(0, Number(pagination.value.total || 0) - movedCount) | |
| 742 | + } | |
| 743 | + | |
| 744 | + // 统计卡片即时变更(有值时才更新,避免 null 场景闪烁) | |
| 745 | + if (stats.value.pending !== null && stats.value.pending !== undefined) { | |
| 746 | + stats.value.pending = Math.max(0, Number(stats.value.pending || 0) - movedCount) | |
| 747 | + } | |
| 748 | + if (stats.value.shipped !== null && stats.value.shipped !== undefined) { | |
| 749 | + stats.value.shipped = Number(stats.value.shipped || 0) + movedCount | |
| 750 | + } | |
| 751 | +} | |
| 752 | + | |
| 682 | 753 | // 获取订单列表 |
| 683 | 754 | const fetchOrders = async () => { |
| 684 | 755 | const requestId = ++listRequestSeq.value |
| ... | ... | @@ -858,12 +929,13 @@ const handleSortChange = ({ prop, order }) => { |
| 858 | 929 | const handleReset = () => { |
| 859 | 930 | filterForm.value = { |
| 860 | 931 | shopId: undefined, |
| 861 | - status: 0, | |
| 932 | + status: undefined, | |
| 862 | 933 | orderId: '', |
| 863 | 934 | receiverName: '', |
| 864 | 935 | receiverPhone: '', |
| 865 | 936 | trackingNumber: '', |
| 866 | 937 | productName: '', |
| 938 | + skuName: '', | |
| 867 | 939 | createTimeRange: null, |
| 868 | 940 | payTimeRange: null |
| 869 | 941 | } |
| ... | ... | @@ -923,6 +995,7 @@ const handleBatchCreateWaybill = async () => { |
| 923 | 995 | let successCount = 0 |
| 924 | 996 | let failCount = 0 |
| 925 | 997 | const errors = [] |
| 998 | + const successOrderIds = [] | |
| 926 | 999 | |
| 927 | 1000 | const concurrency = 3 |
| 928 | 1001 | const queue = ordersWithoutWaybill.slice() |
| ... | ... | @@ -943,6 +1016,7 @@ const handleBatchCreateWaybill = async () => { |
| 943 | 1016 | } |
| 944 | 1017 | |
| 945 | 1018 | successCount++ |
| 1019 | + successOrderIds.push(order.id) | |
| 946 | 1020 | } catch (error) { |
| 947 | 1021 | failCount++ |
| 948 | 1022 | const errorMsg = (error.response && error.response.data && error.response.data.message) || error.message || '未知错误' |
| ... | ... | @@ -952,9 +1026,11 @@ const handleBatchCreateWaybill = async () => { |
| 952 | 1026 | }) |
| 953 | 1027 | await Promise.all(workers) |
| 954 | 1028 | |
| 955 | - // 刷新订单列表 | |
| 956 | - await fetchOrders() | |
| 957 | - fetchStats() | |
| 1029 | + // 先本地即时迁移,再延迟回源刷新,避免“必须手动刷新才更新” | |
| 1030 | + applyWaybillTransitionLocal(successOrderIds) | |
| 1031 | + setTimeout(() => { | |
| 1032 | + refreshListAndStats() | |
| 1033 | + }, 1200) | |
| 958 | 1034 | |
| 959 | 1035 | // 清空选择 |
| 960 | 1036 | selectedOrders.value = [] |
| ... | ... | @@ -1060,10 +1136,12 @@ const handleCreateWaybill = async (order) => { |
| 1060 | 1136 | const errorMsg = (error.response && error.response.data && error.response.data.message) || error.message || '未知错误' |
| 1061 | 1137 | ElMessage.warning('同步抖音发货失败:' + errorMsg) |
| 1062 | 1138 | } |
| 1063 | - | |
| 1064 | - // 最后统一刷新列表和统计 | |
| 1065 | - await fetchOrders() | |
| 1066 | - fetchStats() | |
| 1139 | + | |
| 1140 | + // 先本地即时迁移,再延迟回源刷新,避免“必须手动刷新才更新” | |
| 1141 | + applyWaybillTransitionLocal([order.id]) | |
| 1142 | + setTimeout(() => { | |
| 1143 | + refreshListAndStats() | |
| 1144 | + }, 1200) | |
| 1067 | 1145 | |
| 1068 | 1146 | // 创建运单后不会自动向打印机推送;网络打印机也需在浏览器里选「打印」并选中对应打印机 |
| 1069 | 1147 | try { |
| ... | ... | @@ -1154,6 +1232,15 @@ const manualShipForm = ref({ |
| 1154 | 1232 | trackingNumber: '' |
| 1155 | 1233 | }) |
| 1156 | 1234 | |
| 1235 | +const remarkDialogVisible = ref(false) | |
| 1236 | +const remarkSaving = ref(false) | |
| 1237 | +const remarkForm = ref({ | |
| 1238 | + orderId: 0, | |
| 1239 | + orderDisplay: '', | |
| 1240 | + buyerWords: '', | |
| 1241 | + sellerWords: '' | |
| 1242 | +}) | |
| 1243 | + | |
| 1157 | 1244 | const openManualShipDialog = (order) => { |
| 1158 | 1245 | manualShipForm.value = { |
| 1159 | 1246 | orderId: order.id, |
| ... | ... | @@ -1193,6 +1280,46 @@ const handleManualShip = async () => { |
| 1193 | 1280 | } |
| 1194 | 1281 | } |
| 1195 | 1282 | |
| 1283 | +const openRemarkDialog = (order) => { | |
| 1284 | + if (!order || order.status !== 1) { | |
| 1285 | + ElMessage.warning('仅已发货订单支持备注回传') | |
| 1286 | + return | |
| 1287 | + } | |
| 1288 | + remarkForm.value = { | |
| 1289 | + orderId: Number(order.id) || 0, | |
| 1290 | + orderDisplay: order.orderId || '', | |
| 1291 | + buyerWords: order.buyerWords || '', | |
| 1292 | + sellerWords: order.sellerWords || '' | |
| 1293 | + } | |
| 1294 | + remarkDialogVisible.value = true | |
| 1295 | +} | |
| 1296 | + | |
| 1297 | +const handleSaveRemark = async () => { | |
| 1298 | + const oid = Number(remarkForm.value.orderId) || 0 | |
| 1299 | + if (!oid) { | |
| 1300 | + ElMessage.warning('订单信息无效') | |
| 1301 | + return | |
| 1302 | + } | |
| 1303 | + try { | |
| 1304 | + remarkSaving.value = true | |
| 1305 | + const res = await updateSellerRemark(oid, remarkForm.value.sellerWords || '') | |
| 1306 | + const data = res && res.data ? res.data : {} | |
| 1307 | + if (data.syncSuccess) { | |
| 1308 | + ElMessage.success(data.message || '卖家备注已同步到抖音') | |
| 1309 | + } else { | |
| 1310 | + ElMessage.warning(data.message || '备注已保存,但同步抖音失败') | |
| 1311 | + } | |
| 1312 | + const row = orders.value.find((x) => Number(x.id) === oid) | |
| 1313 | + if (row) row.sellerWords = remarkForm.value.sellerWords || '' | |
| 1314 | + remarkDialogVisible.value = false | |
| 1315 | + } catch (error) { | |
| 1316 | + const msg = (error.response && error.response.data && error.response.data.message) || error.message || '未知错误' | |
| 1317 | + ElMessage.error('备注回传失败:' + msg) | |
| 1318 | + } finally { | |
| 1319 | + remarkSaving.value = false | |
| 1320 | + } | |
| 1321 | +} | |
| 1322 | + | |
| 1196 | 1323 | // 手动创建发货单(合并单跳转到合并编辑页) |
| 1197 | 1324 | const handleManualCreateWaybill = (order) => { |
| 1198 | 1325 | const ids = order.mergedOrderIds && order.mergedOrderIds.length > 1 |
| ... | ... | @@ -1410,6 +1537,8 @@ onMounted(() => { |
| 1410 | 1537 | handleShipToDouyin, |
| 1411 | 1538 | openManualShipDialog, |
| 1412 | 1539 | handleManualShip, |
| 1540 | + openRemarkDialog, | |
| 1541 | + handleSaveRemark, | |
| 1413 | 1542 | onCompanyChange, |
| 1414 | 1543 | handleSizeChange, |
| 1415 | 1544 | handlePageChange, |
| ... | ... | @@ -1424,6 +1553,9 @@ onMounted(() => { |
| 1424 | 1553 | manualShipVisible, |
| 1425 | 1554 | manualShipLoading, |
| 1426 | 1555 | manualShipForm, |
| 1556 | + remarkDialogVisible, | |
| 1557 | + remarkSaving, | |
| 1558 | + remarkForm, | |
| 1427 | 1559 | logisticsCompanyOptions |
| 1428 | 1560 | } |
| 1429 | 1561 | } |
| ... | ... | @@ -1652,6 +1784,10 @@ onMounted(() => { |
| 1652 | 1784 | width: 160px; |
| 1653 | 1785 | } |
| 1654 | 1786 | |
| 1787 | +.filter-w--sku { | |
| 1788 | + width: 170px; | |
| 1789 | +} | |
| 1790 | + | |
| 1655 | 1791 | .filter-w--range { |
| 1656 | 1792 | width: 220px; |
| 1657 | 1793 | } |
| ... | ... | @@ -1671,7 +1807,8 @@ onMounted(() => { |
| 1671 | 1807 | .filter-w--status, |
| 1672 | 1808 | .filter-w--text, |
| 1673 | 1809 | .filter-w--phone, |
| 1674 | - .filter-w--product { | |
| 1810 | + .filter-w--product, | |
| 1811 | + .filter-w--sku { | |
| 1675 | 1812 | width: 118px; |
| 1676 | 1813 | } |
| 1677 | 1814 | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtBjdbd/Form.vue
| ... | ... | @@ -52,14 +52,14 @@ |
| 52 | 52 | <el-table-column type="index" width="50" label="序号" align="center" /> |
| 53 | 53 | <el-table-column prop="rkck" label="入库仓库"> |
| 54 | 54 | <template slot-scope="scope"> |
| 55 | - <el-select v-model="scope.row.rkck" placeholder="请选择" clearable disabled> | |
| 55 | + <el-select size="mini" v-model="scope.row.rkck" placeholder="请选择" clearable disabled> | |
| 56 | 56 | <el-option v-for="(item, index) in rkckOptions" :key="index" :label="item.F_mdmc" :value="item.F_Id" :disabled="item.disabled"></el-option> |
| 57 | 57 | </el-select> |
| 58 | 58 | </template> |
| 59 | 59 | </el-table-column> |
| 60 | 60 | <el-table-column prop="ckck" label="出库仓库"> |
| 61 | 61 | <template slot-scope="scope"> |
| 62 | - <el-select v-model="scope.row.ckck" placeholder="请选择" clearable disabled> | |
| 62 | + <el-select size="mini" v-model="scope.row.ckck" placeholder="请选择" clearable disabled> | |
| 63 | 63 | <el-option v-for="(item, index) in ckckOptions" :key="index" :label="item.F_mdmc" :value="item.F_Id" :disabled="item.disabled"></el-option> |
| 64 | 64 | </el-select> |
| 65 | 65 | </template> |
| ... | ... | @@ -67,6 +67,7 @@ |
| 67 | 67 | <el-table-column prop="spbh" label="商品编号" width="180"> |
| 68 | 68 | <template slot-scope="scope"> |
| 69 | 69 | <el-select |
| 70 | + size="mini" | |
| 70 | 71 | v-model="scope.row.spbh" |
| 71 | 72 | placeholder="请选择商品" |
| 72 | 73 | filterable |
| ... | ... | @@ -86,40 +87,34 @@ |
| 86 | 87 | </el-table-column> |
| 87 | 88 | <el-table-column prop="spmc" label="商品名称"> |
| 88 | 89 | <template slot-scope="scope"> |
| 89 | - <el-input v-model="scope.row.spmc" placeholder="请输入" clearable ></el-input> | |
| 90 | + <el-input size="mini" v-model="scope.row.spmc" placeholder="请输入" clearable ></el-input> | |
| 90 | 91 | </template> |
| 91 | 92 | </el-table-column> |
| 92 | 93 | <el-table-column prop="dw" label="单位" v-if="false"> |
| 93 | 94 | <template slot-scope="scope"> |
| 94 | - <el-input v-model="scope.row.dw" placeholder="请输入" clearable ></el-input> | |
| 95 | + <el-input size="mini" v-model="scope.row.dw" placeholder="请输入" clearable ></el-input> | |
| 95 | 96 | </template> |
| 96 | 97 | </el-table-column> |
| 97 | - <el-table-column prop="kucun" label="账面库存" width="100"> | |
| 98 | + <el-table-column prop="kucun" label="账面库存" width="110" align="right"> | |
| 98 | 99 | <template slot-scope="scope"> |
| 99 | - <div> | |
| 100 | - <div style="display: flex; align-items: center;"> | |
| 101 | - <input | |
| 102 | - type="text" | |
| 103 | - :value="scope.row.kucun" | |
| 104 | - placeholder="自动获取" | |
| 105 | - readonly | |
| 106 | - style="flex: 1; padding: 5px; border: 1px solid #dcdfe6; border-radius: 4px;" | |
| 107 | - /> | |
| 108 | - <button | |
| 109 | - @click="getStockQuantity(scope.row)" | |
| 110 | - :disabled="scope.row.loadingStock" | |
| 111 | - style="margin-left: 5px; padding: 5px; border: 1px solid #dcdfe6; border-radius: 4px; background: white; cursor: pointer;" | |
| 112 | - > | |
| 113 | - <i class="el-icon-refresh"></i> | |
| 114 | - </button> | |
| 115 | - </div> | |
| 116 | - | |
| 100 | + <div class="bjdbd-kucun-cell"> | |
| 101 | + <span class="bjdbd-kucun-text">{{ scope.row.kucun == null || scope.row.kucun === '' ? '-' : scope.row.kucun }}</span> | |
| 102 | + <el-button | |
| 103 | + size="mini" | |
| 104 | + type="text" | |
| 105 | + icon="el-icon-refresh" | |
| 106 | + :loading="scope.row.loadingStock" | |
| 107 | + :disabled="!!isDetail" | |
| 108 | + @click="getStockQuantity(scope.row)" | |
| 109 | + class="bjdbd-kucun-refresh" | |
| 110 | + ></el-button> | |
| 117 | 111 | </div> |
| 118 | 112 | </template> |
| 119 | 113 | </el-table-column> |
| 120 | - <el-table-column prop="sl" label="销售数量" v-if="false"> | |
| 114 | + <el-table-column prop="sl" label="需变价数量" width="110"> | |
| 121 | 115 | <template slot-scope="scope"> |
| 122 | 116 | <el-input |
| 117 | + size="mini" | |
| 123 | 118 | v-model="scope.row.sl" |
| 124 | 119 | placeholder="请输入" |
| 125 | 120 | clearable |
| ... | ... | @@ -130,79 +125,36 @@ |
| 130 | 125 | <el-table-column prop="dj" label="成本单价" width="110"> |
| 131 | 126 | <template slot-scope="scope"> |
| 132 | 127 | <el-input |
| 128 | + size="mini" | |
| 133 | 129 | v-model="scope.row.dj" |
| 134 | - placeholder="参考成本,过账以服务端为准" | |
| 130 | + placeholder="自动获取" | |
| 135 | 131 | clearable |
| 136 | - :readonly="!!isDetail" | |
| 137 | - @input="handleAmountChange(scope.row)" | |
| 132 | + readonly | |
| 133 | + :style='{"width":"100%"}' | |
| 138 | 134 | ></el-input> |
| 139 | 135 | </template> |
| 140 | 136 | </el-table-column> |
| 141 | - <el-table-column prop="bjhcb" label="变价后成本" width="120"> | |
| 137 | + <el-table-column prop="bjhcb" label="变价后成本" width="130"> | |
| 142 | 138 | <template slot-scope="scope"> |
| 143 | 139 | <el-input |
| 140 | + size="mini" | |
| 144 | 141 | v-model="scope.row.bjhcb" |
| 145 | - placeholder="自动计算" | |
| 142 | + placeholder="请输入" | |
| 146 | 143 | clearable |
| 147 | - readonly | |
| 144 | + :readonly="!!isDetail" | |
| 148 | 145 | :style='{"width":"100%"}' |
| 146 | + @input="handleBjhcbInput(scope.row)" | |
| 149 | 147 | ></el-input> |
| 150 | 148 | </template> |
| 151 | 149 | </el-table-column> |
| 152 | 150 | <el-table-column prop="je" label="销售总额" v-if="false"> |
| 153 | 151 | <template slot-scope="scope"> |
| 154 | - <el-input v-model="scope.row.je" placeholder="请输入" clearable readonly></el-input> | |
| 152 | + <el-input size="mini" v-model="scope.row.je" placeholder="请输入" clearable readonly></el-input> | |
| 155 | 153 | </template> |
| 156 | 154 | </el-table-column> |
| 157 | 155 | <el-table-column prop="description" label="备注"> |
| 158 | 156 | <template slot-scope="scope"> |
| 159 | - <el-input v-model="scope.row.description" placeholder="请输入备注" clearable></el-input> | |
| 160 | - </template> | |
| 161 | - </el-table-column> | |
| 162 | - <el-table-column prop="serialNumberType" label="序列号类型" width="140"> | |
| 163 | - <template slot-scope="scope"> | |
| 164 | - <div style="display: flex; align-items: center; gap: 5px;"> | |
| 165 | - <el-tag | |
| 166 | - :type="getSerialNumberTypeTagType(scope.row)" | |
| 167 | - size="mini" | |
| 168 | - :title="getSerialNumberTypeDescription(scope.row)" | |
| 169 | - > | |
| 170 | - {{ getSerialNumberTypeText(scope.row) }} | |
| 171 | - </el-tag> | |
| 172 | - <el-button | |
| 173 | - v-if="scope.row.spbh" | |
| 174 | - size="mini" | |
| 175 | - type="text" | |
| 176 | - icon="el-icon-refresh" | |
| 177 | - @click="refreshSerialNumberType(scope.row)" | |
| 178 | - :title="'刷新序列号类型信息'" | |
| 179 | - ></el-button> | |
| 180 | - </div> | |
| 181 | - </template> | |
| 182 | - </el-table-column> | |
| 183 | - <el-table-column prop="selectedSerialNumbers" label="选择序列号" width="150"> | |
| 184 | - <template slot-scope="scope"> | |
| 185 | - <div class="serial-number-selector"> | |
| 186 | - <div v-if="scope.row.selectedSerialNumbers && scope.row.selectedSerialNumbers.length > 0" class="selected-serial-numbers"> | |
| 187 | - <el-tag | |
| 188 | - v-for="(serialNumber, index) in scope.row.selectedSerialNumbers" | |
| 189 | - :key="index" | |
| 190 | - size="mini" | |
| 191 | - type="warning" | |
| 192 | - style="margin: 2px;" | |
| 193 | - > | |
| 194 | - {{ serialNumber }} | |
| 195 | - </el-tag> | |
| 196 | - </div> | |
| 197 | - <el-button | |
| 198 | - size="mini" | |
| 199 | - type="primary" | |
| 200 | - @click="openSerialNumberSelect(scope.row)" | |
| 201 | - :disabled="!scope.row.spbh" | |
| 202 | - > | |
| 203 | - 选择序列号 | |
| 204 | - </el-button> | |
| 205 | - </div> | |
| 157 | + <el-input size="mini" v-model="scope.row.description" placeholder="请输入备注" clearable></el-input> | |
| 206 | 158 | </template> |
| 207 | 159 | </el-table-column> |
| 208 | 160 | <el-table-column label="操作" width="50"> |
| ... | ... | @@ -274,19 +226,16 @@ |
| 274 | 226 | </span> |
| 275 | 227 | <!-- 商品条码选择弹窗 --> |
| 276 | 228 | <BarcodeSelect ref="barcodeSelect" @select="handleBarcodeSelect" /> |
| 277 | - <!-- 序列号选择弹窗 --> | |
| 278 | - <SerialNumberSelect ref="serialNumberSelect" @confirm="handleSerialNumberSelect" /> | |
| 279 | 229 | </el-dialog> |
| 280 | 230 | </template> |
| 281 | 231 | <script> |
| 282 | 232 | import request from '@/utils/request' |
| 283 | 233 | import { previewDataInterface } from '@/api/systemData/dataInterface' |
| 284 | 234 | import BarcodeSelect from '../wtCgrkd/BarcodeSelect.vue' |
| 285 | - import SerialNumberSelect from './SerialNumberSelect.vue' | |
| 286 | 235 | import { getAccountSelector } from '@/api/extend/wtAccount' |
| 287 | 236 | import { validateMxNoEmptyProductRows } from '@/utils/validateBillMxEmptyRows' |
| 288 | 237 | export default { |
| 289 | - components: { BarcodeSelect, SerialNumberSelect }, | |
| 238 | + components: { BarcodeSelect }, | |
| 290 | 239 | props: [], |
| 291 | 240 | data() { |
| 292 | 241 | return { |
| ... | ... | @@ -448,111 +397,6 @@ |
| 448 | 397 | } |
| 449 | 398 | }, |
| 450 | 399 | |
| 451 | - // 批量获取所有明细商品的序列号类型信息 | |
| 452 | - async getAllProductSerialNumberTypes() { | |
| 453 | - console.log('开始批量获取所有商品的序列号类型信息...'); | |
| 454 | - const productIds = this.dataForm.wtXsckdMxList | |
| 455 | - .filter(row => row.spbh) | |
| 456 | - .map(row => row.spbh); | |
| 457 | - | |
| 458 | - console.log('需要获取信息的商品ID列表:', productIds); | |
| 459 | - | |
| 460 | - // 并行获取所有商品信息 | |
| 461 | - const productPromises = productIds.map(productId => this.getProductInfo(productId)); | |
| 462 | - const productResults = await Promise.allSettled(productPromises); | |
| 463 | - | |
| 464 | - // 统计结果 | |
| 465 | - let successCount = 0; | |
| 466 | - let failCount = 0; | |
| 467 | - const failedProducts = []; | |
| 468 | - | |
| 469 | - productResults.forEach((result, index) => { | |
| 470 | - if (result.status === 'fulfilled' && result.value) { | |
| 471 | - successCount++; | |
| 472 | - console.log(`商品 ${productIds[index]} 信息获取成功`); | |
| 473 | - } else { | |
| 474 | - failCount++; | |
| 475 | - const failedProduct = this.dataForm.wtXsckdMxList.find(row => row.spbh === productIds[index]); | |
| 476 | - if (failedProduct) { | |
| 477 | - failedProducts.push(failedProduct.spmc || productIds[index]); | |
| 478 | - } | |
| 479 | - console.error(`商品 ${productIds[index]} 信息获取失败`); | |
| 480 | - } | |
| 481 | - }); | |
| 482 | - | |
| 483 | - console.log(`批量获取完成: 成功 ${successCount} 个,失败 ${failCount} 个`); | |
| 484 | - | |
| 485 | - if (failedProducts.length > 0) { | |
| 486 | - console.log('获取失败的商品:', failedProducts); | |
| 487 | - } | |
| 488 | - | |
| 489 | - return { | |
| 490 | - successCount, | |
| 491 | - failCount, | |
| 492 | - failedProducts | |
| 493 | - }; | |
| 494 | - }, | |
| 495 | - | |
| 496 | - // 手动设置商品序列号类型(临时解决方案) | |
| 497 | - setProductSerialNumberType(productId, serialNumberType) { | |
| 498 | - const key = String(productId); | |
| 499 | - const product = this.productCache.get(key); | |
| 500 | - if (product) { | |
| 501 | - product.spxlhType = serialNumberType; | |
| 502 | - console.log(`手动设置商品 ${productId} 的序列号类型为: ${serialNumberType}`); | |
| 503 | - } else { | |
| 504 | - // 如果缓存中没有,创建一个默认对象 | |
| 505 | - const defaultProduct = { | |
| 506 | - id: productId, | |
| 507 | - spmc: '未知商品', | |
| 508 | - spxlhType: serialNumberType | |
| 509 | - }; | |
| 510 | - this.productCache.set(key, defaultProduct); | |
| 511 | - console.log(`为商品 ${productId} 创建默认对象并设置序列号类型为: ${serialNumberType}`); | |
| 512 | - } | |
| 513 | - }, | |
| 514 | - | |
| 515 | - // 显示所有明细商品的序列号类型信息 | |
| 516 | - showAllProductSerialNumberTypes() { | |
| 517 | - console.log('=== 所有明细商品的序列号类型信息 ==='); | |
| 518 | - let needSerialNumberCount = 0; | |
| 519 | - let noNeedSerialNumberCount = 0; | |
| 520 | - let unknownCount = 0; | |
| 521 | - | |
| 522 | - this.dataForm.wtXsckdMxList.forEach((row, index) => { | |
| 523 | - if (row.spbh) { | |
| 524 | - const key = String(row.spbh); | |
| 525 | - const product = this.productCache.get(key); | |
| 526 | - if (product && product.spxlhType) { | |
| 527 | - const spxlhType = product.spxlhType; | |
| 528 | - const needsSerialNumber = spxlhType === '1' || spxlhType === 1 || spxlhType === '入1出1' || spxlhType === '2' || spxlhType === 2 || spxlhType === '入0出1'; | |
| 529 | - const hasSerialNumbers = row.selectedSerialNumbers && row.selectedSerialNumbers.length > 0; | |
| 530 | - const serialNumberCount = hasSerialNumbers ? row.selectedSerialNumbers.length : 0; | |
| 531 | - const detailQuantity = parseInt(row.sl) || 0; | |
| 532 | - | |
| 533 | - if (needsSerialNumber) { | |
| 534 | - needSerialNumberCount++; | |
| 535 | - console.log(`第${index + 1}行: ${row.spmc} (${row.spbh}) - 序列号类型: ${spxlhType} [需要序列号] - 已选择: ${serialNumberCount}个 - 明细数量: ${detailQuantity} - 状态: ${serialNumberCount === detailQuantity ? '✓ 正确' : '✗ 不匹配'}`); | |
| 536 | - } else { | |
| 537 | - noNeedSerialNumberCount++; | |
| 538 | - console.log(`第${index + 1}行: ${row.spmc} (${row.spbh}) - 序列号类型: ${spxlhType} [不需要序列号]`); | |
| 539 | - } | |
| 540 | - } else { | |
| 541 | - unknownCount++; | |
| 542 | - console.log(`第${index + 1}行: ${row.spmc} (${row.spbh}) - 序列号类型: 未获取到`); | |
| 543 | - } | |
| 544 | - } else { | |
| 545 | - console.log(`第${index + 1}行: 未选择商品`); | |
| 546 | - } | |
| 547 | - }); | |
| 548 | - | |
| 549 | - console.log(`=== 统计信息 ===`); | |
| 550 | - console.log(`需要序列号的商品: ${needSerialNumberCount} 个`); | |
| 551 | - console.log(`不需要序列号的商品: ${noNeedSerialNumberCount} 个`); | |
| 552 | - console.log(`序列号类型未知的商品: ${unknownCount} 个`); | |
| 553 | - console.log('=== 序列号类型信息显示完成 ==='); | |
| 554 | - }, | |
| 555 | - | |
| 556 | 400 | setFullName(item, row) { |
| 557 | 401 | var md = this.spbhOptions.filter(u => u.F_Id == item); |
| 558 | 402 | if (md && md.length) { |
| ... | ... | @@ -561,19 +405,6 @@ |
| 561 | 405 | if (row.spbh && row.ckck) { |
| 562 | 406 | this.getStockQuantity(row); |
| 563 | 407 | } |
| 564 | - if (row.spbh) { | |
| 565 | - const productId = String(row.spbh); | |
| 566 | - // 选择新商品时先置为false | |
| 567 | - this.$set(row, 'spxlhLoaded', false); | |
| 568 | - this.getProductInfo(productId).then(product => { | |
| 569 | - // 触发当前行的响应式刷新 | |
| 570 | - this.$set(row, 'spxlhLoaded', true); | |
| 571 | - this.$forceUpdate(); | |
| 572 | - if (!(product && product.spxlhType)) { | |
| 573 | - this.$message.warning('无法获取序列号类型信息'); | |
| 574 | - } | |
| 575 | - }); | |
| 576 | - } | |
| 577 | 408 | }, |
| 578 | 409 | |
| 579 | 410 | |
| ... | ... | @@ -702,7 +533,13 @@ |
| 702 | 533 | buildPayload(isDraft) { |
| 703 | 534 | const bjsxNum = parseFloat(this.dataForm.bjsx) |
| 704 | 535 | const mx = (this.dataForm.wtXsckdMxList || []).map(r => { |
| 705 | - const { productQuery, spxlhLoaded, loadingStock, ...rest } = r | |
| 536 | + // 剔除前端临时字段,避免写入后端 | |
| 537 | + const { productQuery, spxlhLoaded, loadingStock, bjhcbManual, xlhList, selectedSerialNumbers, ...rest } = r | |
| 538 | + // 明细变价后成本强制落数值 | |
| 539 | + if (rest.bjhcb !== undefined && rest.bjhcb !== null && rest.bjhcb !== '') { | |
| 540 | + const n = parseFloat(rest.bjhcb) | |
| 541 | + rest.bjhcb = isNaN(n) ? null : n | |
| 542 | + } | |
| 706 | 543 | return rest |
| 707 | 544 | }) |
| 708 | 545 | return { |
| ... | ... | @@ -710,7 +547,6 @@ |
| 710 | 547 | bjsx: isNaN(bjsxNum) ? null : bjsxNum, |
| 711 | 548 | isDraft: !!isDraft, |
| 712 | 549 | djlx: '变价调拨单', |
| 713 | - // 正式保存一律待审核(含从草稿转正);草稿仅保存草稿 | |
| 714 | 550 | djzt: isDraft ? '草稿' : '待审核', |
| 715 | 551 | wtXsckdMxList: mx |
| 716 | 552 | } |
| ... | ... | @@ -721,11 +557,10 @@ |
| 721 | 557 | return '请选择出库仓库与入库仓库' |
| 722 | 558 | if (this.dataForm.cjck === this.dataForm.rkck) |
| 723 | 559 | return '出库仓库与入库仓库不能相同' |
| 724 | - const coef = parseFloat(this.dataForm.bjsx) | |
| 725 | - if (this.dataForm.bjsx === '' || this.dataForm.bjsx === undefined || isNaN(coef) || coef <= 0) | |
| 726 | - return '请输入大于 0 的变价系数(%)' | |
| 727 | 560 | if (!this.dataForm.wtXsckdMxList || !this.dataForm.wtXsckdMxList.length) |
| 728 | 561 | return '请添加至少一条明细' |
| 562 | + const coef = parseFloat(this.dataForm.bjsx) | |
| 563 | + const hasCoef = !isNaN(coef) && coef > 0 | |
| 729 | 564 | let hasSp = false |
| 730 | 565 | for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { |
| 731 | 566 | const row = this.dataForm.wtXsckdMxList[i] |
| ... | ... | @@ -733,7 +568,14 @@ |
| 733 | 568 | hasSp = true |
| 734 | 569 | const q = parseInt(row.sl, 10) |
| 735 | 570 | if (!q || q <= 0) |
| 736 | - return `第${i + 1}行请填写有效数量或选择序列号` | |
| 571 | + return `第${i + 1}行请填写有效的需变价数量` | |
| 572 | + const stock = parseInt(row.kucun, 10) | |
| 573 | + if (!isNaN(stock) && stock >= 0 && q > stock) | |
| 574 | + return `第${i + 1}行需变价数量(${q})不能超过账面库存(${stock})` | |
| 575 | + const bjhcb = parseFloat(row.bjhcb) | |
| 576 | + // 每行必须给出变价后成本:若未填写单行值,则必须提供了变价系数% | |
| 577 | + if ((isNaN(bjhcb) || bjhcb <= 0) && !hasCoef) | |
| 578 | + return `第${i + 1}行请填写变价后成本,或在表头填写变价系数%` | |
| 737 | 579 | } |
| 738 | 580 | if (!hasSp) return '请至少选择一条商品明细' |
| 739 | 581 | return '' |
| ... | ... | @@ -761,12 +603,9 @@ |
| 761 | 603 | method: 'get' |
| 762 | 604 | }).then(res =>{ |
| 763 | 605 | _this.dataForm = res.data; |
| 764 | - console.log('编辑时加载的数据:', _this.dataForm); | |
| 765 | - console.log('明细数据:', _this.dataForm.wtXsckdMxList); | |
| 766 | 606 | if (_this.dataForm.bjsx != null && _this.dataForm.bjsx !== '') |
| 767 | 607 | _this.$set(_this.dataForm, 'bjsx', String(_this.dataForm.bjsx)) |
| 768 | - | |
| 769 | - // 为每个明细项添加productQuery字段 | |
| 608 | + | |
| 770 | 609 | if (_this.dataForm.wtXsckdMxList) { |
| 771 | 610 | _this.dataForm.wtXsckdMxList.forEach(item => { |
| 772 | 611 | if (!item.hasOwnProperty('productQuery')) { |
| ... | ... | @@ -778,20 +617,18 @@ |
| 778 | 617 | } |
| 779 | 618 | if (item.bjhcb != null && item.bjhcb !== '') { |
| 780 | 619 | _this.$set(item, 'bjhcb', Number(item.bjhcb).toFixed(4)) |
| 620 | + // 已落库的 bjhcb 视为用户已确认值,避免被表头变价系数再次覆盖 | |
| 621 | + _this.$set(item, 'bjhcbManual', true) | |
| 622 | + } | |
| 623 | + // 回填账面库存(若后端 kucun 已带出则保留) | |
| 624 | + if (item.spbh && item.ckck) { | |
| 625 | + _this.getStockQuantity(item); | |
| 781 | 626 | } |
| 782 | 627 | }); |
| 783 | 628 | } |
| 784 | - | |
| 785 | - // 初始化时计算总金额 | |
| 629 | + | |
| 786 | 630 | _this.calculateTotalAmount(); |
| 787 | - // 同步明细表出库仓库 | |
| 788 | 631 | _this.syncDetailWarehouses(); |
| 789 | - // 恢复序列号信息 | |
| 790 | - _this.restoreSerialNumbers(); | |
| 791 | - // 如果有变价系数,计算所有明细的变价后成本 | |
| 792 | - if (_this.dataForm.bjsx && _this.dataForm.bjsx !== '') { | |
| 793 | - _this.calculateAllAdjustedCosts(); | |
| 794 | - } | |
| 795 | 632 | }) |
| 796 | 633 | } |
| 797 | 634 | else{ |
| ... | ... | @@ -799,345 +636,92 @@ |
| 799 | 636 | } |
| 800 | 637 | }) |
| 801 | 638 | }, |
| 802 | - // 保存草稿:只做基础表单校验,不做序列号强校验,保存时带isDraft:true字段 | |
| 639 | + // 保存草稿:仅做基础表单校验,不做业务强校验,保存时带 isDraft:true | |
| 803 | 640 | async saveDraft() { |
| 804 | - // 确保单据类型字段赋值 | |
| 805 | 641 | this.dataForm.djlx = '变价调拨单'; |
| 806 | 642 | this.$refs['elForm'].validate(async (valid) => { |
| 807 | 643 | if (valid) { |
| 808 | 644 | try { |
| 809 | 645 | const draftData = this.buildPayload(true); |
| 810 | - let res; | |
| 811 | 646 | if (!this.dataForm.id) { |
| 812 | - res = await request({ | |
| 647 | + await request({ | |
| 813 | 648 | url: `/api/Extend/WtXsckd`, |
| 814 | 649 | method: 'post', |
| 815 | 650 | data: draftData |
| 816 | 651 | }); |
| 817 | - | |
| 818 | - // 保存序列号信息(草稿状态) | |
| 819 | - await this.updateSerialNumberStatus(res.data.id, true); | |
| 820 | 652 | } else { |
| 821 | - res = await request({ | |
| 653 | + await request({ | |
| 822 | 654 | url: '/api/Extend/WtXsckd/' + this.dataForm.id, |
| 823 | 655 | method: 'PUT', |
| 824 | 656 | data: draftData |
| 825 | 657 | }); |
| 826 | - | |
| 827 | - // 保存序列号信息(草稿状态) | |
| 828 | - await this.updateSerialNumberStatus(this.dataForm.id, true); | |
| 829 | 658 | } |
| 830 | 659 | this.visible = false; |
| 831 | 660 | this.$emit('refresh', true); |
| 832 | 661 | this.$message.success('草稿已保存'); |
| 833 | 662 | } catch (error) { |
| 834 | 663 | console.error('保存草稿失败:', error); |
| 835 | - this.$message.error('保存草稿失败,请重试'); | |
| 664 | + const msg = (error && error.message) ? error.message : '保存草稿失败,请重试' | |
| 665 | + this.$message.error(msg); | |
| 836 | 666 | } |
| 837 | 667 | } |
| 838 | 668 | }); |
| 839 | 669 | }, |
| 840 | - async dataFormSubmit() { | |
| 841 | - const basicErr = this.validateBjdFormalBasics() | |
| 842 | - if (basicErr) { | |
| 843 | - this.$message.error(basicErr) | |
| 844 | - return | |
| 845 | - } | |
| 846 | - const mxCheck = validateMxNoEmptyProductRows(this.dataForm.wtXsckdMxList) | |
| 847 | - if (!mxCheck.valid) { | |
| 848 | - this.$message.warning(`第 ${mxCheck.emptyLineNos.join('、')} 行未选择商品,请先删除空白行后再提交`) | |
| 849 | - return | |
| 850 | - } | |
| 851 | - // 1. 明细校验:序列号数量与销售数量一致性 | |
| 852 | - let validationErrors = []; | |
| 853 | - for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { | |
| 854 | - const row = this.dataForm.wtXsckdMxList[i]; | |
| 855 | - if (row.spbh) { | |
| 856 | - const key = String(row.spbh); | |
| 857 | - const product = this.productCache.get(key); | |
| 858 | - if (product && (product.spxlhType === '1' || product.spxlhType === 1 || product.spxlhType === '2' || product.spxlhType === 2)) { | |
| 859 | - if (!row.selectedSerialNumbers || row.selectedSerialNumbers.length === 0) { | |
| 860 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"需要选择序列号`); | |
| 861 | - } else { | |
| 862 | - const serialNumberCount = row.selectedSerialNumbers.length; | |
| 863 | - const detailQuantity = parseInt(row.sl) || 0; | |
| 864 | - if (serialNumberCount !== detailQuantity) { | |
| 865 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"的数量(${detailQuantity})与已选择的序列号数量(${serialNumberCount})不一致,请重新选择序列号或调整数量`); | |
| 866 | - } | |
| 867 | - } | |
| 868 | - } | |
| 869 | - } | |
| 870 | - } | |
| 871 | - if (validationErrors.length > 0) { | |
| 872 | - this.$message.error(validationErrors[0]); | |
| 873 | - return; // 阻止提交 | |
| 874 | - } | |
| 875 | - // 2. 表单校验 | |
| 876 | - this.$refs['elForm'].validate(async (valid) => { | |
| 877 | - if (!valid) return; | |
| 878 | - // 确保单据类型字段赋值 | |
| 879 | - this.dataForm.djlx = '变价调拨单'; | |
| 880 | - // 确保单据状态为待审核 | |
| 881 | - this.dataForm.djzt = '待审核'; | |
| 882 | - // 检查是否有明细数据 | |
| 883 | - if (!this.dataForm.wtXsckdMxList || this.dataForm.wtXsckdMxList.length === 0) { | |
| 884 | - console.log('没有明细数据,跳过序列号验证'); | |
| 885 | - // 继续执行表单验证 | |
| 886 | - this.$refs['elForm'].validate(async (valid) => { | |
| 887 | - if (valid) { | |
| 888 | - // 先批量获取所有商品的序列号类型信息 | |
| 889 | - console.log('开始批量获取商品序列号类型信息...'); | |
| 890 | - const batchResult = await this.getAllProductSerialNumberTypes(); | |
| 891 | - | |
| 892 | - if (batchResult.failCount > 0) { | |
| 893 | - console.warn(`有 ${batchResult.failCount} 个商品无法获取序列号类型信息:`, batchResult.failedProducts); | |
| 894 | - | |
| 895 | - // 临时解决方案:只为真正无法获取序列号类型的商品设置默认值 | |
| 896 | - console.log('应用临时解决方案:为无法获取序列号类型的商品设置默认值'); | |
| 897 | - this.dataForm.wtXsckdMxList.forEach((row, index) => { | |
| 898 | - if (row.spbh) { | |
| 899 | - const key = String(row.spbh); | |
| 900 | - const product = this.productCache.get(key); | |
| 901 | - // 只有当商品信息完全不存在时才设置默认值 | |
| 902 | - if (!product) { | |
| 903 | - // 为这些商品设置默认的序列号类型(入0出0 - 不需要序列号) | |
| 904 | - this.setProductSerialNumberType(row.spbh, '入0出0'); | |
| 905 | - console.log(`为第${index + 1}行商品"${row.spmc}"设置默认序列号类型: 入0出0(因为无法获取商品信息)`); | |
| 906 | - } else if (!product.spxlhType) { | |
| 907 | - // 如果商品信息存在但没有序列号类型,也设置默认值 | |
| 908 | - this.setProductSerialNumberType(row.spbh, '入0出0'); | |
| 909 | - console.log(`为第${index + 1}行商品"${row.spmc}"设置默认序列号类型: 入0出0(因为商品信息中没有序列号类型)`); | |
| 910 | - } | |
| 911 | - } | |
| 912 | - }); | |
| 913 | - } | |
| 914 | - | |
| 915 | - // 显示所有商品的序列号类型信息(调试用) | |
| 916 | - this.showAllProductSerialNumberTypes(); | |
| 917 | - | |
| 918 | - // 进行序列号验证 | |
| 919 | - console.log('开始检查序列号...'); | |
| 920 | - validationErrors = []; | |
| 921 | - | |
| 922 | - for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { | |
| 923 | - const row = this.dataForm.wtXsckdMxList[i]; | |
| 924 | - if (row.spbh) { | |
| 925 | - // 从缓存中获取商品信息 | |
| 926 | - const key = String(row.spbh); | |
| 927 | - const product = this.productCache.get(key); | |
| 928 | - | |
| 929 | - if (product && product.spxlhType) { | |
| 930 | - const spxlhType = product.spxlhType; | |
| 931 | - console.log(`第${i + 1}行商品 ${row.spmc} 的序列号类型: ${spxlhType}`); | |
| 932 | - | |
| 933 | - // 检查是否需要强制序列号选择(只判断'1'和'2') | |
| 934 | - if (spxlhType === '1' || spxlhType === '2') { | |
| 935 | - console.log(`第${i + 1}行商品 ${row.spmc} 需要强制选择序列号`); | |
| 936 | - | |
| 937 | - if (!row.selectedSerialNumbers || row.selectedSerialNumbers.length === 0) { | |
| 938 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"需要选择序列号`); | |
| 939 | - console.log(`第${i + 1}行商品"${row.spmc}"未选择序列号,已添加到验证错误`); | |
| 940 | - } else { | |
| 941 | - // 检查序列号数量与明细数量是否一致 | |
| 942 | - const serialNumberCount = row.selectedSerialNumbers.length; | |
| 943 | - const detailQuantity = parseInt(row.sl) || 0; | |
| 944 | - | |
| 945 | - if (serialNumberCount !== detailQuantity) { | |
| 946 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"的序列号数量(${serialNumberCount})与明细数量(${detailQuantity})不一致,请重新选择序列号或调整数量`); | |
| 947 | - console.log(`第${i + 1}行商品"${row.spmc}"序列号数量不匹配,已添加到验证错误`); | |
| 948 | - } else { | |
| 949 | - console.log(`第${i + 1}行商品序列号验证通过: ${serialNumberCount}个序列号,数量${detailQuantity}`); | |
| 950 | - } | |
| 951 | - } | |
| 952 | - } else { | |
| 953 | - console.log(`第${i + 1}行商品不需要序列号选择,序列号类型: ${spxlhType}`); | |
| 954 | - } | |
| 955 | - } else { | |
| 956 | - console.log(`第${i + 1}行商品"${row.spmc}"无法获取序列号类型信息`); | |
| 957 | - // 如果无法获取商品信息,记录错误但不阻止提交 | |
| 958 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"无法获取序列号类型信息,请确认商品配置`); | |
| 959 | - } | |
| 960 | - } | |
| 961 | - } | |
| 962 | - | |
| 963 | - // 如果有验证错误,显示错误信息并询问是否继续 | |
| 964 | - if (validationErrors.length > 0) { | |
| 965 | - const errorMessage = validationErrors.join('\n'); | |
| 966 | - console.log('序列号验证发现以下问题:', validationErrors); | |
| 967 | - | |
| 968 | - // 添加详细的验证结果说明 | |
| 969 | - let detailedMessage = '序列号验证发现以下问题:\n\n'; | |
| 970 | - detailedMessage += errorMessage; | |
| 971 | - detailedMessage += '\n\n'; | |
| 972 | - detailedMessage += '说明:\n'; | |
| 973 | - detailedMessage += '- 序列号类型为"入1出1"或"入0出1"的商品必须选择序列号\n'; | |
| 974 | - detailedMessage += '- 序列号数量必须与明细数量完全一致\n'; | |
| 975 | - detailedMessage += '- 请为上述商品选择正确的序列号后重新保存'; | |
| 976 | - | |
| 977 | - await this.$alert(detailedMessage, '序列号验证警告', { | |
| 978 | - confirmButtonText: '确定', | |
| 979 | - type: 'warning', | |
| 980 | - dangerouslyUseHTMLString: false | |
| 981 | - }); | |
| 982 | - return; // 用户取消,不继续保存 | |
| 983 | - } | |
| 984 | - | |
| 985 | - console.log('序列号检查完成,开始表单验证...'); | |
| 986 | - | |
| 987 | - this.$refs['elForm'].validate(async (valid) => { | |
| 988 | - if (valid) { | |
| 989 | - console.log('表单验证通过,继续提交...'); | |
| 990 | - try { | |
| 991 | - if (!this.dataForm.id) { | |
| 992 | - const res = await request({ | |
| 993 | - url: `/api/Extend/WtXsckd`, | |
| 994 | - method: 'post', | |
| 995 | - data: this.buildPayload(false), | |
| 996 | - }) | |
| 997 | - | |
| 998 | - // 更新序列号状态 | |
| 999 | - await this.updateSerialNumberStatus(res.data.id) | |
| 1000 | - | |
| 1001 | - // 仅关闭弹窗并刷新,不弹窗提示 | |
| 1002 | - this.visible = false; | |
| 1003 | - this.$emit('refresh', true); | |
| 1004 | - } else { | |
| 1005 | - const res = await request({ | |
| 1006 | - url: '/api/Extend/WtXsckd/' + this.dataForm.id, | |
| 1007 | - method: 'PUT', | |
| 1008 | - data: this.buildPayload(false) | |
| 1009 | - }) | |
| 1010 | - | |
| 1011 | - // 更新序列号状态 | |
| 1012 | - await this.updateSerialNumberStatus(this.dataForm.id) | |
| 1013 | - | |
| 1014 | - // 仅关闭弹窗并刷新,不弹窗提示 | |
| 1015 | - this.visible = false; | |
| 1016 | - this.$emit('refresh', true); | |
| 1017 | - } | |
| 1018 | - } catch (error) { | |
| 1019 | - console.error('保存失败:', error) | |
| 1020 | - this.$message.error('保存失败,请重试') | |
| 1021 | - } | |
| 1022 | - } | |
| 1023 | - }) | |
| 1024 | - } | |
| 1025 | - }); | |
| 1026 | - return; | |
| 1027 | - } | |
| 1028 | - // 序列号数量校验 | |
| 1029 | - validationErrors = []; | |
| 1030 | - for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { | |
| 1031 | - const row = this.dataForm.wtXsckdMxList[i]; | |
| 1032 | - if (row.spbh) { | |
| 1033 | - const key = String(row.spbh); | |
| 1034 | - const product = this.productCache.get(key); | |
| 1035 | - if (product && product.spxlhType) { | |
| 1036 | - const spxlhType = product.spxlhType; | |
| 1037 | - if (spxlhType === '1' || spxlhType === '2') { | |
| 1038 | - if (!row.selectedSerialNumbers || row.selectedSerialNumbers.length === 0) { | |
| 1039 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"需要选择序列号`); | |
| 1040 | - } else { | |
| 1041 | - const serialNumberCount = row.selectedSerialNumbers.length; | |
| 1042 | - const detailQuantity = parseInt(row.sl) || 0; | |
| 1043 | - if (serialNumberCount !== detailQuantity) { | |
| 1044 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"的数量(${detailQuantity})与已选择的序列号数量(${serialNumberCount})不一致,请重新选择序列号或调整数量`); | |
| 1045 | - } | |
| 1046 | - } | |
| 1047 | - } | |
| 1048 | - } | |
| 1049 | - } | |
| 1050 | - } | |
| 1051 | - if (validationErrors.length > 0) { | |
| 1052 | - this.$message.error(validationErrors[0]); | |
| 1053 | - return; // 阻止提交 | |
| 1054 | - } | |
| 1055 | - // 继续执行表单验证 | |
| 1056 | - this.$refs['elForm'].validate(async (valid) => { | |
| 1057 | - if (valid) { | |
| 1058 | - console.log('表单验证通过,继续提交...'); | |
| 1059 | - try { | |
| 1060 | - if (!this.dataForm.id) { | |
| 1061 | - const res = await request({ | |
| 1062 | - url: `/api/Extend/WtXsckd`, | |
| 1063 | - method: 'post', | |
| 1064 | - data: this.buildPayload(false), | |
| 1065 | - }) | |
| 1066 | - | |
| 1067 | - // 更新序列号状态 | |
| 1068 | - await this.updateSerialNumberStatus(res.data.id) | |
| 1069 | - | |
| 1070 | - // 仅关闭弹窗并刷新,不弹窗提示 | |
| 1071 | - this.visible = false; | |
| 1072 | - this.$emit('refresh', true); | |
| 1073 | - } else { | |
| 1074 | - const res = await request({ | |
| 1075 | - url: '/api/Extend/WtXsckd/' + this.dataForm.id, | |
| 1076 | - method: 'PUT', | |
| 1077 | - data: this.buildPayload(false) | |
| 1078 | - }) | |
| 1079 | - | |
| 1080 | - // 更新序列号状态 | |
| 1081 | - await this.updateSerialNumberStatus(this.dataForm.id) | |
| 1082 | - | |
| 1083 | - // 仅关闭弹窗并刷新,不弹窗提示 | |
| 1084 | - this.visible = false; | |
| 1085 | - this.$emit('refresh', true); | |
| 1086 | - } | |
| 1087 | - } catch (error) { | |
| 1088 | - console.error('保存失败:', error) | |
| 1089 | - this.$message.error('保存失败,请重试') | |
| 1090 | - } | |
| 1091 | - } | |
| 1092 | - }) | |
| 1093 | - }); | |
| 1094 | - }, | |
| 1095 | - | |
| 1096 | - // 更新序列号状态 | |
| 1097 | - async updateSerialNumberStatus(documentId, isDraft = false) { | |
| 670 | + async dataFormSubmit() { | |
| 671 | + const basicErr = this.validateBjdFormalBasics() | |
| 672 | + if (basicErr) { | |
| 673 | + this.$message.error(basicErr) | |
| 674 | + return | |
| 675 | + } | |
| 676 | + const mxCheck = validateMxNoEmptyProductRows(this.dataForm.wtXsckdMxList) | |
| 677 | + if (!mxCheck.valid) { | |
| 678 | + this.$message.warning(`第 ${mxCheck.emptyLineNos.join('、')} 行未选择商品,请先删除空白行后再提交`) | |
| 679 | + return | |
| 680 | + } | |
| 681 | + // 表单校验 | |
| 682 | + this.$refs['elForm'].validate(async (valid) => { | |
| 683 | + if (!valid) return; | |
| 684 | + this.dataForm.djlx = '变价调拨单'; | |
| 685 | + this.dataForm.djzt = '待审核'; | |
| 1098 | 686 | try { |
| 1099 | - // 收集所有选择的序列号 | |
| 1100 | - const allSerialNumbers = [] | |
| 1101 | - this.dataForm.wtXsckdMxList.forEach(row => { | |
| 1102 | - if (row.selectedSerialNumbers && row.selectedSerialNumbers.length > 0) { | |
| 1103 | - allSerialNumbers.push(...row.selectedSerialNumbers) | |
| 1104 | - } | |
| 1105 | - }) | |
| 1106 | - | |
| 1107 | - if (allSerialNumbers.length > 0) { | |
| 687 | + if (!this.dataForm.id) { | |
| 1108 | 688 | await request({ |
| 1109 | - url: '/api/Extend/WtXsckd/UpdateSerialNumberStatus', | |
| 689 | + url: `/api/Extend/WtXsckd`, | |
| 1110 | 690 | method: 'post', |
| 1111 | - data: allSerialNumbers, | |
| 1112 | - params: { | |
| 1113 | - outDocumentId: documentId, | |
| 1114 | - outDocumentType: '变价调拨单', | |
| 1115 | - outWarehouse: this.dataForm.cjck, | |
| 1116 | - isDraft: isDraft | |
| 1117 | - } | |
| 691 | + data: this.buildPayload(false), | |
| 692 | + }) | |
| 693 | + } else { | |
| 694 | + await request({ | |
| 695 | + url: '/api/Extend/WtXsckd/' + this.dataForm.id, | |
| 696 | + method: 'PUT', | |
| 697 | + data: this.buildPayload(false) | |
| 1118 | 698 | }) |
| 1119 | 699 | } |
| 700 | + this.visible = false; | |
| 701 | + this.$emit('refresh', true); | |
| 1120 | 702 | } catch (error) { |
| 1121 | - console.error('更新序列号状态失败:', error) | |
| 1122 | - // 不阻止主流程,只记录错误 | |
| 703 | + console.error('保存失败:', error) | |
| 704 | + const msg = (error && error.message) ? error.message : '保存失败,请重试' | |
| 705 | + this.$message.error(msg) | |
| 1123 | 706 | } |
| 1124 | - }, | |
| 707 | + }); | |
| 708 | + }, | |
| 1125 | 709 | addHandleWtXsckdMxEntityList() { |
| 1126 | - let item = { | |
| 1127 | - rkck:undefined, | |
| 1128 | - ckck:this.dataForm.cjck, // 自动使用主表的出库仓库 | |
| 1129 | - spbh:undefined, | |
| 1130 | - spmc:undefined, | |
| 1131 | - sptm:undefined, | |
| 1132 | - dw:undefined, | |
| 1133 | - kucun:undefined, // 库存数量 | |
| 1134 | - sl:undefined, | |
| 1135 | - dj:undefined, | |
| 1136 | - je:undefined, | |
| 1137 | - bjhcb:undefined, // 变价后成本 | |
| 1138 | - selectedSerialNumbers: [], // 添加序列号数组 | |
| 1139 | - loadingStock: false, // 库存加载状态 | |
| 1140 | - productQuery: '', // 为每一行添加独立的查询条件 | |
| 710 | + const item = { | |
| 711 | + rkck: this.dataForm.rkck, | |
| 712 | + ckck: this.dataForm.cjck, | |
| 713 | + spbh: undefined, | |
| 714 | + spmc: undefined, | |
| 715 | + sptm: undefined, | |
| 716 | + dw: undefined, | |
| 717 | + kucun: undefined, | |
| 718 | + sl: undefined, | |
| 719 | + dj: undefined, | |
| 720 | + je: undefined, | |
| 721 | + bjhcb: undefined, | |
| 722 | + bjhcbManual: false, | |
| 723 | + loadingStock: false, | |
| 724 | + productQuery: '', | |
| 1141 | 725 | } |
| 1142 | 726 | this.dataForm.wtXsckdMxList.push(item) |
| 1143 | 727 | }, |
| ... | ... | @@ -1231,11 +815,9 @@ |
| 1231 | 815 | dj: cbj ? Number(cbj).toFixed(4) : undefined, |
| 1232 | 816 | je: undefined, |
| 1233 | 817 | bjhcb: undefined, |
| 1234 | - selectedSerialNumbers: [], | |
| 818 | + bjhcbManual: false, | |
| 1235 | 819 | loadingStock: false, |
| 1236 | 820 | productQuery: '', |
| 1237 | - spxlhLoaded: false, | |
| 1238 | - xlhList: [] | |
| 1239 | 821 | }; |
| 1240 | 822 | this.dataForm.wtXsckdMxList.push(row); |
| 1241 | 823 | appendedRows.push(row); |
| ... | ... | @@ -1246,14 +828,6 @@ |
| 1246 | 828 | this.$message.info('没有需要新增的商品,明细已包含该仓库全部库存'); |
| 1247 | 829 | return; |
| 1248 | 830 | } |
| 1249 | - // 批量预热商品档案缓存(序列号类型等),避免整列显示"加载中..." | |
| 1250 | - Promise.all(appendedRows.map(r => | |
| 1251 | - this.getProductInfo(r.spbh).catch(() => null) | |
| 1252 | - )).then(() => { | |
| 1253 | - appendedRows.forEach(r => { | |
| 1254 | - this.$set(r, 'spxlhLoaded', true); | |
| 1255 | - }); | |
| 1256 | - }); | |
| 1257 | 831 | this.$nextTick(() => { |
| 1258 | 832 | if (this.dataForm.bjsx && this.dataForm.bjsx !== '') { |
| 1259 | 833 | this.calculateAllAdjustedCosts(); |
| ... | ... | @@ -1281,64 +855,22 @@ |
| 1281 | 855 | } |
| 1282 | 856 | }, |
| 1283 | 857 | |
| 1284 | - // 打开序列号选择弹窗 | |
| 1285 | - openSerialNumberSelect(row) { | |
| 1286 | - if (!row.spbh) { | |
| 1287 | - this.$message.warning('请先选择商品') | |
| 1288 | - return | |
| 1289 | - } | |
| 1290 | - // 传递已选序列号,便于弹窗回显 | |
| 1291 | - this.$refs.serialNumberSelect.open( | |
| 1292 | - row.spbh, | |
| 1293 | - row.ckck || this.dataForm.cjck, | |
| 1294 | - row.selectedSerialNumbers || [], | |
| 1295 | - '变价调拨单' // 传递单据类型,查询在库序列号 | |
| 1296 | - ); | |
| 1297 | - }, | |
| 1298 | - | |
| 1299 | - // 处理序列号选择 | |
| 1300 | - handleSerialNumberSelect(selectedSerialNumbers) { | |
| 1301 | - // 找到当前编辑的行并填充序列号 | |
| 1302 | - const currentRow = this.dataForm.wtXsckdMxList.find(item => | |
| 1303 | - item.spbh === this.$refs.serialNumberSelect.currentProductCode | |
| 1304 | - ) | |
| 1305 | - | |
| 1306 | - if (currentRow) { | |
| 1307 | - currentRow.selectedSerialNumbers = selectedSerialNumbers | |
| 1308 | - // 自动设置数量为选择的序列号数量 | |
| 1309 | - currentRow.sl = selectedSerialNumbers.length.toString() | |
| 1310 | - // 自动计算金额 | |
| 1311 | - if (currentRow.dj) { | |
| 1312 | - currentRow.je = (parseFloat(currentRow.sl) * parseFloat(currentRow.dj)).toFixed(2) | |
| 1313 | - } | |
| 1314 | - // 更新总收款金额 | |
| 1315 | - this.calculateTotalAmount(); | |
| 1316 | - | |
| 1317 | - console.log(`商品 ${currentRow.spmc} 选择了 ${selectedSerialNumbers.length} 个序列号,数量已自动更新为 ${currentRow.sl}`); | |
| 1318 | - } | |
| 1319 | - }, | |
| 858 | + /** 需变价数量变化:重算金额与行变价后成本(若未手工录入 bjhcb 且存在系数) */ | |
| 1320 | 859 | handleAmountChange(row) { |
| 1321 | 860 | const sl = parseFloat(row.sl) || 0; |
| 1322 | 861 | const dj = parseFloat(row.dj) || 0; |
| 1323 | 862 | row.je = (sl * dj).toFixed(2); |
| 1324 | - // 自动计算总收款金额 | |
| 1325 | 863 | this.calculateTotalAmount(); |
| 1326 | - | |
| 1327 | - // 当成本单价变化时,重新计算变价后成本 | |
| 1328 | - if (this.dataForm.bjsx && this.dataForm.bjsx !== '') { | |
| 864 | + | |
| 865 | + // 只有用户尚未手工填写 bjhcb 时才根据变价系数自动计算 | |
| 866 | + if (!row.bjhcbManual && this.dataForm.bjsx && this.dataForm.bjsx !== '') { | |
| 1329 | 867 | this.calculateRowAdjustedCost(row); |
| 1330 | 868 | } |
| 1331 | - | |
| 1332 | - // 检查数量与序列号数量是否一致 | |
| 1333 | - if (row.selectedSerialNumbers && row.selectedSerialNumbers.length > 0) { | |
| 1334 | - const serialNumberCount = row.selectedSerialNumbers.length; | |
| 1335 | - if (sl !== serialNumberCount) { | |
| 1336 | - this.$message.error(`商品"${row.spmc}"的销售数量(${sl})与已选择的序列号数量(${serialNumberCount})不一致,请先调整序列号数量!`); | |
| 1337 | - // 恢复为序列号数量 | |
| 1338 | - row.sl = serialNumberCount.toString(); | |
| 1339 | - row.je = (serialNumberCount * dj).toFixed(2); | |
| 1340 | - } | |
| 1341 | - } | |
| 869 | + }, | |
| 870 | + | |
| 871 | + /** 手工修改变价后成本:标记为手动录入,避免被变价系数批量覆盖 */ | |
| 872 | + handleBjhcbInput(row) { | |
| 873 | + this.$set(row, 'bjhcbManual', true); | |
| 1342 | 874 | }, |
| 1343 | 875 | |
| 1344 | 876 | // 处理主表出库仓库变化 |
| ... | ... | @@ -1486,245 +1018,6 @@ |
| 1486 | 1018 | this.dataForm.skje = (totalAmount - ysje).toFixed(2); |
| 1487 | 1019 | }, |
| 1488 | 1020 | |
| 1489 | - // 恢复序列号信息 | |
| 1490 | - async restoreSerialNumbers() { | |
| 1491 | - console.log('开始恢复序列号信息...'); | |
| 1492 | - console.log('完整的dataForm:', JSON.stringify(this.dataForm, null, 2)); | |
| 1493 | - | |
| 1494 | - if (this.dataForm.wtXsckdMxList && this.dataForm.wtXsckdMxList.length > 0) { | |
| 1495 | - console.log('明细列表:', JSON.stringify(this.dataForm.wtXsckdMxList, null, 2)); | |
| 1496 | - | |
| 1497 | - for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { | |
| 1498 | - const row = this.dataForm.wtXsckdMxList[i]; | |
| 1499 | - console.log(`第${i + 1}行完整数据:`, JSON.stringify(row, null, 2)); | |
| 1500 | - | |
| 1501 | - if (row.spbh) { | |
| 1502 | - // 使用后端返回的序列号信息 | |
| 1503 | - if (row.selectedSerialNumbers && row.selectedSerialNumbers.length > 0) { | |
| 1504 | - console.log(`第${i + 1}行商品恢复序列号:`, row.selectedSerialNumbers); | |
| 1505 | - } else { | |
| 1506 | - row.selectedSerialNumbers = []; | |
| 1507 | - console.log(`第${i + 1}行商品无序列号信息,spbh: ${row.spbh}`); | |
| 1508 | - } | |
| 1509 | - } | |
| 1510 | - } | |
| 1511 | - } else { | |
| 1512 | - console.log('明细列表为空或未定义'); | |
| 1513 | - } | |
| 1514 | - }, | |
| 1515 | - | |
| 1516 | - // 获取序列号类型显示文本 | |
| 1517 | - getSerialNumberTypeText(row) { | |
| 1518 | - if (!row.spbh) return '未选择'; | |
| 1519 | - if (!row.spxlhLoaded) return '加载中...'; | |
| 1520 | - const key = String(row.spbh); | |
| 1521 | - const product = this.productCache.get(key); | |
| 1522 | - if (product && product.spxlhType) { | |
| 1523 | - const spxlhType = product.spxlhType; | |
| 1524 | - if (spxlhType === '1') return '入1出1'; | |
| 1525 | - if (spxlhType === '2') return '入0出1'; | |
| 1526 | - if (spxlhType === '3') return '入0出0'; | |
| 1527 | - return spxlhType; | |
| 1528 | - } | |
| 1529 | - return '未知'; | |
| 1530 | - }, | |
| 1531 | - | |
| 1532 | - // 获取序列号类型标签颜色 | |
| 1533 | - getSerialNumberTypeTagType(row) { | |
| 1534 | - if (!row.spbh) return 'info'; | |
| 1535 | - if (!row.spxlhLoaded) return 'info'; | |
| 1536 | - const key = String(row.spbh); | |
| 1537 | - const product = this.productCache.get(key); | |
| 1538 | - if (product && product.spxlhType) { | |
| 1539 | - const spxlhType = product.spxlhType; | |
| 1540 | - if (spxlhType === '1' || spxlhType === '2') return 'warning'; // 需要序列号 - 橙色警告 | |
| 1541 | - if (spxlhType === '3') return 'success'; // 不需要序列号 - 绿色 | |
| 1542 | - } | |
| 1543 | - return 'info'; // 未知 - 蓝色 | |
| 1544 | - }, | |
| 1545 | - | |
| 1546 | - // 获取序列号类型描述 | |
| 1547 | - getSerialNumberTypeDescription(row) { | |
| 1548 | - if (!row.spbh) return '请先选择商品'; | |
| 1549 | - if (!row.spxlhLoaded) return '加载中...'; | |
| 1550 | - const key = String(row.spbh); | |
| 1551 | - const product = this.productCache.get(key); | |
| 1552 | - if (product && product.spxlhType) { | |
| 1553 | - const spxlhType = product.spxlhType; | |
| 1554 | - if (spxlhType === '1') return '入库和出库都需要序列号'; | |
| 1555 | - if (spxlhType === '2') return '入库不需要,出库需要序列号'; | |
| 1556 | - if (spxlhType === '3') return '入库和出库都不需要序列号'; | |
| 1557 | - } | |
| 1558 | - return '序列号类型信息未知'; | |
| 1559 | - }, | |
| 1560 | - | |
| 1561 | - // 刷新序列号类型信息 | |
| 1562 | - async refreshSerialNumberType(row) { | |
| 1563 | - if (!row.spbh) { | |
| 1564 | - this.$message.warning('请先选择商品'); | |
| 1565 | - return; | |
| 1566 | - } | |
| 1567 | - | |
| 1568 | - try { | |
| 1569 | - // 清除缓存中的商品信息 | |
| 1570 | - const key = String(row.spbh); | |
| 1571 | - this.productCache.delete(key); | |
| 1572 | - console.log(`清除商品 ${row.spbh} 的缓存信息`); | |
| 1573 | - | |
| 1574 | - // 重新获取商品信息 | |
| 1575 | - const product = await this.getProductInfo(row.spbh); | |
| 1576 | - if (product && product.spxlhType) { | |
| 1577 | - console.log(`刷新成功: 商品 ${row.spmc} 的序列号类型: ${product.spxlhType}`); | |
| 1578 | - this.$message.success(`序列号类型已刷新: ${product.spxlhType}`); | |
| 1579 | - } else { | |
| 1580 | - console.warn(`刷新失败: 商品 ${row.spmc} 无法获取序列号类型信息`); | |
| 1581 | - this.$message.warning('无法获取序列号类型信息'); | |
| 1582 | - } | |
| 1583 | - | |
| 1584 | - // 强制更新视图 | |
| 1585 | - this.$forceUpdate(); | |
| 1586 | - } catch (error) { | |
| 1587 | - console.error(`刷新序列号类型失败:`, error); | |
| 1588 | - this.$message.error('刷新序列号类型失败'); | |
| 1589 | - } | |
| 1590 | - }, | |
| 1591 | - | |
| 1592 | - // 调试:检查商品缓存内容 | |
| 1593 | - debugProductCache() { | |
| 1594 | - console.log('=== 商品缓存调试信息 ==='); | |
| 1595 | - console.log('缓存大小:', this.productCache.size); | |
| 1596 | - | |
| 1597 | - if (this.productCache.size === 0) { | |
| 1598 | - console.log('商品缓存为空'); | |
| 1599 | - return; | |
| 1600 | - } | |
| 1601 | - | |
| 1602 | - this.productCache.forEach((product, productId) => { | |
| 1603 | - console.log(`商品ID: ${productId}`); | |
| 1604 | - console.log(` 商品名称: ${product.spmc || '未知'}`); | |
| 1605 | - console.log(` 序列号类型: ${product.spxlhType || '未设置'}`); | |
| 1606 | - console.log(` 完整数据:`, product); | |
| 1607 | - }); | |
| 1608 | - | |
| 1609 | - console.log('=== 商品选项数据 ==='); | |
| 1610 | - console.log('商品选项数量:', this.spbhOptions.length); | |
| 1611 | - | |
| 1612 | - // 查找Tomtoc硬壳商品 | |
| 1613 | - const tomtocProducts = this.spbhOptions.filter(item => | |
| 1614 | - item.F_Spmc && item.F_Spmc.includes('Tomtoc') | |
| 1615 | - ); | |
| 1616 | - console.log('Tomtoc相关商品:', tomtocProducts); | |
| 1617 | - | |
| 1618 | - console.log('=== 调试信息结束 ==='); | |
| 1619 | - }, | |
| 1620 | - | |
| 1621 | - // 测试序列号验证 | |
| 1622 | - async testSerialNumberValidation() { | |
| 1623 | - console.log('=== 测试序列号验证 ==='); | |
| 1624 | - console.log('明细数据:', this.dataForm.wtXsckdMxList); | |
| 1625 | - | |
| 1626 | - if (!this.dataForm.wtXsckdMxList || this.dataForm.wtXsckdMxList.length === 0) { | |
| 1627 | - this.$message.info('没有明细数据'); | |
| 1628 | - return; | |
| 1629 | - } | |
| 1630 | - | |
| 1631 | - // 先批量获取所有商品的序列号类型信息 | |
| 1632 | - console.log('开始批量获取商品序列号类型信息...'); | |
| 1633 | - const batchResult = await this.getAllProductSerialNumberTypes(); | |
| 1634 | - | |
| 1635 | - if (batchResult.failCount > 0) { | |
| 1636 | - console.warn(`有 ${batchResult.failCount} 个商品无法获取序列号类型信息:`, batchResult.failedProducts); | |
| 1637 | - | |
| 1638 | - // 临时解决方案:只为真正无法获取序列号类型的商品设置默认值 | |
| 1639 | - console.log('应用临时解决方案:为无法获取序列号类型的商品设置默认值'); | |
| 1640 | - this.dataForm.wtXsckdMxList.forEach((row, index) => { | |
| 1641 | - if (row.spbh) { | |
| 1642 | - const key = String(row.spbh); | |
| 1643 | - const product = this.productCache.get(key); | |
| 1644 | - // 只有当商品信息完全不存在时才设置默认值 | |
| 1645 | - if (!product) { | |
| 1646 | - // 为这些商品设置默认的序列号类型(入0出0 - 不需要序列号) | |
| 1647 | - this.setProductSerialNumberType(row.spbh, '入0出0'); | |
| 1648 | - console.log(`为第${index + 1}行商品"${row.spmc}"设置默认序列号类型: 入0出0(因为无法获取商品信息)`); | |
| 1649 | - } else if (!product.spxlhType) { | |
| 1650 | - // 如果商品信息存在但没有序列号类型,也设置默认值 | |
| 1651 | - this.setProductSerialNumberType(row.spbh, '入0出0'); | |
| 1652 | - console.log(`为第${index + 1}行商品"${row.spmc}"设置默认序列号类型: 入0出0(因为商品信息中没有序列号类型)`); | |
| 1653 | - } | |
| 1654 | - } | |
| 1655 | - }); | |
| 1656 | - } | |
| 1657 | - | |
| 1658 | - // 显示所有商品的序列号类型信息(调试用) | |
| 1659 | - this.showAllProductSerialNumberTypes(); | |
| 1660 | - | |
| 1661 | - // 进行序列号验证 | |
| 1662 | - console.log('开始检查序列号...'); | |
| 1663 | - let validationErrors = []; | |
| 1664 | - | |
| 1665 | - for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { | |
| 1666 | - const row = this.dataForm.wtXsckdMxList[i]; | |
| 1667 | - if (row.spbh) { | |
| 1668 | - // 从缓存中获取商品信息 | |
| 1669 | - const key = String(row.spbh); | |
| 1670 | - const product = this.productCache.get(key); | |
| 1671 | - | |
| 1672 | - if (product && product.spxlhType) { | |
| 1673 | - const spxlhType = product.spxlhType; | |
| 1674 | - console.log(`第${i + 1}行商品 ${row.spmc} 的序列号类型: ${spxlhType}`); | |
| 1675 | - | |
| 1676 | - // 检查是否需要强制序列号选择(只判断'1'和'2') | |
| 1677 | - if (spxlhType === '1' || spxlhType === '2') { | |
| 1678 | - console.log(`第${i + 1}行商品 ${row.spmc} 需要强制选择序列号`); | |
| 1679 | - | |
| 1680 | - if (!row.selectedSerialNumbers || row.selectedSerialNumbers.length === 0) { | |
| 1681 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"需要选择序列号`); | |
| 1682 | - console.log(`第${i + 1}行商品"${row.spmc}"未选择序列号,已添加到验证错误`); | |
| 1683 | - } else { | |
| 1684 | - // 检查序列号数量与明细数量是否一致 | |
| 1685 | - const serialNumberCount = row.selectedSerialNumbers.length; | |
| 1686 | - const detailQuantity = parseInt(row.sl) || 0; | |
| 1687 | - | |
| 1688 | - if (serialNumberCount !== detailQuantity) { | |
| 1689 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"的序列号数量(${serialNumberCount})与明细数量(${detailQuantity})不一致,请重新选择序列号或调整数量`); | |
| 1690 | - console.log(`第${i + 1}行商品"${row.spmc}"序列号数量不匹配,已添加到验证错误`); | |
| 1691 | - } else { | |
| 1692 | - console.log(`第${i + 1}行商品序列号验证通过: ${serialNumberCount}个序列号,数量${detailQuantity}`); | |
| 1693 | - } | |
| 1694 | - } | |
| 1695 | - } else { | |
| 1696 | - console.log(`第${i + 1}行商品不需要序列号选择,序列号类型: ${spxlhType}`); | |
| 1697 | - } | |
| 1698 | - } else { | |
| 1699 | - console.log(`第${i + 1}行商品"${row.spmc}"无法获取序列号类型信息`); | |
| 1700 | - // 如果无法获取商品信息,记录错误但不阻止提交 | |
| 1701 | - validationErrors.push(`第${i + 1}行商品"${row.spmc}"无法获取序列号类型信息,请确认商品配置`); | |
| 1702 | - } | |
| 1703 | - } | |
| 1704 | - } | |
| 1705 | - | |
| 1706 | - // 如果有验证错误,显示错误信息并询问是否继续 | |
| 1707 | - if (validationErrors.length > 0) { | |
| 1708 | - const errorMessage = validationErrors.join('\n'); | |
| 1709 | - console.log('序列号验证发现以下问题:', validationErrors); | |
| 1710 | - | |
| 1711 | - // 添加详细的验证结果说明 | |
| 1712 | - let detailedMessage = '序列号验证发现以下问题:\n\n'; | |
| 1713 | - detailedMessage += errorMessage; | |
| 1714 | - detailedMessage += '\n\n'; | |
| 1715 | - detailedMessage += '说明:\n'; | |
| 1716 | - detailedMessage += '- 序列号类型为"入1出1"或"入0出1"的商品必须选择序列号\n'; | |
| 1717 | - detailedMessage += '- 序列号数量必须与明细数量完全一致\n'; | |
| 1718 | - detailedMessage += '- 请为上述商品选择正确的序列号后重新保存'; | |
| 1719 | - | |
| 1720 | - await this.$alert(detailedMessage, '序列号验证结果', { | |
| 1721 | - confirmButtonText: '确定', | |
| 1722 | - type: 'warning' | |
| 1723 | - }); | |
| 1724 | - } else { | |
| 1725 | - this.$message.success('序列号验证通过!'); | |
| 1726 | - } | |
| 1727 | - }, | |
| 1728 | 1021 | getSummaries(param) { |
| 1729 | 1022 | const { columns, data } = param; |
| 1730 | 1023 | const sums = []; |
| ... | ... | @@ -1760,42 +1053,23 @@ |
| 1760 | 1053 | this.$set(row, 'je', undefined) |
| 1761 | 1054 | this.$set(row, 'description', '') |
| 1762 | 1055 | this.$set(row, 'kucun', undefined) |
| 1763 | - this.$set(row, 'spxlhLoaded', false) | |
| 1764 | - this.$set(row, 'xlhList', []) | |
| 1056 | + this.$set(row, 'bjhcb', undefined) | |
| 1057 | + this.$set(row, 'bjhcbManual', false) | |
| 1765 | 1058 | return |
| 1766 | 1059 | } |
| 1767 | - // 选中商品后可自动回填商品名称等信息 | |
| 1768 | 1060 | const product = this.spbhOptions.find(item => item.F_Id === row.spbh); |
| 1769 | 1061 | if (product) { |
| 1770 | 1062 | row.spmc = product.F_Spmc || ''; |
| 1771 | - // 选中商品后,立即加载商品信息(如序列号类型) | |
| 1772 | - this.getProductInfo(row.spbh).then(productInfo => { | |
| 1773 | - // 触发当前行的响应式刷新 | |
| 1774 | - this.$set(row, 'spxlhLoaded', true); | |
| 1775 | - this.$forceUpdate(); | |
| 1776 | - if (!(productInfo && productInfo.spxlhType)) { | |
| 1777 | - this.$message.warning('无法获取序列号类型信息'); | |
| 1778 | - } | |
| 1779 | - }); | |
| 1780 | - | |
| 1781 | - // 检查出库仓库并获取库存 | |
| 1782 | - console.log('商品选择变化 - 商品ID:', row.spbh, '出库仓库:', row.ckck, '主表出库仓库:', this.dataForm.cjck); | |
| 1783 | - | |
| 1784 | - // 如果明细行没有出库仓库,使用主表的出库仓库 | |
| 1785 | - if (!row.ckck && this.dataForm.cjck) { | |
| 1786 | - row.ckck = this.dataForm.cjck; | |
| 1787 | - console.log('使用主表出库仓库:', row.ckck); | |
| 1788 | - } | |
| 1789 | - | |
| 1790 | - // 如果主表也没有出库仓库,提示用户先选择 | |
| 1063 | + | |
| 1064 | + // 明细行默认使用主表出库/入库仓库 | |
| 1065 | + if (!row.ckck && this.dataForm.cjck) row.ckck = this.dataForm.cjck; | |
| 1066 | + if (!row.rkck && this.dataForm.rkck) row.rkck = this.dataForm.rkck; | |
| 1067 | + | |
| 1791 | 1068 | if (!row.ckck) { |
| 1792 | - console.log('出库仓库未设置,无法获取库存'); | |
| 1793 | 1069 | this.$message.warning('请先选择出库仓库'); |
| 1794 | 1070 | return; |
| 1795 | 1071 | } |
| 1796 | - | |
| 1797 | - // 如果已选择出库仓库,自动获取库存(内部会再拉成本单价) | |
| 1798 | - console.log('开始获取库存...'); | |
| 1072 | + // 自动获取库存 + 成本单价(成本来自 wt_sp_cost 的调出仓加权平均成本) | |
| 1799 | 1073 | await this.getStockQuantity(row); |
| 1800 | 1074 | } |
| 1801 | 1075 | }, |
| ... | ... | @@ -1822,77 +1096,82 @@ |
| 1822 | 1096 | }); |
| 1823 | 1097 | }, |
| 1824 | 1098 | |
| 1825 | - // 计算所有明细的变价后成本 | |
| 1099 | + /** | |
| 1100 | + * 按变价系数自动填充每行"变价后成本": | |
| 1101 | + * - 仅针对未手工录入(bjhcbManual !== true)的行 | |
| 1102 | + * - 变价系数为空时不清空用户已录入的 bjhcb | |
| 1103 | + */ | |
| 1826 | 1104 | calculateAllAdjustedCosts() { |
| 1827 | 1105 | if (!this.dataForm.bjsx || this.dataForm.bjsx === '') { |
| 1828 | - // 如果没有输入变价系数,清空所有变价后成本 | |
| 1829 | 1106 | this.dataForm.wtXsckdMxList.forEach(row => { |
| 1830 | - this.$set(row, 'bjhcb', ''); | |
| 1107 | + if (!row.bjhcbManual) this.$set(row, 'bjhcb', ''); | |
| 1831 | 1108 | }); |
| 1832 | 1109 | return; |
| 1833 | 1110 | } |
| 1834 | - | |
| 1835 | 1111 | const coefficient = parseFloat(this.dataForm.bjsx); |
| 1836 | - if (isNaN(coefficient)) { | |
| 1837 | - this.$message.warning('请输入有效的变价系数'); | |
| 1112 | + if (isNaN(coefficient) || coefficient <= 0) { | |
| 1838 | 1113 | return; |
| 1839 | 1114 | } |
| 1840 | - | |
| 1841 | - // 按照公式计算:变价后成本 = 成本 / (1 - 系数/100) | |
| 1842 | - // 例如:成本100,系数95,则变价后成本 = 100 / (1 - 95/100) = 100 / 0.05 = 2000 | |
| 1843 | - // 但根据你的要求:成本100,系数95,变价后成本是105.26 | |
| 1844 | - // 这个公式应该是:变价后成本 = 成本 / (系数/100) | |
| 1845 | - // 例如:成本100,系数95,则变价后成本 = 100 / (95/100) = 100 / 0.95 = 105.26 | |
| 1846 | - | |
| 1115 | + // 变价后成本 = 成本单价 / (系数/100),例:100 / (95/100) = 105.2632 | |
| 1847 | 1116 | this.dataForm.wtXsckdMxList.forEach(row => { |
| 1848 | - if (row.dj !== undefined && row.dj !== null && row.dj !== '') { | |
| 1849 | - const cost = parseFloat(row.dj); | |
| 1850 | - if (!isNaN(cost) && cost > 0) { | |
| 1851 | - const adjustedCost = cost / (coefficient / 100); | |
| 1852 | - this.$set(row, 'bjhcb', adjustedCost.toFixed(4)); | |
| 1853 | - } else { | |
| 1854 | - this.$set(row, 'bjhcb', ''); | |
| 1855 | - } | |
| 1117 | + if (row.bjhcbManual) return; | |
| 1118 | + const cost = parseFloat(row.dj); | |
| 1119 | + if (!isNaN(cost) && cost > 0) { | |
| 1120 | + this.$set(row, 'bjhcb', (cost / (coefficient / 100)).toFixed(4)); | |
| 1856 | 1121 | } else { |
| 1857 | 1122 | this.$set(row, 'bjhcb', ''); |
| 1858 | 1123 | } |
| 1859 | 1124 | }); |
| 1860 | 1125 | this.$forceUpdate(); |
| 1861 | 1126 | }, |
| 1862 | - | |
| 1863 | - // 计算单行的变价后成本 | |
| 1127 | + | |
| 1128 | + /** 单行根据系数重算变价后成本(仅在未手工录入时) */ | |
| 1864 | 1129 | calculateRowAdjustedCost(row) { |
| 1130 | + if (row.bjhcbManual) return; | |
| 1865 | 1131 | if (!this.dataForm.bjsx || this.dataForm.bjsx === '' || !row.dj || row.dj === '') { |
| 1866 | 1132 | this.$set(row, 'bjhcb', ''); |
| 1867 | 1133 | return; |
| 1868 | 1134 | } |
| 1869 | - | |
| 1870 | 1135 | const coefficient = parseFloat(this.dataForm.bjsx); |
| 1871 | 1136 | const cost = parseFloat(row.dj); |
| 1872 | - | |
| 1873 | - if (isNaN(coefficient) || isNaN(cost) || cost <= 0) { | |
| 1137 | + if (isNaN(coefficient) || coefficient <= 0 || isNaN(cost) || cost <= 0) { | |
| 1874 | 1138 | this.$set(row, 'bjhcb', ''); |
| 1875 | 1139 | return; |
| 1876 | 1140 | } |
| 1877 | - | |
| 1878 | - // 按照公式计算:变价后成本 = 成本 / (系数/100) | |
| 1879 | - // 例如:成本100,系数95,则变价后成本 = 100 / (95/100) = 100 / 0.95 = 105.26 | |
| 1880 | - const adjustedCost = cost / (coefficient / 100); | |
| 1881 | - this.$set(row, 'bjhcb', adjustedCost.toFixed(4)); | |
| 1141 | + this.$set(row, 'bjhcb', (cost / (coefficient / 100)).toFixed(4)); | |
| 1882 | 1142 | } |
| 1883 | 1143 | } |
| 1884 | 1144 | } |
| 1885 | 1145 | </script> |
| 1886 | 1146 | |
| 1887 | 1147 | <style scoped> |
| 1888 | -.serial-number-selector { | |
| 1889 | - min-height: 30px; | |
| 1148 | +/* 账面库存:文本 + 小刷新按钮(与其他 mini 输入框高度对齐) */ | |
| 1149 | +.bjdbd-kucun-cell { | |
| 1150 | + display: flex; | |
| 1151 | + align-items: center; | |
| 1152 | + justify-content: flex-end; | |
| 1153 | + line-height: 28px; | |
| 1154 | + height: 28px; | |
| 1155 | +} | |
| 1156 | +.bjdbd-kucun-text { | |
| 1157 | + flex: 1; | |
| 1158 | + text-align: right; | |
| 1159 | + padding-right: 6px; | |
| 1160 | + color: #303133; | |
| 1161 | + font-size: 13px; | |
| 1162 | + overflow: hidden; | |
| 1163 | + text-overflow: ellipsis; | |
| 1164 | + white-space: nowrap; | |
| 1165 | +} | |
| 1166 | +.bjdbd-kucun-refresh { | |
| 1167 | + padding: 0 4px !important; | |
| 1168 | + height: 24px; | |
| 1890 | 1169 | } |
| 1891 | 1170 | |
| 1892 | -.selected-serial-numbers { | |
| 1893 | - display: flex; | |
| 1894 | - flex-wrap: wrap; | |
| 1895 | - gap: 2px; | |
| 1896 | - margin-bottom: 5px; | |
| 1171 | +/* 明细表内所有 mini 控件统一高度,避免行高差异 */ | |
| 1172 | +.el-table--mini .el-input--mini .el-input__inner, | |
| 1173 | +.el-table--mini .el-input--mini .el-input__icon { | |
| 1174 | + height: 28px; | |
| 1175 | + line-height: 28px; | |
| 1897 | 1176 | } |
| 1898 | 1177 | </style> | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtBsd/Form.vue
| ... | ... | @@ -513,12 +513,12 @@ |
| 513 | 513 | |
| 514 | 514 | |
| 515 | 515 | getcjckOptions(){ |
| 516 | - previewDataInterface('681758216954053893').then(res => { | |
| 516 | + return previewDataInterface('681758216954053893').then(res => { | |
| 517 | 517 | this.cjckOptions = res.data |
| 518 | 518 | }); |
| 519 | 519 | }, |
| 520 | 520 | getrkckOptions(){ |
| 521 | - previewDataInterface('681758216954053893').then(res => { | |
| 521 | + return previewDataInterface('681758216954053893').then(res => { | |
| 522 | 522 | this.rkckOptions = res.data |
| 523 | 523 | }); |
| 524 | 524 | }, |
| ... | ... | @@ -543,10 +543,35 @@ |
| 543 | 543 | // }); |
| 544 | 544 | // }, |
| 545 | 545 | getckckOptions(){ |
| 546 | - previewDataInterface('681758216954053893').then(res => { | |
| 546 | + return previewDataInterface('681758216954053893').then(res => { | |
| 547 | 547 | this.ckckOptions = res.data |
| 548 | 548 | }); |
| 549 | 549 | }, |
| 550 | + /** | |
| 551 | + * 详情接口返回时后端已把 ckck/rkck/cjck/rkck 替换为展示名称(门店名称), | |
| 552 | + * 但编辑页的 el-select 绑定的是 ID;这里把名称反查回 ID,避免: | |
| 553 | + * 1) 仓库下拉显示空 | |
| 554 | + * 2) 按名称查询库存失败 → 账面库存为 0 不显示 | |
| 555 | + */ | |
| 556 | + resolveIdByName(val, options) { | |
| 557 | + if (val == null || val === '') return val; | |
| 558 | + const v = String(val).trim(); | |
| 559 | + if (!options || !options.length) return val; | |
| 560 | + // 若已经是合法 ID(命中选项 F_Id),直接返回 | |
| 561 | + if (options.some(o => o && String(o.F_Id) === v)) return val; | |
| 562 | + // 否则尝试按名称反查 | |
| 563 | + const hit = options.find(o => o && (o.F_mdmc === v || o.F_ckmc === v || o.F_name === v)); | |
| 564 | + return hit ? hit.F_Id : val; | |
| 565 | + }, | |
| 566 | + async ensureWarehouseOptionsReady() { | |
| 567 | + const tasks = []; | |
| 568 | + if (!this.cjckOptions || !this.cjckOptions.length) tasks.push(this.getcjckOptions()); | |
| 569 | + if (!this.rkckOptions || !this.rkckOptions.length) tasks.push(this.getrkckOptions()); | |
| 570 | + if (!this.ckckOptions || !this.ckckOptions.length) tasks.push(this.getckckOptions()); | |
| 571 | + if (tasks.length > 0) { | |
| 572 | + try { await Promise.all(tasks); } catch (e) { /* 选项加载失败时降级继续 */ } | |
| 573 | + } | |
| 574 | + }, | |
| 550 | 575 | // getspbhOptions(){ |
| 551 | 576 | // previewDataInterface('675937572047815941').then(res => { |
| 552 | 577 | // this.spbhOptions = res.data |
| ... | ... | @@ -612,26 +637,86 @@ |
| 612 | 637 | request({ |
| 613 | 638 | url: '/api/Extend/WtXsckd/' +id, |
| 614 | 639 | method: 'get' |
| 615 | - }).then(res =>{ | |
| 640 | + }).then(async res =>{ | |
| 616 | 641 | _this.dataForm = res.data; |
| 617 | 642 | console.log('编辑时加载的数据:', _this.dataForm); |
| 618 | 643 | console.log('明细数据:', _this.dataForm.wtXsckdMxList); |
| 619 | 644 | |
| 620 | - // 为每个明细项添加productQuery字段 | |
| 645 | + // 后端详情接口会把 cjck / 明细 ckck 等字段由 ID 替换为展示名称; | |
| 646 | + // 编辑页 el-select 绑的是 ID,且 getStockQuantity 也需要 ID, | |
| 647 | + // 这里等仓库选项加载好后做一次「名称 → ID」反查,解决账面库存不显示问题 | |
| 648 | + await _this.ensureWarehouseOptionsReady(); | |
| 649 | + _this.dataForm.cjck = _this.resolveIdByName(_this.dataForm.cjck, _this.cjckOptions); | |
| 650 | + _this.dataForm.rkck = _this.resolveIdByName(_this.dataForm.rkck, _this.rkckOptions); | |
| 651 | + | |
| 621 | 652 | if (_this.dataForm.wtXsckdMxList) { |
| 622 | 653 | _this.dataForm.wtXsckdMxList.forEach(item => { |
| 623 | 654 | if (!item.hasOwnProperty('productQuery')) { |
| 624 | 655 | _this.$set(item, 'productQuery', ''); |
| 625 | 656 | } |
| 657 | + if (!item.hasOwnProperty('loadingStock')) { | |
| 658 | + _this.$set(item, 'loadingStock', false); | |
| 659 | + } | |
| 660 | + // kucun 后端未落库,这里先显式声明为响应式属性, | |
| 661 | + // 后续 getStockQuantity 在 catch/else 分支中直接赋值才能触发视图更新 | |
| 662 | + if (!item.hasOwnProperty('kucun')) { | |
| 663 | + _this.$set(item, 'kucun', undefined); | |
| 664 | + } | |
| 665 | + // 明细仓库字段同样做名称→ID 反查 | |
| 666 | + _this.$set(item, 'ckck', _this.resolveIdByName(item.ckck, _this.ckckOptions)); | |
| 667 | + if (item.rkck) { | |
| 668 | + _this.$set(item, 'rkck', _this.resolveIdByName(item.rkck, _this.rkckOptions)); | |
| 669 | + } | |
| 670 | + // 金额回显:若后端 je 为 0 但 sl/dj 有值,则按 sl*dj 重算;否则回退使用 cbje | |
| 671 | + const slNum = parseFloat(item.sl) || 0; | |
| 672 | + const djNum = parseFloat(item.dj) || 0; | |
| 673 | + const jeNum = parseFloat(item.je) || 0; | |
| 674 | + const cbjeNum = parseFloat(item.cbje) || 0; | |
| 675 | + if (jeNum === 0) { | |
| 676 | + if (slNum > 0 && djNum > 0) { | |
| 677 | + _this.$set(item, 'je', (slNum * djNum).toFixed(2)); | |
| 678 | + } else if (cbjeNum > 0) { | |
| 679 | + _this.$set(item, 'je', cbjeNum.toFixed(2)); | |
| 680 | + if (djNum === 0) { | |
| 681 | + const cbdjNum = parseFloat(item.cbdj) || 0; | |
| 682 | + if (cbdjNum > 0) _this.$set(item, 'dj', cbdjNum.toFixed(4)); | |
| 683 | + } | |
| 684 | + } | |
| 685 | + } | |
| 626 | 686 | }); |
| 627 | 687 | } |
| 628 | 688 | |
| 629 | - // 初始化时计算总金额 | |
| 630 | 689 | _this.calculateTotalAmount(); |
| 631 | - // 同步明细表出库仓库 | |
| 632 | 690 | _this.syncDetailWarehouses(); |
| 633 | - // 恢复序列号信息 | |
| 634 | 691 | _this.restoreSerialNumbers(); |
| 692 | + // 序列号类型(spxlhType)只缓存在 productCache 中,且 row.spxlhLoaded | |
| 693 | + // 仅在 handleProductChange 里被置 true;打开已有单据需要主动拉一次, | |
| 694 | + // 否则「序列号类型」列会一直显示「加载中...」 | |
| 695 | + if (_this.dataForm.wtXsckdMxList && _this.dataForm.wtXsckdMxList.length > 0) { | |
| 696 | + _this.dataForm.wtXsckdMxList.forEach(item => { | |
| 697 | + if (!item.hasOwnProperty('spxlhLoaded')) { | |
| 698 | + _this.$set(item, 'spxlhLoaded', false); | |
| 699 | + } | |
| 700 | + }); | |
| 701 | + _this.getAllProductSerialNumberTypes().then(() => { | |
| 702 | + _this.dataForm.wtXsckdMxList.forEach(item => { | |
| 703 | + if (item.spbh) { | |
| 704 | + _this.$set(item, 'spxlhLoaded', true); | |
| 705 | + } | |
| 706 | + }); | |
| 707 | + _this.$forceUpdate(); | |
| 708 | + }).catch(err => { | |
| 709 | + console.error('批量获取序列号类型失败:', err); | |
| 710 | + }); | |
| 711 | + } | |
| 712 | + // 账面库存不落库,重新点开必须按商品+仓库查询刷新(此时 ckck 已是 ID) | |
| 713 | + if (_this.dataForm.wtXsckdMxList) { | |
| 714 | + _this.dataForm.wtXsckdMxList.forEach(item => { | |
| 715 | + if (item.spbh && item.ckck) { | |
| 716 | + _this.getStockQuantity(item); | |
| 717 | + } | |
| 718 | + }); | |
| 719 | + } | |
| 635 | 720 | }) |
| 636 | 721 | } |
| 637 | 722 | else{ |
| ... | ... | @@ -639,10 +724,26 @@ |
| 639 | 724 | } |
| 640 | 725 | }) |
| 641 | 726 | }, |
| 727 | + // 校验所有明细的报损数量不超过账面库存 | |
| 728 | + validateBsdSlNotExceedKucun() { | |
| 729 | + const list = this.dataForm.wtXsckdMxList || []; | |
| 730 | + for (let i = 0; i < list.length; i++) { | |
| 731 | + const row = list[i]; | |
| 732 | + if (!row || !row.spbh) continue; | |
| 733 | + const sl = parseFloat(row.sl); | |
| 734 | + const kucun = parseFloat(row.kucun); | |
| 735 | + if (!isNaN(kucun) && !isNaN(sl) && sl > kucun) { | |
| 736 | + this.$message.error(`第${i + 1}行商品"${row.spmc || ''}"报损数量(${sl})超过账面库存(${kucun}),请先修正`); | |
| 737 | + return false; | |
| 738 | + } | |
| 739 | + } | |
| 740 | + return true; | |
| 741 | + }, | |
| 642 | 742 | // 保存草稿:只做基础表单校验,不做序列号强校验,保存时带isDraft:true字段 |
| 643 | 743 | async saveDraft() { |
| 644 | 744 | // 确保单据类型字段赋值 |
| 645 | 745 | this.dataForm.djlx = '报损单'; |
| 746 | + if (!this.validateBsdSlNotExceedKucun()) return; | |
| 646 | 747 | this.$refs['elForm'].validate(async (valid) => { |
| 647 | 748 | if (valid) { |
| 648 | 749 | try { |
| ... | ... | @@ -685,6 +786,7 @@ |
| 685 | 786 | this.$message.warning(`第 ${mxCheck.emptyLineNos.join('、')} 行未选择商品,请先删除空白行后再提交`) |
| 686 | 787 | return |
| 687 | 788 | } |
| 789 | + if (!this.validateBsdSlNotExceedKucun()) return; | |
| 688 | 790 | // 1. 明细校验:序列号数量与销售数量一致性 |
| 689 | 791 | let validationErrors = []; |
| 690 | 792 | for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { |
| ... | ... | @@ -1032,8 +1134,15 @@ |
| 1032 | 1134 | } |
| 1033 | 1135 | }, |
| 1034 | 1136 | handleAmountChange(row) { |
| 1035 | - const sl = parseFloat(row.sl) || 0; | |
| 1137 | + let sl = parseFloat(row.sl) || 0; | |
| 1036 | 1138 | const dj = parseFloat(row.dj) || 0; |
| 1139 | + // 报损数量不能超过账面库存 | |
| 1140 | + const kucun = parseFloat(row.kucun); | |
| 1141 | + if (!isNaN(kucun) && sl > kucun) { | |
| 1142 | + this.$message.warning(`商品"${row.spmc || ''}"报损数量(${sl})不能超过账面库存(${kucun})`); | |
| 1143 | + sl = kucun; | |
| 1144 | + row.sl = kucun.toString(); | |
| 1145 | + } | |
| 1037 | 1146 | row.je = (sl * dj).toFixed(2); |
| 1038 | 1147 | // 自动计算总收款金额 |
| 1039 | 1148 | this.calculateTotalAmount(); | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtByd/Form.vue
| ... | ... | @@ -513,12 +513,12 @@ |
| 513 | 513 | |
| 514 | 514 | |
| 515 | 515 | getcjckOptions(){ |
| 516 | - previewDataInterface('681758216954053893').then(res => { | |
| 516 | + return previewDataInterface('681758216954053893').then(res => { | |
| 517 | 517 | this.cjckOptions = res.data |
| 518 | 518 | }); |
| 519 | 519 | }, |
| 520 | 520 | getrkckOptions(){ |
| 521 | - previewDataInterface('681758216954053893').then(res => { | |
| 521 | + return previewDataInterface('681758216954053893').then(res => { | |
| 522 | 522 | this.rkckOptions = res.data |
| 523 | 523 | }); |
| 524 | 524 | }, |
| ... | ... | @@ -543,10 +543,31 @@ |
| 543 | 543 | // }); |
| 544 | 544 | // }, |
| 545 | 545 | getckckOptions(){ |
| 546 | - previewDataInterface('681758216954053893').then(res => { | |
| 546 | + return previewDataInterface('681758216954053893').then(res => { | |
| 547 | 547 | this.ckckOptions = res.data |
| 548 | 548 | }); |
| 549 | 549 | }, |
| 550 | + /** | |
| 551 | + * 后端详情接口会把仓库字段由 ID 替换成展示名称,编辑页绑定 ID, | |
| 552 | + * 这里做名称→ID 反查,确保下拉能回显、按 ID 查账面库存生效 | |
| 553 | + */ | |
| 554 | + resolveIdByName(val, options) { | |
| 555 | + if (val == null || val === '') return val; | |
| 556 | + const v = String(val).trim(); | |
| 557 | + if (!options || !options.length) return val; | |
| 558 | + if (options.some(o => o && String(o.F_Id) === v)) return val; | |
| 559 | + const hit = options.find(o => o && (o.F_mdmc === v || o.F_ckmc === v || o.F_name === v)); | |
| 560 | + return hit ? hit.F_Id : val; | |
| 561 | + }, | |
| 562 | + async ensureWarehouseOptionsReady() { | |
| 563 | + const tasks = []; | |
| 564 | + if (!this.cjckOptions || !this.cjckOptions.length) tasks.push(this.getcjckOptions()); | |
| 565 | + if (!this.rkckOptions || !this.rkckOptions.length) tasks.push(this.getrkckOptions()); | |
| 566 | + if (!this.ckckOptions || !this.ckckOptions.length) tasks.push(this.getckckOptions()); | |
| 567 | + if (tasks.length > 0) { | |
| 568 | + try { await Promise.all(tasks); } catch (e) { /* 选项加载失败时降级继续 */ } | |
| 569 | + } | |
| 570 | + }, | |
| 550 | 571 | // getspbhOptions(){ |
| 551 | 572 | // previewDataInterface('675937572047815941').then(res => { |
| 552 | 573 | // this.spbhOptions = res.data |
| ... | ... | @@ -612,26 +633,86 @@ |
| 612 | 633 | request({ |
| 613 | 634 | url: '/api/Extend/WtXsckd/' +id, |
| 614 | 635 | method: 'get' |
| 615 | - }).then(res =>{ | |
| 636 | + }).then(async res =>{ | |
| 616 | 637 | _this.dataForm = res.data; |
| 617 | 638 | console.log('编辑时加载的数据:', _this.dataForm); |
| 618 | 639 | console.log('明细数据:', _this.dataForm.wtXsckdMxList); |
| 619 | 640 | |
| 620 | - // 为每个明细项添加productQuery字段 | |
| 641 | + // 后端详情接口会把 cjck / 明细 ckck 字段由 ID 替换为展示名称; | |
| 642 | + // 编辑页 el-select 绑的是 ID,且 getStockQuantity 也需要 ID, | |
| 643 | + // 这里等仓库选项加载好后做一次「名称 → ID」反查 | |
| 644 | + await _this.ensureWarehouseOptionsReady(); | |
| 645 | + _this.dataForm.cjck = _this.resolveIdByName(_this.dataForm.cjck, _this.cjckOptions); | |
| 646 | + _this.dataForm.rkck = _this.resolveIdByName(_this.dataForm.rkck, _this.rkckOptions); | |
| 647 | + | |
| 621 | 648 | if (_this.dataForm.wtXsckdMxList) { |
| 622 | 649 | _this.dataForm.wtXsckdMxList.forEach(item => { |
| 623 | 650 | if (!item.hasOwnProperty('productQuery')) { |
| 624 | 651 | _this.$set(item, 'productQuery', ''); |
| 625 | 652 | } |
| 653 | + if (!item.hasOwnProperty('loadingStock')) { | |
| 654 | + _this.$set(item, 'loadingStock', false); | |
| 655 | + } | |
| 656 | + // kucun 后端未落库,这里先显式声明为响应式属性, | |
| 657 | + // 后续 getStockQuantity 在 catch/else 分支中直接赋值才能触发视图更新 | |
| 658 | + if (!item.hasOwnProperty('kucun')) { | |
| 659 | + _this.$set(item, 'kucun', undefined); | |
| 660 | + } | |
| 661 | + // 明细仓库字段同样做名称→ID 反查 | |
| 662 | + _this.$set(item, 'ckck', _this.resolveIdByName(item.ckck, _this.ckckOptions)); | |
| 663 | + if (item.rkck) { | |
| 664 | + _this.$set(item, 'rkck', _this.resolveIdByName(item.rkck, _this.rkckOptions)); | |
| 665 | + } | |
| 666 | + // 金额回显:若后端 je 为 0 但 sl/dj 有值,则按 sl*dj 重算;否则回退使用 cbje | |
| 667 | + const slNum = parseFloat(item.sl) || 0; | |
| 668 | + const djNum = parseFloat(item.dj) || 0; | |
| 669 | + const jeNum = parseFloat(item.je) || 0; | |
| 670 | + const cbjeNum = parseFloat(item.cbje) || 0; | |
| 671 | + if (jeNum === 0) { | |
| 672 | + if (slNum > 0 && djNum > 0) { | |
| 673 | + _this.$set(item, 'je', (slNum * djNum).toFixed(2)); | |
| 674 | + } else if (cbjeNum > 0) { | |
| 675 | + _this.$set(item, 'je', cbjeNum.toFixed(2)); | |
| 676 | + if (djNum === 0) { | |
| 677 | + const cbdjNum = parseFloat(item.cbdj) || 0; | |
| 678 | + if (cbdjNum > 0) _this.$set(item, 'dj', cbdjNum.toFixed(4)); | |
| 679 | + } | |
| 680 | + } | |
| 681 | + } | |
| 626 | 682 | }); |
| 627 | 683 | } |
| 628 | 684 | |
| 629 | - // 初始化时计算总金额 | |
| 630 | 685 | _this.calculateTotalAmount(); |
| 631 | - // 同步明细表出库仓库 | |
| 632 | 686 | _this.syncDetailWarehouses(); |
| 633 | - // 恢复序列号信息 | |
| 634 | 687 | _this.restoreSerialNumbers(); |
| 688 | + // 序列号类型(spxlhType)只缓存在 productCache 中,且 row.spxlhLoaded | |
| 689 | + // 仅在 handleProductChange 里被置 true;打开已有单据需要主动拉一次, | |
| 690 | + // 否则「序列号类型」列会一直显示「加载中...」 | |
| 691 | + if (_this.dataForm.wtXsckdMxList && _this.dataForm.wtXsckdMxList.length > 0) { | |
| 692 | + _this.dataForm.wtXsckdMxList.forEach(item => { | |
| 693 | + if (!item.hasOwnProperty('spxlhLoaded')) { | |
| 694 | + _this.$set(item, 'spxlhLoaded', false); | |
| 695 | + } | |
| 696 | + }); | |
| 697 | + _this.getAllProductSerialNumberTypes().then(() => { | |
| 698 | + _this.dataForm.wtXsckdMxList.forEach(item => { | |
| 699 | + if (item.spbh) { | |
| 700 | + _this.$set(item, 'spxlhLoaded', true); | |
| 701 | + } | |
| 702 | + }); | |
| 703 | + _this.$forceUpdate(); | |
| 704 | + }).catch(err => { | |
| 705 | + console.error('批量获取序列号类型失败:', err); | |
| 706 | + }); | |
| 707 | + } | |
| 708 | + // 账面库存不落库,重新点开必须按商品+仓库查询刷新(此时 ckck 已是 ID) | |
| 709 | + if (_this.dataForm.wtXsckdMxList) { | |
| 710 | + _this.dataForm.wtXsckdMxList.forEach(item => { | |
| 711 | + if (item.spbh && item.ckck) { | |
| 712 | + _this.getStockQuantity(item); | |
| 713 | + } | |
| 714 | + }); | |
| 715 | + } | |
| 635 | 716 | }) |
| 636 | 717 | } |
| 637 | 718 | else{ | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgrkd/Form.vue
| 1 | 1 | <template> |
| 2 | 2 | <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%"> |
| 3 | 3 | <el-row :gutter="15" class="" > |
| 4 | - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> | |
| 4 | + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="formDisabled" :rules="rules"> | |
| 5 | 5 | <el-col :span="12" v-if="false"> |
| 6 | 6 | <el-form-item label="单据编号" prop="id"> |
| 7 | 7 | <el-input v-model="dataForm.id" placeholder="请输入" clearable readonly :style='{"width":"100%"}' > |
| ... | ... | @@ -196,8 +196,10 @@ |
| 196 | 196 | </el-row> |
| 197 | 197 | <span slot="footer" class="dialog-footer"> |
| 198 | 198 | <el-button @click="visible = false">取 消</el-button> |
| 199 | - <el-button type="default" @click="saveDraft()" v-if="!isDetail">保存草稿</el-button> | |
| 200 | - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">保 存</el-button> | |
| 199 | + <template v-if="!formDisabled"> | |
| 200 | + <el-button type="default" @click="saveDraft()">保存草稿</el-button> | |
| 201 | + <el-button type="primary" @click="dataFormSubmit()">提交审核</el-button> | |
| 202 | + </template> | |
| 201 | 203 | </span> |
| 202 | 204 | |
| 203 | 205 | <!-- 商品条码选择弹窗 --> |
| ... | ... | @@ -237,6 +239,9 @@ |
| 237 | 239 | |
| 238 | 240 | }, |
| 239 | 241 | rules: { |
| 242 | + gys: [ | |
| 243 | + { required: true, message: '请选择往来单位', trigger: 'change' } | |
| 244 | + ] | |
| 240 | 245 | }, |
| 241 | 246 | cjckOptions : [], |
| 242 | 247 | // rkckOptions : [], |
| ... | ... | @@ -249,6 +254,15 @@ |
| 249 | 254 | } |
| 250 | 255 | }, |
| 251 | 256 | watch: {}, |
| 257 | + computed: { | |
| 258 | + isBillLockedForEdit() { | |
| 259 | + const z = this.dataForm && this.dataForm.djzt != null ? String(this.dataForm.djzt).trim() : '' | |
| 260 | + return z === '待审核' || z === '已审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级' | |
| 261 | + }, | |
| 262 | + formDisabled() { | |
| 263 | + return !!this.isDetail || this.isBillLockedForEdit | |
| 264 | + } | |
| 265 | + }, | |
| 252 | 266 | created() { |
| 253 | 267 | this.getcjckOptions(); |
| 254 | 268 | // this.getrkckOptions(); |
| ... | ... | @@ -390,13 +404,22 @@ setFullName(item,row){ |
| 390 | 404 | }); |
| 391 | 405 | } |
| 392 | 406 | }) |
| 407 | + } else { | |
| 408 | + // 新建时 resetFields 会清空默认值,这里重新回填经手人/类型/状态 | |
| 409 | + const user = (this.$store && this.$store.getters && this.$store.getters.userInfo) ? this.$store.getters.userInfo : {} | |
| 410 | + this.dataForm.djrq = Date.now() | |
| 411 | + this.dataForm.jsr = user.userId || user.id || '' | |
| 412 | + this.dataForm.djlx = '采购入库单' | |
| 413 | + if (!this.dataForm.djzt) this.dataForm.djzt = '草稿' | |
| 393 | 414 | } |
| 394 | 415 | }) |
| 395 | 416 | }, |
| 396 | 417 | async saveDraft() { |
| 418 | + if (this.formDisabled) return | |
| 397 | 419 | await this.submitWithMode(true) |
| 398 | 420 | }, |
| 399 | 421 | async dataFormSubmit() { |
| 422 | + if (this.formDisabled) return | |
| 400 | 423 | await this.submitWithMode(false) |
| 401 | 424 | }, |
| 402 | 425 | async submitWithMode(isDraftMode) { | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgrkd/index.vue
| ... | ... | @@ -16,13 +16,18 @@ |
| 16 | 16 | </el-col> |
| 17 | 17 | <el-col :span="6"> |
| 18 | 18 | <el-form-item label="入库仓库"> |
| 19 | - <el-select v-model="query.cjck" placeholder="入库仓库" clearable > | |
| 19 | + <el-select v-model="query.rkck" placeholder="入库仓库" clearable > | |
| 20 | 20 | <el-option v-for="(item, index) in cjckOptions" :key="index" :label="item.F_mdmc" :value="item.F_Id" /> |
| 21 | 21 | </el-select> |
| 22 | 22 | </el-form-item> |
| 23 | 23 | </el-col> |
| 24 | 24 | <template v-if="showAll"> |
| 25 | 25 | <el-col :span="6"> |
| 26 | + <el-form-item label="往来单位"> | |
| 27 | + <el-input v-model="query.gys" placeholder="往来单位" clearable /> | |
| 28 | + </el-form-item> | |
| 29 | + </el-col> | |
| 30 | + <el-col :span="6"> | |
| 26 | 31 | <el-form-item label="经手人"> |
| 27 | 32 | <userSelect v-model="query.jsr" placeholder="请选择经手人" /> |
| 28 | 33 | </el-form-item> |
| ... | ... | @@ -84,26 +89,27 @@ |
| 84 | 89 | <screenfull isContainer /> |
| 85 | 90 | </div> |
| 86 | 91 | </div> |
| 87 | - <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange"> | |
| 88 | - <el-table-column prop="id" label="单据编号" align="left" /> | |
| 89 | - <el-table-column prop="djrq" label="单据日期" align="left" :formatter="ncc.tableDateFormat" /> | |
| 90 | - <el-table-column label="入库仓库" prop="cjck" align="left"> | |
| 91 | - <template slot-scope="scope">{{ scope.row.cjck | dynamicText(cjckOptions) }}</template> | |
| 92 | + <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange"> | |
| 93 | + <el-table-column prop="id" label="单据编号" align="left" show-overflow-tooltip class-name="cell-nowrap" /> | |
| 94 | + <el-table-column prop="djrq" label="单据日期" align="left" :formatter="ncc.tableDateFormat" show-overflow-tooltip class-name="cell-nowrap" /> | |
| 95 | + <el-table-column label="入库仓库" prop="rkck" align="left" show-overflow-tooltip class-name="cell-nowrap"> | |
| 96 | + <template slot-scope="scope">{{ scope.row.rkck | dynamicText(cjckOptions) }}</template> | |
| 92 | 97 | </el-table-column> |
| 93 | - <el-table-column prop="jsr" label="经手人" align="left" /> | |
| 94 | - <el-table-column label="付款账户" prop="skzh" align="left"> | |
| 98 | + <el-table-column prop="jsr" label="经手人" align="left" show-overflow-tooltip class-name="cell-nowrap" /> | |
| 99 | + <el-table-column prop="gys" label="往来单位" align="left" min-width="140" show-overflow-tooltip class-name="cell-nowrap" /> | |
| 100 | + <el-table-column label="付款账户" prop="skzh" align="left" show-overflow-tooltip class-name="cell-nowrap"> | |
| 95 | 101 | <template slot-scope="scope">{{ scope.row.skzh | dynamicText(skzhOptions) }}</template> |
| 96 | 102 | </el-table-column> |
| 97 | - <el-table-column prop="skje" label="付款金额" align="left" /> | |
| 98 | - <el-table-column prop="shr" label="审核人" align="left"> | |
| 103 | + <el-table-column prop="skje" label="付款金额" align="left" show-overflow-tooltip class-name="cell-nowrap" /> | |
| 104 | + <el-table-column prop="shr" label="审核人" align="left" show-overflow-tooltip class-name="cell-nowrap"> | |
| 99 | 105 | <template slot-scope="scope">{{ getShrDisplay(scope.row) }}</template> |
| 100 | 106 | </el-table-column> |
| 101 | - <el-table-column prop="gzr" label="过账人" align="left"> | |
| 107 | + <el-table-column prop="gzr" label="过账人" align="left" show-overflow-tooltip class-name="cell-nowrap"> | |
| 102 | 108 | <template slot-scope="scope">{{ getGzrDisplay(scope.row) }}</template> |
| 103 | 109 | </el-table-column> |
| 104 | - <el-table-column prop="bz" label="备注" align="left" /> | |
| 105 | - <el-table-column prop="djlx" label="单据类型" align="left" /> | |
| 106 | - <el-table-column prop="djzt" label="审核状态" align="left"> | |
| 110 | + <el-table-column prop="bz" label="备注" align="left" show-overflow-tooltip class-name="cell-nowrap" /> | |
| 111 | + <el-table-column prop="djlx" label="单据类型" align="left" show-overflow-tooltip class-name="cell-nowrap" /> | |
| 112 | + <el-table-column prop="djzt" label="审核状态" align="left" show-overflow-tooltip class-name="cell-nowrap"> | |
| 107 | 113 | <template slot-scope="scope"> |
| 108 | 114 | <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag> |
| 109 | 115 | <el-tag v-else-if="scope.row.djzt === '一级已审'" type="">一级已审</el-tag> |
| ... | ... | @@ -117,19 +123,14 @@ |
| 117 | 123 | <ncc-table-summary-cell :row="scope.row" fields="zy,Zy" /> |
| 118 | 124 | </template> |
| 119 | 125 | </el-table-column> |
| 120 | - <el-table-column label="操作" fixed="right" width="310"> | |
| 126 | + <el-table-column label="操作" fixed="right" width="310" show-overflow-tooltip class-name="cell-nowrap"> | |
| 121 | 127 | <template slot-scope="scope"> |
| 122 | 128 | <el-button type="text" @click="openDetail(scope.row.id)">查看</el-button> |
| 123 | - <el-button | |
| 124 | - type="text" | |
| 125 | - :disabled="scope.row.djzt && scope.row.djzt !== '草稿' && scope.row.djzt !== '审核不通过'" | |
| 126 | - @click="addOrUpdateHandle(scope.row.id)" | |
| 127 | - >编辑</el-button> | |
| 129 | + <el-button v-if="isDraftRow(scope.row)" type="text" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button> | |
| 128 | 130 | <el-button type="text" @click="handleApprove(scope.row.id)" v-if="scope.row.djzt === '待审核' || !scope.row.djzt" style="color:#E6A23C">一级审核</el-button> |
| 129 | 131 | <el-button type="text" @click="handleApprove(scope.row.id)" v-if="scope.row.djzt === '一级已审' || scope.row.djzt === '待二级' || scope.row.djzt === '一级已审/待二级'" style="color:#409EFF">二级审核</el-button> |
| 130 | - <el-button type="text" @click="handleReject(scope.row.id)" v-if="canRejectAuditPurchaseInbound(scope.row)" style="color:#F56C6C">审核不通过</el-button> | |
| 131 | 132 | <el-button type="text" @click="handleReverseApproval(scope.row.id)" v-if="scope.row.djzt === '已审核'" style="color:#F56C6C">反审</el-button> |
| 132 | - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 133 | + <el-button v-if="isDraftRow(scope.row)" type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button> | |
| 133 | 134 | </template> |
| 134 | 135 | </el-table-column> |
| 135 | 136 | </NCC-table> |
| ... | ... | @@ -147,7 +148,7 @@ |
| 147 | 148 | import DetailView from './detail-view' |
| 148 | 149 | import ExportBox from './ExportBox' |
| 149 | 150 | import { previewDataInterface } from '@/api/systemData/dataInterface' |
| 150 | - import { promptApprovalRemark, postApprovePurchaseInbound, postRejectGeneric } from '@/utils/wtRejectApproval' | |
| 151 | + import { promptApprovalRemark, postApprovePurchaseInbound } from '@/utils/wtRejectApproval' | |
| 151 | 152 | import { getAccountSelector } from '@/api/extend/wtAccount' |
| 152 | 153 | export default { |
| 153 | 154 | components: { NCCForm, DetailView, ExportBox }, |
| ... | ... | @@ -157,7 +158,8 @@ |
| 157 | 158 | query: { |
| 158 | 159 | id:undefined, |
| 159 | 160 | djrq:undefined, |
| 160 | - cjck:undefined, | |
| 161 | + rkck:undefined, | |
| 162 | + gys: undefined, | |
| 161 | 163 | jsr:undefined, |
| 162 | 164 | skzh:undefined, |
| 163 | 165 | skje:undefined, |
| ... | ... | @@ -204,6 +206,9 @@ |
| 204 | 206 | this.getskzhOptions(); |
| 205 | 207 | }, |
| 206 | 208 | methods: { |
| 209 | + isDraftRow(row) { | |
| 210 | + return row && String(row.djzt || '').trim() === '草稿' | |
| 211 | + }, | |
| 207 | 212 | getShrDisplay(row) { |
| 208 | 213 | const status = row && row.djzt |
| 209 | 214 | if (status === '一级已审' || status === '已审核') return (row && row.shr) || '' |
| ... | ... | @@ -295,24 +300,6 @@ |
| 295 | 300 | } |
| 296 | 301 | }).catch(() => {}); |
| 297 | 302 | }, |
| 298 | - canRejectAuditPurchaseInbound(row) { | |
| 299 | - const z = row && row.djzt | |
| 300 | - if (!row || z === '已审核' || z === '草稿' || z === '审核不通过') return false | |
| 301 | - return (z === '待审核' || !z) || z === '一级已审' || z === '待二级' || z === '一级已审/待二级' | |
| 302 | - }, | |
| 303 | - handleReject(id) { | |
| 304 | - promptApprovalRemark(this, '审核不通过') | |
| 305 | - .then(reason => postRejectGeneric(id, reason)) | |
| 306 | - .then(res => { | |
| 307 | - if (res.data && res.data.success) { | |
| 308 | - this.$message({ type: 'success', message: res.data.message || '已标记审核不通过' }) | |
| 309 | - this.initData() | |
| 310 | - } else { | |
| 311 | - this.$message({ type: 'error', message: (res.data && res.data.message) || '操作失败' }) | |
| 312 | - } | |
| 313 | - }) | |
| 314 | - .catch(() => {}) | |
| 315 | - }, | |
| 316 | 303 | handleReverseApproval(id) { |
| 317 | 304 | this.$confirm('确认反审该单据?反审后单据将恢复为草稿状态,可重新编辑。', '反审确认', { |
| 318 | 305 | type: 'warning' | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgthd/Form.vue
| 1 | 1 | <template> |
| 2 | - <el-dialog :title="isNew ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%" append-to-body> | |
| 2 | + <el-dialog :title="dialogTitle" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%" append-to-body> | |
| 3 | 3 | <el-row :gutter="15" class="" > |
| 4 | - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> | |
| 4 | + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="formDisabled" :rules="rules"> | |
| 5 | 5 | <el-col :span="12"> |
| 6 | 6 | <el-form-item label="单据编号" prop="id"> |
| 7 | 7 | <el-input v-model="dataForm.id" placeholder="保存后自动生成" readonly :style='{"width":"100%"}' /> |
| ... | ... | @@ -245,7 +245,10 @@ |
| 245 | 245 | </el-row> |
| 246 | 246 | <span slot="footer" class="dialog-footer"> |
| 247 | 247 | <el-button @click="visible = false">取 消</el-button> |
| 248 | - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button> | |
| 248 | + <template v-if="!formDisabled"> | |
| 249 | + <el-button @click="saveDraft">保存草稿</el-button> | |
| 250 | + <el-button type="primary" @click="dataFormSubmit">提交审核</el-button> | |
| 251 | + </template> | |
| 249 | 252 | </span> |
| 250 | 253 | <!-- 序列号选择弹窗 --> |
| 251 | 254 | <SerialNumberSelect ref="serialNumberSelect" @confirm="handleSerialNumberSelect" /> |
| ... | ... | @@ -305,6 +308,18 @@ |
| 305 | 308 | totalJe() { |
| 306 | 309 | return (this.dataForm.wtXsckdMxList || []).reduce((sum, row) => sum + (parseFloat(row.je) || 0), 0) |
| 307 | 310 | }, |
| 311 | + isBillLockedForEdit() { | |
| 312 | + const z = this.dataForm && this.dataForm.djzt != null ? String(this.dataForm.djzt).trim() : '' | |
| 313 | + return z === '待审核' || z === '已审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级' | |
| 314 | + }, | |
| 315 | + formDisabled() { | |
| 316 | + return !!this.isDetail || this.isBillLockedForEdit | |
| 317 | + }, | |
| 318 | + dialogTitle() { | |
| 319 | + if (this.isNew) return '新建' | |
| 320 | + if (this.isDetail || this.isBillLockedForEdit) return '详情' | |
| 321 | + return '编辑' | |
| 322 | + } | |
| 308 | 323 | }, |
| 309 | 324 | watch: {}, |
| 310 | 325 | created() { |
| ... | ... | @@ -448,9 +463,27 @@ |
| 448 | 463 | this.dataForm.wtXsckdMxList = []; |
| 449 | 464 | } |
| 450 | 465 | this.dataForm.wtXsckdMxList.forEach(item => { |
| 451 | - if (!item.selectedSerialNumbers) { | |
| 452 | - this.$set(item, 'selectedSerialNumbers', []); | |
| 466 | + // 兼容后端可能返回 selectedSerialNumbers / serialNumbers / serialNumber / xlh | |
| 467 | + const normalizeSnList = (val) => { | |
| 468 | + if (Array.isArray(val)) return val.map(s => String(s == null ? '' : s).trim()).filter(Boolean) | |
| 469 | + if (val == null || val === '') return [] | |
| 470 | + const txt = String(val).trim() | |
| 471 | + if (!txt) return [] | |
| 472 | + if ((txt.startsWith('[') && txt.endsWith(']')) || (txt.startsWith('{') && txt.endsWith('}'))) { | |
| 473 | + try { | |
| 474 | + const parsed = JSON.parse(txt) | |
| 475 | + if (Array.isArray(parsed)) return parsed.map(s => String(s == null ? '' : s).trim()).filter(Boolean) | |
| 476 | + } catch (e) {} | |
| 477 | + } | |
| 478 | + return txt.split(/[\n,,;;\s]+/).map(s => s.trim()).filter(Boolean) | |
| 453 | 479 | } |
| 480 | + const snList = normalizeSnList( | |
| 481 | + item.selectedSerialNumbers != null | |
| 482 | + ? item.selectedSerialNumbers | |
| 483 | + : (item.serialNumbers != null ? item.serialNumbers : (item.serialNumber != null ? item.serialNumber : item.xlh)) | |
| 484 | + ) | |
| 485 | + this.$set(item, 'selectedSerialNumbers', snList); | |
| 486 | + this.$set(item, 'serialNumbers', snList); | |
| 454 | 487 | if (!item.hasOwnProperty('spxlhLoaded')) { |
| 455 | 488 | this.$set(item, 'spxlhLoaded', false); |
| 456 | 489 | } |
| ... | ... | @@ -472,12 +505,26 @@ |
| 472 | 505 | this.$set(item, 'thdj', item.thdj || item.dj || undefined); |
| 473 | 506 | } |
| 474 | 507 | }); |
| 508 | + // 编辑回填:补拉商品信息,确保序列号类型可显示 | |
| 509 | + this.dataForm.wtXsckdMxList.forEach(item => { | |
| 510 | + if (item && item.spbh) { | |
| 511 | + this.getProductInfo(item.spbh).then(() => { | |
| 512 | + this.$set(item, 'spxlhLoaded', true); | |
| 513 | + this.$forceUpdate(); | |
| 514 | + }); | |
| 515 | + } | |
| 516 | + }); | |
| 475 | 517 | this.syncDetailWarehouses(); |
| 476 | 518 | this.updateTotalAmount(); |
| 477 | 519 | this.reloadInboundOrdersForExistingRows(); |
| 478 | 520 | }) |
| 479 | 521 | } else { |
| 480 | 522 | this.dataForm.wtXsckdMxList = []; |
| 523 | + this.dataForm.djrq = Date.now(); | |
| 524 | + const user = (this.$store && this.$store.getters && this.$store.getters.userInfo) ? this.$store.getters.userInfo : {}; | |
| 525 | + this.dataForm.jsr = user.userId || user.id || ''; | |
| 526 | + this.dataForm.djlx = '采购退货单'; | |
| 527 | + this.dataForm.djzt = '草稿'; | |
| 481 | 528 | // 预生成单据编号 |
| 482 | 529 | request({ |
| 483 | 530 | url: '/api/Extend/WtXsckd/Actions/GenerateBillNo', |
| ... | ... | @@ -491,7 +538,15 @@ |
| 491 | 538 | } |
| 492 | 539 | }) |
| 493 | 540 | }, |
| 541 | + saveDraft() { | |
| 542 | + if (this.formDisabled) return | |
| 543 | + this.submitCore(true) | |
| 544 | + }, | |
| 494 | 545 | dataFormSubmit() { |
| 546 | + if (this.formDisabled) return | |
| 547 | + this.submitCore(false) | |
| 548 | + }, | |
| 549 | + submitCore(isDraftMode) { | |
| 495 | 550 | const mxCheck = validateMxNoEmptyProductRows(this.dataForm.wtXsckdMxList) |
| 496 | 551 | if (!mxCheck.valid) { |
| 497 | 552 | this.$message.warning(`第 ${mxCheck.emptyLineNos.join('、')} 行未选择商品,请先删除空白行后再提交`) |
| ... | ... | @@ -574,14 +629,19 @@ |
| 574 | 629 | |
| 575 | 630 | this.$refs['elForm'].validate((valid) => { |
| 576 | 631 | if (valid) { |
| 632 | + const payload = { | |
| 633 | + ...this.dataForm, | |
| 634 | + isDraft: !!isDraftMode, | |
| 635 | + djzt: isDraftMode ? '草稿' : '待审核' | |
| 636 | + } | |
| 577 | 637 | if (this.isNew) { |
| 578 | 638 | request({ |
| 579 | 639 | url: `/api/Extend/WtXsckd`, |
| 580 | 640 | method: 'post', |
| 581 | - data: this.dataForm, | |
| 641 | + data: payload, | |
| 582 | 642 | }).then((res) => { |
| 583 | 643 | this.$message({ |
| 584 | - message: res.msg, | |
| 644 | + message: isDraftMode ? '草稿保存成功' : (res.msg || '保存成功,已提交审核'), | |
| 585 | 645 | type: 'success', |
| 586 | 646 | duration: 1000, |
| 587 | 647 | onClose: () => { |
| ... | ... | @@ -594,10 +654,10 @@ |
| 594 | 654 | request({ |
| 595 | 655 | url: '/api/Extend/WtXsckd/' + this.dataForm.id, |
| 596 | 656 | method: 'PUT', |
| 597 | - data: this.dataForm | |
| 657 | + data: payload | |
| 598 | 658 | }).then((res) => { |
| 599 | 659 | this.$message({ |
| 600 | - message: res.msg, | |
| 660 | + message: isDraftMode ? '草稿保存成功' : (res.msg || '保存成功,已提交审核'), | |
| 601 | 661 | type: 'success', |
| 602 | 662 | duration: 1000, |
| 603 | 663 | onClose: () => { | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgthd/detail-view.vue
| ... | ... | @@ -251,9 +251,25 @@ export default { |
| 251 | 251 | }, |
| 252 | 252 | methods: { |
| 253 | 253 | serialList(row) { |
| 254 | - const raw = row && row.selectedSerialNumbers | |
| 255 | - if (!raw || !raw.length) return [] | |
| 256 | - return raw.map(s => (s == null ? '' : String(s)).trim()).filter(Boolean) | |
| 254 | + if (!row) return [] | |
| 255 | + // 兼容多来源:selectedSerialNumbers / serialNumbers / serialNumber / xlh | |
| 256 | + const raw = row.selectedSerialNumbers != null | |
| 257 | + ? row.selectedSerialNumbers | |
| 258 | + : (row.serialNumbers != null ? row.serialNumbers : (row.serialNumber != null ? row.serialNumber : row.xlh)) | |
| 259 | + if (Array.isArray(raw)) { | |
| 260 | + return raw.map(s => (s == null ? '' : String(s)).trim()).filter(Boolean) | |
| 261 | + } | |
| 262 | + if (raw == null || raw === '') return [] | |
| 263 | + // 兼容后端可能返回 JSON 字符串或逗号/换行分隔字符串 | |
| 264 | + const text = String(raw).trim() | |
| 265 | + if (!text) return [] | |
| 266 | + if ((text.startsWith('[') && text.endsWith(']')) || (text.startsWith('{') && text.endsWith('}'))) { | |
| 267 | + try { | |
| 268 | + const arr = JSON.parse(text) | |
| 269 | + if (Array.isArray(arr)) return arr.map(s => (s == null ? '' : String(s)).trim()).filter(Boolean) | |
| 270 | + } catch (e) {} | |
| 271 | + } | |
| 272 | + return text.split(/[\n,,;;\s]+/).map(s => s.trim()).filter(Boolean) | |
| 257 | 273 | }, |
| 258 | 274 | serialPreview(row) { |
| 259 | 275 | const all = this.serialList(row) | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgthd/index.vue
| ... | ... | @@ -123,16 +123,25 @@ |
| 123 | 123 | <el-table-column prop="gzr" label="过账人" align="left" /> |
| 124 | 124 | <el-table-column prop="bz" label="备注" align="left" /> |
| 125 | 125 | <el-table-column prop="djlx" label="单据类型" align="left" /> |
| 126 | + <el-table-column prop="djzt" label="审核状态" align="left" width="120"> | |
| 127 | + <template slot-scope="scope"> | |
| 128 | + <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag> | |
| 129 | + <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag> | |
| 130 | + <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag> | |
| 131 | + </template> | |
| 132 | + </el-table-column> | |
| 126 | 133 | <el-table-column label="摘要" align="left" min-width="200" show-overflow-tooltip class-name="cell-nowrap"> |
| 127 | 134 | <template slot-scope="scope"> |
| 128 | 135 | <ncc-table-summary-cell :row="scope.row" fields="zy,Zy" /> |
| 129 | 136 | </template> |
| 130 | 137 | </el-table-column> |
| 131 | - <el-table-column label="操作" fixed="right" width="140"> | |
| 138 | + <el-table-column label="操作" fixed="right" width="260"> | |
| 132 | 139 | <template slot-scope="scope"> |
| 133 | 140 | <el-button type="text" @click="openDetail(scope.row.id)" >查看</el-button> |
| 134 | - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 135 | - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 141 | + <el-button v-if="isDraftRow(scope.row)" type="text" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button> | |
| 142 | + <el-button v-if="isPendingAudit(scope.row)" type="text" style="color:#409EFF" @click="handleApprove(scope.row.id)">审核</el-button> | |
| 143 | + <el-button v-if="scope.row.djzt === '已审核'" type="text" style="color:#E6A23C" @click="handleReverse(scope.row.id)">反审</el-button> | |
| 144 | + <el-button v-if="isDraftRow(scope.row)" type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button> | |
| 136 | 145 | </template> |
| 137 | 146 | </el-table-column> |
| 138 | 147 | </NCC-table> |
| ... | ... | @@ -214,6 +223,51 @@ |
| 214 | 223 | this.getgysOptions(); |
| 215 | 224 | }, |
| 216 | 225 | methods: { |
| 226 | + isDraftRow(row) { | |
| 227 | + return row && String(row.djzt || '').trim() === '草稿' | |
| 228 | + }, | |
| 229 | + isPendingAudit(row) { | |
| 230 | + const z = row && String(row.djzt || '').trim() | |
| 231 | + return z === '待审核' || z === '' | |
| 232 | + }, | |
| 233 | + handleApprove(id) { | |
| 234 | + this.$confirm('确认审核该采购退货单?', '提示', { type: 'warning' }).then(() => { | |
| 235 | + request({ | |
| 236 | + url: `/api/Extend/WtXsckd/ApproveGeneric/${id}`, | |
| 237 | + method: 'POST', | |
| 238 | + data: {} | |
| 239 | + }).then(res => { | |
| 240 | + const d = (res && res.data) || {} | |
| 241 | + const ok = d.success === true || String(res && res.code) === '200' | |
| 242 | + const msg = d.message || res.msg || (ok ? '审核成功' : '审核失败') | |
| 243 | + if (ok) { | |
| 244 | + this.$message.success(msg) | |
| 245 | + this.initData() | |
| 246 | + } else { | |
| 247 | + this.$message.error(msg) | |
| 248 | + } | |
| 249 | + }) | |
| 250 | + }).catch(() => {}) | |
| 251 | + }, | |
| 252 | + handleReverse(id) { | |
| 253 | + this.$confirm('反审后单据将恢复草稿并可编辑,是否继续?', '反审确认', { type: 'warning' }).then(() => { | |
| 254 | + request({ | |
| 255 | + url: `/api/Extend/WtXsckd/ReverseApproval/${id}`, | |
| 256 | + method: 'POST', | |
| 257 | + data: {} | |
| 258 | + }).then(res => { | |
| 259 | + const d = (res && res.data) || {} | |
| 260 | + const ok = d.success === true || String(res && res.code) === '200' | |
| 261 | + const msg = d.message || res.msg || (ok ? '反审成功' : '反审失败') | |
| 262 | + if (ok) { | |
| 263 | + this.$message.success(msg) | |
| 264 | + this.initData() | |
| 265 | + } else { | |
| 266 | + this.$message.error(msg) | |
| 267 | + } | |
| 268 | + }) | |
| 269 | + }).catch(() => {}) | |
| 270 | + }, | |
| 217 | 271 | getcjckOptions(){ |
| 218 | 272 | previewDataInterface('681758216954053893').then(res => { |
| 219 | 273 | this.cjckOptions = res.data | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_fkd/Form.vue
| 1 | 1 | <template> |
| 2 | - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%"> | |
| 2 | + <el-dialog :title="dialogTitle" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%"> | |
| 3 | 3 | <el-row :gutter="15" class="" > |
| 4 | - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> | |
| 4 | + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="formDisabled" :rules="rules"> | |
| 5 | 5 | <el-col :span="8"> |
| 6 | 6 | <el-form-item label="单据编号" prop="id"> |
| 7 | 7 | <el-input v-model="dataForm.id" placeholder="请输入" clearable readonly :style='{"width":"100%"}' > |
| ... | ... | @@ -23,8 +23,7 @@ |
| 23 | 23 | </el-col> |
| 24 | 24 | <el-col :span="8"> |
| 25 | 25 | <el-form-item label="经手人" prop="jsr"> |
| 26 | - <user-select v-model="dataForm.jsr" placeholder="请选择" clearable > | |
| 27 | - </user-select> | |
| 26 | + <el-input :value="jsrDisplayText" readonly placeholder="由明细所选账号带出" /> | |
| 28 | 27 | </el-form-item> |
| 29 | 28 | </el-col> |
| 30 | 29 | <el-col :span="16"> |
| ... | ... | @@ -33,6 +32,11 @@ |
| 33 | 32 | </el-input> |
| 34 | 33 | </el-form-item> |
| 35 | 34 | </el-col> |
| 35 | + <el-col :span="8" v-if="dataForm.id"> | |
| 36 | + <el-form-item label="审核状态"> | |
| 37 | + <el-input :value="auditStatusText" readonly /> | |
| 38 | + </el-form-item> | |
| 39 | + </el-col> | |
| 36 | 40 | <el-col :span="24" v-if="dataForm.id"> |
| 37 | 41 | <el-form-item label="业务摘要"> |
| 38 | 42 | <ncc-bill-summary :value="dataForm.billZy" mode="block" /> |
| ... | ... | @@ -45,7 +49,7 @@ |
| 45 | 49 | <!-- 费用项目 / 固定资产:按需求取消 --> |
| 46 | 50 | <el-table-column prop="zhbh" label="账号"> |
| 47 | 51 | <template slot-scope="scope"> |
| 48 | - <el-select v-model="scope.row.zhbh" placeholder="请选择" clearable filterable> | |
| 52 | + <el-select v-model="scope.row.zhbh" placeholder="请选择" clearable filterable @change="syncFsjeFromMx"> | |
| 49 | 53 | <el-option v-for="(item, index) in zhbhOptions" :key="index" :label="item.fullName" :value="item.id" :disabled="item.disabled"></el-option> |
| 50 | 54 | </el-select> |
| 51 | 55 | </template> |
| ... | ... | @@ -73,13 +77,13 @@ |
| 73 | 77 | <el-input v-model="scope.row.djlx" placeholder="请输入" clearable ></el-input> |
| 74 | 78 | </template> |
| 75 | 79 | </el-table-column> |
| 76 | - <el-table-column label="操作" width="50"> | |
| 80 | + <el-table-column v-if="!formDisabled" label="操作" width="50"> | |
| 77 | 81 | <template slot-scope="scope"> |
| 78 | 82 | <el-button size="mini" type="text" class="NCC-table-delBtn" @click="handleDelWtCwdjmxEntityList(scope.$index)">删除</el-button> |
| 79 | 83 | </template> |
| 80 | 84 | </el-table-column> |
| 81 | 85 | </el-table> |
| 82 | - <div class="table-actions" @click="addHandleWtCwdjmxEntityList()"> | |
| 86 | + <div v-if="!formDisabled" class="table-actions" @click="addHandleWtCwdjmxEntityList()"> | |
| 83 | 87 | <el-button type="text" icon="el-icon-plus">新增</el-button> |
| 84 | 88 | </div> |
| 85 | 89 | </el-form-item> |
| ... | ... | @@ -113,7 +117,10 @@ |
| 113 | 117 | </el-row> |
| 114 | 118 | <span slot="footer" class="dialog-footer"> |
| 115 | 119 | <el-button @click="visible = false">取 消</el-button> |
| 116 | - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button> | |
| 120 | + <template v-if="!formDisabled"> | |
| 121 | + <el-button @click="submitAsDraft">保存草稿</el-button> | |
| 122 | + <el-button type="primary" @click="submitForAudit">提 交</el-button> | |
| 123 | + </template> | |
| 117 | 124 | </span> |
| 118 | 125 | </el-dialog> |
| 119 | 126 | </template> |
| ... | ... | @@ -130,9 +137,9 @@ |
| 130 | 137 | loading: false, |
| 131 | 138 | visible: false, |
| 132 | 139 | isDetail: false, |
| 140 | + pendingDjzt: '待审核', | |
| 133 | 141 | dataForm: { |
| 134 | - id:'', | |
| 135 | - id:undefined, | |
| 142 | + id: undefined, | |
| 136 | 143 | ldrq:undefined, |
| 137 | 144 | wldw:undefined, |
| 138 | 145 | jsr:undefined, |
| ... | ... | @@ -143,6 +150,7 @@ |
| 143 | 150 | skzh:undefined, |
| 144 | 151 | fsje:undefined, |
| 145 | 152 | djlx:undefined, |
| 153 | + djzt: undefined, | |
| 146 | 154 | }, |
| 147 | 155 | rules: { |
| 148 | 156 | wldw: [{ required: true, message: '请选择往来单位', trigger: 'change' }], |
| ... | ... | @@ -159,6 +167,39 @@ |
| 159 | 167 | const rows = Array.isArray(this.dataForm.wtCwdjmxList) ? this.dataForm.wtCwdjmxList : [] |
| 160 | 168 | return rows.reduce((s, r) => s + (Number(r.ybje) || 0), 0) |
| 161 | 169 | }, |
| 170 | + auditStatusText() { | |
| 171 | + const z = this.dataForm.djzt != null && this.dataForm.djzt !== '' ? String(this.dataForm.djzt).trim() : '' | |
| 172 | + if (z) return z | |
| 173 | + return '—' | |
| 174 | + }, | |
| 175 | + isBillLockedForEdit() { | |
| 176 | + const z = this.auditStatusText | |
| 177 | + return z === '待审核' || z === '已审核' | |
| 178 | + }, | |
| 179 | + formDisabled() { | |
| 180 | + return !!this.isDetail || this.isBillLockedForEdit | |
| 181 | + }, | |
| 182 | + dialogTitle() { | |
| 183 | + if (!this.dataForm.id) return '新建' | |
| 184 | + if (this.isDetail) return '详情' | |
| 185 | + const z = this.auditStatusText | |
| 186 | + if (z === '已审核') return '查看(已审核)' | |
| 187 | + if (z === '待审核') return '查看(待审核)' | |
| 188 | + if (z === '草稿') return '编辑' | |
| 189 | + return '编辑' | |
| 190 | + }, | |
| 191 | + jsrDisplayText() { | |
| 192 | + const rows = Array.isArray(this.dataForm.wtCwdjmxList) ? this.dataForm.wtCwdjmxList : [] | |
| 193 | + for (let i = 0; i < rows.length; i++) { | |
| 194 | + const r = rows[i] | |
| 195 | + if (r && r.zhbh) { | |
| 196 | + const opt = (this.zhbhOptions || []).find(o => o && o.id === r.zhbh) | |
| 197 | + if (opt && opt.fullName) return opt.fullName | |
| 198 | + } | |
| 199 | + } | |
| 200 | + const j = this.dataForm.jsr | |
| 201 | + return (j != null && j !== '') ? String(j) : '—' | |
| 202 | + }, | |
| 162 | 203 | }, |
| 163 | 204 | watch: {}, |
| 164 | 205 | created() { |
| ... | ... | @@ -215,6 +256,7 @@ |
| 215 | 256 | this.dataForm.id = id || 0; |
| 216 | 257 | this.visible = true; |
| 217 | 258 | this.isDetail = isDetail || false; |
| 259 | + this.pendingDjzt = '待审核' | |
| 218 | 260 | this.$nextTick(() => { |
| 219 | 261 | this.$refs['elForm'].resetFields(); |
| 220 | 262 | if (this.dataForm.id) { |
| ... | ... | @@ -227,18 +269,28 @@ |
| 227 | 269 | } else { |
| 228 | 270 | // 新建时设置默认值 |
| 229 | 271 | this.dataForm.ldrq = Date.now(); // 录单日期默认为当前日期 |
| 230 | - var user = (this.$store && this.$store.getters && this.$store.getters.userInfo) ? this.$store.getters.userInfo : {}; | |
| 231 | - this.dataForm.jsr = user.userId || user.id || ''; // 经手人默认为当前用户 | |
| 232 | 272 | this.dataForm.djlx = '付款单'; // 主表单据类型默认值 |
| 273 | + this.dataForm.djzt = '草稿' | |
| 233 | 274 | this.dataForm.wtCwdjmxList = [] |
| 234 | 275 | this.addHandleWtCwdjmxEntityList() |
| 235 | 276 | } |
| 236 | 277 | }) |
| 237 | 278 | }, |
| 279 | + submitAsDraft() { | |
| 280 | + this.pendingDjzt = '草稿' | |
| 281 | + this.dataFormSubmit() | |
| 282 | + }, | |
| 283 | + submitForAudit() { | |
| 284 | + this.pendingDjzt = '待审核' | |
| 285 | + this.dataFormSubmit() | |
| 286 | + }, | |
| 238 | 287 | dataFormSubmit() { |
| 288 | + if (this.formDisabled) return | |
| 239 | 289 | this.syncFsjeFromMx() |
| 240 | 290 | this.$refs['elForm'].validate((valid) => { |
| 241 | 291 | if (valid) { |
| 292 | + this.dataForm.djlx = '付款单'; | |
| 293 | + this.$set(this.dataForm, 'djzt', this.pendingDjzt || '待审核') | |
| 242 | 294 | if (!this.dataForm.id) { |
| 243 | 295 | request({ |
| 244 | 296 | url: `/api/Extend/WtCwdj`, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_fkd/index.vue
| ... | ... | @@ -24,7 +24,7 @@ |
| 24 | 24 | <template v-if="showAll"> |
| 25 | 25 | <el-col :span="6"> |
| 26 | 26 | <el-form-item label="经手人"> |
| 27 | - <userSelect v-model="query.jsr" placeholder="请选择经手人" /> | |
| 27 | + <el-input v-model="query.jsr" placeholder="账户名称等" clearable /> | |
| 28 | 28 | </el-form-item> |
| 29 | 29 | </el-col> |
| 30 | 30 | <el-col :span="6"> |
| ... | ... | @@ -68,16 +68,25 @@ |
| 68 | 68 | <template slot-scope="scope">{{ scope.row.wldw | dynamicText(wldwOptions) }}</template> |
| 69 | 69 | </el-table-column> |
| 70 | 70 | <el-table-column prop="jsr" label="经手人" align="left" /> |
| 71 | + <el-table-column prop="djzt" label="审核状态" align="left" width="110"> | |
| 72 | + <template slot-scope="scope"> | |
| 73 | + <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag> | |
| 74 | + <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag> | |
| 75 | + <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag> | |
| 76 | + </template> | |
| 77 | + </el-table-column> | |
| 71 | 78 | <el-table-column label="摘要" align="left" min-width="200"> |
| 72 | 79 | <template slot-scope="scope"> |
| 73 | 80 | <ncc-table-summary-cell :row="scope.row" fields="billZy,BillZy,zy,Zy" /> |
| 74 | 81 | </template> |
| 75 | 82 | </el-table-column> |
| 76 | - <el-table-column label="操作" fixed="right" width="140"> | |
| 83 | + <el-table-column label="操作" fixed="right" width="240"> | |
| 77 | 84 | <template slot-scope="scope"> |
| 78 | 85 | <el-button type="text" @click="openDetail(scope.row.id)" >查看</el-button> |
| 79 | - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 80 | - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 86 | + <el-button type="text" v-if="isDraftRow(scope.row)" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 87 | + <el-button type="text" v-if="isPendingAudit(scope.row)" @click="handleApprove(scope.row.id)" style="color:#409EFF">审核</el-button> | |
| 88 | + <el-button type="text" v-if="scope.row.djzt === '已审核'" @click="handleReverse(scope.row.id)" style="color:#E6A23C">反审</el-button> | |
| 89 | + <el-button type="text" v-if="isDraftRow(scope.row)" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 81 | 90 | </template> |
| 82 | 91 | </el-table-column> |
| 83 | 92 | </NCC-table> |
| ... | ... | @@ -140,6 +149,48 @@ |
| 140 | 149 | this.getskzhOptions(); |
| 141 | 150 | }, |
| 142 | 151 | methods: { |
| 152 | + isDraftRow(row) { | |
| 153 | + return row && String(row.djzt || '').trim() === '草稿' | |
| 154 | + }, | |
| 155 | + isPendingAudit(row) { | |
| 156 | + const z = row && String(row.djzt || '').trim() | |
| 157 | + return z === '待审核' || z === '' | |
| 158 | + }, | |
| 159 | + handleApprove(id) { | |
| 160 | + this.$confirm('确认审核该付款单?', '提示', { type: 'warning' }).then(() => { | |
| 161 | + request({ | |
| 162 | + url: `/api/Extend/WtCwdj/Actions/ApprovePayment/${id}`, | |
| 163 | + method: 'POST', | |
| 164 | + data: {} | |
| 165 | + }).then(res => { | |
| 166 | + this.$message({ type: 'success', message: res.msg || '审核成功', duration: 1200 }) | |
| 167 | + this.initData() | |
| 168 | + }) | |
| 169 | + }).catch(() => {}) | |
| 170 | + }, | |
| 171 | + handleReverse(id) { | |
| 172 | + if (!id) return | |
| 173 | + this.$confirm('反审后单据将恢复为草稿,可再次编辑;摘要汇总表中对应记录将移除。是否继续?', '反审确认', { type: 'warning' }) | |
| 174 | + .then(() => { | |
| 175 | + return request({ | |
| 176 | + url: `/api/Extend/WtCwdj/Actions/ReversePaymentAudit/${id}`, | |
| 177 | + method: 'POST', | |
| 178 | + data: {} | |
| 179 | + }) | |
| 180 | + }) | |
| 181 | + .then((res) => { | |
| 182 | + const d = (res && res.data) || {} | |
| 183 | + const ok = d.success === true || String(res && res.code) === '200' | |
| 184 | + const msg = d.message || res.msg || (ok ? '反审成功' : '操作失败') | |
| 185 | + if (ok) { | |
| 186 | + this.$message.success(msg) | |
| 187 | + this.initData() | |
| 188 | + } else { | |
| 189 | + this.$message.error(msg) | |
| 190 | + } | |
| 191 | + }) | |
| 192 | + .catch(() => {}) | |
| 193 | + }, | |
| 143 | 194 | getwldwOptions(){ |
| 144 | 195 | previewDataInterface('716168694526379269').then(res => { |
| 145 | 196 | this.wldwOptions = res.data | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_fyd/Form.vue
| ... | ... | @@ -51,11 +51,9 @@ |
| 51 | 51 | <template slot-scope="scope"> |
| 52 | 52 | <el-select |
| 53 | 53 | v-model="scope.row.fyxmbh" |
| 54 | - placeholder="请选择或输入" | |
| 54 | + placeholder="请选择费用项目" | |
| 55 | 55 | clearable |
| 56 | - filterable | |
| 57 | - allow-create | |
| 58 | - default-first-option> | |
| 56 | + filterable> | |
| 59 | 57 | <el-option v-for="(item, index) in fyxmbhOptions" :key="index" :label="item.fullName" :value="item.id" :disabled="item.disabled"></el-option> |
| 60 | 58 | </el-select> |
| 61 | 59 | </template> |
| ... | ... | @@ -143,6 +141,10 @@ |
| 143 | 141 | import { getDictionaryDataSelector } from '@/api/systemData/dictionary' |
| 144 | 142 | import { previewDataInterface } from '@/api/systemData/dataInterface' |
| 145 | 143 | import { getAccountSelector } from '@/api/extend/wtAccount' |
| 144 | + | |
| 145 | + /** 数据字典「费用项目」字典类型 id(与 /api/system/DictionaryData/{id} 一致) */ | |
| 146 | + const FYXM_DICT_ID = '816913803978474757' | |
| 147 | + | |
| 146 | 148 | export default { |
| 147 | 149 | components: {}, |
| 148 | 150 | props: [], |
| ... | ... | @@ -223,8 +225,8 @@ |
| 223 | 225 | }); |
| 224 | 226 | }, |
| 225 | 227 | getfyxmbhOptions(){ |
| 226 | - getDictionaryDataSelector('715562947862070533').then(res => { | |
| 227 | - this.fyxmbhOptions = res.data.list | |
| 228 | + getDictionaryDataSelector(FYXM_DICT_ID).then(res => { | |
| 229 | + this.fyxmbhOptions = (res.data && res.data.list) ? res.data.list : [] | |
| 228 | 230 | }); |
| 229 | 231 | }, |
| 230 | 232 | getgdzcbhOptions(){ | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_fyd/detail-view.vue
| ... | ... | @@ -145,6 +145,9 @@ import { getDictionaryDataSelector } from '@/api/systemData/dictionary' |
| 145 | 145 | import { dynamicText } from '@/filters' |
| 146 | 146 | import { getAccountSelector } from '@/api/extend/wtAccount' |
| 147 | 147 | |
| 148 | +/** 数据字典「费用项目」字典类型 id */ | |
| 149 | +const FYXM_DICT_ID = '816913803978474757' | |
| 150 | + | |
| 148 | 151 | export default { |
| 149 | 152 | name: 'WtCwdjFydDetailView', |
| 150 | 153 | data() { |
| ... | ... | @@ -242,8 +245,8 @@ export default { |
| 242 | 245 | getAccountSelector().then(res => { |
| 243 | 246 | this.zhbhOptions = res.data.list || [] |
| 244 | 247 | }) |
| 245 | - getDictionaryDataSelector('715562947862070533').then(res => { | |
| 246 | - this.fyxmbhOptions = res.data.list || [] | |
| 248 | + getDictionaryDataSelector(FYXM_DICT_ID).then(res => { | |
| 249 | + this.fyxmbhOptions = (res.data && res.data.list) ? res.data.list : [] | |
| 247 | 250 | }) |
| 248 | 251 | } |
| 249 | 252 | } | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_skd/Form.vue
| 1 | 1 | <template> |
| 2 | - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%"> | |
| 2 | + <el-dialog :title="dialogTitle" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%"> | |
| 3 | 3 | <el-row :gutter="15" class="" > |
| 4 | - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> | |
| 4 | + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="formDisabled" :rules="rules"> | |
| 5 | 5 | <el-col :span="8"> |
| 6 | 6 | <el-form-item label="单据编号" prop="id"> |
| 7 | 7 | <el-input v-model="dataForm.id" placeholder="请输入" clearable readonly :style='{"width":"100%"}' > |
| ... | ... | @@ -23,8 +23,7 @@ |
| 23 | 23 | </el-col> |
| 24 | 24 | <el-col :span="8"> |
| 25 | 25 | <el-form-item label="经手人" prop="jsr"> |
| 26 | - <user-select v-model="dataForm.jsr" placeholder="请选择" clearable > | |
| 27 | - </user-select> | |
| 26 | + <el-input :value="jsrDisplayText" readonly placeholder="由明细所选账号带出" /> | |
| 28 | 27 | </el-form-item> |
| 29 | 28 | </el-col> |
| 30 | 29 | <el-col :span="16"> |
| ... | ... | @@ -33,6 +32,11 @@ |
| 33 | 32 | </el-input> |
| 34 | 33 | </el-form-item> |
| 35 | 34 | </el-col> |
| 35 | + <el-col :span="8" v-if="dataForm.id"> | |
| 36 | + <el-form-item label="审核状态"> | |
| 37 | + <el-input :value="auditStatusText" readonly /> | |
| 38 | + </el-form-item> | |
| 39 | + </el-col> | |
| 36 | 40 | <el-col :span="24" v-if="dataForm.id"> |
| 37 | 41 | <el-form-item label="业务摘要"> |
| 38 | 42 | <ncc-bill-summary :value="dataForm.billZy" mode="block" /> |
| ... | ... | @@ -44,7 +48,7 @@ |
| 44 | 48 | <el-table-column type="index" width="50" label="序号" align="center" /> |
| 45 | 49 | <el-table-column prop="zhbh" label="账号"> |
| 46 | 50 | <template slot-scope="scope"> |
| 47 | - <el-select v-model="scope.row.zhbh" placeholder="请选择" clearable filterable> | |
| 51 | + <el-select v-model="scope.row.zhbh" placeholder="请选择" clearable filterable @change="syncFsjeFromMx"> | |
| 48 | 52 | <el-option v-for="(item, index) in zhbhOptions" :key="index" :label="item.fullName" :value="item.id" :disabled="item.disabled"></el-option> |
| 49 | 53 | </el-select> |
| 50 | 54 | </template> |
| ... | ... | @@ -72,13 +76,13 @@ |
| 72 | 76 | <el-input v-model="scope.row.djlx" placeholder="请输入" clearable ></el-input> |
| 73 | 77 | </template> |
| 74 | 78 | </el-table-column> |
| 75 | - <el-table-column label="操作" width="50"> | |
| 79 | + <el-table-column v-if="!formDisabled" label="操作" width="50"> | |
| 76 | 80 | <template slot-scope="scope"> |
| 77 | 81 | <el-button size="mini" type="text" class="NCC-table-delBtn" @click="handleDelWtCwdjmxEntityList(scope.$index)">删除</el-button> |
| 78 | 82 | </template> |
| 79 | 83 | </el-table-column> |
| 80 | 84 | </el-table> |
| 81 | - <div class="table-actions" @click="addHandleWtCwdjmxEntityList()"> | |
| 85 | + <div v-if="!formDisabled" class="table-actions" @click="addHandleWtCwdjmxEntityList()"> | |
| 82 | 86 | <el-button type="text" icon="el-icon-plus">新增</el-button> |
| 83 | 87 | </div> |
| 84 | 88 | </el-form-item> |
| ... | ... | @@ -112,7 +116,10 @@ |
| 112 | 116 | </el-row> |
| 113 | 117 | <span slot="footer" class="dialog-footer"> |
| 114 | 118 | <el-button @click="visible = false">取 消</el-button> |
| 115 | - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button> | |
| 119 | + <template v-if="!formDisabled"> | |
| 120 | + <el-button @click="submitAsDraft">保存草稿</el-button> | |
| 121 | + <el-button type="primary" @click="submitForAudit">提 交</el-button> | |
| 122 | + </template> | |
| 116 | 123 | </span> |
| 117 | 124 | </el-dialog> |
| 118 | 125 | </template> |
| ... | ... | @@ -129,9 +136,9 @@ |
| 129 | 136 | loading: false, |
| 130 | 137 | visible: false, |
| 131 | 138 | isDetail: false, |
| 139 | + pendingDjzt: '待审核', | |
| 132 | 140 | dataForm: { |
| 133 | - id:'', | |
| 134 | - id:undefined, | |
| 141 | + id: undefined, | |
| 135 | 142 | ldrq:undefined, |
| 136 | 143 | wldw:undefined, |
| 137 | 144 | jsr:undefined, |
| ... | ... | @@ -142,6 +149,7 @@ |
| 142 | 149 | skzh:undefined, |
| 143 | 150 | fsje:undefined, |
| 144 | 151 | djlx:undefined, |
| 152 | + djzt: undefined, | |
| 145 | 153 | }, |
| 146 | 154 | rules: { |
| 147 | 155 | wldw: [{ required: true, message: '请选择往来单位', trigger: 'change' }], |
| ... | ... | @@ -158,6 +166,39 @@ |
| 158 | 166 | const rows = Array.isArray(this.dataForm.wtCwdjmxList) ? this.dataForm.wtCwdjmxList : [] |
| 159 | 167 | return rows.reduce((s, r) => s + (Number(r.ybje) || 0), 0) |
| 160 | 168 | }, |
| 169 | + auditStatusText() { | |
| 170 | + const z = this.dataForm.djzt != null && this.dataForm.djzt !== '' ? String(this.dataForm.djzt).trim() : '' | |
| 171 | + if (z) return z | |
| 172 | + return '—' | |
| 173 | + }, | |
| 174 | + isBillLockedForEdit() { | |
| 175 | + const z = this.auditStatusText | |
| 176 | + return z === '待审核' || z === '已审核' | |
| 177 | + }, | |
| 178 | + formDisabled() { | |
| 179 | + return !!this.isDetail || this.isBillLockedForEdit | |
| 180 | + }, | |
| 181 | + dialogTitle() { | |
| 182 | + if (!this.dataForm.id) return '新建' | |
| 183 | + if (this.isDetail) return '详情' | |
| 184 | + const z = this.auditStatusText | |
| 185 | + if (z === '已审核') return '查看(已审核)' | |
| 186 | + if (z === '待审核') return '查看(待审核)' | |
| 187 | + if (z === '草稿') return '编辑' | |
| 188 | + return '编辑' | |
| 189 | + }, | |
| 190 | + jsrDisplayText() { | |
| 191 | + const rows = Array.isArray(this.dataForm.wtCwdjmxList) ? this.dataForm.wtCwdjmxList : [] | |
| 192 | + for (let i = 0; i < rows.length; i++) { | |
| 193 | + const r = rows[i] | |
| 194 | + if (r && r.zhbh) { | |
| 195 | + const opt = (this.zhbhOptions || []).find(o => o && o.id === r.zhbh) | |
| 196 | + if (opt && opt.fullName) return opt.fullName | |
| 197 | + } | |
| 198 | + } | |
| 199 | + const j = this.dataForm.jsr | |
| 200 | + return (j != null && j !== '') ? String(j) : '—' | |
| 201 | + }, | |
| 161 | 202 | }, |
| 162 | 203 | watch: {}, |
| 163 | 204 | created() { |
| ... | ... | @@ -215,6 +256,7 @@ |
| 215 | 256 | this.dataForm.id = id || 0; |
| 216 | 257 | this.visible = true; |
| 217 | 258 | this.isDetail = isDetail || false; |
| 259 | + this.pendingDjzt = '待审核' | |
| 218 | 260 | this.$nextTick(() => { |
| 219 | 261 | this.$refs['elForm'].resetFields(); |
| 220 | 262 | if (this.dataForm.id) { |
| ... | ... | @@ -227,18 +269,28 @@ |
| 227 | 269 | } else { |
| 228 | 270 | // 新建时设置默认值 |
| 229 | 271 | this.dataForm.ldrq = Date.now(); // 录单日期默认为当前日期 |
| 230 | - var user = (this.$store && this.$store.getters && this.$store.getters.userInfo) ? this.$store.getters.userInfo : {}; | |
| 231 | - this.dataForm.jsr = user.userId || user.id || ''; // 经手人默认为当前用户 | |
| 232 | 272 | this.dataForm.djlx = '收款单'; // 主表单据类型默认值 |
| 273 | + this.dataForm.djzt = '草稿' | |
| 233 | 274 | this.dataForm.wtCwdjmxList = [] |
| 234 | 275 | this.addHandleWtCwdjmxEntityList() |
| 235 | 276 | } |
| 236 | 277 | }) |
| 237 | 278 | }, |
| 279 | + submitAsDraft() { | |
| 280 | + this.pendingDjzt = '草稿' | |
| 281 | + this.dataFormSubmit() | |
| 282 | + }, | |
| 283 | + submitForAudit() { | |
| 284 | + this.pendingDjzt = '待审核' | |
| 285 | + this.dataFormSubmit() | |
| 286 | + }, | |
| 238 | 287 | dataFormSubmit() { |
| 288 | + if (this.formDisabled) return | |
| 239 | 289 | this.syncFsjeFromMx() |
| 240 | 290 | this.$refs['elForm'].validate((valid) => { |
| 241 | 291 | if (valid) { |
| 292 | + this.dataForm.djlx = '收款单'; | |
| 293 | + this.$set(this.dataForm, 'djzt', this.pendingDjzt || '待审核') | |
| 242 | 294 | if (!this.dataForm.id) { |
| 243 | 295 | request({ |
| 244 | 296 | url: `/api/Extend/WtCwdj`, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_skd/index.vue
| ... | ... | @@ -24,7 +24,7 @@ |
| 24 | 24 | <template v-if="showAll"> |
| 25 | 25 | <el-col :span="6"> |
| 26 | 26 | <el-form-item label="经手人"> |
| 27 | - <userSelect v-model="query.jsr" placeholder="请选择经手人" /> | |
| 27 | + <el-input v-model="query.jsr" placeholder="账户名称等" clearable /> | |
| 28 | 28 | </el-form-item> |
| 29 | 29 | </el-col> |
| 30 | 30 | <el-col :span="6"> |
| ... | ... | @@ -71,6 +71,7 @@ |
| 71 | 71 | <el-table-column prop="djzt" label="审核状态" align="left" width="110"> |
| 72 | 72 | <template slot-scope="scope"> |
| 73 | 73 | <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag> |
| 74 | + <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag> | |
| 74 | 75 | <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag> |
| 75 | 76 | </template> |
| 76 | 77 | </el-table-column> |
| ... | ... | @@ -79,12 +80,13 @@ |
| 79 | 80 | <ncc-table-summary-cell :row="scope.row" fields="billZy,BillZy,zy,Zy" /> |
| 80 | 81 | </template> |
| 81 | 82 | </el-table-column> |
| 82 | - <el-table-column label="操作" fixed="right" width="200"> | |
| 83 | + <el-table-column label="操作" fixed="right" width="240"> | |
| 83 | 84 | <template slot-scope="scope"> |
| 84 | 85 | <el-button type="text" @click="openDetail(scope.row.id)" >查看</el-button> |
| 85 | - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 86 | - <el-button type="text" v-if="scope.row.djzt !== '已审核'" @click="handleApprove(scope.row.id)" style="color:#409EFF">审核</el-button> | |
| 87 | - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 86 | + <el-button type="text" v-if="isDraftRow(scope.row)" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 87 | + <el-button type="text" v-if="isPendingAudit(scope.row)" @click="handleApprove(scope.row.id)" style="color:#409EFF">审核</el-button> | |
| 88 | + <el-button type="text" v-if="scope.row.djzt === '已审核'" @click="handleReverse(scope.row.id)" style="color:#E6A23C">反审</el-button> | |
| 89 | + <el-button type="text" v-if="isDraftRow(scope.row)" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 88 | 90 | </template> |
| 89 | 91 | </el-table-column> |
| 90 | 92 | </NCC-table> |
| ... | ... | @@ -147,6 +149,13 @@ |
| 147 | 149 | this.getskzhOptions(); |
| 148 | 150 | }, |
| 149 | 151 | methods: { |
| 152 | + isDraftRow(row) { | |
| 153 | + return row && String(row.djzt || '').trim() === '草稿' | |
| 154 | + }, | |
| 155 | + isPendingAudit(row) { | |
| 156 | + const z = row && String(row.djzt || '').trim() | |
| 157 | + return z === '待审核' || z === '' | |
| 158 | + }, | |
| 150 | 159 | handleApprove(id) { |
| 151 | 160 | this.$confirm('确认审核该收款单?', '提示', { type: 'warning' }).then(() => { |
| 152 | 161 | request({ |
| ... | ... | @@ -159,6 +168,29 @@ |
| 159 | 168 | }) |
| 160 | 169 | }).catch(() => {}) |
| 161 | 170 | }, |
| 171 | + handleReverse(id) { | |
| 172 | + if (!id) return | |
| 173 | + this.$confirm('反审后单据将恢复为草稿,可再次编辑;摘要汇总表中对应记录将移除。是否继续?', '反审确认', { type: 'warning' }) | |
| 174 | + .then(() => { | |
| 175 | + return request({ | |
| 176 | + url: `/api/Extend/WtCwdj/Actions/ReverseReceiptAudit/${id}`, | |
| 177 | + method: 'POST', | |
| 178 | + data: {} | |
| 179 | + }) | |
| 180 | + }) | |
| 181 | + .then((res) => { | |
| 182 | + const d = (res && res.data) || {} | |
| 183 | + const ok = d.success === true || String(res && res.code) === '200' | |
| 184 | + const msg = d.message || res.msg || (ok ? '反审成功' : '操作失败') | |
| 185 | + if (ok) { | |
| 186 | + this.$message.success(msg) | |
| 187 | + this.initData() | |
| 188 | + } else { | |
| 189 | + this.$message.error(msg) | |
| 190 | + } | |
| 191 | + }) | |
| 192 | + .catch(() => {}) | |
| 193 | + }, | |
| 162 | 194 | getwldwOptions(){ |
| 163 | 195 | previewDataInterface('716168694526379269').then(res => { |
| 164 | 196 | this.wldwOptions = res.data | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtFpspdzb/Form.vue
| ... | ... | @@ -38,8 +38,32 @@ |
| 38 | 38 | </el-col> |
| 39 | 39 | <el-col :span="24"> |
| 40 | 40 | <el-form-item label="项目名称" prop="xm"> |
| 41 | - <el-input v-model="dataForm.xm" placeholder="请输入" clearable :style='{"width":"100%"}' > | |
| 42 | - </el-input> | |
| 41 | + <div | |
| 42 | + v-for="(item, idx) in xmRows" | |
| 43 | + :key="idx" | |
| 44 | + style="display:flex;align-items:center;margin-bottom:8px;" | |
| 45 | + > | |
| 46 | + <el-input | |
| 47 | + v-model="xmRows[idx]" | |
| 48 | + placeholder="请输入项目名称" | |
| 49 | + clearable | |
| 50 | + :style='{"width":"100%"}' | |
| 51 | + /> | |
| 52 | + <el-button | |
| 53 | + v-if="!isDetail" | |
| 54 | + type="text" | |
| 55 | + icon="el-icon-plus" | |
| 56 | + style="margin-left:8px;" | |
| 57 | + @click="addXmRow" | |
| 58 | + /> | |
| 59 | + <el-button | |
| 60 | + v-if="!isDetail && xmRows.length > 1" | |
| 61 | + type="text" | |
| 62 | + icon="el-icon-delete" | |
| 63 | + class="NCC-table-delBtn" | |
| 64 | + @click="removeXmRow(idx)" | |
| 65 | + /> | |
| 66 | + </div> | |
| 43 | 67 | </el-form-item> |
| 44 | 68 | </el-col> |
| 45 | 69 | <el-col :span="24"> |
| ... | ... | @@ -50,7 +74,8 @@ |
| 50 | 74 | </el-col> |
| 51 | 75 | <el-col :span="24"> |
| 52 | 76 | <el-form-item label="税率" prop="sl"> |
| 53 | - <el-input v-model="dataForm.sl" placeholder="请输入" clearable :style='{"width":"100%"}' > | |
| 77 | + <el-input v-model="dataForm.sl" placeholder="请输入数字,如 13" clearable :style='{"width":"100%"}' > | |
| 78 | + <template slot="append">%</template> | |
| 54 | 79 | </el-input> |
| 55 | 80 | </el-form-item> |
| 56 | 81 | </el-col> |
| ... | ... | @@ -84,6 +109,7 @@ |
| 84 | 109 | }, |
| 85 | 110 | rules: { |
| 86 | 111 | }, |
| 112 | + xmRows: [''], | |
| 87 | 113 | spLoading: false, |
| 88 | 114 | spOptions: [], |
| 89 | 115 | } |
| ... | ... | @@ -111,6 +137,9 @@ |
| 111 | 137 | method: 'get' |
| 112 | 138 | }).then(res =>{ |
| 113 | 139 | this.dataForm = res.data; |
| 140 | + this.$set(this.dataForm, 'sl', this.normalizeTaxRateForDisplay(this.dataForm.sl)) | |
| 141 | + const parsedXm = this.parseXmList(this.dataForm.xm) | |
| 142 | + this.xmRows = parsedXm.length ? parsedXm : [''] | |
| 114 | 143 | if (this.dataForm && this.dataForm.sp) { |
| 115 | 144 | this.spOptions = [{ |
| 116 | 145 | id: this.dataForm.sp, |
| ... | ... | @@ -120,8 +149,36 @@ |
| 120 | 149 | } |
| 121 | 150 | }) |
| 122 | 151 | } |
| 152 | + if (!this.dataForm.id) { | |
| 153 | + this.xmRows = [''] | |
| 154 | + } | |
| 123 | 155 | }) |
| 124 | 156 | }, |
| 157 | + addXmRow() { | |
| 158 | + this.xmRows.push('') | |
| 159 | + }, | |
| 160 | + removeXmRow(index) { | |
| 161 | + this.xmRows.splice(index, 1) | |
| 162 | + if (!this.xmRows.length) this.xmRows = [''] | |
| 163 | + }, | |
| 164 | + parseXmList(raw) { | |
| 165 | + if (raw == null) return [] | |
| 166 | + const txt = String(raw).trim() | |
| 167 | + if (!txt) return [] | |
| 168 | + return txt.split(/[,,;;\n]+/).map(s => s.trim()).filter(Boolean) | |
| 169 | + }, | |
| 170 | + normalizeTaxRateForDisplay(raw) { | |
| 171 | + if (raw == null || raw === '') return '' | |
| 172 | + return String(raw).replace(/%/g, '').trim() | |
| 173 | + }, | |
| 174 | + buildSubmitData() { | |
| 175 | + const submit = { ...this.dataForm } | |
| 176 | + const xm = (this.xmRows || []).map(s => String(s || '').trim()).filter(Boolean) | |
| 177 | + submit.xm = xm.join('\n') | |
| 178 | + const slRaw = this.normalizeTaxRateForDisplay(submit.sl) | |
| 179 | + submit.sl = slRaw === '' ? null : Number(slRaw) | |
| 180 | + return submit | |
| 181 | + }, | |
| 125 | 182 | async handleSpSearch(query) { |
| 126 | 183 | const q = query != null ? String(query).trim() : '' |
| 127 | 184 | if (!q) { this.spOptions = []; return } |
| ... | ... | @@ -153,11 +210,16 @@ |
| 153 | 210 | dataFormSubmit() { |
| 154 | 211 | this.$refs['elForm'].validate((valid) => { |
| 155 | 212 | if (valid) { |
| 213 | + const submitData = this.buildSubmitData() | |
| 214 | + if (submitData.sl == null || Number.isNaN(submitData.sl)) { | |
| 215 | + this.$message.warning('税率请输入数字,如 13') | |
| 216 | + return | |
| 217 | + } | |
| 156 | 218 | if (!this.dataForm.id) { |
| 157 | 219 | request({ |
| 158 | 220 | url: `/api/Extend/WtFpspdzb`, |
| 159 | 221 | method: 'post', |
| 160 | - data: this.dataForm, | |
| 222 | + data: submitData, | |
| 161 | 223 | }).then((res) => { |
| 162 | 224 | this.$message({ |
| 163 | 225 | message: res.msg, |
| ... | ... | @@ -173,7 +235,7 @@ |
| 173 | 235 | request({ |
| 174 | 236 | url: '/api/Extend/WtFpspdzb/' + this.dataForm.id, |
| 175 | 237 | method: 'PUT', |
| 176 | - data: this.dataForm | |
| 238 | + data: submitData | |
| 177 | 239 | }).then((res) => { |
| 178 | 240 | this.$message({ |
| 179 | 241 | message: res.msg, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtFpspdzb/index.vue
| ... | ... | @@ -26,7 +26,7 @@ |
| 26 | 26 | </el-col> |
| 27 | 27 | <el-col :span="6"> |
| 28 | 28 | <el-form-item label="税率"> |
| 29 | - <el-input v-model="query.sl" placeholder="税率" clearable /> | |
| 29 | + <el-input v-model="query.sl" placeholder="税率,如 13" clearable /> | |
| 30 | 30 | </el-form-item> |
| 31 | 31 | </el-col> |
| 32 | 32 | </template> |
| ... | ... | @@ -57,9 +57,17 @@ |
| 57 | 57 | <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange"> |
| 58 | 58 | <el-table-column prop="sp" label="商品名称" align="left" /> |
| 59 | 59 | <el-table-column prop="spbh" label="商品编号" align="left" /> |
| 60 | - <el-table-column prop="xm" label="项目名称" align="left" /> | |
| 60 | + <el-table-column prop="xm" label="项目名称" align="left" min-width="180"> | |
| 61 | + <template slot-scope="scope"> | |
| 62 | + <div class="fpspdzb-xm-multiline">{{ formatXmCell(scope.row.xm) }}</div> | |
| 63 | + </template> | |
| 64 | + </el-table-column> | |
| 61 | 65 | <el-table-column prop="gg" label="规格型号" align="left" /> |
| 62 | - <el-table-column prop="sl" label="税率" align="left" /> | |
| 66 | + <el-table-column prop="sl" label="税率" align="left"> | |
| 67 | + <template slot-scope="scope"> | |
| 68 | + {{ formatTaxRate(scope.row.sl) }} | |
| 69 | + </template> | |
| 70 | + </el-table-column> | |
| 63 | 71 | <el-table-column label="操作" fixed="right" width="100"> |
| 64 | 72 | <template slot-scope="scope"> |
| 65 | 73 | <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> |
| ... | ... | @@ -117,6 +125,16 @@ |
| 117 | 125 | this.initData() |
| 118 | 126 | }, |
| 119 | 127 | methods: { |
| 128 | + formatTaxRate(v) { | |
| 129 | + if (v === undefined || v === null || v === '') return '' | |
| 130 | + const txt = String(v).replace(/%/g, '').trim() | |
| 131 | + if (!txt) return '' | |
| 132 | + return `${txt}%` | |
| 133 | + }, | |
| 134 | + formatXmCell(v) { | |
| 135 | + if (v == null || v === '') return '' | |
| 136 | + return String(v).replace(/[,,;;]+/g, '\n') | |
| 137 | + }, | |
| 120 | 138 | initData() { |
| 121 | 139 | this.listLoading = true; |
| 122 | 140 | let _query = { |
| ... | ... | @@ -131,6 +149,9 @@ |
| 131 | 149 | query[key] = _query[key] |
| 132 | 150 | } |
| 133 | 151 | } |
| 152 | + if (query.sl != null && query.sl !== '') { | |
| 153 | + query.sl = String(query.sl).replace(/%/g, '').trim() | |
| 154 | + } | |
| 134 | 155 | request({ |
| 135 | 156 | url: `/api/Extend/WtFpspdzb`, |
| 136 | 157 | method: 'GET', |
| ... | ... | @@ -244,4 +265,10 @@ |
| 244 | 265 | } |
| 245 | 266 | } |
| 246 | 267 | } |
| 247 | -</script> | |
| 248 | 268 | \ No newline at end of file |
| 269 | +</script> | |
| 270 | +<style scoped> | |
| 271 | +.fpspdzb-xm-multiline { | |
| 272 | + white-space: pre-line; | |
| 273 | + line-height: 1.5; | |
| 274 | +} | |
| 275 | +</style> | |
| 249 | 276 | \ No newline at end of file | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtFysrd_qtsr/Form.vue
| ... | ... | @@ -4,7 +4,7 @@ |
| 4 | 4 | <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> |
| 5 | 5 | <el-col :span="8"> |
| 6 | 6 | <el-form-item label="单据编号" prop="id"> |
| 7 | - <el-input v-model="dataForm.id" placeholder="请输入" clearable :style='{"width":"100%"}' > | |
| 7 | + <el-input v-model="dataForm.id" placeholder="系统自动生成" clearable readonly :style='{"width":"100%"}' > | |
| 8 | 8 | </el-input> |
| 9 | 9 | </el-form-item> |
| 10 | 10 | </el-col> |
| ... | ... | @@ -40,7 +40,7 @@ |
| 40 | 40 | </el-form-item> |
| 41 | 41 | </el-col> |
| 42 | 42 | <el-col :span="8"> |
| 43 | - <el-form-item label="结算账户" prop="jszh"> | |
| 43 | + <el-form-item label="收款账户" prop="jszh"> | |
| 44 | 44 | <el-select v-model="dataForm.jszh" placeholder="请选择" clearable :style='{"width":"100%"}' > |
| 45 | 45 | <el-option v-for="(item, index) in jszhOptions" :key="index" :label="item.fullName" :value="item.id" ></el-option> |
| 46 | 46 | </el-select> |
| ... | ... | @@ -132,6 +132,7 @@ |
| 132 | 132 | import request from '@/utils/request' |
| 133 | 133 | import { getDictionaryDataSelector } from '@/api/systemData/dictionary' |
| 134 | 134 | import { previewDataInterface } from '@/api/systemData/dataInterface' |
| 135 | + import { getAccountSelector } from '@/api/extend/wtAccount' | |
| 135 | 136 | export default { |
| 136 | 137 | components: {}, |
| 137 | 138 | props: [], |
| ... | ... | @@ -141,7 +142,6 @@ |
| 141 | 142 | visible: false, |
| 142 | 143 | isDetail: false, |
| 143 | 144 | dataForm: { |
| 144 | - id:'', | |
| 145 | 145 | id:undefined, |
| 146 | 146 | djrq:undefined, |
| 147 | 147 | fzjg:undefined, |
| ... | ... | @@ -156,12 +156,12 @@ |
| 156 | 156 | gzr:undefined, |
| 157 | 157 | bz:undefined, |
| 158 | 158 | zy:undefined, |
| 159 | - djlx:'其它收入', | |
| 159 | + djlx:'其他收入单', | |
| 160 | 160 | }, |
| 161 | 161 | rules: { |
| 162 | 162 | }, |
| 163 | 163 | fkdwOptions : [], |
| 164 | - jszhOptions:[{"fullName":"选项一","id":"1"},{"fullName":"选项二","id":"2"}], | |
| 164 | + jszhOptions:[], | |
| 165 | 165 | kmOptions:[{"fullName":"选项一","id":"1"},{"fullName":"选项二","id":"2"}], |
| 166 | 166 | } |
| 167 | 167 | }, |
| ... | ... | @@ -169,6 +169,7 @@ |
| 169 | 169 | watch: {}, |
| 170 | 170 | created() { |
| 171 | 171 | this.getfkdwOptions(); |
| 172 | + this.getjszhOptions(); | |
| 172 | 173 | }, |
| 173 | 174 | mounted() { |
| 174 | 175 | }, |
| ... | ... | @@ -178,15 +179,23 @@ |
| 178 | 179 | this.fkdwOptions = res.data |
| 179 | 180 | }); |
| 180 | 181 | }, |
| 182 | + getjszhOptions(){ | |
| 183 | + getAccountSelector().then(res => { | |
| 184 | + this.jszhOptions = (res.data && res.data.list) ? res.data.list : (res.data || []) | |
| 185 | + }); | |
| 186 | + }, | |
| 181 | 187 | goBack() { |
| 182 | 188 | this.$emit('refresh') |
| 183 | 189 | }, |
| 184 | 190 | init(id, isDetail) { |
| 185 | - this.dataForm.id = id || 0; | |
| 191 | + this.dataForm.id = id || undefined; | |
| 186 | 192 | this.visible = true; |
| 187 | 193 | this.isDetail = isDetail || false; |
| 188 | 194 | this.$nextTick(() => { |
| 189 | 195 | this.$refs['elForm'].resetFields(); |
| 196 | + if (!id) { | |
| 197 | + this.dataForm.djlx = '其他收入单'; | |
| 198 | + } | |
| 190 | 199 | if (this.dataForm.id) { |
| 191 | 200 | request({ |
| 192 | 201 | url: '/api/Extend/WtFysrd/' + this.dataForm.id, |
| ... | ... | @@ -201,6 +210,7 @@ |
| 201 | 210 | this.$refs['elForm'].validate((valid) => { |
| 202 | 211 | if (valid) { |
| 203 | 212 | if (!this.dataForm.id) { |
| 213 | + this.dataForm.djlx = '其他收入单' | |
| 204 | 214 | request({ |
| 205 | 215 | url: `/api/Extend/WtFysrd`, |
| 206 | 216 | method: 'post', |
| ... | ... | @@ -217,6 +227,7 @@ |
| 217 | 227 | }) |
| 218 | 228 | }) |
| 219 | 229 | } else { |
| 230 | + this.dataForm.djlx = this.dataForm.djlx || '其他收入单' | |
| 220 | 231 | request({ |
| 221 | 232 | url: '/api/Extend/WtFysrd/' + this.dataForm.id, |
| 222 | 233 | method: 'PUT', | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtFysrd_qtsr/index.vue
| ... | ... | @@ -31,8 +31,8 @@ |
| 31 | 31 | </el-form-item> |
| 32 | 32 | </el-col> |
| 33 | 33 | <el-col :span="6"> |
| 34 | - <el-form-item label="结算账户"> | |
| 35 | - <el-select v-model="query.jszh" placeholder="结算账户" clearable > | |
| 34 | + <el-form-item label="收款账户"> | |
| 35 | + <el-select v-model="query.jszh" placeholder="收款账户" clearable > | |
| 36 | 36 | <el-option v-for="(item, index) in jszhOptions" :key="index" :label="item.fullName" :value="item.id" /> |
| 37 | 37 | </el-select> |
| 38 | 38 | </el-form-item> |
| ... | ... | @@ -103,7 +103,7 @@ |
| 103 | 103 | <el-table-column prop="fzjg" label="分支机构" align="left" /> |
| 104 | 104 | <el-table-column prop="bm" label="部门" align="left" /> |
| 105 | 105 | <el-table-column prop="jsr" label="经手人" align="left" /> |
| 106 | - <el-table-column label="结算账户" prop="jszh" align="left"> | |
| 106 | + <el-table-column label="收款账户" prop="jszh" align="left"> | |
| 107 | 107 | <template slot-scope="scope">{{ scope.row.jszh | dynamicText(jszhOptions) }}</template> |
| 108 | 108 | </el-table-column> |
| 109 | 109 | <el-table-column prop="je" label="金额" align="left" /> |
| ... | ... | @@ -133,6 +133,7 @@ |
| 133 | 133 | import NCCForm from './Form' |
| 134 | 134 | import ExportBox from './ExportBox' |
| 135 | 135 | import { previewDataInterface } from '@/api/systemData/dataInterface' |
| 136 | + import { getAccountSelector } from '@/api/extend/wtAccount' | |
| 136 | 137 | export default { |
| 137 | 138 | components: { NCCForm, ExportBox }, |
| 138 | 139 | data() { |
| ... | ... | @@ -170,7 +171,7 @@ |
| 170 | 171 | { prop: 'fzjg', label: '分支机构' }, |
| 171 | 172 | { prop: 'bm', label: '部门' }, |
| 172 | 173 | { prop: 'jsr', label: '经手人' }, |
| 173 | - { prop: 'jszh', label: '结算账户' }, | |
| 174 | + { prop: 'jszh', label: '收款账户' }, | |
| 174 | 175 | { prop: 'je', label: '金额' }, |
| 175 | 176 | { prop: 'zdr', label: '制单人' }, |
| 176 | 177 | { prop: 'shr', label: '审核人' }, |
| ... | ... | @@ -180,13 +181,14 @@ |
| 180 | 181 | { prop: 'djlx', label: '单据类型' }, |
| 181 | 182 | ], |
| 182 | 183 | fkdwOptions : [], |
| 183 | - jszhOptions:[{"fullName":"选项一","id":"1"},{"fullName":"选项二","id":"2"}], | |
| 184 | + jszhOptions:[], | |
| 184 | 185 | } |
| 185 | 186 | }, |
| 186 | 187 | computed: {}, |
| 187 | 188 | created() { |
| 188 | 189 | this.initData() |
| 189 | 190 | this.getfkdwOptions(); |
| 191 | + this.getjszhOptions(); | |
| 190 | 192 | }, |
| 191 | 193 | methods: { |
| 192 | 194 | getfkdwOptions(){ |
| ... | ... | @@ -194,6 +196,11 @@ |
| 194 | 196 | this.fkdwOptions = res.data |
| 195 | 197 | }); |
| 196 | 198 | }, |
| 199 | + getjszhOptions(){ | |
| 200 | + getAccountSelector().then(res => { | |
| 201 | + this.jszhOptions = (res.data && res.data.list) ? res.data.list : (res.data || []) | |
| 202 | + }); | |
| 203 | + }, | |
| 197 | 204 | initData() { |
| 198 | 205 | this.listLoading = true; |
| 199 | 206 | let _query = { | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtHzd/Form.vue
| 1 | 1 | <template> |
| 2 | - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%"> | |
| 2 | + <el-dialog :title="dialogTitle" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%"> | |
| 3 | 3 | <el-row :gutter="15" class="" > |
| 4 | - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> | |
| 4 | + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="formDisabled" :rules="rules"> | |
| 5 | 5 | <el-col :span="12"> |
| 6 | 6 | <el-form-item label="单据编号" prop="id"> |
| 7 | 7 | <el-input v-model="dataForm.id" placeholder="系统自动生成" clearable readonly :style='{"width":"100%"}' /> |
| ... | ... | @@ -9,7 +9,12 @@ |
| 9 | 9 | </el-col> |
| 10 | 10 | <el-col :span="12"> |
| 11 | 11 | <el-form-item label="单据日期" prop="djrq"> |
| 12 | - <el-date-picker v-model="dataForm.djrq" placeholder="请选择" clearable :style='{"width":"100%"}' type='date' format="yyyy-MM-dd" value-format="timestamp" readonly /> | |
| 12 | + <el-date-picker v-model="dataForm.djrq" placeholder="请选择" clearable :style='{"width":"100%"}' type='date' format="yyyy-MM-dd" value-format="timestamp" :readonly="formDisabled" /> | |
| 13 | + </el-form-item> | |
| 14 | + </el-col> | |
| 15 | + <el-col :span="12" v-if="dataForm.id"> | |
| 16 | + <el-form-item label="审核状态"> | |
| 17 | + <el-input :value="auditStatusText" readonly /> | |
| 13 | 18 | </el-form-item> |
| 14 | 19 | </el-col> |
| 15 | 20 | <el-col :span="12" v-if="false"> |
| ... | ... | @@ -213,13 +218,13 @@ |
| 213 | 218 | </div> |
| 214 | 219 | </template> |
| 215 | 220 | </el-table-column> |
| 216 | - <el-table-column label="操作" width="50"> | |
| 221 | + <el-table-column v-if="!formDisabled" label="操作" width="50"> | |
| 217 | 222 | <template slot-scope="scope"> |
| 218 | 223 | <el-button size="mini" type="text" class="NCC-table-delBtn" @click="handleDelWtXsckdMxEntityList(scope.$index)">删除</el-button> |
| 219 | 224 | </template> |
| 220 | 225 | </el-table-column> |
| 221 | 226 | </el-table> |
| 222 | - <div class="table-actions" @click="addHandleWtXsckdMxEntityList()"> | |
| 227 | + <div v-if="!formDisabled" class="table-actions" @click="addHandleWtXsckdMxEntityList()"> | |
| 223 | 228 | <el-button type="text" icon="el-icon-plus">新增</el-button> |
| 224 | 229 | </div> |
| 225 | 230 | </el-form-item> |
| ... | ... | @@ -276,8 +281,10 @@ |
| 276 | 281 | </el-row> |
| 277 | 282 | <span slot="footer" class="dialog-footer"> |
| 278 | 283 | <el-button @click="visible = false">取 消</el-button> |
| 279 | - <el-button type="default" @click="saveDraft" v-if="!isDetail">保存草稿</el-button> | |
| 280 | - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button> | |
| 284 | + <template v-if="!formDisabled"> | |
| 285 | + <el-button @click="saveDraft">保存草稿</el-button> | |
| 286 | + <el-button type="primary" @click="submitForAudit">提交审核</el-button> | |
| 287 | + </template> | |
| 281 | 288 | </span> |
| 282 | 289 | <!-- 商品条码选择弹窗 --> |
| 283 | 290 | <BarcodeSelect ref="barcodeSelect" @select="handleBarcodeSelect" /> |
| ... | ... | @@ -344,7 +351,27 @@ |
| 344 | 351 | totalJe() { |
| 345 | 352 | return (this.dataForm.wtXsckdMxList || []).reduce((sum, row) => sum + (parseFloat(row.je) || 0), 0) |
| 346 | 353 | }, |
| 347 | - | |
| 354 | + auditStatusText() { | |
| 355 | + const z = this.dataForm.djzt != null && this.dataForm.djzt !== '' ? String(this.dataForm.djzt).trim() : '' | |
| 356 | + if (z) return z | |
| 357 | + return '—' | |
| 358 | + }, | |
| 359 | + isBillLockedForEdit() { | |
| 360 | + const z = this.auditStatusText | |
| 361 | + return z === '待审核' || z === '已审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级' | |
| 362 | + }, | |
| 363 | + formDisabled() { | |
| 364 | + return !!this.isDetail || this.isBillLockedForEdit | |
| 365 | + }, | |
| 366 | + dialogTitle() { | |
| 367 | + if (!this.dataForm.id) return '新建' | |
| 368 | + if (this.isDetail) return '详情' | |
| 369 | + const z = this.auditStatusText | |
| 370 | + if (z === '已审核') return '查看(已审核)' | |
| 371 | + if (z === '待审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级') return '查看(待审核)' | |
| 372 | + if (z === '草稿') return '编辑' | |
| 373 | + return '编辑' | |
| 374 | + }, | |
| 348 | 375 | }, |
| 349 | 376 | watch: {}, |
| 350 | 377 | created() { |
| ... | ... | @@ -662,14 +689,21 @@ |
| 662 | 689 | // 恢复序列号信息 |
| 663 | 690 | _this.restoreSerialNumbers(); |
| 664 | 691 | }) |
| 665 | - } | |
| 666 | - else{ | |
| 667 | - | |
| 692 | + } else { | |
| 693 | + _this.dataForm.djrq = Date.now() | |
| 694 | + _this.dataForm.djlx = '获赠单' | |
| 695 | + _this.dataForm.djzt = '草稿' | |
| 696 | + if (_this.$store && _this.$store.getters && _this.$store.getters.userInfo) { | |
| 697 | + _this.dataForm.jsr = _this.$store.getters.userInfo.userId || _this.$store.getters.userInfo.id | |
| 698 | + } | |
| 699 | + _this.dataForm.wtXsckdMxList = [] | |
| 700 | + _this.addHandleWtXsckdMxEntityList() | |
| 668 | 701 | } |
| 669 | 702 | }) |
| 670 | 703 | }, |
| 671 | 704 | // 保存草稿:只做基础表单校验,不做序列号强校验,保存时带isDraft:true字段 |
| 672 | 705 | async saveDraft() { |
| 706 | + if (this.formDisabled) return | |
| 673 | 707 | // 确保单据类型字段赋值 |
| 674 | 708 | this.dataForm.djlx = '获赠单'; |
| 675 | 709 | this.$refs['elForm'].validate(async (valid) => { |
| ... | ... | @@ -708,7 +742,8 @@ |
| 708 | 742 | } |
| 709 | 743 | }); |
| 710 | 744 | }, |
| 711 | - async dataFormSubmit() { | |
| 745 | + async submitForAudit() { | |
| 746 | + if (this.formDisabled) return | |
| 712 | 747 | const mxCheck = validateMxNoEmptyProductRows(this.dataForm.wtXsckdMxList) |
| 713 | 748 | if (!mxCheck.valid) { |
| 714 | 749 | this.$message.warning(`第 ${mxCheck.emptyLineNos.join('、')} 行未选择商品,请先删除空白行后再提交`) |
| ... | ... | @@ -720,7 +755,7 @@ |
| 720 | 755 | // 确保单据类型字段赋值 |
| 721 | 756 | this.dataForm.djlx = '获赠单'; |
| 722 | 757 | // 确保单据状态为待审核 |
| 723 | - this.dataForm.djzt = '待审核'; | |
| 758 | + this.$set(this.dataForm, 'djzt', '待审核') | |
| 724 | 759 | |
| 725 | 760 | // 检查是否有明细数据 |
| 726 | 761 | if (!this.dataForm.wtXsckdMxList || this.dataForm.wtXsckdMxList.length === 0) { |
| ... | ... | @@ -839,7 +874,7 @@ |
| 839 | 874 | this.currentBarcodeRow.sptm = barcodeData.barcode |
| 840 | 875 | this.currentBarcodeRow.spmc = barcodeData.productName |
| 841 | 876 | this.currentBarcodeRow.spbh = barcodeData.productCode |
| 842 | - // 可根据需要回填其他字段 | |
| 877 | + this.handleProductChange(this.currentBarcodeRow) | |
| 843 | 878 | } |
| 844 | 879 | }, |
| 845 | 880 | |
| ... | ... | @@ -1002,6 +1037,7 @@ |
| 1002 | 1037 | // 再次强制更新,确保界面刷新 |
| 1003 | 1038 | this.$forceUpdate(); |
| 1004 | 1039 | }); |
| 1040 | + this.fillAvgCostForRow(row); | |
| 1005 | 1041 | } else { |
| 1006 | 1042 | if (response.msg !== '操作成功') { |
| 1007 | 1043 | this.$message.error(response.msg || '获取库存失败'); |
| ... | ... | @@ -1009,6 +1045,7 @@ |
| 1009 | 1045 | // 使用$set确保响应式更新 |
| 1010 | 1046 | this.$set(row, 'kucun', 0); |
| 1011 | 1047 | console.log('设置库存值为0 (失败)'); |
| 1048 | + this.fillAvgCostForRow(row); | |
| 1012 | 1049 | } |
| 1013 | 1050 | } catch (error) { |
| 1014 | 1051 | console.error('获取库存失败:', error); |
| ... | ... | @@ -1017,6 +1054,7 @@ |
| 1017 | 1054 | } |
| 1018 | 1055 | row.kucun = 0; |
| 1019 | 1056 | console.log('设置库存值为0 (异常)'); |
| 1057 | + this.fillAvgCostForRow(row); | |
| 1020 | 1058 | } finally { |
| 1021 | 1059 | row.loadingStock = false; |
| 1022 | 1060 | console.log('=== 库存查询完成 ==='); |
| ... | ... | @@ -1348,24 +1386,23 @@ |
| 1348 | 1386 | } |
| 1349 | 1387 | }, |
| 1350 | 1388 | |
| 1351 | - // 按入库仓库的加权平均成本带出成本;无库存/无成本记录则保留为空,让用户手工填写 | |
| 1389 | + // 获赠单:仅当所选入库仓(及展开子仓)在 wt_sp_cost 上有在库数量时,按 Σ(sl×cbj)/Σ(sl) 带出成本;无在库则清空单价由用户手填(可改) | |
| 1352 | 1390 | async fillAvgCostForRow(row) { |
| 1353 | 1391 | try { |
| 1354 | 1392 | if (!row.spbh || !row.rkck) return; |
| 1393 | + // 注意:全局 request 对 GET 会把 config.params 赋成 config.data,不能只用 params,否则查询串为空 | |
| 1394 | + const url = `/api/Extend/WtXsckd/GetWarehouseWeightedAvgCost?spbh=${encodeURIComponent(String(row.spbh))}&ckOrMdId=${encodeURIComponent(String(row.rkck))}` | |
| 1355 | 1395 | const res = await request({ |
| 1356 | - url: '/api/Extend/WtXsckd/GetOutboundCostPrice', | |
| 1357 | - method: 'get', | |
| 1358 | - params: { spbh: row.spbh, ckOrMdId: row.rkck } | |
| 1396 | + url, | |
| 1397 | + method: 'get' | |
| 1359 | 1398 | }); |
| 1360 | 1399 | const body = res && res.data ? res.data : res; |
| 1361 | 1400 | if (body && body.success && body.data != null && Number(body.data) > 0) { |
| 1362 | 1401 | this.$set(row, 'dj', Number(body.data)); |
| 1363 | 1402 | this.handleAmountChange(row); |
| 1364 | 1403 | } else { |
| 1365 | - // 未查询到成本或成本为 0 → 允许手动编辑 | |
| 1366 | - if (row.dj === undefined || row.dj === null || row.dj === '') { | |
| 1367 | - this.$set(row, 'dj', undefined); | |
| 1368 | - } | |
| 1404 | + this.$set(row, 'dj', undefined); | |
| 1405 | + this.handleAmountChange(row); | |
| 1369 | 1406 | } |
| 1370 | 1407 | } catch (e) { |
| 1371 | 1408 | console.warn('[fillAvgCostForRow] 查询成本失败:', e); | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtHzd/index.vue
| ... | ... | @@ -109,16 +109,25 @@ |
| 109 | 109 | <el-table-column prop="gzr" label="过账人" align="left" /> |
| 110 | 110 | <el-table-column prop="bz" label="备注" align="left" /> |
| 111 | 111 | <el-table-column prop="djlx" label="单据类型" align="left" /> |
| 112 | - <el-table-column prop="djzt" label="单据状态" align="left" /> | |
| 112 | + <el-table-column prop="djzt" label="审核状态" align="left" width="120"> | |
| 113 | + <template slot-scope="scope"> | |
| 114 | + <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag> | |
| 115 | + <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag> | |
| 116 | + <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag> | |
| 117 | + </template> | |
| 118 | + </el-table-column> | |
| 113 | 119 | <el-table-column label="摘要" align="left" min-width="200" show-overflow-tooltip class-name="cell-nowrap"> |
| 114 | 120 | <template slot-scope="scope"> |
| 115 | 121 | <ncc-table-summary-cell :row="scope.row" fields="zy,Zy" /> |
| 116 | 122 | </template> |
| 117 | 123 | </el-table-column> |
| 118 | - <el-table-column label="操作" fixed="right" width="100"> | |
| 124 | + <el-table-column label="操作" fixed="right" width="260"> | |
| 119 | 125 | <template slot-scope="scope"> |
| 120 | - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 121 | - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 126 | + <el-button type="text" @click="openDetail(scope.row.id)">查看</el-button> | |
| 127 | + <el-button type="text" v-if="isDraftRow(scope.row)" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button> | |
| 128 | + <el-button type="text" v-if="isPendingAudit(scope.row)" @click="handleApprove(scope.row.id)" style="color:#409EFF">审核</el-button> | |
| 129 | + <el-button type="text" v-if="scope.row.djzt === '已审核'" @click="handleReverse(scope.row.id)" style="color:#E6A23C">反审</el-button> | |
| 130 | + <el-button type="text" v-if="isDraftRow(scope.row)" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button> | |
| 122 | 131 | </template> |
| 123 | 132 | </el-table-column> |
| 124 | 133 | </NCC-table> |
| ... | ... | @@ -188,6 +197,54 @@ |
| 188 | 197 | this.getkhOptions(); |
| 189 | 198 | }, |
| 190 | 199 | methods: { |
| 200 | + isDraftRow(row) { | |
| 201 | + return row && String(row.djzt || '').trim() === '草稿' | |
| 202 | + }, | |
| 203 | + isPendingAudit(row) { | |
| 204 | + const z = row && String(row.djzt || '').trim() | |
| 205 | + return z === '待审核' || z === '' | |
| 206 | + }, | |
| 207 | + openDetail(id) { | |
| 208 | + this.formVisible = true | |
| 209 | + this.$nextTick(() => { | |
| 210 | + this.$refs.NCCForm.init(id, true) | |
| 211 | + }) | |
| 212 | + }, | |
| 213 | + handleApprove(id) { | |
| 214 | + this.$confirm('确认审核该获赠单?', '提示', { type: 'warning' }).then(() => { | |
| 215 | + request({ | |
| 216 | + url: `/api/Extend/WtXsckd/ApproveGeneric/${id}`, | |
| 217 | + method: 'POST', | |
| 218 | + data: {} | |
| 219 | + }).then(res => { | |
| 220 | + this.$message({ type: 'success', message: res.msg || '审核成功', duration: 1200 }) | |
| 221 | + this.initData() | |
| 222 | + }) | |
| 223 | + }).catch(() => {}) | |
| 224 | + }, | |
| 225 | + handleReverse(id) { | |
| 226 | + if (!id) return | |
| 227 | + this.$confirm('反审后单据将恢复为可再次处理状态,是否继续?', '反审确认', { type: 'warning' }) | |
| 228 | + .then(() => { | |
| 229 | + return request({ | |
| 230 | + url: `/api/Extend/WtXsckd/ReverseApproval/${id}`, | |
| 231 | + method: 'POST', | |
| 232 | + data: {} | |
| 233 | + }) | |
| 234 | + }) | |
| 235 | + .then((res) => { | |
| 236 | + const d = (res && res.data) || {} | |
| 237 | + const ok = d.success === true || String(res && res.code) === '200' | |
| 238 | + const msg = d.message || res.msg || (ok ? '反审成功' : '操作失败') | |
| 239 | + if (ok) { | |
| 240 | + this.$message.success(msg) | |
| 241 | + this.initData() | |
| 242 | + } else { | |
| 243 | + this.$message.error(msg) | |
| 244 | + } | |
| 245 | + }) | |
| 246 | + .catch(() => {}) | |
| 247 | + }, | |
| 191 | 248 | getrkckOptions(){ |
| 192 | 249 | previewDataInterface('681758216954053893').then(res => { |
| 193 | 250 | this.rkckOptions = res.data |
| ... | ... | @@ -321,6 +378,7 @@ |
| 321 | 378 | for (let key in this.query) { |
| 322 | 379 | this.query[key] = undefined |
| 323 | 380 | } |
| 381 | + this.query.djlx = '获赠单' | |
| 324 | 382 | this.listQuery = { |
| 325 | 383 | currentPage: 1, |
| 326 | 384 | pageSize: 20, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPl/Form.vue
| ... | ... | @@ -22,6 +22,12 @@ |
| 22 | 22 | </el-radio-group> |
| 23 | 23 | </el-form-item> |
| 24 | 24 | </el-col> |
| 25 | + <el-col :span="24"> | |
| 26 | + <el-form-item label="序号" prop="xh"> | |
| 27 | + <el-input-number v-model="dataForm.xh" :min="0" :max="999999" :step="1" :controls-position="'right'" placeholder="数值越小越靠前" :style='{"width":"100%"}' /> | |
| 28 | + <div style="color:#999;font-size:12px;line-height:18px;">数值越小越靠前;留空则排序靠后</div> | |
| 29 | + </el-form-item> | |
| 30 | + </el-col> | |
| 25 | 31 | </el-form> |
| 26 | 32 | </el-row> |
| 27 | 33 | <span slot="footer" class="dialog-footer"> |
| ... | ... | @@ -44,9 +50,9 @@ |
| 44 | 50 | isDetail: false, |
| 45 | 51 | dataForm: { |
| 46 | 52 | id:'', |
| 47 | - id:undefined, | |
| 48 | 53 | plmc:undefined, |
| 49 | 54 | sfmdfl:'0', |
| 55 | + xh:undefined, | |
| 50 | 56 | }, |
| 51 | 57 | rules: { |
| 52 | 58 | }, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPl/index.vue
| ... | ... | @@ -36,6 +36,11 @@ |
| 36 | 36 | </div> |
| 37 | 37 | </div> |
| 38 | 38 | <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange"> |
| 39 | + <el-table-column prop="xh" label="序号" align="center" width="80"> | |
| 40 | + <template slot-scope="scope"> | |
| 41 | + <span>{{ scope.row.xh === null || scope.row.xh === undefined ? '-' : scope.row.xh }}</span> | |
| 42 | + </template> | |
| 43 | + </el-table-column> | |
| 39 | 44 | <el-table-column prop="id" label="分类编号" align="left" /> |
| 40 | 45 | <el-table-column prop="plmc" label="品类名称" align="left" /> |
| 41 | 46 | <el-table-column prop="sfmdfl" label="是否门店分类" align="center" width="120"> |
| ... | ... | @@ -85,6 +90,7 @@ |
| 85 | 90 | formVisible: false, |
| 86 | 91 | exportBoxVisible: false, |
| 87 | 92 | columnList: [ |
| 93 | + { prop: 'xh', label: '序号' }, | |
| 88 | 94 | { prop: 'id', label: '分类编号' }, |
| 89 | 95 | { prop: 'plmc', label: '品类名称' }, |
| 90 | 96 | { prop: 'sfmdfl', label: '是否门店分类' }, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPp/Form.vue
| ... | ... | @@ -8,6 +8,12 @@ |
| 8 | 8 | </el-input> |
| 9 | 9 | </el-form-item> |
| 10 | 10 | </el-col> |
| 11 | + <el-col :span="24"> | |
| 12 | + <el-form-item label="序号" prop="xh"> | |
| 13 | + <el-input-number v-model="dataForm.xh" :min="0" :max="999999" :step="1" :controls-position="'right'" placeholder="数值越小越靠前" :style='{"width":"100%"}' /> | |
| 14 | + <div style="color:#999;font-size:12px;line-height:18px;">数值越小越靠前;留空则排序靠后</div> | |
| 15 | + </el-form-item> | |
| 16 | + </el-col> | |
| 11 | 17 | </el-form> |
| 12 | 18 | </el-row> |
| 13 | 19 | <span slot="footer" class="dialog-footer"> |
| ... | ... | @@ -31,6 +37,7 @@ |
| 31 | 37 | dataForm: { |
| 32 | 38 | id:'', |
| 33 | 39 | ppmc:undefined, |
| 40 | + xh:undefined, | |
| 34 | 41 | }, |
| 35 | 42 | rules: { |
| 36 | 43 | }, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPp/index.vue
| ... | ... | @@ -29,6 +29,11 @@ |
| 29 | 29 | </div> |
| 30 | 30 | </div> |
| 31 | 31 | <NCC-table v-loading="listLoading" :data="list"> |
| 32 | + <el-table-column prop="xh" label="序号" align="center" width="80"> | |
| 33 | + <template slot-scope="scope"> | |
| 34 | + <span>{{ scope.row.xh === null || scope.row.xh === undefined ? '-' : scope.row.xh }}</span> | |
| 35 | + </template> | |
| 36 | + </el-table-column> | |
| 32 | 37 | <el-table-column prop="ppmc" label="品牌名称" align="left" /> |
| 33 | 38 | <el-table-column label="操作" fixed="right" width="100"> |
| 34 | 39 | <template slot-scope="scope"> | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtProductSummary/index.vue
| ... | ... | @@ -332,6 +332,12 @@ |
| 332 | 332 | <template slot-scope="s">{{ formatNumber(s.row['销售'], 4) }}</template> |
| 333 | 333 | </el-table-column> |
| 334 | 334 | <el-table-column prop="单价" label="单价" min-width="96" align="right" show-overflow-tooltip> |
| 335 | + <template slot="header"> | |
| 336 | + <span class="sortable-th" @click.stop="toggleSort('brand', '单价')"> | |
| 337 | + 单价 | |
| 338 | + <i :class="sortCaretClass('brand', '单价')" class="sort-caret" /> | |
| 339 | + </span> | |
| 340 | + </template> | |
| 335 | 341 | <template slot-scope="s">{{ formatNumber(s.row['单价'], 4) }}</template> |
| 336 | 342 | </el-table-column> |
| 337 | 343 | <el-table-column prop="销售金额" label="销售金额" min-width="110" align="right" show-overflow-tooltip> |
| ... | ... | @@ -344,12 +350,30 @@ |
| 344 | 350 | <template slot-scope="s">{{ formatNumber(s.row['销售金额'], 2) }}</template> |
| 345 | 351 | </el-table-column> |
| 346 | 352 | <el-table-column prop="成本单价" label="成本单价" min-width="96" align="right" show-overflow-tooltip> |
| 353 | + <template slot="header"> | |
| 354 | + <span class="sortable-th" @click.stop="toggleSort('brand', '成本单价')"> | |
| 355 | + 成本单价 | |
| 356 | + <i :class="sortCaretClass('brand', '成本单价')" class="sort-caret" /> | |
| 357 | + </span> | |
| 358 | + </template> | |
| 347 | 359 | <template slot-scope="s">{{ formatNumber(s.row['成本单价'], 4) }}</template> |
| 348 | 360 | </el-table-column> |
| 349 | 361 | <el-table-column prop="成本金额" label="成本金额" min-width="110" align="right" show-overflow-tooltip> |
| 362 | + <template slot="header"> | |
| 363 | + <span class="sortable-th" @click.stop="toggleSort('brand', '成本金额')"> | |
| 364 | + 成本金额 | |
| 365 | + <i :class="sortCaretClass('brand', '成本金额')" class="sort-caret" /> | |
| 366 | + </span> | |
| 367 | + </template> | |
| 350 | 368 | <template slot-scope="s">{{ formatNumber(s.row['成本金额'], 2) }}</template> |
| 351 | 369 | </el-table-column> |
| 352 | 370 | <el-table-column prop="折前销售金额" label="折前销售金额" min-width="120" align="right" show-overflow-tooltip> |
| 371 | + <template slot="header"> | |
| 372 | + <span class="sortable-th" @click.stop="toggleSort('brand', '折前销售金额')"> | |
| 373 | + 折前销售金额 | |
| 374 | + <i :class="sortCaretClass('brand', '折前销售金额')" class="sort-caret" /> | |
| 375 | + </span> | |
| 376 | + </template> | |
| 353 | 377 | <template slot-scope="s">{{ formatNumber(s.row['折前销售金额'], 2) }}</template> |
| 354 | 378 | </el-table-column> |
| 355 | 379 | <el-table-column prop="毛利" label="毛利" min-width="96" align="right" show-overflow-tooltip> |
| ... | ... | @@ -362,9 +386,21 @@ |
| 362 | 386 | <template slot-scope="s">{{ formatNumber(s.row['毛利'], 2) }}</template> |
| 363 | 387 | </el-table-column> |
| 364 | 388 | <el-table-column prop="毛利率(%)" label="毛利率(%)" min-width="100" align="right" show-overflow-tooltip> |
| 389 | + <template slot="header"> | |
| 390 | + <span class="sortable-th" @click.stop="toggleSort('brand', '毛利率(%)')"> | |
| 391 | + 毛利率(%) | |
| 392 | + <i :class="sortCaretClass('brand', '毛利率(%)')" class="sort-caret" /> | |
| 393 | + </span> | |
| 394 | + </template> | |
| 365 | 395 | <template slot-scope="s">{{ formatNumber(s.row['毛利率(%)'], 2) }}</template> |
| 366 | 396 | </el-table-column> |
| 367 | 397 | <el-table-column prop="0单价数量" label="0单价数量" min-width="100" align="right" show-overflow-tooltip> |
| 398 | + <template slot="header"> | |
| 399 | + <span class="sortable-th" @click.stop="toggleSort('brand', '0单价数量')"> | |
| 400 | + 0单价数量 | |
| 401 | + <i :class="sortCaretClass('brand', '0单价数量')" class="sort-caret" /> | |
| 402 | + </span> | |
| 403 | + </template> | |
| 368 | 404 | <template slot-scope="s">{{ formatNumber(s.row['0单价数量'], 4) }}</template> |
| 369 | 405 | </el-table-column> |
| 370 | 406 | </el-table> |
| ... | ... | @@ -378,6 +414,11 @@ |
| 378 | 414 | </template> |
| 379 | 415 | </el-table-column> |
| 380 | 416 | <el-table-column type="index" label="行号" width="56" align="center" /> |
| 417 | + <el-table-column label="线性列表" width="90" align="center"> | |
| 418 | + <template slot-scope="scope"> | |
| 419 | + <el-link type="primary" :underline="false" @click="openLinearList(scope.row)">查看</el-link> | |
| 420 | + </template> | |
| 421 | + </el-table-column> | |
| 381 | 422 | <el-table-column label="分类名称" min-width="160" show-overflow-tooltip> |
| 382 | 423 | <template slot="header"> |
| 383 | 424 | <span class="sortable-th" @click.stop="toggleSort('category', '分类名称')"> |
| ... | ... | @@ -400,6 +441,12 @@ |
| 400 | 441 | <template slot-scope="scope">{{ formatNumber(scope.row['销售'], 4) }}</template> |
| 401 | 442 | </el-table-column> |
| 402 | 443 | <el-table-column prop="单价" label="单价" min-width="96" align="right" show-overflow-tooltip> |
| 444 | + <template slot="header"> | |
| 445 | + <span class="sortable-th" @click.stop="toggleSort('category', '单价')"> | |
| 446 | + 单价 | |
| 447 | + <i :class="sortCaretClass('category', '单价')" class="sort-caret" /> | |
| 448 | + </span> | |
| 449 | + </template> | |
| 403 | 450 | <template slot-scope="scope">{{ formatNumber(scope.row['单价'], 4) }}</template> |
| 404 | 451 | </el-table-column> |
| 405 | 452 | <el-table-column prop="销售金额" label="销售金额" min-width="110" align="right" show-overflow-tooltip> |
| ... | ... | @@ -412,12 +459,30 @@ |
| 412 | 459 | <template slot-scope="scope">{{ formatNumber(scope.row['销售金额'], 2) }}</template> |
| 413 | 460 | </el-table-column> |
| 414 | 461 | <el-table-column prop="成本单价" label="成本单价" min-width="96" align="right" show-overflow-tooltip> |
| 462 | + <template slot="header"> | |
| 463 | + <span class="sortable-th" @click.stop="toggleSort('category', '成本单价')"> | |
| 464 | + 成本单价 | |
| 465 | + <i :class="sortCaretClass('category', '成本单价')" class="sort-caret" /> | |
| 466 | + </span> | |
| 467 | + </template> | |
| 415 | 468 | <template slot-scope="scope">{{ formatNumber(scope.row['成本单价'], 4) }}</template> |
| 416 | 469 | </el-table-column> |
| 417 | 470 | <el-table-column prop="成本金额" label="成本金额" min-width="110" align="right" show-overflow-tooltip> |
| 471 | + <template slot="header"> | |
| 472 | + <span class="sortable-th" @click.stop="toggleSort('category', '成本金额')"> | |
| 473 | + 成本金额 | |
| 474 | + <i :class="sortCaretClass('category', '成本金额')" class="sort-caret" /> | |
| 475 | + </span> | |
| 476 | + </template> | |
| 418 | 477 | <template slot-scope="scope">{{ formatNumber(scope.row['成本金额'], 2) }}</template> |
| 419 | 478 | </el-table-column> |
| 420 | 479 | <el-table-column prop="折前销售金额" label="折前销售金额" min-width="120" align="right" show-overflow-tooltip> |
| 480 | + <template slot="header"> | |
| 481 | + <span class="sortable-th" @click.stop="toggleSort('category', '折前销售金额')"> | |
| 482 | + 折前销售金额 | |
| 483 | + <i :class="sortCaretClass('category', '折前销售金额')" class="sort-caret" /> | |
| 484 | + </span> | |
| 485 | + </template> | |
| 421 | 486 | <template slot-scope="scope">{{ formatNumber(scope.row['折前销售金额'], 2) }}</template> |
| 422 | 487 | </el-table-column> |
| 423 | 488 | <el-table-column prop="毛利" label="毛利" min-width="96" align="right" show-overflow-tooltip> |
| ... | ... | @@ -430,14 +495,50 @@ |
| 430 | 495 | <template slot-scope="scope">{{ formatNumber(scope.row['毛利'], 2) }}</template> |
| 431 | 496 | </el-table-column> |
| 432 | 497 | <el-table-column prop="毛利率(%)" label="毛利率(%)" min-width="100" align="right" show-overflow-tooltip> |
| 498 | + <template slot="header"> | |
| 499 | + <span class="sortable-th" @click.stop="toggleSort('category', '毛利率(%)')"> | |
| 500 | + 毛利率(%) | |
| 501 | + <i :class="sortCaretClass('category', '毛利率(%)')" class="sort-caret" /> | |
| 502 | + </span> | |
| 503 | + </template> | |
| 433 | 504 | <template slot-scope="scope">{{ formatNumber(scope.row['毛利率(%)'], 2) }}</template> |
| 434 | 505 | </el-table-column> |
| 435 | 506 | <el-table-column prop="0单价数量" label="0单价数量" min-width="100" align="right" show-overflow-tooltip> |
| 507 | + <template slot="header"> | |
| 508 | + <span class="sortable-th" @click.stop="toggleSort('category', '0单价数量')"> | |
| 509 | + 0单价数量 | |
| 510 | + <i :class="sortCaretClass('category', '0单价数量')" class="sort-caret" /> | |
| 511 | + </span> | |
| 512 | + </template> | |
| 436 | 513 | <template slot-scope="scope">{{ formatNumber(scope.row['0单价数量'], 4) }}</template> |
| 437 | 514 | </el-table-column> |
| 438 | 515 | </el-table> |
| 439 | 516 | </div> |
| 440 | 517 | </div> |
| 518 | + <el-dialog | |
| 519 | + :title="`线性列表 - ${linearCategoryName || ''}`" | |
| 520 | + :visible.sync="linearDialogVisible" | |
| 521 | + width="88%" | |
| 522 | + :close-on-click-modal="false" | |
| 523 | + > | |
| 524 | + <el-table v-loading="linearLoading" :data="linearRows" border size="small" class="nested-table"> | |
| 525 | + <el-table-column prop="商品编号" label="商品编号" min-width="110" show-overflow-tooltip /> | |
| 526 | + <el-table-column prop="商品名称" label="商品名称" min-width="160" show-overflow-tooltip /> | |
| 527 | + <el-table-column prop="品牌名称" label="品牌名称" min-width="120" show-overflow-tooltip /> | |
| 528 | + <el-table-column prop="销售" label="销售" min-width="90" align="right" show-overflow-tooltip> | |
| 529 | + <template slot-scope="scope">{{ formatNumber(scope.row['销售'], 4) }}</template> | |
| 530 | + </el-table-column> | |
| 531 | + <el-table-column prop="销售金额" label="销售金额" min-width="110" align="right" show-overflow-tooltip> | |
| 532 | + <template slot-scope="scope">{{ formatNumber(scope.row['销售金额'], 2) }}</template> | |
| 533 | + </el-table-column> | |
| 534 | + <el-table-column prop="成本金额" label="成本金额" min-width="110" align="right" show-overflow-tooltip> | |
| 535 | + <template slot-scope="scope">{{ formatNumber(scope.row['成本金额'], 2) }}</template> | |
| 536 | + </el-table-column> | |
| 537 | + <el-table-column prop="毛利" label="毛利" min-width="96" align="right" show-overflow-tooltip> | |
| 538 | + <template slot-scope="scope">{{ formatNumber(scope.row['毛利'], 2) }}</template> | |
| 539 | + </el-table-column> | |
| 540 | + </el-table> | |
| 541 | + </el-dialog> | |
| 441 | 542 | </div> |
| 442 | 543 | </template> |
| 443 | 544 | |
| ... | ... | @@ -449,8 +550,8 @@ import { getProductSummaryHierarchy } from '@/api/extend/wtXsckdProductSummary' |
| 449 | 550 | const DEFAULT_BILL_TYPES = ['销售出库单', '零售单', '委托代销结算单'] |
| 450 | 551 | |
| 451 | 552 | /** 与后端 BuildProductSummaryOrderBy(category) 白名单一致,避免非法排序列 */ |
| 452 | -const SORT_WHITELIST_CATEGORY = ['分类名称', '销售', '销售金额', '毛利'] | |
| 453 | -const SORT_WHITELIST_BRAND = ['品牌名称', '销售', '销售金额', '毛利'] | |
| 553 | +const SORT_WHITELIST_CATEGORY = ['分类名称', '销售', '单价', '销售金额', '成本单价', '成本金额', '折前销售金额', '毛利', '毛利率(%)', '0单价数量'] | |
| 554 | +const SORT_WHITELIST_BRAND = ['品牌名称', '销售', '单价', '销售金额', '成本单价', '成本金额', '折前销售金额', '毛利', '毛利率(%)', '0单价数量'] | |
| 454 | 555 | /** 与后端 BuildProductSummaryOrderBy(product) 白名单一致 */ |
| 455 | 556 | const SORT_WHITELIST_PRODUCT = ['商品编号', '商品名称', '销售', '单价', '销售金额', '成本单价', '成本金额', '毛利', '毛利率(%)'] |
| 456 | 557 | |
| ... | ... | @@ -468,6 +569,10 @@ export default { |
| 468 | 569 | sortCategory: { field: '销售金额', order: 'desc' }, |
| 469 | 570 | sortBrand: { field: '销售金额', order: 'desc' }, |
| 470 | 571 | sortProduct: { field: '销售金额', order: 'desc' }, |
| 572 | + linearDialogVisible: false, | |
| 573 | + linearCategoryName: '', | |
| 574 | + linearRows: [], | |
| 575 | + linearLoading: false, | |
| 471 | 576 | filters: { |
| 472 | 577 | dateRange: [], |
| 473 | 578 | productSpId: '', |
| ... | ... | @@ -646,6 +751,7 @@ export default { |
| 646 | 751 | }, |
| 647 | 752 | buildBasePayload() { |
| 648 | 753 | const params = {} |
| 754 | + params.includeAllProducts = true | |
| 649 | 755 | if (this.filters.dateRange && this.filters.dateRange.length === 2) { |
| 650 | 756 | params.startDate = this.filters.dateRange[0] |
| 651 | 757 | params.endDate = this.filters.dateRange[1] |
| ... | ... | @@ -785,6 +891,30 @@ export default { |
| 785 | 891 | this.$set(this.productLoading, k, false) |
| 786 | 892 | }) |
| 787 | 893 | }, |
| 894 | + openLinearList(catRow) { | |
| 895 | + const cid = this.categoryRowKey(catRow) | |
| 896 | + if (!cid) return | |
| 897 | + this.linearCategoryName = this.displayText(catRow['分类名称']) | |
| 898 | + this.linearDialogVisible = true | |
| 899 | + this.linearLoading = true | |
| 900 | + getProductSummaryHierarchy({ | |
| 901 | + groupLevel: 'product', | |
| 902 | + categoryId: cid, | |
| 903 | + ...this.buildBasePayload(), | |
| 904 | + sortField: this.sortProduct.field, | |
| 905 | + sortOrder: this.sortProduct.order | |
| 906 | + }) | |
| 907 | + .then((res) => { | |
| 908 | + this.linearRows = this.normalizeRows(res.data) | |
| 909 | + }) | |
| 910 | + .catch((e) => { | |
| 911 | + this.linearRows = [] | |
| 912 | + this.$message.error((e && e.message) || '加载线性列表失败') | |
| 913 | + }) | |
| 914 | + .finally(() => { | |
| 915 | + this.linearLoading = false | |
| 916 | + }) | |
| 917 | + }, | |
| 788 | 918 | sortState(level) { |
| 789 | 919 | if (level === 'category') return this.sortCategory |
| 790 | 920 | if (level === 'brand') return this.sortBrand | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtSkfkChannelStat/index.vue
| 1 | 1 | <template> |
| 2 | - <div class="NCC-common-layout skfk-channel-stat-page"> | |
| 2 | + <div class="NCC-common-layout"> | |
| 3 | 3 | <div class="NCC-common-layout-center"> |
| 4 | 4 | <el-row class="NCC-common-search-box" :gutter="16"> |
| 5 | - <el-form :model="filters" label-width="90px" size="small" @submit.native.prevent> | |
| 5 | + <el-form @submit.native.prevent> | |
| 6 | + <el-col :span="6"> | |
| 7 | + <el-form-item label="日期"> | |
| 8 | + <el-date-picker | |
| 9 | + v-model="filters.date" | |
| 10 | + type="date" | |
| 11 | + value-format="yyyy-MM-dd" | |
| 12 | + placeholder="选择日期" | |
| 13 | + style="width: 100%" | |
| 14 | + /> | |
| 15 | + </el-form-item> | |
| 16 | + </el-col> | |
| 17 | + <el-col :span="6"> | |
| 18 | + <el-form-item label="账户"> | |
| 19 | + <el-select | |
| 20 | + v-model="filters.accountId" | |
| 21 | + filterable | |
| 22 | + clearable | |
| 23 | + placeholder="全部账户" | |
| 24 | + style="width: 100%" | |
| 25 | + > | |
| 26 | + <el-option v-for="item in accountOptions" :key="item.id" :label="item.fullName" :value="item.id" /> | |
| 27 | + </el-select> | |
| 28 | + </el-form-item> | |
| 29 | + </el-col> | |
| 30 | + <el-col :span="6"> | |
| 31 | + <el-form-item> | |
| 32 | + <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button> | |
| 33 | + <el-button icon="el-icon-refresh-right" @click="handleReset">重置</el-button> | |
| 34 | + </el-form-item> | |
| 35 | + </el-col> | |
| 36 | + </el-form> | |
| 37 | + </el-row> | |
| 38 | + | |
| 39 | + <div class="NCC-common-layout-main NCC-flex-main"> | |
| 40 | + <div class="title-line">《账户对账表》</div> | |
| 41 | + <el-table | |
| 42 | + v-loading="summaryLoading" | |
| 43 | + :data="tableRows" | |
| 44 | + border | |
| 45 | + row-key="rowKey" | |
| 46 | + class="cell-nowrap reconcile-table" | |
| 47 | + :row-class-name="rowClassName" | |
| 48 | + > | |
| 49 | + <el-table-column prop="date" label="日期" width="110" align="center"> | |
| 50 | + <template slot-scope="scope"> | |
| 51 | + <span v-if="!scope.row.isCategory">{{ scope.row.date }}</span> | |
| 52 | + </template> | |
| 53 | + </el-table-column> | |
| 54 | + <el-table-column prop="account" label="账户分类" min-width="220"> | |
| 55 | + <template slot-scope="scope"> | |
| 56 | + <span v-if="scope.row.isCategory" class="category-text">{{ scope.row.account }}</span> | |
| 57 | + <el-link v-else type="primary" :underline="false" class="account-link" @click="openLedgerDialog(scope.row)"> | |
| 58 | + -- {{ scope.row.account }} | |
| 59 | + </el-link> | |
| 60 | + </template> | |
| 61 | + </el-table-column> | |
| 62 | + <el-table-column prop="openingBalance" label="上期余额" min-width="120" align="right"> | |
| 63 | + <template slot-scope="scope">{{ formatMoney(scope.row.openingBalance) }}</template> | |
| 64 | + </el-table-column> | |
| 65 | + <el-table-column prop="todayIncome" label="今日收入" min-width="120" align="right"> | |
| 66 | + <template slot-scope="scope">{{ formatMoney(scope.row.todayIncome) }}</template> | |
| 67 | + </el-table-column> | |
| 68 | + <el-table-column prop="todayExpense" label="今日支出" min-width="120" align="right"> | |
| 69 | + <template slot-scope="scope">{{ formatMoney(scope.row.todayExpense) }}</template> | |
| 70 | + </el-table-column> | |
| 71 | + <el-table-column prop="periodBalance" label="本期余额" min-width="120" align="right"> | |
| 72 | + <template slot-scope="scope">{{ formatMoney(scope.row.periodBalance) }}</template> | |
| 73 | + </el-table-column> | |
| 74 | + <el-table-column prop="realtimeBalance" label="实时余额" min-width="120" align="right"> | |
| 75 | + <template slot-scope="scope">{{ formatMoney(scope.row.realtimeBalance) }}</template> | |
| 76 | + </el-table-column> | |
| 77 | + </el-table> | |
| 78 | + </div> | |
| 79 | + </div> | |
| 80 | + | |
| 81 | + <el-dialog | |
| 82 | + :title="`账户明细 - ${currentLedgerAccountName || ''}`" | |
| 83 | + :visible.sync="ledgerDialogVisible" | |
| 84 | + width="90%" | |
| 85 | + :close-on-click-modal="false" | |
| 86 | + > | |
| 87 | + <el-row class="NCC-common-search-box" :gutter="16"> | |
| 88 | + <el-form @submit.native.prevent> | |
| 6 | 89 | <el-col :span="8"> |
| 7 | - <el-form-item label="查询日期"> | |
| 90 | + <el-form-item label="日期范围"> | |
| 8 | 91 | <el-date-picker |
| 9 | - v-model="filters.dateRange" | |
| 92 | + v-model="ledgerFilters.dateRange" | |
| 10 | 93 | type="daterange" |
| 94 | + value-format="yyyy-MM-dd" | |
| 11 | 95 | range-separator="至" |
| 12 | 96 | start-placeholder="开始日期" |
| 13 | 97 | end-placeholder="结束日期" |
| 14 | 98 | style="width: 100%" |
| 15 | - value-format="yyyy-MM-dd" | |
| 16 | 99 | /> |
| 17 | 100 | </el-form-item> |
| 18 | 101 | </el-col> |
| 19 | 102 | <el-col :span="6"> |
| 20 | - <el-form-item label="收付方向"> | |
| 21 | - <el-select v-model="filters.fx" clearable placeholder="全部" style="width: 100%"> | |
| 22 | - <el-option label="全部" value="" /> | |
| 23 | - <el-option label="收款" value="sk" /> | |
| 24 | - <el-option label="付款" value="fk" /> | |
| 103 | + <el-form-item label="账户"> | |
| 104 | + <el-select v-model="ledgerFilters.accountId" filterable clearable style="width: 100%"> | |
| 105 | + <el-option v-for="item in accountOptions" :key="item.id" :label="item.fullName" :value="item.id" /> | |
| 25 | 106 | </el-select> |
| 26 | 107 | </el-form-item> |
| 27 | 108 | </el-col> |
| 28 | - <template v-if="showAll"> | |
| 29 | - <el-col :span="6"> | |
| 30 | - <el-form-item label="关键字"> | |
| 31 | - <el-input v-model="filters.qd" clearable placeholder="资金账户或方式" /> | |
| 32 | - </el-form-item> | |
| 33 | - </el-col> | |
| 34 | - <el-col :span="12"> | |
| 35 | - <el-form-item label="单据类型"> | |
| 36 | - <el-input v-model="filters.djlx" clearable placeholder="模糊匹配单据类型" /> | |
| 37 | - </el-form-item> | |
| 38 | - </el-col> | |
| 39 | - </template> | |
| 40 | - <el-col :span="showAll ? 24 : 10"> | |
| 41 | - <el-form-item label-width="0"> | |
| 42 | - <span class="action-row"> | |
| 43 | - <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button> | |
| 44 | - <el-button icon="el-icon-refresh-right" @click="handleReset">重置</el-button> | |
| 45 | - <el-button type="text" icon="el-icon-arrow-down" v-if="!showAll" @click="showAll = true">展开</el-button> | |
| 46 | - <el-button type="text" icon="el-icon-arrow-up" v-else @click="showAll = false">收起</el-button> | |
| 47 | - </span> | |
| 109 | + <el-col :span="6"> | |
| 110 | + <el-form-item> | |
| 111 | + <el-button type="primary" icon="el-icon-search" @click="handleLedgerSearch">查询</el-button> | |
| 112 | + <el-button icon="el-icon-refresh-right" @click="handleLedgerReset">重置</el-button> | |
| 48 | 113 | </el-form-item> |
| 49 | 114 | </el-col> |
| 50 | 115 | </el-form> |
| 51 | 116 | </el-row> |
| 52 | 117 | |
| 53 | - <div class="NCC-common-layout-main NCC-flex-main skfk-channel-stat-page__main"> | |
| 54 | - <div v-loading="summaryLoading" class="summary-wrap"> | |
| 55 | - <el-row :gutter="16" class="summary-cards-row"> | |
| 56 | - <el-col :xs="24" :sm="12"> | |
| 57 | - <div class="stat-card stat-card--sk"> | |
| 58 | - <i class="el-icon-top stat-card__icon stat-card__icon--sk" /> | |
| 59 | - <div class="stat-card__body"> | |
| 60 | - <div class="stat-card__label">收款侧合计</div> | |
| 61 | - <div class="stat-card__value">{{ formatMoney(skSideTotal) }}</div> | |
| 62 | - </div> | |
| 63 | - </div> | |
| 64 | - </el-col> | |
| 65 | - <el-col :xs="24" :sm="12"> | |
| 66 | - <div class="stat-card stat-card--fk"> | |
| 67 | - <i class="el-icon-bottom stat-card__icon stat-card__icon--fk" /> | |
| 68 | - <div class="stat-card__body"> | |
| 69 | - <div class="stat-card__label">付款侧合计</div> | |
| 70 | - <div class="stat-card__value">{{ formatMoney(fkSideTotal) }}</div> | |
| 71 | - </div> | |
| 72 | - </div> | |
| 73 | - </el-col> | |
| 74 | - </el-row> | |
| 75 | - </div> | |
| 76 | - | |
| 77 | - <div class="subsection-title">收款侧</div> | |
| 78 | - <el-row :gutter="16" class="channel-tables-row"> | |
| 79 | - <el-col v-for="p in skPanels" :key="p.key" :xs="24" :lg="12"> | |
| 80 | - <div class="table-section channel-block"> | |
| 81 | - <div class="table-title-bar"> | |
| 82 | - <i :class="[p.icon, 'title-icon', 'title-icon--' + p.tone]" /> | |
| 83 | - <span>{{ p.title }}</span> | |
| 84 | - </div> | |
| 85 | - <el-table | |
| 86 | - :data="p.list" | |
| 87 | - border | |
| 88 | - stripe | |
| 89 | - size="small" | |
| 90 | - :header-cell-style="tableHeaderStyle" | |
| 91 | - class="channel-table cell-nowrap" | |
| 92 | - empty-text= "" | |
| 93 | - > | |
| 94 | - <el-table-column label="" width="44" align="center"> | |
| 95 | - <template slot-scope="scope"> | |
| 96 | - <i :class="[p.rowIcon, 'row-ico', 'row-ico--' + p.rowTone]" /> | |
| 97 | - </template> | |
| 98 | - </el-table-column> | |
| 99 | - <el-table-column prop="qd" :label="p.colLabel" min-width="140" show-overflow-tooltip> | |
| 100 | - <template slot-scope="scope"> | |
| 101 | - <span>{{ cellText(scope.row.qd) }}</span> | |
| 102 | - </template> | |
| 103 | - </el-table-column> | |
| 104 | - <el-table-column prop="je" label="金额" width="130" align="right"> | |
| 105 | - <template slot-scope="scope"> | |
| 106 | - <span class="amount-cell">{{ formatMoney(scope.row.je) }}</span> | |
| 107 | - </template> | |
| 108 | - </el-table-column> | |
| 109 | - </el-table> | |
| 110 | - </div> | |
| 111 | - </el-col> | |
| 112 | - </el-row> | |
| 113 | - | |
| 114 | - <div class="subsection-title">付款侧</div> | |
| 115 | - <el-row :gutter="16" class="channel-tables-row"> | |
| 116 | - <el-col v-for="p in fkPanels" :key="p.key" :xs="24" :lg="12"> | |
| 117 | - <div class="table-section channel-block"> | |
| 118 | - <div class="table-title-bar"> | |
| 119 | - <i :class="[p.icon, 'title-icon', 'title-icon--' + p.tone]" /> | |
| 120 | - <span>{{ p.title }}</span> | |
| 121 | - </div> | |
| 122 | - <el-table | |
| 123 | - :data="p.list" | |
| 124 | - border | |
| 125 | - stripe | |
| 126 | - size="small" | |
| 127 | - :header-cell-style="tableHeaderStyle" | |
| 128 | - class="channel-table cell-nowrap" | |
| 129 | - empty-text= "" | |
| 130 | - > | |
| 131 | - <el-table-column label="" width="44" align="center"> | |
| 132 | - <template slot-scope="scope"> | |
| 133 | - <i :class="[p.rowIcon, 'row-ico', 'row-ico--' + p.rowTone]" /> | |
| 134 | - </template> | |
| 135 | - </el-table-column> | |
| 136 | - <el-table-column prop="qd" :label="p.colLabel" min-width="140" show-overflow-tooltip> | |
| 137 | - <template slot-scope="scope"> | |
| 138 | - <span>{{ cellText(scope.row.qd) }}</span> | |
| 139 | - </template> | |
| 140 | - </el-table-column> | |
| 141 | - <el-table-column prop="je" label="金额" width="130" align="right"> | |
| 142 | - <template slot-scope="scope"> | |
| 143 | - <span class="amount-cell">{{ formatMoney(scope.row.je) }}</span> | |
| 144 | - </template> | |
| 145 | - </el-table-column> | |
| 146 | - </el-table> | |
| 147 | - </div> | |
| 148 | - </el-col> | |
| 149 | - </el-row> | |
| 150 | - | |
| 151 | - <div class="table-section ledger-section"> | |
| 152 | - <div class="table-title-bar ledger-title-bar"> | |
| 153 | - <i class="el-icon-tickets title-icon title-icon--ledger" /> | |
| 154 | - <span>流水明细</span> | |
| 155 | - </div> | |
| 156 | - <div class="table-scroll"> | |
| 157 | - <el-table | |
| 158 | - :data="ledgerList" | |
| 159 | - border | |
| 160 | - stripe | |
| 161 | - v-loading="ledgerLoading" | |
| 162 | - :header-cell-style="tableHeaderStyle" | |
| 163 | - class="ledger-table cell-nowrap" | |
| 164 | - > | |
| 165 | - <el-table-column type="index" label="行号" width="56" :index="indexMethod" fixed /> | |
| 166 | - <el-table-column label="" width="40" align="center" fixed> | |
| 167 | - <template slot-scope="scope"> | |
| 168 | - <i :class="ledgerRowIcon(scope.row)" /> | |
| 169 | - </template> | |
| 170 | - </el-table-column> | |
| 171 | - <el-table-column prop="djrq" label="单据日期" width="108" show-overflow-tooltip /> | |
| 172 | - <el-table-column prop="djbh" label="单据编号" min-width="140" show-overflow-tooltip> | |
| 173 | - <template slot-scope="scope"> | |
| 174 | - <i class="el-icon-document row-ico row-ico--doc" /> | |
| 175 | - {{ cellText(scope.row.djbh) }} | |
| 176 | - </template> | |
| 177 | - </el-table-column> | |
| 178 | - <el-table-column prop="djlx" label="单据类型" min-width="120" show-overflow-tooltip> | |
| 179 | - <template slot-scope="scope"> | |
| 180 | - <i class="el-icon-files row-ico row-ico--type" /> | |
| 181 | - {{ cellText(scope.row.djlx) }} | |
| 182 | - </template> | |
| 183 | - </el-table-column> | |
| 184 | - <el-table-column prop="fx" label="方向" width="72" show-overflow-tooltip> | |
| 185 | - <template slot-scope="scope"> | |
| 186 | - <el-tag :type="scope.row.fx === '收款' ? 'success' : 'danger'" size="mini" effect="plain">{{ | |
| 187 | - cellText(scope.row.fx) | |
| 188 | - }}</el-tag> | |
| 189 | - </template> | |
| 190 | - </el-table-column> | |
| 191 | - <el-table-column prop="fffs" label="方式/渠道" min-width="120" show-overflow-tooltip> | |
| 192 | - <template slot-scope="scope"> | |
| 193 | - <i class="el-icon-mobile-phone row-ico row-ico--fk" /> | |
| 194 | - {{ cellText(scope.row.fffs) }} | |
| 195 | - </template> | |
| 196 | - </el-table-column> | |
| 197 | - <el-table-column prop="skzhMc" label="资金账户" min-width="140" show-overflow-tooltip> | |
| 198 | - <template slot-scope="scope"> | |
| 199 | - <i | |
| 200 | - :class=" | |
| 201 | - scope.row.fx === '收款' | |
| 202 | - ? 'el-icon-wallet row-ico row-ico--sk' | |
| 203 | - : 'el-icon-wallet row-ico row-ico--payacc' | |
| 204 | - " | |
| 205 | - /> | |
| 206 | - {{ cellText(scope.row.skzhMc) }} | |
| 207 | - </template> | |
| 208 | - </el-table-column> | |
| 209 | - <el-table-column prop="je" label="金额" width="120" align="right"> | |
| 210 | - <template slot-scope="scope"> | |
| 211 | - <span :class="ledgerAmountClass(scope.row)">{{ formatMoney(scope.row.je) }}</span> | |
| 212 | - </template> | |
| 213 | - </el-table-column> | |
| 214 | - <el-table-column prop="ly" label="来源" width="100" show-overflow-tooltip> | |
| 215 | - <template slot-scope="scope"> | |
| 216 | - <i class="el-icon-link row-ico row-ico--ly" /> | |
| 217 | - {{ cellText(scope.row.ly) }} | |
| 218 | - </template> | |
| 219 | - </el-table-column> | |
| 220 | - <el-table-column prop="ycddh" label="原单号" min-width="130" show-overflow-tooltip> | |
| 221 | - <template slot-scope="scope"> | |
| 222 | - <i class="el-icon-back row-ico row-ico--ret" /> | |
| 223 | - {{ cellText(scope.row.ycddh) }} | |
| 224 | - </template> | |
| 225 | - </el-table-column> | |
| 226 | - <el-table-column prop="jsr" label="经手人" width="88" show-overflow-tooltip /> | |
| 227 | - <el-table-column prop="zy" label="备注" min-width="160" show-overflow-tooltip /> | |
| 228 | - </el-table> | |
| 229 | - <div class="pager-wrap"> | |
| 230 | - <el-pagination | |
| 231 | - :current-page="pagination.currentPage" | |
| 232 | - :page-sizes="[50, 100, 200, 500]" | |
| 233 | - :page-size="pagination.pageSize" | |
| 234 | - layout="total, sizes, prev, pager, next, jumper" | |
| 235 | - :total="pagination.total" | |
| 236 | - @size-change="handleSizeChange" | |
| 237 | - @current-change="handleCurrentChange" | |
| 238 | - /> | |
| 239 | - </div> | |
| 240 | - </div> | |
| 241 | - </div> | |
| 118 | + <el-table v-loading="ledgerLoading" :data="ledgerRows" border stripe class="cell-nowrap"> | |
| 119 | + <el-table-column type="index" label="行号" width="56" align="center" /> | |
| 120 | + <el-table-column prop="djrq" label="日期" width="110" align="center" /> | |
| 121 | + <el-table-column prop="djbh" label="单据编号" min-width="150" show-overflow-tooltip /> | |
| 122 | + <el-table-column prop="djlx" label="单据类型" min-width="140" show-overflow-tooltip /> | |
| 123 | + <el-table-column prop="fx" label="方向" width="80" align="center"> | |
| 124 | + <template slot-scope="scope"> | |
| 125 | + <el-tag :type="scope.row.fx === '收款' ? 'success' : 'danger'" size="mini" effect="plain">{{ scope.row.fx }}</el-tag> | |
| 126 | + </template> | |
| 127 | + </el-table-column> | |
| 128 | + <el-table-column prop="fffs" label="方式" min-width="120" show-overflow-tooltip /> | |
| 129 | + <el-table-column prop="skzhMc" label="账户" min-width="140" show-overflow-tooltip /> | |
| 130 | + <el-table-column prop="je" label="金额" width="120" align="right"> | |
| 131 | + <template slot-scope="scope">{{ formatMoney(scope.row.je) }}</template> | |
| 132 | + </el-table-column> | |
| 133 | + <el-table-column prop="jsr" label="经手人" width="90" show-overflow-tooltip /> | |
| 134 | + <el-table-column prop="zy" label="备注" min-width="180" show-overflow-tooltip /> | |
| 135 | + </el-table> | |
| 136 | + <div class="pager-wrap"> | |
| 137 | + <el-pagination | |
| 138 | + :current-page="ledgerPagination.currentPage" | |
| 139 | + :page-size="ledgerPagination.pageSize" | |
| 140 | + :page-sizes="[50, 100, 200, 500]" | |
| 141 | + layout="total, sizes, prev, pager, next, jumper" | |
| 142 | + :total="ledgerPagination.total" | |
| 143 | + @size-change="handleLedgerSizeChange" | |
| 144 | + @current-change="handleLedgerPageChange" | |
| 145 | + /> | |
| 242 | 146 | </div> |
| 243 | - </div> | |
| 147 | + </el-dialog> | |
| 244 | 148 | </div> |
| 245 | 149 | </template> |
| 246 | 150 | |
| 247 | 151 | <script> |
| 248 | 152 | import request from '@/utils/request' |
| 153 | +import { getAccountSelector } from '@/api/extend/wtAccount' | |
| 249 | 154 | |
| 250 | 155 | export default { |
| 251 | 156 | name: 'WtSkfkChannelStat', |
| 252 | - | |
| 253 | 157 | data() { |
| 254 | - const y = new Date().getFullYear() | |
| 255 | - const m = String(new Date().getMonth() + 1).padStart(2, '0') | |
| 256 | - const start = `${y}-${m}-01` | |
| 257 | - const end = `${y}-${m}-${String(new Date(y, new Date().getMonth() + 1, 0).getDate()).padStart(2, '0')}` | |
| 258 | 158 | return { |
| 259 | - showAll: false, | |
| 260 | 159 | summaryLoading: false, |
| 261 | 160 | filters: { |
| 262 | - dateRange: [start, end], | |
| 263 | - fx: '', | |
| 264 | - qd: '', | |
| 265 | - djlx: '' | |
| 161 | + date: this.today(), | |
| 162 | + accountId: '' | |
| 266 | 163 | }, |
| 267 | - skAccountList: [], | |
| 268 | - skMethodList: [], | |
| 269 | - fkAccountList: [], | |
| 270 | - fkMethodList: [], | |
| 271 | - skSideTotal: 0, | |
| 272 | - fkSideTotal: 0, | |
| 273 | - ledgerList: [], | |
| 164 | + accountOptions: [], | |
| 165 | + tableRows: [], | |
| 166 | + ledgerDialogVisible: false, | |
| 167 | + currentLedgerAccountName: '', | |
| 274 | 168 | ledgerLoading: false, |
| 275 | - pagination: { | |
| 169 | + ledgerRows: [], | |
| 170 | + ledgerFilters: { | |
| 171 | + dateRange: this.defaultLedgerRange(), | |
| 172 | + accountId: '' | |
| 173 | + }, | |
| 174 | + ledgerPagination: { | |
| 276 | 175 | currentPage: 1, |
| 277 | 176 | pageSize: 100, |
| 278 | 177 | total: 0 |
| 279 | - }, | |
| 280 | - tableHeaderStyle: { background: '#f5f7fa' } | |
| 281 | - } | |
| 282 | - }, | |
| 283 | - | |
| 284 | - computed: { | |
| 285 | - skPanels() { | |
| 286 | - return [ | |
| 287 | - { | |
| 288 | - key: 'skacc', | |
| 289 | - title: '入账账户汇总', | |
| 290 | - icon: 'el-icon-coin', | |
| 291 | - tone: 'sk', | |
| 292 | - rowIcon: 'el-icon-coin', | |
| 293 | - rowTone: 'sk', | |
| 294 | - colLabel: '入账账户', | |
| 295 | - list: this.skAccountList | |
| 296 | - }, | |
| 297 | - { | |
| 298 | - key: 'skmeth', | |
| 299 | - title: '买方付款方式汇总', | |
| 300 | - icon: 'el-icon-bank-card', | |
| 301 | - tone: 'fk', | |
| 302 | - rowIcon: 'el-icon-bank-card', | |
| 303 | - rowTone: 'fk', | |
| 304 | - colLabel: '付款方式', | |
| 305 | - list: this.skMethodList | |
| 306 | - } | |
| 307 | - ] | |
| 308 | - }, | |
| 309 | - fkPanels() { | |
| 310 | - return [ | |
| 311 | - { | |
| 312 | - key: 'fkacc', | |
| 313 | - title: '付款账户汇总', | |
| 314 | - icon: 'el-icon-wallet', | |
| 315 | - tone: 'payacc', | |
| 316 | - rowIcon: 'el-icon-wallet', | |
| 317 | - rowTone: 'payacc', | |
| 318 | - colLabel: '付款账户', | |
| 319 | - list: this.fkAccountList | |
| 320 | - }, | |
| 321 | - { | |
| 322 | - key: 'fkmeth', | |
| 323 | - title: '方式/渠道汇总', | |
| 324 | - icon: 'el-icon-sort', | |
| 325 | - tone: 'fk', | |
| 326 | - rowIcon: 'el-icon-sort', | |
| 327 | - rowTone: 'fk', | |
| 328 | - colLabel: '方式', | |
| 329 | - list: this.fkMethodList | |
| 330 | - } | |
| 331 | - ] | |
| 178 | + } | |
| 332 | 179 | } |
| 333 | 180 | }, |
| 334 | - | |
| 335 | 181 | methods: { |
| 336 | - indexMethod(index) { | |
| 337 | - return (this.pagination.currentPage - 1) * this.pagination.pageSize + index + 1 | |
| 182 | + today() { | |
| 183 | + const d = new Date() | |
| 184 | + const y = d.getFullYear() | |
| 185 | + const m = String(d.getMonth() + 1).padStart(2, '0') | |
| 186 | + const day = String(d.getDate()).padStart(2, '0') | |
| 187 | + return `${y}-${m}-${day}` | |
| 338 | 188 | }, |
| 339 | - cellText(val) { | |
| 340 | - if (val === null || val === undefined || val === '') return '' | |
| 341 | - return String(val) | |
| 189 | + defaultLedgerRange() { | |
| 190 | + const d = new Date() | |
| 191 | + const y = d.getFullYear() | |
| 192 | + const today = this.today() | |
| 193 | + return [`${y}-01-01`, today] | |
| 342 | 194 | }, |
| 343 | 195 | formatMoney(val) { |
| 344 | 196 | if (val === null || val === undefined || val === '') return '' |
| ... | ... | @@ -346,323 +198,178 @@ export default { |
| 346 | 198 | if (Number.isNaN(n)) return String(val) |
| 347 | 199 | return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) |
| 348 | 200 | }, |
| 349 | - ledgerRowIcon(row) { | |
| 350 | - if (row && row.fx === '收款') return 'el-icon-top row-ico row-ico--sk' | |
| 351 | - return 'el-icon-bottom row-ico row-ico--fk' | |
| 201 | + rowClassName({ row }) { | |
| 202 | + return row.isCategory ? 'category-row' : '' | |
| 352 | 203 | }, |
| 353 | - ledgerAmountClass(row) { | |
| 354 | - if (row && row.fx === '收款') return 'amount-cell amount-cell--sk' | |
| 355 | - return 'amount-cell amount-cell--fk' | |
| 204 | + async fetchAccountOptions() { | |
| 205 | + const res = await getAccountSelector() | |
| 206 | + const list = (res.data && res.data.list) ? res.data.list : [] | |
| 207 | + this.accountOptions = list | |
| 356 | 208 | }, |
| 357 | - buildDatePayload() { | |
| 358 | - const p = {} | |
| 359 | - if (this.filters.dateRange && this.filters.dateRange.length === 2) { | |
| 360 | - p.startDate = this.filters.dateRange[0] | |
| 361 | - p.endDate = this.filters.dateRange[1] | |
| 362 | - } | |
| 363 | - return p | |
| 364 | - }, | |
| 365 | - fetchSummary() { | |
| 366 | - this.summaryLoading = true | |
| 367 | - const data = this.buildDatePayload() | |
| 368 | - return request({ | |
| 369 | - url: '/api/Extend/WtSkfkChannelStat/Actions/GetChannelSummary', | |
| 370 | - method: 'GET', | |
| 371 | - data | |
| 372 | - }) | |
| 373 | - .then(res => { | |
| 374 | - const body = res.data || {} | |
| 375 | - this.skAccountList = Array.isArray(body.skAccountList) ? body.skAccountList : [] | |
| 376 | - this.skMethodList = Array.isArray(body.skMethodList) ? body.skMethodList : [] | |
| 377 | - this.fkAccountList = Array.isArray(body.fkAccountList) ? body.fkAccountList : [] | |
| 378 | - this.fkMethodList = Array.isArray(body.fkMethodList) ? body.fkMethodList : [] | |
| 379 | - this.skSideTotal = body.skSideTotal != null ? Number(body.skSideTotal) : 0 | |
| 380 | - this.fkSideTotal = body.fkSideTotal != null ? Number(body.fkSideTotal) : 0 | |
| 209 | + buildTableRows(groups, statDate) { | |
| 210 | + const rows = [] | |
| 211 | + ;(groups || []).forEach(group => { | |
| 212 | + rows.push({ | |
| 213 | + rowKey: `cat-${group.category}`, | |
| 214 | + isCategory: true, | |
| 215 | + date: '', | |
| 216 | + account: group.category, | |
| 217 | + openingBalance: group.totalOpeningBalance, | |
| 218 | + todayIncome: group.totalTodayIncome, | |
| 219 | + todayExpense: group.totalTodayExpense, | |
| 220 | + periodBalance: group.totalPeriodBalance, | |
| 221 | + realtimeBalance: group.totalRealtimeBalance | |
| 381 | 222 | }) |
| 382 | - .catch(() => { | |
| 383 | - this.$message.error('加载汇总失败,请稍后重试') | |
| 223 | + ;(group.rows || []).forEach(item => { | |
| 224 | + rows.push({ | |
| 225 | + rowKey: `acc-${item.accountId}`, | |
| 226 | + isCategory: false, | |
| 227 | + accountId: item.accountId, | |
| 228 | + date: statDate || item.date, | |
| 229 | + account: item.account, | |
| 230 | + openingBalance: item.openingBalance, | |
| 231 | + todayIncome: item.todayIncome, | |
| 232 | + todayExpense: item.todayExpense, | |
| 233 | + periodBalance: item.periodBalance, | |
| 234 | + realtimeBalance: item.realtimeBalance | |
| 235 | + }) | |
| 384 | 236 | }) |
| 385 | - .finally(() => { | |
| 386 | - this.summaryLoading = false | |
| 237 | + }) | |
| 238 | + this.tableRows = rows | |
| 239 | + }, | |
| 240 | + async fetchSummary() { | |
| 241 | + this.summaryLoading = true | |
| 242 | + try { | |
| 243 | + const data = { | |
| 244 | + date: this.filters.date, | |
| 245 | + accountId: this.filters.accountId || undefined | |
| 246 | + } | |
| 247 | + const res = await request({ | |
| 248 | + url: '/api/Extend/WtSkfkChannelStat/Actions/GetAccountReconcileSummary', | |
| 249 | + method: 'GET', | |
| 250 | + data | |
| 387 | 251 | }) |
| 252 | + const body = res.data || {} | |
| 253 | + const groups = Array.isArray(body.categoryGroups) ? body.categoryGroups : [] | |
| 254 | + this.buildTableRows(groups, body.date || this.filters.date) | |
| 255 | + } catch (e) { | |
| 256 | + this.$message.error('加载账户对账表失败') | |
| 257 | + this.tableRows = [] | |
| 258 | + } finally { | |
| 259 | + this.summaryLoading = false | |
| 260 | + } | |
| 388 | 261 | }, |
| 389 | - fetchLedger() { | |
| 390 | - this.ledgerLoading = true | |
| 262 | + buildLedgerPayload() { | |
| 391 | 263 | const data = { |
| 392 | - ...this.buildDatePayload(), | |
| 393 | - currentPage: this.pagination.currentPage, | |
| 394 | - pageSize: this.pagination.pageSize | |
| 264 | + currentPage: this.ledgerPagination.currentPage, | |
| 265 | + pageSize: this.ledgerPagination.pageSize, | |
| 266 | + accountId: this.ledgerFilters.accountId || undefined | |
| 395 | 267 | } |
| 396 | - if (this.filters.fx) data.fx = this.filters.fx | |
| 397 | - if (this.filters.qd && this.filters.qd.trim()) data.qd = this.filters.qd.trim() | |
| 398 | - if (this.filters.djlx && this.filters.djlx.trim()) data.djlx = this.filters.djlx.trim() | |
| 399 | - | |
| 400 | - return request({ | |
| 401 | - url: '/api/Extend/WtSkfkChannelStat/Actions/GetChannelLedger', | |
| 402 | - method: 'GET', | |
| 403 | - data | |
| 404 | - }) | |
| 405 | - .then(res => { | |
| 406 | - const body = res.data || {} | |
| 407 | - this.ledgerList = Array.isArray(body.list) ? body.list : [] | |
| 408 | - this.pagination.total = body.total != null ? Number(body.total) : 0 | |
| 409 | - }) | |
| 410 | - .catch(() => { | |
| 411 | - this.$message.error('加载流水明细失败,请稍后重试') | |
| 412 | - this.ledgerList = [] | |
| 413 | - this.pagination.total = 0 | |
| 414 | - }) | |
| 415 | - .finally(() => { | |
| 416 | - this.ledgerLoading = false | |
| 268 | + if (this.ledgerFilters.dateRange && this.ledgerFilters.dateRange.length === 2) { | |
| 269 | + data.startDate = this.ledgerFilters.dateRange[0] | |
| 270 | + data.endDate = this.ledgerFilters.dateRange[1] | |
| 271 | + } | |
| 272 | + return data | |
| 273 | + }, | |
| 274 | + async fetchLedger() { | |
| 275 | + this.ledgerLoading = true | |
| 276 | + try { | |
| 277 | + const res = await request({ | |
| 278 | + url: '/api/Extend/WtSkfkChannelStat/Actions/GetAccountReconcileLedger', | |
| 279 | + method: 'GET', | |
| 280 | + data: this.buildLedgerPayload() | |
| 417 | 281 | }) |
| 282 | + const body = res.data || {} | |
| 283 | + this.ledgerRows = Array.isArray(body.list) ? body.list : [] | |
| 284 | + this.ledgerPagination.total = body.total ? Number(body.total) : 0 | |
| 285 | + } catch (e) { | |
| 286 | + this.$message.error('加载账户明细失败') | |
| 287 | + this.ledgerRows = [] | |
| 288 | + this.ledgerPagination.total = 0 | |
| 289 | + } finally { | |
| 290 | + this.ledgerLoading = false | |
| 291 | + } | |
| 418 | 292 | }, |
| 419 | - refreshAll() { | |
| 420 | - return Promise.all([this.fetchSummary(), this.fetchLedger()]) | |
| 293 | + openLedgerDialog(row) { | |
| 294 | + if (!row || row.isCategory || !row.accountId) return | |
| 295 | + this.currentLedgerAccountName = row.account || '' | |
| 296 | + this.ledgerFilters.accountId = row.accountId | |
| 297 | + this.ledgerFilters.dateRange = this.defaultLedgerRange() | |
| 298 | + this.ledgerPagination.currentPage = 1 | |
| 299 | + this.ledgerDialogVisible = true | |
| 300 | + this.fetchLedger() | |
| 421 | 301 | }, |
| 422 | - handleSearch() { | |
| 423 | - this.pagination.currentPage = 1 | |
| 424 | - this.refreshAll() | |
| 302 | + handleLedgerSearch() { | |
| 303 | + this.ledgerPagination.currentPage = 1 | |
| 304 | + this.fetchLedger() | |
| 425 | 305 | }, |
| 426 | - handleReset() { | |
| 427 | - const y = new Date().getFullYear() | |
| 428 | - const m = String(new Date().getMonth() + 1).padStart(2, '0') | |
| 429 | - const start = `${y}-${m}-01` | |
| 430 | - const end = `${y}-${m}-${String(new Date(y, new Date().getMonth() + 1, 0).getDate()).padStart(2, '0')}` | |
| 431 | - this.filters = { | |
| 432 | - dateRange: [start, end], | |
| 433 | - fx: '', | |
| 434 | - qd: '', | |
| 435 | - djlx: '' | |
| 306 | + handleLedgerReset() { | |
| 307 | + this.ledgerFilters = { | |
| 308 | + dateRange: this.defaultLedgerRange(), | |
| 309 | + accountId: '' | |
| 436 | 310 | } |
| 437 | - this.showAll = false | |
| 438 | - this.pagination.currentPage = 1 | |
| 439 | - this.refreshAll() | |
| 311 | + this.ledgerPagination.currentPage = 1 | |
| 312 | + this.fetchLedger() | |
| 440 | 313 | }, |
| 441 | - handleSizeChange(size) { | |
| 442 | - this.pagination.pageSize = size | |
| 443 | - this.pagination.currentPage = 1 | |
| 314 | + handleLedgerSizeChange(size) { | |
| 315 | + this.ledgerPagination.pageSize = size | |
| 316 | + this.ledgerPagination.currentPage = 1 | |
| 444 | 317 | this.fetchLedger() |
| 445 | 318 | }, |
| 446 | - handleCurrentChange(page) { | |
| 447 | - this.pagination.currentPage = page | |
| 319 | + handleLedgerPageChange(page) { | |
| 320 | + this.ledgerPagination.currentPage = page | |
| 448 | 321 | this.fetchLedger() |
| 322 | + }, | |
| 323 | + handleSearch() { | |
| 324 | + this.fetchSummary() | |
| 325 | + }, | |
| 326 | + handleReset() { | |
| 327 | + this.filters = { | |
| 328 | + date: this.today(), | |
| 329 | + accountId: '' | |
| 330 | + } | |
| 331 | + this.fetchSummary() | |
| 449 | 332 | } |
| 450 | 333 | }, |
| 451 | - | |
| 452 | - created() { | |
| 453 | - this.refreshAll() | |
| 334 | + async created() { | |
| 335 | + await this.fetchAccountOptions() | |
| 336 | + await this.fetchSummary() | |
| 454 | 337 | } |
| 455 | 338 | } |
| 456 | 339 | </script> |
| 457 | 340 | |
| 458 | 341 | <style scoped lang="scss"> |
| 459 | -.skfk-channel-stat-page { | |
| 460 | - &.NCC-common-layout { | |
| 461 | - background: #ebeef5; | |
| 462 | - } | |
| 463 | - | |
| 464 | - &__main { | |
| 465 | - padding: 10px 16px 16px; | |
| 466 | - overflow: auto; | |
| 467 | - box-sizing: border-box; | |
| 468 | - } | |
| 469 | -} | |
| 470 | - | |
| 471 | -.summary-wrap { | |
| 472 | - min-height: 120px; | |
| 473 | - margin-bottom: 4px; | |
| 474 | -} | |
| 475 | - | |
| 476 | -.summary-cards-row { | |
| 477 | - margin-bottom: 10px; | |
| 478 | -} | |
| 479 | - | |
| 480 | -.subsection-title { | |
| 481 | - font-size: 15px; | |
| 342 | +.title-line { | |
| 343 | + margin: 0 0 8px 2px; | |
| 344 | + font-size: 16px; | |
| 482 | 345 | font-weight: 600; |
| 483 | 346 | color: #303133; |
| 484 | - margin: 14px 0 8px 2px; | |
| 485 | - padding-left: 8px; | |
| 486 | - border-left: 3px solid #409eff; | |
| 487 | 347 | } |
| 488 | 348 | |
| 489 | -.stat-card { | |
| 490 | - height: 100px; | |
| 491 | - border-radius: 12px; | |
| 492 | - padding: 12px; | |
| 493 | - box-sizing: border-box; | |
| 494 | - display: flex; | |
| 495 | - align-items: center; | |
| 496 | - gap: 12px; | |
| 497 | - margin-bottom: 10px; | |
| 498 | - background: #fff; | |
| 499 | - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); | |
| 500 | - | |
| 501 | - &--sk { | |
| 502 | - border-left: 4px solid #67c23a; | |
| 503 | - } | |
| 504 | - | |
| 505 | - &--fk { | |
| 506 | - border-left: 4px solid #f56c6c; | |
| 507 | - } | |
| 508 | - | |
| 509 | - &__icon { | |
| 510 | - font-size: 36px; | |
| 511 | - color: #409eff; | |
| 512 | - | |
| 513 | - &--sk { | |
| 514 | - color: #67c23a; | |
| 515 | - } | |
| 516 | - | |
| 517 | - &--fk { | |
| 518 | - color: #f56c6c; | |
| 519 | - } | |
| 520 | - } | |
| 521 | - | |
| 522 | - &__body { | |
| 523 | - flex: 1; | |
| 524 | - display: flex; | |
| 525 | - flex-direction: column; | |
| 526 | - justify-content: center; | |
| 527 | - min-width: 0; | |
| 528 | - } | |
| 529 | - | |
| 530 | - &__label { | |
| 531 | - font-size: 13px; | |
| 532 | - color: #909399; | |
| 533 | - margin-bottom: 6px; | |
| 534 | - line-height: 1.3; | |
| 535 | - } | |
| 536 | - | |
| 537 | - &__value { | |
| 538 | - font-size: 22px; | |
| 539 | - font-weight: 600; | |
| 540 | - color: #303133; | |
| 541 | - font-variant-numeric: tabular-nums; | |
| 542 | - } | |
| 543 | -} | |
| 544 | - | |
| 545 | -.action-row { | |
| 546 | - display: inline-flex; | |
| 547 | - flex-wrap: wrap; | |
| 548 | - justify-content: flex-start; | |
| 549 | - align-items: center; | |
| 550 | - gap: 8px; | |
| 551 | -} | |
| 552 | - | |
| 553 | -.table-section { | |
| 554 | - background: #fff; | |
| 555 | - border-radius: 12px; | |
| 556 | - padding: 12px 12px 10px 12px; | |
| 557 | - margin: 0; | |
| 558 | - width: 100%; | |
| 559 | - box-sizing: border-box; | |
| 560 | - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); | |
| 349 | +.cell-nowrap ::v-deep .cell { | |
| 350 | + white-space: nowrap; | |
| 561 | 351 | } |
| 562 | 352 | |
| 563 | -.channel-block { | |
| 564 | - margin-bottom: 12px; | |
| 353 | +.reconcile-table ::v-deep .el-table__header th { | |
| 354 | + background: #f5f7fa; | |
| 565 | 355 | } |
| 566 | 356 | |
| 567 | -.table-title-bar { | |
| 568 | - font-size: 15px; | |
| 357 | +.category-text { | |
| 569 | 358 | font-weight: 600; |
| 570 | - margin-bottom: 10px; | |
| 571 | - display: flex; | |
| 572 | - align-items: center; | |
| 573 | - flex-wrap: wrap; | |
| 574 | - gap: 8px; | |
| 575 | -} | |
| 576 | - | |
| 577 | -.ledger-title-bar { | |
| 578 | - margin-bottom: 12px; | |
| 579 | -} | |
| 580 | - | |
| 581 | -.title-icon { | |
| 582 | - font-size: 20px; | |
| 583 | - margin-right: 2px; | |
| 584 | - | |
| 585 | - &--sk { | |
| 586 | - color: #67c23a; | |
| 587 | - } | |
| 588 | - | |
| 589 | - &--fk { | |
| 590 | - color: #f56c6c; | |
| 591 | - } | |
| 592 | - | |
| 593 | - &--payacc { | |
| 594 | - color: #e6a23c; | |
| 595 | - } | |
| 596 | - | |
| 597 | - &--ledger { | |
| 598 | - color: #409eff; | |
| 599 | - } | |
| 600 | -} | |
| 601 | - | |
| 602 | -.row-ico { | |
| 603 | - margin-right: 4px; | |
| 604 | - | |
| 605 | - &--sk { | |
| 606 | - color: #67c23a; | |
| 607 | - } | |
| 608 | - | |
| 609 | - &--fk { | |
| 610 | - color: #f56c6c; | |
| 611 | - } | |
| 612 | - | |
| 613 | - &--doc { | |
| 614 | - color: #409eff; | |
| 615 | - } | |
| 616 | - | |
| 617 | - &--type { | |
| 618 | - color: #e6a23c; | |
| 619 | - } | |
| 620 | - | |
| 621 | - &--ly { | |
| 622 | - color: #909399; | |
| 623 | - } | |
| 624 | - | |
| 625 | - &--ret { | |
| 626 | - color: #f56c6c; | |
| 627 | - } | |
| 628 | - | |
| 629 | - &--payacc { | |
| 630 | - color: #e6a23c; | |
| 631 | - } | |
| 359 | + color: #303133; | |
| 632 | 360 | } |
| 633 | 361 | |
| 634 | -.amount-cell { | |
| 635 | - font-variant-numeric: tabular-nums; | |
| 636 | - font-weight: 500; | |
| 637 | - | |
| 638 | - &--sk { | |
| 639 | - color: #67c23a; | |
| 640 | - } | |
| 641 | - | |
| 642 | - &--fk { | |
| 643 | - color: #f56c6c; | |
| 644 | - } | |
| 362 | +.account-link { | |
| 363 | + color: #303133; | |
| 645 | 364 | } |
| 646 | 365 | |
| 647 | -.table-scroll { | |
| 648 | - max-height: none; | |
| 366 | +::v-deep .category-row > td { | |
| 367 | + background: #f5f7fa !important; | |
| 649 | 368 | } |
| 650 | 369 | |
| 651 | 370 | .pager-wrap { |
| 652 | 371 | display: flex; |
| 653 | 372 | justify-content: flex-start; |
| 654 | - padding: 12px 0 4px; | |
| 655 | -} | |
| 656 | - | |
| 657 | -.cell-nowrap ::v-deep .cell { | |
| 658 | - white-space: nowrap; | |
| 659 | -} | |
| 660 | - | |
| 661 | -.channel-tables-row { | |
| 662 | - margin-bottom: 4px; | |
| 663 | -} | |
| 664 | - | |
| 665 | -.ledger-section { | |
| 666 | - margin-top: 8px; | |
| 373 | + padding-top: 12px; | |
| 667 | 374 | } |
| 668 | 375 | </style> | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtSp/Form.vue
| ... | ... | @@ -686,8 +686,9 @@ |
| 686 | 686 | } |
| 687 | 687 | }) |
| 688 | 688 | }) |
| 689 | - .catch(() => { | |
| 690 | - this.$message({ message: '提交失败,请检查数据格式', type: 'error', duration: 3000 }) | |
| 689 | + .catch((err) => { | |
| 690 | + const msg = (err && err.message) ? err.message : '提交失败,请检查数据格式' | |
| 691 | + this.$message({ message: msg, type: 'error', duration: 3000 }) | |
| 691 | 692 | }) |
| 692 | 693 | } else { |
| 693 | 694 | request({ |
| ... | ... | @@ -706,8 +707,9 @@ |
| 706 | 707 | } |
| 707 | 708 | }) |
| 708 | 709 | }) |
| 709 | - .catch(() => { | |
| 710 | - this.$message({ message: '更新失败,请检查数据格式', type: 'error', duration: 3000 }) | |
| 710 | + .catch((err) => { | |
| 711 | + const msg = (err && err.message) ? err.message : '更新失败,请检查数据格式' | |
| 712 | + this.$message({ message: msg, type: 'error', duration: 3000 }) | |
| 711 | 713 | }) |
| 712 | 714 | } |
| 713 | 715 | }) | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtSp/index.vue
| ... | ... | @@ -5,7 +5,7 @@ |
| 5 | 5 | <el-form @submit.native.prevent> |
| 6 | 6 | <el-col :span="6"> |
| 7 | 7 | <el-form-item label="商品名称"> |
| 8 | - <el-input v-model="query.spmc" placeholder="商品名称" clearable /> | |
| 8 | + <el-input v-model="query.spmc" placeholder="商品名称" clearable @keyup.enter.native="search()" /> | |
| 9 | 9 | </el-form-item> |
| 10 | 10 | </el-col> |
| 11 | 11 | <el-col :span="6"> |
| ... | ... | @@ -25,7 +25,7 @@ |
| 25 | 25 | <template v-if="showAll"> |
| 26 | 26 | <el-col :span="6"> |
| 27 | 27 | <el-form-item label="商品编码"> |
| 28 | - <el-input v-model="query.spbm" placeholder="商品编码" clearable /> | |
| 28 | + <el-input v-model="query.spbm" placeholder="商品编码" clearable @keyup.enter.native="search()" /> | |
| 29 | 29 | </el-form-item> |
| 30 | 30 | </el-col> |
| 31 | 31 | <el-col :span="6"> |
| ... | ... | @@ -47,7 +47,7 @@ |
| 47 | 47 | </el-col> |
| 48 | 48 | <el-col :span="6"> |
| 49 | 49 | <el-form-item label="售后规则"> |
| 50 | - <el-input v-model="query.shgz" placeholder="如:质保 1 年 / 7 天无理由" /> | |
| 50 | + <el-input v-model="query.shgz" placeholder="如:质保 1 年 / 7 天无理由" clearable @keyup.enter.native="search()" /> | |
| 51 | 51 | </el-form-item> |
| 52 | 52 | </el-col> |
| 53 | 53 | <el-col :span="6"> |
| ... | ... | @@ -64,6 +64,11 @@ |
| 64 | 64 | </el-select> |
| 65 | 65 | </el-form-item> |
| 66 | 66 | </el-col> |
| 67 | + <el-col :span="6"> | |
| 68 | + <el-form-item label="抖音SKU"> | |
| 69 | + <el-input v-model="query.dyspid" placeholder="抖音SKU" clearable @keyup.enter.native="search()" /> | |
| 70 | + </el-form-item> | |
| 71 | + </el-col> | |
| 67 | 72 | </template> |
| 68 | 73 | <el-col :span="6"> |
| 69 | 74 | <el-form-item> |
| ... | ... | @@ -81,6 +86,8 @@ |
| 81 | 86 | <el-button type="primary" icon="el-icon-plus" @click="addOrUpdateHandle()">新增</el-button> |
| 82 | 87 | <el-button type="text" icon="el-icon-download" @click="exportData()">导出</el-button> |
| 83 | 88 | <el-button type="text" icon="el-icon-delete" @click="handleBatchRemoveDel()">批量删除</el-button> |
| 89 | + <el-button type="text" icon="el-icon-postcard" @click="openBatchSetHyxzDialog">批量设置权益卡</el-button> | |
| 90 | + <el-button type="text" icon="el-icon-close" @click="handleBatchClearHyxz">批量取消权益卡</el-button> | |
| 84 | 91 | </div> |
| 85 | 92 | <div class="NCC-common-head-right"> |
| 86 | 93 | <el-tooltip effect="dark" content="刷新" placement="top"> |
| ... | ... | @@ -162,6 +169,34 @@ |
| 162 | 169 | </div> |
| 163 | 170 | <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" /> |
| 164 | 171 | <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" /> |
| 172 | + <el-dialog | |
| 173 | + title="批量设置权益卡" | |
| 174 | + :visible.sync="batchSetHyxzVisible" | |
| 175 | + width="560px" | |
| 176 | + :close-on-click-modal="false" | |
| 177 | + > | |
| 178 | + <el-form size="small" label-width="100px"> | |
| 179 | + <el-form-item label="权益卡"> | |
| 180 | + <el-select | |
| 181 | + v-model="batchSetHyxzCardIds" | |
| 182 | + multiple | |
| 183 | + filterable | |
| 184 | + clearable | |
| 185 | + placeholder="请选择权益卡" | |
| 186 | + style="width: 100%" | |
| 187 | + > | |
| 188 | + <el-option v-for="card in hyxzCardOptions" :key="card.id" :label="card.kjmc" :value="card.id" /> | |
| 189 | + </el-select> | |
| 190 | + </el-form-item> | |
| 191 | + <div class="wt-sp-batch-tip"> | |
| 192 | + 仅对当前勾选的商品生效。 | |
| 193 | + </div> | |
| 194 | + </el-form> | |
| 195 | + <span slot="footer" class="dialog-footer"> | |
| 196 | + <el-button @click="batchSetHyxzVisible = false">取 消</el-button> | |
| 197 | + <el-button type="primary" :loading="batchHyxzLoading" @click="handleBatchSetHyxz">确 定</el-button> | |
| 198 | + </span> | |
| 199 | + </el-dialog> | |
| 165 | 200 | </div> |
| 166 | 201 | </template> |
| 167 | 202 | <script> |
| ... | ... | @@ -186,6 +221,7 @@ |
| 186 | 221 | shgz:undefined, |
| 187 | 222 | xsqd:undefined, |
| 188 | 223 | xsmd:undefined, |
| 224 | + dyspid:undefined, | |
| 189 | 225 | }, |
| 190 | 226 | list: [], |
| 191 | 227 | listLoading: true, |
| ... | ... | @@ -198,6 +234,9 @@ |
| 198 | 234 | }, |
| 199 | 235 | formVisible: false, |
| 200 | 236 | exportBoxVisible: false, |
| 237 | + batchSetHyxzVisible: false, | |
| 238 | + batchSetHyxzCardIds: [], | |
| 239 | + batchHyxzLoading: false, | |
| 201 | 240 | columnList: [ |
| 202 | 241 | { prop: 'spmc', label: '商品名称' }, |
| 203 | 242 | { prop: 'pl', label: '商品品类' }, |
| ... | ... | @@ -216,6 +255,7 @@ |
| 216 | 255 | spxlhTypeOptions:[{"fullName":"入1出1","id":"1"},{"fullName":"入0出1","id":"2"},{"fullName":"入0出0","id":"3"}], |
| 217 | 256 | xsqdOptions:[{"fullName":"门店组","id":"1"},{"fullName":"网店组","id":"2"}], |
| 218 | 257 | mdfzOptions : [], |
| 258 | + hyxzCardOptions: [], | |
| 219 | 259 | } |
| 220 | 260 | }, |
| 221 | 261 | computed: {}, |
| ... | ... | @@ -224,6 +264,7 @@ |
| 224 | 264 | this.getplOptions(); |
| 225 | 265 | this.getppOptions(); |
| 226 | 266 | this.getMdfzOptions(); |
| 267 | + this.getHyxzCardOptions(); | |
| 227 | 268 | }, |
| 228 | 269 | methods: { |
| 229 | 270 | getplOptions(){ |
| ... | ... | @@ -244,6 +285,76 @@ |
| 244 | 285 | this.mdfzOptions = res.data || [] |
| 245 | 286 | }).catch(() => { this.mdfzOptions = [] }) |
| 246 | 287 | }, |
| 288 | + getHyxzCardOptions() { | |
| 289 | + request({ | |
| 290 | + url: '/api/Extend/WtHyKjqy', | |
| 291 | + method: 'GET', | |
| 292 | + data: { currentPage: 1, pageSize: 200 } | |
| 293 | + }) | |
| 294 | + .then(res => { | |
| 295 | + this.hyxzCardOptions = res.data && res.data.list ? res.data.list : [] | |
| 296 | + }) | |
| 297 | + .catch(() => { | |
| 298 | + this.hyxzCardOptions = [] | |
| 299 | + }) | |
| 300 | + }, | |
| 301 | + openBatchSetHyxzDialog() { | |
| 302 | + this.batchSetHyxzCardIds = [] | |
| 303 | + this.batchSetHyxzVisible = true | |
| 304 | + }, | |
| 305 | + handleBatchSetHyxz() { | |
| 306 | + if (!this.multipleSelection.length) { | |
| 307 | + this.$message.warning('请先勾选需要设置权益卡的商品') | |
| 308 | + return | |
| 309 | + } | |
| 310 | + if (!this.batchSetHyxzCardIds.length) { | |
| 311 | + this.$message.warning('请至少选择一张权益卡') | |
| 312 | + return | |
| 313 | + } | |
| 314 | + this.batchHyxzLoading = true | |
| 315 | + request({ | |
| 316 | + url: '/api/Extend/WtSp/BatchSetHyxz', | |
| 317 | + method: 'POST', | |
| 318 | + data: { | |
| 319 | + ids: this.multipleSelection, | |
| 320 | + hyxzCardIds: this.batchSetHyxzCardIds | |
| 321 | + } | |
| 322 | + }) | |
| 323 | + .then(res => { | |
| 324 | + this.$message.success((res.data && res.data.message) || res.msg || '批量设置成功') | |
| 325 | + this.batchSetHyxzVisible = false | |
| 326 | + this.initData() | |
| 327 | + }) | |
| 328 | + .finally(() => { | |
| 329 | + this.batchHyxzLoading = false | |
| 330 | + }) | |
| 331 | + }, | |
| 332 | + handleBatchClearHyxz() { | |
| 333 | + if (!this.multipleSelection.length) { | |
| 334 | + this.$message.warning('请先勾选需要取消权益卡的商品') | |
| 335 | + return | |
| 336 | + } | |
| 337 | + const submit = () => { | |
| 338 | + this.batchHyxzLoading = true | |
| 339 | + request({ | |
| 340 | + url: '/api/Extend/WtSp/BatchClearHyxz', | |
| 341 | + method: 'POST', | |
| 342 | + data: { | |
| 343 | + ids: this.multipleSelection | |
| 344 | + } | |
| 345 | + }) | |
| 346 | + .then(res => { | |
| 347 | + this.$message.success((res.data && res.data.message) || res.msg || '批量取消成功') | |
| 348 | + this.initData() | |
| 349 | + }) | |
| 350 | + .finally(() => { | |
| 351 | + this.batchHyxzLoading = false | |
| 352 | + }) | |
| 353 | + } | |
| 354 | + this.$confirm('确认对已勾选商品批量取消权益卡吗?', '提示', { type: 'warning' }) | |
| 355 | + .then(submit) | |
| 356 | + .catch(() => {}) | |
| 357 | + }, | |
| 247 | 358 | initData() { |
| 248 | 359 | this.listLoading = true; |
| 249 | 360 | let _query = { |
| ... | ... | @@ -448,4 +559,9 @@ |
| 448 | 559 | .wt-sp-col-icon--info { |
| 449 | 560 | color: #909399; |
| 450 | 561 | } |
| 562 | +.wt-sp-batch-tip { | |
| 563 | + color: #909399; | |
| 564 | + font-size: 12px; | |
| 565 | + line-height: 1.5; | |
| 566 | +} | |
| 451 | 567 | </style> |
| 452 | 568 | \ No newline at end of file | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtTjdbd/detail-view.vue
| ... | ... | @@ -149,13 +149,55 @@ export default { |
| 149 | 149 | }, |
| 150 | 150 | resolveBookStock(row) { |
| 151 | 151 | if (!row || typeof row !== 'object') return '' |
| 152 | - // 优先展示单据明细上的库存快照字段,避免显示实时库存 | |
| 153 | - const candidates = [row.kucun, row.kc, row.kcsl, row.stockQty, row.stock, row.mdxx] | |
| 152 | + // 明细接口无持久化 kucun:由 enrichMxBookStock 拉取后写入;兼容 PascalCase / 旧字段 | |
| 153 | + const candidates = [ | |
| 154 | + row.kucun, | |
| 155 | + row.Kucun, | |
| 156 | + row.kc, | |
| 157 | + row.Kc, | |
| 158 | + row.kcsl, | |
| 159 | + row.Kcsl, | |
| 160 | + row.stockQty, | |
| 161 | + row.StockQty, | |
| 162 | + row.stock, | |
| 163 | + row.Stock, | |
| 164 | + row.mdxx, | |
| 165 | + row.Mdxx | |
| 166 | + ] | |
| 154 | 167 | for (let i = 0; i < candidates.length; i++) { |
| 155 | 168 | if (!this.isBlankValue(candidates[i])) return String(candidates[i]) |
| 156 | 169 | } |
| 157 | 170 | return '' |
| 158 | 171 | }, |
| 172 | + /** | |
| 173 | + * 与编辑页一致:账面库存来自 wt_sp_cost 汇总接口;GetInfo 仅返回明细表字段,不含 kucun。 | |
| 174 | + * ckck 已被后端转为门店/仓库名称时,GetStockQuantity 仍支持按名称反查(见服务端注释)。 | |
| 175 | + */ | |
| 176 | + async enrichMxBookStock(detail) { | |
| 177 | + if (!detail || !Array.isArray(detail.wtXsckdMxList)) return | |
| 178 | + const rows = detail.wtXsckdMxList | |
| 179 | + await Promise.all( | |
| 180 | + rows.map(async row => { | |
| 181 | + const spbh = row.spbh != null ? row.spbh : row.Spbh | |
| 182 | + const ck = row.ckck != null ? row.ckck : row.Ckck | |
| 183 | + if (this.isBlankValue(spbh) || this.isBlankValue(ck)) return | |
| 184 | + try { | |
| 185 | + const url = `/api/Extend/WtXsckd/GetStockQuantity?productId=${encodeURIComponent(String(spbh).trim())}&warehouseId=${encodeURIComponent(String(ck).trim())}` | |
| 186 | + const response = await request({ | |
| 187 | + url, | |
| 188 | + method: 'get' | |
| 189 | + }) | |
| 190 | + const inner = response && response.data | |
| 191 | + if (inner && inner.success) { | |
| 192 | + const qty = inner.data != null ? inner.data : 0 | |
| 193 | + this.$set(row, 'kucun', qty) | |
| 194 | + } | |
| 195 | + } catch (e) { | |
| 196 | + // 静默失败,保持空;避免详情弹窗刷屏 | |
| 197 | + } | |
| 198 | + }) | |
| 199 | + ) | |
| 200 | + }, | |
| 159 | 201 | auditStatus(row) { |
| 160 | 202 | if (!row) return '' |
| 161 | 203 | const s = String(row.djzt || row.shzt || '').trim() |
| ... | ... | @@ -222,6 +264,7 @@ export default { |
| 222 | 264 | detail.wtXsckdMxList = Array.isArray(detail.wtXsckdMxList) ? detail.wtXsckdMxList : [] |
| 223 | 265 | this.detail = detail |
| 224 | 266 | this.$emit('loaded', detail) |
| 267 | + this.enrichMxBookStock(detail) | |
| 225 | 268 | }).catch(() => { |
| 226 | 269 | this.$message.error('加载详情失败') |
| 227 | 270 | this.visible = false | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/SerialNumberSelect.vue
| ... | ... | @@ -32,8 +32,17 @@ |
| 32 | 32 | <el-form-item label="序列号"> |
| 33 | 33 | <el-input v-model="searchForm.serialNumber" placeholder="支持模糊查询"></el-input> |
| 34 | 34 | </el-form-item> |
| 35 | + <el-form-item label="扫码录入"> | |
| 36 | + <el-input | |
| 37 | + v-model="scanSerialNumber" | |
| 38 | + placeholder="扫码枪扫入后回车" | |
| 39 | + clearable | |
| 40 | + @keyup.enter.native="handleScanInput" | |
| 41 | + ></el-input> | |
| 42 | + </el-form-item> | |
| 35 | 43 | <el-form-item> |
| 36 | 44 | <el-button type="primary" @click="searchSerialNumbers">查询</el-button> |
| 45 | + <el-button type="success" @click="handleScanInput">扫码添加</el-button> | |
| 37 | 46 | <el-button @click="resetSearch">重置</el-button> |
| 38 | 47 | </el-form-item> |
| 39 | 48 | </el-form> |
| ... | ... | @@ -117,6 +126,8 @@ export default { |
| 117 | 126 | currentWarehouse: '', |
| 118 | 127 | currentDocumentType: '', |
| 119 | 128 | productCodeDisplay: '' // 商品编码(用于界面显示,如 206466) |
| 129 | + , | |
| 130 | + scanSerialNumber: '' | |
| 120 | 131 | } |
| 121 | 132 | }, |
| 122 | 133 | computed: { |
| ... | ... | @@ -139,6 +150,7 @@ export default { |
| 139 | 150 | this.searchForm.productCode = productCode |
| 140 | 151 | this.searchForm.warehouse = warehouse |
| 141 | 152 | this.searchForm.serialNumber = '' // Reset serial number when opening |
| 153 | + this.scanSerialNumber = '' | |
| 142 | 154 | this.selectedSerialNumbers = [...selectedSerialNumbers] // 用于回显 |
| 143 | 155 | this.searchSerialNumbers().then(() => { |
| 144 | 156 | this.$nextTick(() => { |
| ... | ... | @@ -153,6 +165,7 @@ export default { |
| 153 | 165 | this.serialNumberList = [] |
| 154 | 166 | this.selectedSerialNumbers = [] |
| 155 | 167 | this.productCodeDisplay = '' |
| 168 | + this.scanSerialNumber = '' | |
| 156 | 169 | }, |
| 157 | 170 | |
| 158 | 171 | // 获取仓库选项 |
| ... | ... | @@ -243,6 +256,42 @@ export default { |
| 243 | 256 | this.handleClose() |
| 244 | 257 | }, |
| 245 | 258 | |
| 259 | + // 扫码录入:优先从当前列表中精确匹配并勾选;未命中时自动按序列号查询后再匹配 | |
| 260 | + async handleScanInput() { | |
| 261 | + const sn = String(this.scanSerialNumber || '').trim() | |
| 262 | + if (!sn) { | |
| 263 | + this.$message.warning('请先扫码或输入序列号') | |
| 264 | + return | |
| 265 | + } | |
| 266 | + const trySelectBySerial = (serial) => { | |
| 267 | + if (!this.$refs.serialTable || !Array.isArray(this.serialNumberList)) return false | |
| 268 | + const row = this.serialNumberList.find(item => String(item.serialNumber || '').trim() === serial) | |
| 269 | + if (!row) return false | |
| 270 | + this.$refs.serialTable.toggleRowSelection(row, true) | |
| 271 | + if (!this.selectedSerialNumbers.includes(serial)) { | |
| 272 | + this.selectedSerialNumbers.push(serial) | |
| 273 | + } | |
| 274 | + return true | |
| 275 | + } | |
| 276 | + | |
| 277 | + // 先从现有结果匹配 | |
| 278 | + if (trySelectBySerial(sn)) { | |
| 279 | + this.scanSerialNumber = '' | |
| 280 | + return | |
| 281 | + } | |
| 282 | + | |
| 283 | + // 现有列表无,按该序列号精确查询一次 | |
| 284 | + this.searchForm.serialNumber = sn | |
| 285 | + await this.searchSerialNumbers() | |
| 286 | + this.$nextTick(() => { | |
| 287 | + const ok = trySelectBySerial(sn) | |
| 288 | + if (!ok) { | |
| 289 | + this.$message.warning(`未找到序列号:${sn}`) | |
| 290 | + } | |
| 291 | + this.scanSerialNumber = '' | |
| 292 | + }) | |
| 293 | + }, | |
| 294 | + | |
| 246 | 295 | // 获取仓库名称 |
| 247 | 296 | getWarehouseName(warehouseId) { |
| 248 | 297 | const warehouse = this.warehouseOptions.find(item => item.F_Id === warehouseId) | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/index.vue
| ... | ... | @@ -117,6 +117,9 @@ |
| 117 | 117 | <el-table-column prop="jsr" label="经手人" align="left" min-width="88" show-overflow-tooltip> |
| 118 | 118 | <template slot-scope="scope">{{ displayText(scope.row.jsr) }}</template> |
| 119 | 119 | </el-table-column> |
| 120 | + <el-table-column prop="kh" label="往来单位" align="left" min-width="120" show-overflow-tooltip> | |
| 121 | + <template slot-scope="scope">{{ displayText(scope.row.kh) }}</template> | |
| 122 | + </el-table-column> | |
| 120 | 123 | <el-table-column prop="fhr" label="发货人" align="left" min-width="88" show-overflow-tooltip> |
| 121 | 124 | <template slot-scope="scope">{{ displayText(scope.row.fhr) }}</template> |
| 122 | 125 | </el-table-column> |
| ... | ... | @@ -166,7 +169,7 @@ |
| 166 | 169 | <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag> |
| 167 | 170 | <el-tag v-else-if="scope.row.djzt === '一级已审'" type="">一级已审</el-tag> |
| 168 | 171 | <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag> |
| 169 | - <el-tag v-else-if="isSkipErpAuditSource(scope.row)" type="success" effect="plain">无需审核</el-tag> | |
| 172 | + <el-tag v-else-if="isSkipErpAuditSource(scope.row) && (!scope.row.djzt || scope.row.djzt === '已审核')" type="success" effect="plain">无需审核</el-tag> | |
| 170 | 173 | <el-tag v-else-if="scope.row.djzt === '待审核'" type="warning">待审核</el-tag> |
| 171 | 174 | <el-tag v-else-if="scope.row.djzt === '审核不通过'" type="danger">审核不通过</el-tag> |
| 172 | 175 | <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag> |
| ... | ... | @@ -250,6 +253,7 @@ |
| 250 | 253 | { prop: 'djrq', label: '单据日期' }, |
| 251 | 254 | { prop: 'cjck', label: '出库仓库' }, |
| 252 | 255 | { prop: 'jsr', label: '经手人' }, |
| 256 | + { prop: 'kh', label: '往来单位' }, | |
| 253 | 257 | { prop: 'fhr', label: '发货人' }, |
| 254 | 258 | { prop: 'skzh', label: '收款账户' }, |
| 255 | 259 | { prop: 'skje', label: '收款金额' }, |
| ... | ... | @@ -309,28 +313,25 @@ |
| 309 | 313 | }, |
| 310 | 314 | /** 后台来源且草稿可编辑 */ |
| 311 | 315 | canEditDraft(row) { |
| 312 | - return !this.isSkipErpAuditSource(row) && (row.djzt === '草稿' || row.djzt === '审核不通过') | |
| 316 | + return row && (row.djzt === '草稿' || row.djzt === '审核不通过') | |
| 313 | 317 | }, |
| 314 | - /** 抖音来源已审核:允许反审(回退为待审核),用于更正与重提 */ | |
| 318 | + /** 非后台来源(抖音/门店)已审核:允许反审(回退草稿),用于更正与重提 */ | |
| 315 | 319 | canReverseApproval(row) { |
| 316 | 320 | if (!row) return false |
| 317 | - // 仅对抖音来源(含早期只写备注的单)开放反审入口 | |
| 318 | - const isDy = (row.ly != null && String(row.ly).trim() === '抖音订单') || this.isSkipErpAuditSource(row) | |
| 319 | - if (!isDy) return false | |
| 321 | + // 非后台来源(含早期只写备注的抖音单)开放反审入口 | |
| 322 | + const isNonBackend = this.isSkipErpAuditSource(row) | |
| 323 | + if (!isNonBackend) return false | |
| 320 | 324 | return row.djzt === '已审核' || row.djzt === '一级已审' || row.djzt === '一级已审/待二级' |
| 321 | 325 | }, |
| 322 | 326 | canFirstApprove(row) { |
| 323 | - if (this.isSkipErpAuditSource(row)) return false | |
| 324 | 327 | if (row.djzt === '草稿' || row.djzt === '已审核') return false |
| 325 | 328 | return row.djzt === '待审核' || row.djzt === '' || row.djzt == null |
| 326 | 329 | }, |
| 327 | 330 | canSecondApprove(row) { |
| 328 | - if (this.isSkipErpAuditSource(row)) return false | |
| 329 | 331 | const z = row.djzt |
| 330 | 332 | return z === '一级已审' || z === '待二级' || z === '一级已审/待二级' |
| 331 | 333 | }, |
| 332 | 334 | canRejectAudit(row) { |
| 333 | - if (this.isSkipErpAuditSource(row)) return false | |
| 334 | 335 | if (row.djzt === '草稿' || row.djzt === '已审核' || row.djzt === '审核不通过') return false |
| 335 | 336 | return this.canFirstApprove(row) || this.canSecondApprove(row) |
| 336 | 337 | }, |
| ... | ... | @@ -531,7 +532,7 @@ |
| 531 | 532 | .catch(() => {}) |
| 532 | 533 | }, |
| 533 | 534 | handleReverseApproval(id) { |
| 534 | - this.$confirm('确认反审该销售出库单吗?反审后将恢复为待审核状态。', '提示', { | |
| 535 | + this.$confirm('确认反审该销售出库单吗?反审后将恢复为草稿,可再次编辑。', '提示', { | |
| 535 | 536 | type: 'warning' |
| 536 | 537 | }).then(() => { |
| 537 | 538 | return request({ | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXswtdxjsd/Form.vue
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXswtdxjsd/ShipmentOrderSelect.vue
| ... | ... | @@ -82,7 +82,7 @@ |
| 82 | 82 | </el-col> |
| 83 | 83 | <el-col :span="6"> |
| 84 | 84 | <el-form-item label="审核状态"> |
| 85 | - <el-select v-model="query.djzt" placeholder="审核状态" clearable style="width: 100%"> | |
| 85 | + <el-select v-model="query.djzt" placeholder="审核状态" clearable style="width: 100%" :disabled="requireApprovedSource"> | |
| 86 | 86 | <el-option label="草稿" value="草稿" /> |
| 87 | 87 | <el-option label="待审核" value="待审核" /> |
| 88 | 88 | <el-option label="一级已审" value="一级已审" /> |
| ... | ... | @@ -247,6 +247,11 @@ export default { |
| 247 | 247 | hideZdrQuery: { |
| 248 | 248 | type: Boolean, |
| 249 | 249 | default: false |
| 250 | + }, | |
| 251 | + /** 为 true 时仅允许选择已审核(过账)原单 */ | |
| 252 | + requireApprovedSource: { | |
| 253 | + type: Boolean, | |
| 254 | + default: false | |
| 250 | 255 | } |
| 251 | 256 | }, |
| 252 | 257 | data() { |
| ... | ... | @@ -263,7 +268,7 @@ export default { |
| 263 | 268 | zdr: '', |
| 264 | 269 | cjck: '', |
| 265 | 270 | jsr: '', |
| 266 | - djzt: '', | |
| 271 | + djzt: this.requireApprovedSource ? '已审核' : '', | |
| 267 | 272 | bz: '' |
| 268 | 273 | }, |
| 269 | 274 | shipmentList: [], |
| ... | ... | @@ -297,6 +302,9 @@ export default { |
| 297 | 302 | // 打开弹窗 |
| 298 | 303 | open() { |
| 299 | 304 | this.visible = true |
| 305 | + if (this.requireApprovedSource) { | |
| 306 | + this.query.djzt = '已审核' | |
| 307 | + } | |
| 300 | 308 | this.$nextTick(() => { |
| 301 | 309 | this.search() |
| 302 | 310 | }) |
| ... | ... | @@ -420,6 +428,9 @@ export default { |
| 420 | 428 | if (this.query.djzt) { |
| 421 | 429 | query.djzt = this.normalizeScalar(this.query.djzt) |
| 422 | 430 | } |
| 431 | + if (this.requireApprovedSource) { | |
| 432 | + query.djzt = '已审核' | |
| 433 | + } | |
| 423 | 434 | if (this.query.bz) { |
| 424 | 435 | query.bz = this.normalizeScalar(this.query.bz) |
| 425 | 436 | } |
| ... | ... | @@ -477,7 +488,7 @@ export default { |
| 477 | 488 | zdr: '', |
| 478 | 489 | cjck: '', |
| 479 | 490 | jsr: '', |
| 480 | - djzt: '', | |
| 491 | + djzt: this.requireApprovedSource ? '已审核' : '', | |
| 481 | 492 | bz: '' |
| 482 | 493 | } |
| 483 | 494 | this.selectedShipments = [] | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXswtdxthd/Form.vue
Antis.Erp.Plat/antis-ncc-admin/src/views/wtYskzjjs_xj/Form.vue
| 1 | 1 | <template> |
| 2 | - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%"> | |
| 2 | + <el-dialog :title="dialogTitle" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%"> | |
| 3 | 3 | <el-row :gutter="15" class="" > |
| 4 | - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules"> | |
| 4 | + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="formDisabled" :rules="rules"> | |
| 5 | 5 | <el-col :span="8"> |
| 6 | 6 | <el-form-item label="单据编号" prop="id"> |
| 7 | 7 | <el-input v-model="dataForm.id" placeholder="请输入" clearable readonly :style='{"width":"100%"}' > |
| ... | ... | @@ -20,6 +20,11 @@ |
| 20 | 20 | </user-select> |
| 21 | 21 | </el-form-item> |
| 22 | 22 | </el-col> |
| 23 | + <el-col :span="8" v-if="dataForm.id"> | |
| 24 | + <el-form-item label="审核状态"> | |
| 25 | + <el-input :value="auditStatusText" readonly /> | |
| 26 | + </el-form-item> | |
| 27 | + </el-col> | |
| 23 | 28 | <el-col :span="24"> |
| 24 | 29 | <el-form-item label-width="0"> |
| 25 | 30 | <el-table :data="dataForm.wtYskzjjsMxList" size='mini'> |
| ... | ... | @@ -55,13 +60,13 @@ |
| 55 | 60 | <el-input v-model="scope.row.bz" placeholder="请输入" clearable ></el-input> |
| 56 | 61 | </template> |
| 57 | 62 | </el-table-column> |
| 58 | - <el-table-column label="操作" width="50"> | |
| 63 | + <el-table-column v-if="!formDisabled" label="操作" width="50"> | |
| 59 | 64 | <template slot-scope="scope"> |
| 60 | 65 | <el-button size="mini" type="text" class="NCC-table-delBtn" @click="handleDelWtYskzjjsMxEntityList(scope.$index)">删除</el-button> |
| 61 | 66 | </template> |
| 62 | 67 | </el-table-column> |
| 63 | 68 | </el-table> |
| 64 | - <div class="table-actions" @click="addHandleWtYskzjjsMxEntityList()"> | |
| 69 | + <div v-if="!formDisabled" class="table-actions" @click="addHandleWtYskzjjsMxEntityList()"> | |
| 65 | 70 | <el-button type="text" icon="el-icon-plus">新增</el-button> |
| 66 | 71 | </div> |
| 67 | 72 | </el-form-item> |
| ... | ... | @@ -83,7 +88,10 @@ |
| 83 | 88 | </el-row> |
| 84 | 89 | <span slot="footer" class="dialog-footer"> |
| 85 | 90 | <el-button @click="visible = false">取 消</el-button> |
| 86 | - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button> | |
| 91 | + <template v-if="!formDisabled"> | |
| 92 | + <el-button @click="submitAsDraft">保存草稿</el-button> | |
| 93 | + <el-button type="primary" @click="submitForAudit">提 交</el-button> | |
| 94 | + </template> | |
| 87 | 95 | </span> |
| 88 | 96 | </el-dialog> |
| 89 | 97 | </template> |
| ... | ... | @@ -91,6 +99,7 @@ |
| 91 | 99 | import request from '@/utils/request' |
| 92 | 100 | import { getDictionaryDataSelector } from '@/api/systemData/dictionary' |
| 93 | 101 | import { getAccountSelector } from '@/api/extend/wtAccount' |
| 102 | +const FYXM_DICT_ID = '816913803978474757' | |
| 94 | 103 | export default { |
| 95 | 104 | components: {}, |
| 96 | 105 | props: [], |
| ... | ... | @@ -99,6 +108,7 @@ |
| 99 | 108 | loading: false, |
| 100 | 109 | visible: false, |
| 101 | 110 | isDetail: false, |
| 111 | + pendingDjzt: '待审核', | |
| 102 | 112 | dataForm: { |
| 103 | 113 | id: undefined, |
| 104 | 114 | djrq: undefined, |
| ... | ... | @@ -117,7 +127,26 @@ |
| 117 | 127 | skzhOptions: [], |
| 118 | 128 | } |
| 119 | 129 | }, |
| 120 | - computed: {}, | |
| 130 | + computed: { | |
| 131 | + auditStatusText() { | |
| 132 | + const z = this.dataForm.djzt != null && this.dataForm.djzt !== '' ? String(this.dataForm.djzt).trim() : '' | |
| 133 | + return z || '—' | |
| 134 | + }, | |
| 135 | + isBillLockedForEdit() { | |
| 136 | + const z = this.auditStatusText | |
| 137 | + return z === '待审核' || z === '已审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级' | |
| 138 | + }, | |
| 139 | + formDisabled() { | |
| 140 | + return !!this.isDetail || this.isBillLockedForEdit | |
| 141 | + }, | |
| 142 | + dialogTitle() { | |
| 143 | + if (!this.dataForm.id) return '新建' | |
| 144 | + if (this.isDetail) return '详情' | |
| 145 | + const z = this.auditStatusText | |
| 146 | + if (z === '草稿') return '编辑' | |
| 147 | + return `查看(${z})` | |
| 148 | + } | |
| 149 | + }, | |
| 121 | 150 | watch: {}, |
| 122 | 151 | created() { |
| 123 | 152 | this.getfyxmmcOptions(); |
| ... | ... | @@ -135,8 +164,8 @@ |
| 135 | 164 | }, |
| 136 | 165 | methods: { |
| 137 | 166 | getfyxmmcOptions(){ |
| 138 | - getDictionaryDataSelector('715562947862070533').then(res => { | |
| 139 | - this.fyxmmcOptions = res.data.list | |
| 167 | + getDictionaryDataSelector(FYXM_DICT_ID).then(res => { | |
| 168 | + this.fyxmmcOptions = (res.data && res.data.list) ? res.data.list : [] | |
| 140 | 169 | }); |
| 141 | 170 | }, |
| 142 | 171 | getfkzhOptions(){ |
| ... | ... | @@ -156,6 +185,7 @@ |
| 156 | 185 | this.dataForm.id = id || 0; |
| 157 | 186 | this.visible = true; |
| 158 | 187 | this.isDetail = isDetail || false; |
| 188 | + this.pendingDjzt = '待审核' | |
| 159 | 189 | this.$nextTick(() => { |
| 160 | 190 | this.$refs['elForm'].resetFields(); |
| 161 | 191 | if (this.dataForm.id) { |
| ... | ... | @@ -174,13 +204,25 @@ |
| 174 | 204 | this.dataForm.jsr = this.$store.getters.userInfo.userId || this.$store.getters.userInfo.id; |
| 175 | 205 | } |
| 176 | 206 | this.dataForm.djlx = '现金费用单'; |
| 207 | + this.dataForm.djzt = '草稿'; | |
| 177 | 208 | this.dataForm.wtYskzjjsMxList = []; |
| 178 | 209 | } |
| 179 | 210 | }) |
| 180 | 211 | }, |
| 212 | + submitAsDraft() { | |
| 213 | + this.pendingDjzt = '草稿' | |
| 214 | + this.dataFormSubmit() | |
| 215 | + }, | |
| 216 | + submitForAudit() { | |
| 217 | + this.pendingDjzt = '待审核' | |
| 218 | + this.dataFormSubmit() | |
| 219 | + }, | |
| 181 | 220 | dataFormSubmit() { |
| 221 | + if (this.formDisabled) return | |
| 182 | 222 | this.$refs['elForm'].validate((valid) => { |
| 183 | 223 | if (valid) { |
| 224 | + this.dataForm.djlx = '现金费用单' | |
| 225 | + this.$set(this.dataForm, 'djzt', this.pendingDjzt || '待审核') | |
| 184 | 226 | if (!this.dataForm.id) { |
| 185 | 227 | request({ |
| 186 | 228 | url: `/api/Extend/WtYskzjjs`, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtYskzjjs_xj/detail-view.vue
| ... | ... | @@ -45,6 +45,7 @@ |
| 45 | 45 | import request from '@/utils/request' |
| 46 | 46 | import { getDictionaryDataSelector } from '@/api/systemData/dictionary' |
| 47 | 47 | import { getAccountSelector } from '@/api/extend/wtAccount' |
| 48 | +const FYXM_DICT_ID = '816913803978474757' | |
| 48 | 49 | |
| 49 | 50 | export default { |
| 50 | 51 | name: 'WtYskzjjsXjDetailView', |
| ... | ... | @@ -77,7 +78,7 @@ export default { |
| 77 | 78 | }, |
| 78 | 79 | methods: { |
| 79 | 80 | loadFeeItems() { |
| 80 | - getDictionaryDataSelector('715562947862070533').then(res => { | |
| 81 | + getDictionaryDataSelector(FYXM_DICT_ID).then(res => { | |
| 81 | 82 | this.fyxmmcOptions = res.data.list || [] |
| 82 | 83 | }) |
| 83 | 84 | }, | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtYskzjjs_xj/index.vue
| ... | ... | @@ -106,9 +106,14 @@ |
| 106 | 106 | </el-table-column> |
| 107 | 107 | <el-table-column label="操作" fixed="right" width="140"> |
| 108 | 108 | <template slot-scope="scope"> |
| 109 | - <el-button type="text" @click="openDetail(scope.row.id)" >查看</el-button> | |
| 110 | - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 111 | - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 109 | + <template v-if="canEditCashExpense(scope.row)"> | |
| 110 | + <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> | |
| 111 | + <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button> | |
| 112 | + </template> | |
| 113 | + <template v-else> | |
| 114 | + <el-button type="text" @click="openDetail(scope.row.id)" >查看</el-button> | |
| 115 | + <el-button v-if="isCashExpenseApproved(scope.row)" type="text" @click="handleReverseAudit(scope.row.id)">反审</el-button> | |
| 116 | + </template> | |
| 112 | 117 | </template> |
| 113 | 118 | </el-table-column> |
| 114 | 119 | </NCC-table> |
| ... | ... | @@ -173,6 +178,17 @@ |
| 173 | 178 | this.getskzhOptions(); |
| 174 | 179 | }, |
| 175 | 180 | methods: { |
| 181 | + cashExpenseDjzt(row) { | |
| 182 | + if (!row) return '' | |
| 183 | + const z = row.djzt != null && row.djzt !== '' ? String(row.djzt).trim() : '' | |
| 184 | + return z || (row.shzt != null ? String(row.shzt).trim() : '') | |
| 185 | + }, | |
| 186 | + canEditCashExpense(row) { | |
| 187 | + return this.cashExpenseDjzt(row) === '草稿' | |
| 188 | + }, | |
| 189 | + isCashExpenseApproved(row) { | |
| 190 | + return this.cashExpenseDjzt(row) === '已审核' | |
| 191 | + }, | |
| 176 | 192 | getfkzhOptions(){ |
| 177 | 193 | getAccountSelector().then(res => { |
| 178 | 194 | this.fkzhOptions = res.data.list |
| ... | ... | @@ -226,7 +242,33 @@ |
| 226 | 242 | this.$refs.NCCDetailView && this.$refs.NCCDetailView.init(id) |
| 227 | 243 | }) |
| 228 | 244 | }, |
| 245 | + handleReverseAudit(id) { | |
| 246 | + if (!id) return | |
| 247 | + this.$confirm('反审后单据将恢复为草稿,可再次编辑。是否继续?', '反审确认', { type: 'warning' }) | |
| 248 | + .then(() => request({ | |
| 249 | + url: `/api/Extend/WtYskzjjs/Actions/ReverseAudit/${id}`, | |
| 250 | + method: 'POST', | |
| 251 | + data: {} | |
| 252 | + })) | |
| 253 | + .then((res) => { | |
| 254 | + const d = (res && res.data) || {} | |
| 255 | + const ok = d.success === true || String(res && res.code) === '200' | |
| 256 | + const msg = d.message || res.msg || (ok ? '反审成功' : '操作失败') | |
| 257 | + if (ok) { | |
| 258 | + this.$message.success(msg) | |
| 259 | + this.initData() | |
| 260 | + } else { | |
| 261 | + this.$message.error(msg) | |
| 262 | + } | |
| 263 | + }) | |
| 264 | + .catch(() => {}) | |
| 265 | + }, | |
| 229 | 266 | handleDel(id) { |
| 267 | + const row = this.list.find(r => r.id === id) | |
| 268 | + if (row && !this.canEditCashExpense(row)) { | |
| 269 | + this.$message.warning('仅草稿状态的现金费用单可删除') | |
| 270 | + return | |
| 271 | + } | |
| 230 | 272 | this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', { |
| 231 | 273 | type: 'warning' |
| 232 | 274 | }).then(() => { |
| ... | ... | @@ -258,6 +300,11 @@ |
| 258 | 300 | }) |
| 259 | 301 | return |
| 260 | 302 | } |
| 303 | + const locked = this.list.filter(r => this.multipleSelection.includes(r.id) && !this.canEditCashExpense(r)) | |
| 304 | + if (locked.length) { | |
| 305 | + this.$message.warning(`仅草稿可删除,请去掉已提交/已审核单据(${locked.length} 条)后重试`) | |
| 306 | + return | |
| 307 | + } | |
| 261 | 308 | const ids = this.multipleSelection |
| 262 | 309 | this.$confirm('您确定要删除这些数据吗, 是否继续?', '提示', { |
| 263 | 310 | type: 'warning' | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtZsd/Form.vue
| 1 | 1 | <template> |
| 2 | - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情' : '编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%"> | |
| 2 | + <el-dialog :title="dialogTitle" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%"> | |
| 3 | 3 | <el-row :gutter="15"> |
| 4 | - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail"> | |
| 4 | + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="formDisabled"> | |
| 5 | 5 | <el-col :span="8"> |
| 6 | 6 | <el-form-item label="单据编号"> |
| 7 | 7 | <el-input v-model="dataForm.id" placeholder="系统自动生成" readonly /> |
| ... | ... | @@ -9,7 +9,7 @@ |
| 9 | 9 | </el-col> |
| 10 | 10 | <el-col :span="8"> |
| 11 | 11 | <el-form-item label="单据日期"> |
| 12 | - <el-date-picker v-model="dataForm.djrq" type="date" format="yyyy-MM-dd" value-format="timestamp" readonly style="width:100%" /> | |
| 12 | + <el-date-picker v-model="dataForm.djrq" type="date" format="yyyy-MM-dd" value-format="timestamp" :readonly="formDisabled" style="width:100%" /> | |
| 13 | 13 | </el-form-item> |
| 14 | 14 | </el-col> |
| 15 | 15 | <el-col :span="8"> |
| ... | ... | @@ -26,7 +26,7 @@ |
| 26 | 26 | </el-col> |
| 27 | 27 | <el-col :span="8"> |
| 28 | 28 | <el-form-item label="审核状态"> |
| 29 | - <el-input v-model="dataForm.djzt" readonly placeholder="系统自动带出" /> | |
| 29 | + <el-input :value="auditStatusText" readonly placeholder="系统自动带出" /> | |
| 30 | 30 | </el-form-item> |
| 31 | 31 | </el-col> |
| 32 | 32 | <el-col :span="24"> |
| ... | ... | @@ -95,13 +95,13 @@ |
| 95 | 95 | <el-input v-model="scope.row.description" /> |
| 96 | 96 | </template> |
| 97 | 97 | </el-table-column> |
| 98 | - <el-table-column label="操作" width="60"> | |
| 98 | + <el-table-column v-if="!formDisabled" label="操作" width="60"> | |
| 99 | 99 | <template slot-scope="scope"> |
| 100 | 100 | <el-button size="mini" type="text" class="NCC-table-delBtn" @click="removeRow(scope.$index)">删除</el-button> |
| 101 | 101 | </template> |
| 102 | 102 | </el-table-column> |
| 103 | 103 | </el-table> |
| 104 | - <div class="table-actions" @click="addRow"> | |
| 104 | + <div v-if="!formDisabled" class="table-actions" @click="addRow"> | |
| 105 | 105 | <el-button type="text" icon="el-icon-plus">新增</el-button> |
| 106 | 106 | </div> |
| 107 | 107 | </el-form-item> |
| ... | ... | @@ -110,8 +110,10 @@ |
| 110 | 110 | </el-row> |
| 111 | 111 | <span slot="footer" class="dialog-footer"> |
| 112 | 112 | <el-button @click="visible = false">取 消</el-button> |
| 113 | - <el-button type="default" @click="saveDraft" v-if="!isDetail">保存草稿</el-button> | |
| 114 | - <el-button type="primary" @click="dataFormSubmit" v-if="!isDetail">确 定</el-button> | |
| 113 | + <template v-if="!formDisabled"> | |
| 114 | + <el-button type="default" @click="saveDraft">保存草稿</el-button> | |
| 115 | + <el-button type="primary" @click="submitForAudit">提交审核</el-button> | |
| 116 | + </template> | |
| 115 | 117 | </span> |
| 116 | 118 | </el-dialog> |
| 117 | 119 | </template> |
| ... | ... | @@ -144,6 +146,25 @@ export default { |
| 144 | 146 | this.getcjckOptions() |
| 145 | 147 | this.getspbhOptions() |
| 146 | 148 | }, |
| 149 | + computed: { | |
| 150 | + auditStatusText() { | |
| 151 | + const z = this.dataForm.djzt != null && this.dataForm.djzt !== '' ? String(this.dataForm.djzt).trim() : '' | |
| 152 | + return z || '—' | |
| 153 | + }, | |
| 154 | + isBillLockedForEdit() { | |
| 155 | + const z = this.auditStatusText | |
| 156 | + return z === '待审核' || z === '已审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级' | |
| 157 | + }, | |
| 158 | + formDisabled() { | |
| 159 | + return !!this.isDetail || this.isBillLockedForEdit | |
| 160 | + }, | |
| 161 | + dialogTitle() { | |
| 162 | + if (!this.dataForm.id) return '新建' | |
| 163 | + if (this.isDetail) return '详情' | |
| 164 | + if (this.isBillLockedForEdit) return '查看' | |
| 165 | + return '编辑' | |
| 166 | + } | |
| 167 | + }, | |
| 147 | 168 | methods: { |
| 148 | 169 | getcjckOptions() { |
| 149 | 170 | previewDataInterface('681758216954053893').then(res => { |
| ... | ... | @@ -257,9 +278,11 @@ export default { |
| 257 | 278 | return sums |
| 258 | 279 | }, |
| 259 | 280 | async saveDraft() { |
| 281 | + if (this.formDisabled) return | |
| 260 | 282 | await this.submitByStatus('草稿') |
| 261 | 283 | }, |
| 262 | - async dataFormSubmit() { | |
| 284 | + async submitForAudit() { | |
| 285 | + if (this.formDisabled) return | |
| 263 | 286 | await this.submitByStatus('待审核') |
| 264 | 287 | }, |
| 265 | 288 | async submitByStatus(status) { | ... | ... |
Antis.Erp.Plat/antis-ncc-admin/src/views/wtZsd/index.vue
| ... | ... | @@ -60,7 +60,13 @@ |
| 60 | 60 | <template slot-scope="scope">{{ scope.row.cjck | dynamicText(cjckOptions) }}</template> |
| 61 | 61 | </el-table-column> |
| 62 | 62 | <el-table-column prop="jsr" label="经手人" align="left" min-width="100" /> |
| 63 | - <el-table-column prop="djzt" label="审核状态" align="left" min-width="100" /> | |
| 63 | + <el-table-column prop="djzt" label="审核状态" align="left" min-width="120"> | |
| 64 | + <template slot-scope="scope"> | |
| 65 | + <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag> | |
| 66 | + <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag> | |
| 67 | + <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag> | |
| 68 | + </template> | |
| 69 | + </el-table-column> | |
| 64 | 70 | <el-table-column prop="zsl" label="合计数量" align="right" min-width="100"> |
| 65 | 71 | <template slot-scope="scope">{{ formatQty(scope.row.zsl) }}</template> |
| 66 | 72 | </el-table-column> |
| ... | ... | @@ -72,10 +78,13 @@ |
| 72 | 78 | <ncc-table-summary-cell :row="scope.row" fields="zy,Zy" /> |
| 73 | 79 | </template> |
| 74 | 80 | </el-table-column> |
| 75 | - <el-table-column label="操作" fixed="right" width="100"> | |
| 81 | + <el-table-column label="操作" fixed="right" width="260"> | |
| 76 | 82 | <template slot-scope="scope"> |
| 77 | - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button> | |
| 78 | - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button> | |
| 83 | + <el-button type="text" @click="openDetail(scope.row.id)">查看</el-button> | |
| 84 | + <el-button v-if="isDraftRow(scope.row)" type="text" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button> | |
| 85 | + <el-button v-if="isPendingAudit(scope.row)" type="text" style="color:#409EFF" @click="handleApprove(scope.row.id)">审核</el-button> | |
| 86 | + <el-button v-if="scope.row.djzt === '已审核'" type="text" style="color:#E6A23C" @click="handleReverse(scope.row.id)">反审</el-button> | |
| 87 | + <el-button v-if="isDraftRow(scope.row)" type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button> | |
| 79 | 88 | </template> |
| 80 | 89 | </el-table-column> |
| 81 | 90 | </NCC-table> |
| ... | ... | @@ -136,6 +145,48 @@ export default { |
| 136 | 145 | this.getcjckOptions() |
| 137 | 146 | }, |
| 138 | 147 | methods: { |
| 148 | + isDraftRow(row) { | |
| 149 | + return row && String(row.djzt || '').trim() === '草稿' | |
| 150 | + }, | |
| 151 | + isPendingAudit(row) { | |
| 152 | + const z = row && String(row.djzt || '').trim() | |
| 153 | + return z === '待审核' || z === '' | |
| 154 | + }, | |
| 155 | + openDetail(id) { | |
| 156 | + this.formVisible = true | |
| 157 | + this.$nextTick(() => this.$refs.NCCForm.init(id, true)) | |
| 158 | + }, | |
| 159 | + handleApprove(id) { | |
| 160 | + this.$confirm('确认审核该赠送单?', '提示', { type: 'warning' }).then(() => { | |
| 161 | + request({ | |
| 162 | + url: `/api/Extend/WtXsckd/ApproveGeneric/${id}`, | |
| 163 | + method: 'POST', | |
| 164 | + data: {} | |
| 165 | + }).then(res => { | |
| 166 | + this.$message({ type: 'success', message: res.msg || '审核成功', duration: 1200 }) | |
| 167 | + this.initData() | |
| 168 | + }) | |
| 169 | + }).catch(() => {}) | |
| 170 | + }, | |
| 171 | + handleReverse(id) { | |
| 172 | + this.$confirm('反审后将恢复草稿并可编辑,是否继续?', '反审确认', { type: 'warning' }).then(() => { | |
| 173 | + request({ | |
| 174 | + url: `/api/Extend/WtXsckd/ReverseApproval/${id}`, | |
| 175 | + method: 'POST', | |
| 176 | + data: {} | |
| 177 | + }).then((res) => { | |
| 178 | + const d = (res && res.data) || {} | |
| 179 | + const ok = d.success === true || String(res && res.code) === '200' | |
| 180 | + const msg = d.message || res.msg || (ok ? '反审成功' : '操作失败') | |
| 181 | + if (ok) { | |
| 182 | + this.$message.success(msg) | |
| 183 | + this.initData() | |
| 184 | + } else { | |
| 185 | + this.$message.error(msg) | |
| 186 | + } | |
| 187 | + }) | |
| 188 | + }).catch(() => {}) | |
| 189 | + }, | |
| 139 | 190 | formatQty(v) { |
| 140 | 191 | const n = Number(v || 0) |
| 141 | 192 | return Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/\.?0+$/, '') | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.DouyinLogistics/Controllers/OrdersController.cs
| ... | ... | @@ -61,6 +61,7 @@ public class OrdersController : ControllerBase |
| 61 | 61 | [FromQuery] string? receiverPhone = null, |
| 62 | 62 | [FromQuery] string? trackingNumber = null, |
| 63 | 63 | [FromQuery] string? productName = null, |
| 64 | + [FromQuery] string? skuName = null, | |
| 64 | 65 | [FromQuery] bool? hasWaybill = null, |
| 65 | 66 | /// <summary>为 true 时:待发货 + 已有运单号 + 尚未成功提交发货单(waybills 无 SalesOrderId)</summary> |
| 66 | 67 | [FromQuery] bool pendingShipmentForm = false, |
| ... | ... | @@ -74,14 +75,16 @@ public class OrdersController : ControllerBase |
| 74 | 75 | [FromQuery] string? sortOrder = null, |
| 75 | 76 | /// <summary>为 true 时:仅「已发货」且已在系统中提交发货单(waybills 有 SalesOrderId)</summary> |
| 76 | 77 | [FromQuery] bool? shipmentFormSubmitted = null, |
| 78 | + /// <summary>为 true 时:仅异常订单(已取消/已退款/退款中)</summary> | |
| 79 | + [FromQuery] bool abnormalOnly = false, | |
| 77 | 80 | [FromQuery] long? shopId = null) |
| 78 | 81 | { |
| 79 | 82 | try |
| 80 | 83 | { |
| 81 | 84 | var (orders, total) = await _orderService.GetOrdersWithPagingAsync( |
| 82 | 85 | pageIndex, pageSize, status, orderId, receiverName, receiverPhone, |
| 83 | - trackingNumber, productName, hasWaybill, pendingShipmentForm, createTimeStart, createTimeEnd, payTimeStart, payTimeEnd, | |
| 84 | - douyinOrderTimeStart, douyinOrderTimeEnd, sortBy, sortOrder, shipmentFormSubmitted, shopId); | |
| 86 | + trackingNumber, productName, skuName, hasWaybill, pendingShipmentForm, createTimeStart, createTimeEnd, payTimeStart, payTimeEnd, | |
| 87 | + douyinOrderTimeStart, douyinOrderTimeEnd, sortBy, sortOrder, shipmentFormSubmitted, abnormalOnly, shopId); | |
| 85 | 88 | return Ok(new { data = orders, total, pageIndex, pageSize }); |
| 86 | 89 | } |
| 87 | 90 | catch (Exception ex) | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.DouyinLogistics/Services/OrderService.cs
| ... | ... | @@ -773,6 +773,7 @@ public class OrderService |
| 773 | 773 | string? receiverPhone = null, |
| 774 | 774 | string? trackingNumber = null, |
| 775 | 775 | string? productName = null, |
| 776 | + string? skuName = null, | |
| 776 | 777 | bool? hasWaybill = null, |
| 777 | 778 | bool pendingShipmentForm = false, |
| 778 | 779 | DateTime? createTimeStart = null, |
| ... | ... | @@ -784,6 +785,7 @@ public class OrderService |
| 784 | 785 | string? sortBy = null, |
| 785 | 786 | string? sortOrder = null, |
| 786 | 787 | bool? shipmentFormSubmitted = null, |
| 788 | + bool abnormalOnly = false, | |
| 787 | 789 | long? shopId = null) |
| 788 | 790 | { |
| 789 | 791 | var query = _db.Queryable<Order>(); |
| ... | ... | @@ -793,7 +795,11 @@ public class OrderService |
| 793 | 795 | query = query.Where(o => o.ShopId == shopId.Value); |
| 794 | 796 | } |
| 795 | 797 | |
| 796 | - if (status.HasValue) | |
| 798 | + if (abnormalOnly) | |
| 799 | + { | |
| 800 | + query = query.Where(o => o.Status == 2 || o.Status == 3 || o.Status == 4); | |
| 801 | + } | |
| 802 | + else if (status.HasValue) | |
| 797 | 803 | { |
| 798 | 804 | query = query.Where(o => o.Status == status.Value); |
| 799 | 805 | } |
| ... | ... | @@ -823,6 +829,12 @@ public class OrderService |
| 823 | 829 | query = query.Where(o => o.ProductName != null && o.ProductName.Contains(productName)); |
| 824 | 830 | } |
| 825 | 831 | |
| 832 | + if (!string.IsNullOrWhiteSpace(skuName)) | |
| 833 | + { | |
| 834 | + // SKU 名称在商品明细 JSON(ProductItems)中,按关键词模糊筛选。 | |
| 835 | + query = query.Where(o => o.ProductItems != null && o.ProductItems.Contains(skuName)); | |
| 836 | + } | |
| 837 | + | |
| 826 | 838 | // hasWaybill 在「同人同址合并」之后处理:否则只有子单有 waybills/运单号时,SQL 会整组拆散,列表里筛不出已建运单的合并单 |
| 827 | 839 | |
| 828 | 840 | if (createTimeStart.HasValue) |
| ... | ... | @@ -859,8 +871,8 @@ public class OrderService |
| 859 | 871 | // shipmentFormSubmitted 不参与缓存 Key:缓存的是「合并 + 运单 enrich」后的候选集;该条件只在内存中过滤, |
| 860 | 872 | // 避免与其它条件组合时缓存条目不一致。 |
| 861 | 873 | var cacheKey = BuildOrderListCacheKey( |
| 862 | - shopId, status, orderId, receiverName, receiverPhone, trackingNumber, productName, | |
| 863 | - hasWaybill, pendingShipmentForm, createTimeStart, createTimeEnd, | |
| 874 | + shopId, status, orderId, receiverName, receiverPhone, trackingNumber, productName, skuName, | |
| 875 | + hasWaybill, pendingShipmentForm, abnormalOnly, createTimeStart, createTimeEnd, | |
| 864 | 876 | payTimeStart, payTimeEnd, douyinOrderTimeStart, douyinOrderTimeEnd); |
| 865 | 877 | |
| 866 | 878 | List<Order> merged; |
| ... | ... | @@ -910,7 +922,7 @@ public class OrderService |
| 910 | 922 | /// </summary> |
| 911 | 923 | private static string BuildOrderListCacheKey( |
| 912 | 924 | long? shopId, int? status, string? orderId, string? receiverName, string? receiverPhone, |
| 913 | - string? trackingNumber, string? productName, bool? hasWaybill, bool pendingShipmentForm, | |
| 925 | + string? trackingNumber, string? productName, string? skuName, bool? hasWaybill, bool pendingShipmentForm, bool abnormalOnly, | |
| 914 | 926 | DateTime? createTimeStart, DateTime? createTimeEnd, |
| 915 | 927 | DateTime? payTimeStart, DateTime? payTimeEnd, |
| 916 | 928 | DateTime? douyinOrderTimeStart, DateTime? douyinOrderTimeEnd) |
| ... | ... | @@ -926,8 +938,10 @@ public class OrderService |
| 926 | 938 | receiverPhone?.Trim() ?? "", "|", |
| 927 | 939 | trackingNumber?.Trim() ?? "", "|", |
| 928 | 940 | productName?.Trim() ?? "", "|", |
| 941 | + skuName?.Trim() ?? "", "|", | |
| 929 | 942 | hasWaybill?.ToString() ?? "", "|", |
| 930 | 943 | pendingShipmentForm, "|", |
| 944 | + abnormalOnly, "|", | |
| 931 | 945 | F(createTimeStart), "|", F(createTimeEnd), "|", |
| 932 | 946 | F(payTimeStart), "|", F(payTimeEnd), "|", |
| 933 | 947 | F(douyinOrderTimeStart), "|", F(douyinOrderTimeEnd), "|"); | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtCwdjCrInput.cs
| ... | ... | @@ -64,7 +64,7 @@ namespace NCC.Extend.Entitys.Dto.WtCwdj |
| 64 | 64 | public string djlx { get; set; } |
| 65 | 65 | |
| 66 | 66 | /// <summary> |
| 67 | - /// 单据状态(费用单:草稿 / 待审核;其它类型可按原逻辑由服务端默认) | |
| 67 | + /// 单据状态(费用单 / 收款单 / 付款单:草稿 / 待审核;其它类型可按原逻辑由服务端默认) | |
| 68 | 68 | /// </summary> |
| 69 | 69 | public string djzt { get; set; } |
| 70 | 70 | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPlCrInput.cs
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPlInfoOutput.cs
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPlListOutput.cs
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPpCrInput.cs
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPpInfoOutput.cs
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPpListOutput.cs
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtSkfkChannelStat/WtSkfkChannelStatQueryInput.cs
| ... | ... | @@ -6,6 +6,11 @@ namespace NCC.Extend.Entitys.Dto.WtSkfkChannelStat |
| 6 | 6 | public class WtSkfkChannelStatQueryInput |
| 7 | 7 | { |
| 8 | 8 | /// <summary> |
| 9 | + /// 对账日期 yyyy-MM-dd(单日口径)。传值时优先于 startDate/endDate。 | |
| 10 | + /// </summary> | |
| 11 | + public string date { get; set; } | |
| 12 | + | |
| 13 | + /// <summary> | |
| 9 | 14 | /// 开始日期 yyyy-MM-dd(含) |
| 10 | 15 | /// </summary> |
| 11 | 16 | public string startDate { get; set; } |
| ... | ... | @@ -26,6 +31,16 @@ namespace NCC.Extend.Entitys.Dto.WtSkfkChannelStat |
| 26 | 31 | public string qd { get; set; } |
| 27 | 32 | |
| 28 | 33 | /// <summary> |
| 34 | + /// 账户主键(wt_account.F_Id) | |
| 35 | + /// </summary> | |
| 36 | + public string accountId { get; set; } | |
| 37 | + | |
| 38 | + /// <summary> | |
| 39 | + /// 账户关键字(名称/编码/分类) | |
| 40 | + /// </summary> | |
| 41 | + public string accountKeyword { get; set; } | |
| 42 | + | |
| 43 | + /// <summary> | |
| 29 | 44 | /// 单据类型模糊匹配(流水) |
| 30 | 45 | /// </summary> |
| 31 | 46 | public string djlx { get; set; } | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdMxCrInput.cs
| ... | ... | @@ -64,7 +64,7 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd |
| 64 | 64 | public decimal je { get; set; } |
| 65 | 65 | |
| 66 | 66 | /// <summary> |
| 67 | - /// 变价后成本单价(变价调拨单,服务端会按系数重算覆盖) | |
| 67 | + /// 变价后成本单价(变价调拨单):优先取明细录入值,未填写时按表头变价系数(bjsx)折算 | |
| 68 | 68 | /// </summary> |
| 69 | 69 | public decimal? bjhcb { get; set; } |
| 70 | 70 | |
| ... | ... | @@ -82,6 +82,13 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd |
| 82 | 82 | /// 已选择的序列号列表 |
| 83 | 83 | /// </summary> |
| 84 | 84 | public List<string> selectedSerialNumbers { get; set; } |
| 85 | - | |
| 85 | + | |
| 86 | + /// <summary> | |
| 87 | + /// 售后规则快照(质保时间等)。 | |
| 88 | + /// 前端可不传,服务端会按 <c>spbh</c> 从 <c>wt_sp.F_Shgz</c> 抓取落库作为快照; | |
| 89 | + /// 前端传入时以前端值为准(保留下单时点的真实规则),保证商品档案后续变更不影响历史单。 | |
| 90 | + /// </summary> | |
| 91 | + public string shgz { get; set; } | |
| 92 | + | |
| 86 | 93 | } |
| 87 | 94 | } | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdMxInfoOutput.cs
| ... | ... | @@ -99,7 +99,9 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd |
| 99 | 99 | public List<string> selectedSerialNumbers { get; set; } |
| 100 | 100 | |
| 101 | 101 | /// <summary> |
| 102 | - /// 商品档案售后规则(质保时间等):不落库,由服务端按 <c>spbh</c> 从 <c>wt_sp.F_Shgz</c> 关联填入,供前端在收银台 / 出库单据明细中提示。 | |
| 102 | + /// 售后规则(质保时间等)。 | |
| 103 | + /// 优先读取 <c>wt_xsckd_mx.shgz</c> 快照(新单据在 Create/Update 时落库),只有旧数据快照为空时才回退到 <c>wt_sp.F_Shgz</c>, | |
| 104 | + /// 以确保商品档案后续修改(例如平时 1 年、大促期间 2 年质保)不会影响历史订单的展示。 | |
| 103 | 105 | /// </summary> |
| 104 | 106 | public string shgz { get; set; } |
| 105 | 107 | } | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtPlEntity.cs
| ... | ... | @@ -28,6 +28,12 @@ namespace NCC.Extend.Entitys |
| 28 | 28 | /// </summary> |
| 29 | 29 | [SugarColumn(ColumnName = "F_Sfmdfl")] |
| 30 | 30 | public string Sfmdfl { get; set; } |
| 31 | + | |
| 32 | + /// <summary> | |
| 33 | + /// 序号 - 用于自定义排序,升序显示 | |
| 34 | + /// </summary> | |
| 35 | + [SugarColumn(ColumnName = "F_Xh", IsNullable = true)] | |
| 36 | + public int? Xh { get; set; } | |
| 31 | 37 | |
| 32 | 38 | |
| 33 | 39 | } | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtPpEntity.cs
| ... | ... | @@ -22,6 +22,12 @@ namespace NCC.Extend.Entitys |
| 22 | 22 | /// </summary> |
| 23 | 23 | [SugarColumn(ColumnName = "F_Ppmc")] |
| 24 | 24 | public string Ppmc { get; set; } |
| 25 | + | |
| 26 | + /// <summary> | |
| 27 | + /// 序号 - 用于自定义排序,升序显示 | |
| 28 | + /// </summary> | |
| 29 | + [SugarColumn(ColumnName = "F_Xh", IsNullable = true)] | |
| 30 | + public int? Xh { get; set; } | |
| 25 | 31 | |
| 26 | 32 | } |
| 27 | 33 | } |
| 28 | 34 | \ No newline at end of file | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtXsckdMxEntity.cs
| ... | ... | @@ -112,6 +112,13 @@ namespace NCC.Extend.Entitys |
| 112 | 112 | /// </summary> |
| 113 | 113 | [SugarColumn(ColumnName = "bjhcb", IsNullable = true)] |
| 114 | 114 | public decimal? Bjhcb { get; set; } |
| 115 | - | |
| 115 | + | |
| 116 | + /// <summary> | |
| 117 | + /// 售后规则快照:出库时从 <c>wt_sp.F_Shgz</c> 按 <c>spbh</c> 抓取落库。 | |
| 118 | + /// 商品档案后续修改售后规则(例如大促把质保由 1 年改成 2 年),历史单据应继续展示购买当时的质保,故必须作为快照。 | |
| 119 | + /// </summary> | |
| 120 | + [SugarColumn(ColumnName = "shgz", IsNullable = true)] | |
| 121 | + public string Shgz { get; set; } | |
| 122 | + | |
| 116 | 123 | } |
| 117 | 124 | } | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtCwdjService.cs
| ... | ... | @@ -93,7 +93,7 @@ namespace NCC.Extend.WtCwdj |
| 93 | 93 | .WhereIF(queryLdrq != null, p => p.Ldrq >= new DateTime(startLdrq.ToDate().Year, startLdrq.ToDate().Month, startLdrq.ToDate().Day, 0, 0, 0)) |
| 94 | 94 | .WhereIF(queryLdrq != null, p => p.Ldrq <= new DateTime(endLdrq.ToDate().Year, endLdrq.ToDate().Month, endLdrq.ToDate().Day, 23, 59, 59)) |
| 95 | 95 | .WhereIF(!string.IsNullOrEmpty(input.wldw), p => p.Wldw.Equals(input.wldw)) |
| 96 | - .WhereIF(!string.IsNullOrEmpty(input.jsr), p => p.Jsr.Equals(input.jsr)) | |
| 96 | + .WhereIF(!string.IsNullOrEmpty(input.jsr), p => (p.Jsr ?? "").Contains(input.jsr)) | |
| 97 | 97 | .WhereIF(input.txqs.HasValue, p => p.Txqs == input.txqs) |
| 98 | 98 | .WhereIF(input.zjqs.HasValue, p => p.Zjqs == input.zjqs) |
| 99 | 99 | .WhereIF(!string.IsNullOrEmpty(input.zy), p => p.Zy.Contains(input.zy)) |
| ... | ... | @@ -155,8 +155,17 @@ namespace NCC.Extend.WtCwdj |
| 155 | 155 | ? "草稿" |
| 156 | 156 | : "待审核"; |
| 157 | 157 | } |
| 158 | + else if (IsReceiptOrPaymentBill(resolvedDjlx)) | |
| 159 | + { | |
| 160 | + entity.Djzt = string.Equals(input?.djzt?.Trim(), "草稿", StringComparison.Ordinal) | |
| 161 | + ? "草稿" | |
| 162 | + : "待审核"; | |
| 163 | + } | |
| 158 | 164 | else if (string.IsNullOrWhiteSpace(entity.Djzt)) |
| 159 | 165 | entity.Djzt = "待审核"; |
| 166 | + | |
| 167 | + if (IsReceiptOrPaymentBill(resolvedDjlx)) | |
| 168 | + entity.Jsr = await ResolveJsrAccountNameFromMxInputAsync(input.wtCwdjmxList); | |
| 160 | 169 | |
| 161 | 170 | // 生成每日递增单号,前缀根据djlx判断 |
| 162 | 171 | var today = DateTime.Now.ToString("yyyyMMdd"); |
| ... | ... | @@ -294,7 +303,7 @@ namespace NCC.Extend.WtCwdj |
| 294 | 303 | .WhereIF(queryLdrq != null, p => p.Ldrq >= new DateTime(startLdrq.ToDate().Year, startLdrq.ToDate().Month, startLdrq.ToDate().Day, 0, 0, 0)) |
| 295 | 304 | .WhereIF(queryLdrq != null, p => p.Ldrq <= new DateTime(endLdrq.ToDate().Year, endLdrq.ToDate().Month, endLdrq.ToDate().Day, 23, 59, 59)) |
| 296 | 305 | .WhereIF(!string.IsNullOrEmpty(input.wldw), p => p.Wldw.Equals(input.wldw)) |
| 297 | - .WhereIF(!string.IsNullOrEmpty(input.jsr), p => p.Jsr.Equals(input.jsr)) | |
| 306 | + .WhereIF(!string.IsNullOrEmpty(input.jsr), p => (p.Jsr ?? "").Contains(input.jsr)) | |
| 298 | 307 | .WhereIF(input.txqs.HasValue, p => p.Txqs == input.txqs) |
| 299 | 308 | .WhereIF(input.zjqs.HasValue, p => p.Zjqs == input.zjqs) |
| 300 | 309 | .WhereIF(!string.IsNullOrEmpty(input.zy), p => p.Zy.Contains(input.zy)) |
| ... | ... | @@ -414,6 +423,7 @@ namespace NCC.Extend.WtCwdj |
| 414 | 423 | var djlxForMx = (entity.Djlx ?? input.djlx ?? string.Empty).Trim(); |
| 415 | 424 | FillFydMxZhbhFromFkzh(input, djlxForMx); |
| 416 | 425 | ApplyExpenseDjztOnUpdate(entity, existingHeader, input, djlxForMx); |
| 426 | + ApplyReceiptPaymentDjztOnUpdate(entity, existingHeader, input, djlxForMx); | |
| 417 | 427 | try |
| 418 | 428 | { |
| 419 | 429 | //开启事务 |
| ... | ... | @@ -437,7 +447,9 @@ namespace NCC.Extend.WtCwdj |
| 437 | 447 | throw NCCException.Bah("明细原币金额必填且必须大于0"); |
| 438 | 448 | entity.Fsje = input.wtCwdjmxList.Sum(x => x.ybje); |
| 439 | 449 | if (entity.Fsje <= 0) throw NCCException.Bah("发生金额必须大于0"); |
| 440 | - | |
| 450 | + if (IsReceiptOrPaymentBill(djlxForMx)) | |
| 451 | + entity.Jsr = await ResolveJsrAccountNameFromMxInputAsync(input.wtCwdjmxList); | |
| 452 | + | |
| 441 | 453 | //更新财务单据记录 |
| 442 | 454 | await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); |
| 443 | 455 | |
| ... | ... | @@ -536,19 +548,19 @@ namespace NCC.Extend.WtCwdj |
| 536 | 548 | } |
| 537 | 549 | } |
| 538 | 550 | |
| 539 | - /// <summary>费用单:已提交(待审核)或已审核后不可修改(草稿、反审后可改)。</summary> | |
| 551 | + /// <summary>费用单 / 收款单 / 付款单:已提交(待审核)或已审核后不可修改(草稿、反审后可改)。</summary> | |
| 540 | 552 | private static void ThrowIfExpenseBillCannotEdit(WtCwdjEntity entity) |
| 541 | 553 | { |
| 542 | - if (entity == null || (!IsExpenseBill(entity.Djlx) && !IsDeferredAmortization(entity.Djlx))) return; | |
| 554 | + if (entity == null || (!IsExpenseBill(entity.Djlx) && !IsDeferredAmortization(entity.Djlx) && !IsReceiptOrPaymentBill(entity.Djlx))) return; | |
| 543 | 555 | var zt = (entity.Djzt ?? string.Empty).Trim(); |
| 544 | 556 | if (zt == "待审核" || zt == "已审核") |
| 545 | 557 | throw NCCException.Bah("单据已提交或已审核,仅可查看;反审后可再次编辑"); |
| 546 | 558 | } |
| 547 | 559 | |
| 548 | - /// <summary>费用单:仅草稿可删;已提交、已审核不可删。</summary> | |
| 560 | + /// <summary>费用单 / 收款单 / 付款单:仅草稿可删;已提交、已审核不可删。</summary> | |
| 549 | 561 | private static void ThrowIfExpenseBillCannotDelete(WtCwdjEntity entity) |
| 550 | 562 | { |
| 551 | - if (entity == null || (!IsExpenseBill(entity.Djlx) && !IsDeferredAmortization(entity.Djlx))) return; | |
| 563 | + if (entity == null || (!IsExpenseBill(entity.Djlx) && !IsDeferredAmortization(entity.Djlx) && !IsReceiptOrPaymentBill(entity.Djlx))) return; | |
| 552 | 564 | var zt = (entity.Djzt ?? string.Empty).Trim(); |
| 553 | 565 | if (zt == "待审核" || zt == "已审核") |
| 554 | 566 | throw NCCException.Bah("已提交或已审核的单据不能删除"); |
| ... | ... | @@ -597,6 +609,51 @@ namespace NCC.Extend.WtCwdj |
| 597 | 609 | entity.Djzt = cur; |
| 598 | 610 | } |
| 599 | 611 | |
| 612 | + /// <summary>收款单 / 付款单编辑时:仅草稿允许在「草稿 / 待审核」间切换。</summary> | |
| 613 | + private static void ApplyReceiptPaymentDjztOnUpdate( | |
| 614 | + WtCwdjEntity entity, | |
| 615 | + WtCwdjEntity existingHeader, | |
| 616 | + WtCwdjCrInput input, | |
| 617 | + string djlxForMx) | |
| 618 | + { | |
| 619 | + if (!IsReceiptOrPaymentBill(djlxForMx)) return; | |
| 620 | + var cur = (existingHeader.Djzt ?? string.Empty).Trim(); | |
| 621 | + var inc = (input?.djzt ?? string.Empty).Trim(); | |
| 622 | + if (cur == "草稿") | |
| 623 | + { | |
| 624 | + if (inc == "待审核" || inc == "草稿") | |
| 625 | + entity.Djzt = inc; | |
| 626 | + else | |
| 627 | + entity.Djzt = cur; | |
| 628 | + } | |
| 629 | + else | |
| 630 | + entity.Djzt = cur; | |
| 631 | + } | |
| 632 | + | |
| 633 | + private static bool IsReceiptBill(string djlx) => | |
| 634 | + !string.IsNullOrWhiteSpace(djlx) && djlx.Trim().Contains("收款单", StringComparison.Ordinal); | |
| 635 | + | |
| 636 | + private static bool IsPaymentBill(string djlx) => | |
| 637 | + !string.IsNullOrWhiteSpace(djlx) && djlx.Trim().Contains("付款单", StringComparison.Ordinal); | |
| 638 | + | |
| 639 | + private static bool IsReceiptOrPaymentBill(string djlx) => | |
| 640 | + IsReceiptBill(djlx) || IsPaymentBill(djlx); | |
| 641 | + | |
| 642 | + /// <summary>经手人存所选明细首行账号的账户名称(<c>WtAccount.AccountName</c>)。</summary> | |
| 643 | + private async Task<string> ResolveJsrAccountNameFromMxInputAsync(List<WtCwdjmxCrInput> mxList) | |
| 644 | + { | |
| 645 | + if (mxList == null || mxList.Count == 0) return null; | |
| 646 | + var firstZhbh = mxList.Select(x => x?.zhbh).FirstOrDefault(z => !string.IsNullOrWhiteSpace(z)); | |
| 647 | + if (string.IsNullOrWhiteSpace(firstZhbh)) return null; | |
| 648 | + var tid = firstZhbh.Trim(); | |
| 649 | + var accList = await _db.Queryable<WtAccountEntity>().Where(a => a.Id == tid).Take(1).ToListAsync(); | |
| 650 | + if (accList.Count == 0) throw NCCException.Bah("明细账号不存在,请重新选择"); | |
| 651 | + var acc = accList[0]; | |
| 652 | + var name = (acc.AccountName ?? string.Empty).Trim(); | |
| 653 | + if (!string.IsNullOrEmpty(name)) return name; | |
| 654 | + return (acc.AccountCode ?? string.Empty).Trim(); | |
| 655 | + } | |
| 656 | + | |
| 600 | 657 | /// <summary> |
| 601 | 658 | /// Create 时解析单据类型,避免费用单流程中 djlx 为空导致列表过滤不到。 |
| 602 | 659 | /// </summary> |
| ... | ... | @@ -635,6 +692,10 @@ namespace NCC.Extend.WtCwdj |
| 635 | 692 | /// <summary> |
| 636 | 693 | /// 收款单审核:更新单据状态、审核人、审核时间 |
| 637 | 694 | /// </summary> |
| 695 | + /// <param name="id">单据主键</param> | |
| 696 | + /// <returns>审核结果</returns> | |
| 697 | + /// <response code="200">成功</response> | |
| 698 | + /// <response code="400">参数或业务校验失败</response> | |
| 638 | 699 | [HttpPost("Actions/ApproveReceipt/{id}")] |
| 639 | 700 | public async Task<dynamic> ApproveReceipt(string id) |
| 640 | 701 | { |
| ... | ... | @@ -642,6 +703,19 @@ namespace NCC.Extend.WtCwdj |
| 642 | 703 | } |
| 643 | 704 | |
| 644 | 705 | /// <summary> |
| 706 | + /// 付款单审核:待审核单据一级审核通过 | |
| 707 | + /// </summary> | |
| 708 | + /// <param name="id">单据主键</param> | |
| 709 | + /// <returns>审核结果</returns> | |
| 710 | + /// <response code="200">成功</response> | |
| 711 | + /// <response code="400">参数或业务校验失败</response> | |
| 712 | + [HttpPost("Actions/ApprovePayment/{id}")] | |
| 713 | + public async Task<dynamic> ApprovePayment(string id) | |
| 714 | + { | |
| 715 | + return await ApproveCwdjSingleLevelAsync(id, "付款", "付款单", "仅付款单支持审核"); | |
| 716 | + } | |
| 717 | + | |
| 718 | + /// <summary> | |
| 645 | 719 | /// 费用单审核(wt_cwdj.djlx = 费用单):一级审核,配置项为审核人员设置中的「费用单」 |
| 646 | 720 | /// </summary> |
| 647 | 721 | [HttpPost("Actions/ApproveExpense/{id}")] |
| ... | ... | @@ -662,9 +736,65 @@ namespace NCC.Extend.WtCwdj |
| 662 | 736 | /// <summary> |
| 663 | 737 | /// 费用单反审:已审核 → 草稿,清除审核信息并移除单据摘要行,与应收类反审后「可再次编辑」一致。 |
| 664 | 738 | /// </summary> |
| 739 | + /// <param name="id">单据主键</param> | |
| 740 | + /// <returns>反审结果</returns> | |
| 741 | + /// <response code="200">成功</response> | |
| 742 | + /// <response code="400">参数或业务校验失败</response> | |
| 665 | 743 | [HttpPost("Actions/ReverseExpenseAudit/{id}")] |
| 666 | 744 | public async Task<dynamic> ReverseExpenseAudit(string id) |
| 667 | 745 | { |
| 746 | + return await ReverseCwdjSingleLevelAuditCoreAsync( | |
| 747 | + id, | |
| 748 | + dl => dl != null && dl.Contains("费用单", StringComparison.Ordinal), | |
| 749 | + "仅费用单支持反审", | |
| 750 | + "费用单", | |
| 751 | + "仅已审核的费用单可反审"); | |
| 752 | + } | |
| 753 | + | |
| 754 | + /// <summary> | |
| 755 | + /// 收款单反审:已审核 → 草稿,可再次编辑。 | |
| 756 | + /// </summary> | |
| 757 | + /// <param name="id">单据主键</param> | |
| 758 | + /// <returns>反审结果</returns> | |
| 759 | + /// <response code="200">成功</response> | |
| 760 | + /// <response code="400">参数或业务校验失败</response> | |
| 761 | + [HttpPost("Actions/ReverseReceiptAudit/{id}")] | |
| 762 | + public async Task<dynamic> ReverseReceiptAudit(string id) | |
| 763 | + { | |
| 764 | + return await ReverseCwdjSingleLevelAuditCoreAsync( | |
| 765 | + id, | |
| 766 | + dl => dl != null && dl.Contains("收款单", StringComparison.Ordinal), | |
| 767 | + "仅收款单支持反审", | |
| 768 | + "收款单", | |
| 769 | + "仅已审核的收款单可反审"); | |
| 770 | + } | |
| 771 | + | |
| 772 | + /// <summary> | |
| 773 | + /// 付款单反审:已审核 → 草稿,可再次编辑。 | |
| 774 | + /// </summary> | |
| 775 | + /// <param name="id">单据主键</param> | |
| 776 | + /// <returns>反审结果</returns> | |
| 777 | + /// <response code="200">成功</response> | |
| 778 | + /// <response code="400">参数或业务校验失败</response> | |
| 779 | + [HttpPost("Actions/ReversePaymentAudit/{id}")] | |
| 780 | + public async Task<dynamic> ReversePaymentAudit(string id) | |
| 781 | + { | |
| 782 | + return await ReverseCwdjSingleLevelAuditCoreAsync( | |
| 783 | + id, | |
| 784 | + dl => dl != null && dl.Contains("付款单", StringComparison.Ordinal), | |
| 785 | + "仅付款单支持反审", | |
| 786 | + "付款单", | |
| 787 | + "仅已审核的付款单可反审"); | |
| 788 | + } | |
| 789 | + | |
| 790 | + /// <summary>一级审核反审:已审核 → 草稿,并清理单据摘要。</summary> | |
| 791 | + private async Task<dynamic> ReverseCwdjSingleLevelAuditCoreAsync( | |
| 792 | + string id, | |
| 793 | + Func<string, bool> isAllowedDjlx, | |
| 794 | + string wrongDjlxMessage, | |
| 795 | + string shryszDjmc, | |
| 796 | + string wrongDjztMessage) | |
| 797 | + { | |
| 668 | 798 | if (string.IsNullOrWhiteSpace(id)) throw NCCException.Bah("id 不能为空"); |
| 669 | 799 | var userInfo = await _userManager.GetUserInfo(); |
| 670 | 800 | var userId = userInfo?.userId ?? _userManager.UserId; |
| ... | ... | @@ -673,12 +803,11 @@ namespace NCC.Extend.WtCwdj |
| 673 | 803 | userAccount = _userManager.Account?.Trim(); |
| 674 | 804 | var entity = await _db.Queryable<WtCwdjEntity>().FirstAsync(x => x.Id == id); |
| 675 | 805 | if (entity == null) throw NCCException.Bah("单据不存在"); |
| 676 | - if (entity.Djlx == null || !entity.Djlx.Contains("费用单", StringComparison.Ordinal)) | |
| 677 | - throw NCCException.Bah("仅费用单支持反审"); | |
| 806 | + if (!isAllowedDjlx(entity.Djlx)) | |
| 807 | + throw NCCException.Bah(wrongDjlxMessage); | |
| 678 | 808 | if (!string.Equals((entity.Djzt ?? string.Empty).Trim(), "已审核", StringComparison.Ordinal)) |
| 679 | - throw NCCException.Bah("仅已审核的费用单可反审"); | |
| 809 | + throw NCCException.Bah(wrongDjztMessage); | |
| 680 | 810 | |
| 681 | - const string shryszDjmc = "费用单"; | |
| 682 | 811 | var approvalConfig = await _db.Queryable<WtShryszEntity>() |
| 683 | 812 | .Where(c => c.Djmc == shryszDjmc) |
| 684 | 813 | .FirstAsync(); |
| ... | ... | @@ -729,18 +858,21 @@ namespace NCC.Extend.WtCwdj |
| 729 | 858 | if (entity.Djlx == null || !entity.Djlx.Contains(djlxContains, StringComparison.Ordinal)) |
| 730 | 859 | throw NCCException.Bah(wrongDjlxMessage); |
| 731 | 860 | var ztHead = (entity.Djzt ?? string.Empty).Trim(); |
| 732 | - if (djlxContains.Contains("费用单", StringComparison.Ordinal)) | |
| 861 | + var isExpenseFlow = djlxContains.Contains("费用单", StringComparison.Ordinal); | |
| 862 | + var isSkFkFlow = djlxContains.Contains("收款", StringComparison.Ordinal) | |
| 863 | + || djlxContains.Contains("付款", StringComparison.Ordinal); | |
| 864 | + if (isExpenseFlow || isSkFkFlow) | |
| 733 | 865 | { |
| 734 | 866 | if (string.Equals(ztHead, "草稿", StringComparison.Ordinal)) |
| 735 | - throw NCCException.Bah("费用单为草稿,请先提交后再审核"); | |
| 867 | + throw NCCException.Bah(isExpenseFlow ? "费用单为草稿,请先提交后再审核" : "单据为草稿,请先提交后再审核"); | |
| 736 | 868 | if (!string.Equals(ztHead, "待审核", StringComparison.Ordinal) && !string.IsNullOrEmpty(ztHead)) |
| 737 | 869 | { |
| 738 | 870 | if (string.Equals(ztHead, "已审核", StringComparison.Ordinal)) |
| 739 | 871 | return new { success = false, message = "该单据已经审核,无需重复审核" }; |
| 740 | - throw NCCException.Bah("当前费用单状态不可审核"); | |
| 872 | + throw NCCException.Bah(isExpenseFlow ? "当前费用单状态不可审核" : "当前单据状态不可审核"); | |
| 741 | 873 | } |
| 742 | 874 | } |
| 743 | - else if (string.Equals(entity.Djzt, "已审核", StringComparison.Ordinal)) | |
| 875 | + else if (string.Equals(ztHead, "已审核", StringComparison.Ordinal)) | |
| 744 | 876 | return new { success = false, message = "该单据已经审核,无需重复审核" }; |
| 745 | 877 | |
| 746 | 878 | var approvalConfig = await _db.Queryable<WtShryszEntity>() | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtFysrdService.cs
| ... | ... | @@ -205,6 +205,7 @@ namespace NCC.Extend.WtFysrd |
| 205 | 205 | var userInfo = await _userManager.GetUserInfo(); |
| 206 | 206 | var entity = input.Adapt<WtFysrdEntity>(); |
| 207 | 207 | NormalizeHeaderFromInput(input, entity); |
| 208 | + entity.Djlx = WtFysrdWorkflowHelper.NormalizeOtherIncomeDjlx(entity.Djlx) ?? entity.Djlx; | |
| 208 | 209 | entity.Djzt = "草稿"; |
| 209 | 210 | // 生成每日递增单号 |
| 210 | 211 | var today = DateTime.Now.ToString("yyyyMMdd"); |
| ... | ... | @@ -212,7 +213,8 @@ namespace NCC.Extend.WtFysrd |
| 212 | 213 | // 根据单据类型设置前缀 |
| 213 | 214 | if (!string.IsNullOrEmpty(entity.Djlx)) { |
| 214 | 215 | if (entity.Djlx.Contains("现金费用单")) prefix = "XF"; |
| 215 | - else if (WtFysrdWorkflowHelper.IsOtherIncomeDjlx(entity.Djlx)) prefix = "QT"; | |
| 216 | + else if (string.Equals(entity.Djlx, WtFysrdWorkflowHelper.BillName, StringComparison.Ordinal) | |
| 217 | + || WtFysrdWorkflowHelper.IsOtherIncomeDjlx(entity.Djlx)) prefix = "QT"; | |
| 216 | 218 | else prefix = "FY"; // 默认费用单前缀 |
| 217 | 219 | } |
| 218 | 220 | var maxId = await _db.Queryable<WtFysrdEntity>() | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtPlService.cs
| ... | ... | @@ -36,6 +36,8 @@ namespace NCC.Extend.WtPl |
| 36 | 36 | private readonly SqlSugarScope _db; |
| 37 | 37 | private readonly IUserManager _userManager; |
| 38 | 38 | |
| 39 | + private static bool _xhColumnChecked; | |
| 40 | + | |
| 39 | 41 | /// <summary> |
| 40 | 42 | /// 初始化一个<see cref="WtPlService"/>类型的新实例 |
| 41 | 43 | /// </summary> |
| ... | ... | @@ -49,6 +51,32 @@ namespace NCC.Extend.WtPl |
| 49 | 51 | } |
| 50 | 52 | |
| 51 | 53 | /// <summary> |
| 54 | + /// 确保 wt_pl 表存在「序号」字段 F_Xh(用于商品分类自定义排序) | |
| 55 | + /// </summary> | |
| 56 | + private void EnsureXhColumn() | |
| 57 | + { | |
| 58 | + if (_xhColumnChecked) return; | |
| 59 | + lock (typeof(WtPlService)) | |
| 60 | + { | |
| 61 | + if (_xhColumnChecked) return; | |
| 62 | + try | |
| 63 | + { | |
| 64 | + if (!_db.DbMaintenance.IsAnyTable("wt_pl")) { _xhColumnChecked = true; return; } | |
| 65 | + var columns = _db.DbMaintenance.GetColumnInfosByTableName("wt_pl"); | |
| 66 | + var columnNames = columns.Select(c => c.DbColumnName.ToLower()).ToHashSet(); | |
| 67 | + if (!columnNames.Contains("f_xh")) | |
| 68 | + { | |
| 69 | + _db.Ado.ExecuteCommand( | |
| 70 | + "ALTER TABLE `wt_pl` ADD COLUMN `F_Xh` int NULL COMMENT '序号(自定义排序,升序)'"); | |
| 71 | + Console.WriteLine("✅ wt_pl 已添加字段: F_Xh (序号)"); | |
| 72 | + } | |
| 73 | + } | |
| 74 | + catch (Exception ex) { Console.WriteLine($"EnsureXhColumn(wt_pl): {ex.Message}"); } | |
| 75 | + _xhColumnChecked = true; | |
| 76 | + } | |
| 77 | + } | |
| 78 | + | |
| 79 | + /// <summary> | |
| 52 | 80 | /// 获取商品分类 |
| 53 | 81 | /// </summary> |
| 54 | 82 | /// <param name="id">参数</param> |
| ... | ... | @@ -56,6 +84,7 @@ namespace NCC.Extend.WtPl |
| 56 | 84 | [HttpGet("{id}")] |
| 57 | 85 | public async Task<dynamic> GetInfo(string id) |
| 58 | 86 | { |
| 87 | + EnsureXhColumn(); | |
| 59 | 88 | var entity = await _db.Queryable<WtPlEntity>().FirstAsync(p => p.Id == id); |
| 60 | 89 | var output = entity.Adapt<WtPlInfoOutput>(); |
| 61 | 90 | return output; |
| ... | ... | @@ -69,7 +98,10 @@ namespace NCC.Extend.WtPl |
| 69 | 98 | [HttpGet("")] |
| 70 | 99 | public async Task<dynamic> GetList([FromQuery] WtPlListQueryInput input) |
| 71 | 100 | { |
| 72 | - var sidx = input.sidx == null ? "id" : input.sidx; | |
| 101 | + EnsureXhColumn(); | |
| 102 | + // 默认按「序号」升序,空序号的排到最后,同序号按主键 id 倒序 | |
| 103 | + var defaultOrder = "ISNULL(xh) ASC, xh ASC, id DESC"; | |
| 104 | + var orderBy = string.IsNullOrEmpty(input.sidx) ? defaultOrder : (input.sidx + " " + input.sort); | |
| 73 | 105 | var data = await _db.Queryable<WtPlEntity>() |
| 74 | 106 | .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) |
| 75 | 107 | .WhereIF(!string.IsNullOrEmpty(input.plmc), p => p.Plmc.Contains(input.plmc)) |
| ... | ... | @@ -79,7 +111,8 @@ namespace NCC.Extend.WtPl |
| 79 | 111 | id = it.Id, |
| 80 | 112 | plmc=it.Plmc, |
| 81 | 113 | sfmdfl=it.Sfmdfl, |
| 82 | - }).MergeTable().OrderBy(sidx+" "+input.sort).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 114 | + xh=it.Xh, | |
| 115 | + }).MergeTable().OrderBy(orderBy).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 83 | 116 | return PageResult<WtPlListOutput>.SqlSugarPageResult(data); |
| 84 | 117 | } |
| 85 | 118 | |
| ... | ... | @@ -91,6 +124,7 @@ namespace NCC.Extend.WtPl |
| 91 | 124 | [HttpPost("")] |
| 92 | 125 | public async Task Create([FromBody] WtPlCrInput input) |
| 93 | 126 | { |
| 127 | + EnsureXhColumn(); | |
| 94 | 128 | var userInfo = await _userManager.GetUserInfo(); |
| 95 | 129 | var entity = input.Adapt<WtPlEntity>(); |
| 96 | 130 | entity.Id = YitIdHelper.NextId().ToString(); |
| ... | ... | @@ -106,7 +140,9 @@ namespace NCC.Extend.WtPl |
| 106 | 140 | [NonAction] |
| 107 | 141 | public async Task<dynamic> GetNoPagingList([FromQuery] WtPlListQueryInput input) |
| 108 | 142 | { |
| 109 | - var sidx = input.sidx == null ? "id" : input.sidx; | |
| 143 | + EnsureXhColumn(); | |
| 144 | + var defaultOrder = "ISNULL(xh) ASC, xh ASC, id DESC"; | |
| 145 | + var orderBy = string.IsNullOrEmpty(input.sidx) ? defaultOrder : (input.sidx + " " + input.sort); | |
| 110 | 146 | var data = await _db.Queryable<WtPlEntity>() |
| 111 | 147 | .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) |
| 112 | 148 | .WhereIF(!string.IsNullOrEmpty(input.plmc), p => p.Plmc.Contains(input.plmc)) |
| ... | ... | @@ -115,7 +151,8 @@ namespace NCC.Extend.WtPl |
| 115 | 151 | id = it.Id, |
| 116 | 152 | plmc=it.Plmc, |
| 117 | 153 | sfmdfl=it.Sfmdfl, |
| 118 | - }).MergeTable().OrderBy(sidx+" "+input.sort).ToListAsync(); | |
| 154 | + xh=it.Xh, | |
| 155 | + }).MergeTable().OrderBy(orderBy).ToListAsync(); | |
| 119 | 156 | return data; |
| 120 | 157 | } |
| 121 | 158 | |
| ... | ... | @@ -138,7 +175,7 @@ namespace NCC.Extend.WtPl |
| 138 | 175 | { |
| 139 | 176 | exportData = await this.GetNoPagingList(input); |
| 140 | 177 | } |
| 141 | - List<ParamsModel> paramList = "[{\"value\":\"分类编号\",\"field\":\"id\"},{\"value\":\"品类名称\",\"field\":\"plmc\"},]".ToList<ParamsModel>(); | |
| 178 | + List<ParamsModel> paramList = "[{\"value\":\"序号\",\"field\":\"xh\"},{\"value\":\"分类编号\",\"field\":\"id\"},{\"value\":\"品类名称\",\"field\":\"plmc\"},]".ToList<ParamsModel>(); | |
| 142 | 179 | ExcelConfig excelconfig = new ExcelConfig(); |
| 143 | 180 | excelconfig.FileName = "商品分类.xls"; |
| 144 | 181 | excelconfig.HeadFont = "微软雅黑"; |
| ... | ... | @@ -203,9 +240,15 @@ namespace NCC.Extend.WtPl |
| 203 | 240 | [HttpPut("{id}")] |
| 204 | 241 | public async Task Update(string id, [FromBody] WtPlUpInput input) |
| 205 | 242 | { |
| 243 | + EnsureXhColumn(); | |
| 206 | 244 | var entity = input.Adapt<WtPlEntity>(); |
| 207 | 245 | var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); |
| 208 | 246 | if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); |
| 247 | + // xh 允许清空为 null:单独显式写入(避开 IgnoreColumns:ignoreAllNullColumns 忽略 null 的行为) | |
| 248 | + await _db.Updateable<WtPlEntity>() | |
| 249 | + .SetColumns(it => it.Xh == input.xh) | |
| 250 | + .Where(it => it.Id == id) | |
| 251 | + .ExecuteCommandAsync(); | |
| 209 | 252 | } |
| 210 | 253 | |
| 211 | 254 | /// <summary> | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtPpService.cs
| ... | ... | @@ -32,6 +32,8 @@ namespace NCC.Extend.WtPp |
| 32 | 32 | private readonly SqlSugarScope _db; |
| 33 | 33 | private readonly IUserManager _userManager; |
| 34 | 34 | |
| 35 | + private static bool _xhColumnChecked; | |
| 36 | + | |
| 35 | 37 | /// <summary> |
| 36 | 38 | /// 初始化一个<see cref="WtPpService"/>类型的新实例 |
| 37 | 39 | /// </summary> |
| ... | ... | @@ -45,6 +47,32 @@ namespace NCC.Extend.WtPp |
| 45 | 47 | } |
| 46 | 48 | |
| 47 | 49 | /// <summary> |
| 50 | + /// 确保 wt_pp 表存在「序号」字段 F_Xh(用于品牌自定义排序) | |
| 51 | + /// </summary> | |
| 52 | + private void EnsureXhColumn() | |
| 53 | + { | |
| 54 | + if (_xhColumnChecked) return; | |
| 55 | + lock (typeof(WtPpService)) | |
| 56 | + { | |
| 57 | + if (_xhColumnChecked) return; | |
| 58 | + try | |
| 59 | + { | |
| 60 | + if (!_db.DbMaintenance.IsAnyTable("wt_pp")) { _xhColumnChecked = true; return; } | |
| 61 | + var columns = _db.DbMaintenance.GetColumnInfosByTableName("wt_pp"); | |
| 62 | + var columnNames = columns.Select(c => c.DbColumnName.ToLower()).ToHashSet(); | |
| 63 | + if (!columnNames.Contains("f_xh")) | |
| 64 | + { | |
| 65 | + _db.Ado.ExecuteCommand( | |
| 66 | + "ALTER TABLE `wt_pp` ADD COLUMN `F_Xh` int NULL COMMENT '序号(自定义排序,升序)'"); | |
| 67 | + Console.WriteLine("✅ wt_pp 已添加字段: F_Xh (序号)"); | |
| 68 | + } | |
| 69 | + } | |
| 70 | + catch (Exception ex) { Console.WriteLine($"EnsureXhColumn(wt_pp): {ex.Message}"); } | |
| 71 | + _xhColumnChecked = true; | |
| 72 | + } | |
| 73 | + } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 48 | 76 | /// 获取商品品牌 |
| 49 | 77 | /// </summary> |
| 50 | 78 | /// <param name="id">参数</param> |
| ... | ... | @@ -52,6 +80,7 @@ namespace NCC.Extend.WtPp |
| 52 | 80 | [HttpGet("{id}")] |
| 53 | 81 | public async Task<dynamic> GetInfo(string id) |
| 54 | 82 | { |
| 83 | + EnsureXhColumn(); | |
| 55 | 84 | var entity = await _db.Queryable<WtPpEntity>().FirstAsync(p => p.Id == id); |
| 56 | 85 | var output = entity.Adapt<WtPpInfoOutput>(); |
| 57 | 86 | return output; |
| ... | ... | @@ -65,14 +94,18 @@ namespace NCC.Extend.WtPp |
| 65 | 94 | [HttpGet("")] |
| 66 | 95 | public async Task<dynamic> GetList([FromQuery] WtPpListQueryInput input) |
| 67 | 96 | { |
| 68 | - var sidx = input.sidx == null ? "id" : input.sidx; | |
| 97 | + EnsureXhColumn(); | |
| 98 | + // 默认按「序号」升序,同序号或空序号的按创建时间倒序(以主键 id 近似) | |
| 99 | + var defaultOrder = "ISNULL(xh) ASC, xh ASC, id DESC"; | |
| 100 | + var orderBy = string.IsNullOrEmpty(input.sidx) ? defaultOrder : (input.sidx + " " + input.sort); | |
| 69 | 101 | var data = await _db.Queryable<WtPpEntity>() |
| 70 | 102 | .WhereIF(!string.IsNullOrEmpty(input.ppmc), p => p.Ppmc.Contains(input.ppmc)) |
| 71 | 103 | .Select(it=> new WtPpListOutput |
| 72 | 104 | { |
| 73 | 105 | id = it.Id, |
| 74 | 106 | ppmc=it.Ppmc, |
| 75 | - }).MergeTable().OrderBy(sidx+" "+input.sort).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 107 | + xh=it.Xh, | |
| 108 | + }).MergeTable().OrderBy(orderBy).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 76 | 109 | return PageResult<WtPpListOutput>.SqlSugarPageResult(data); |
| 77 | 110 | } |
| 78 | 111 | |
| ... | ... | @@ -84,6 +117,7 @@ namespace NCC.Extend.WtPp |
| 84 | 117 | [HttpPost("")] |
| 85 | 118 | public async Task Create([FromBody] WtPpCrInput input) |
| 86 | 119 | { |
| 120 | + EnsureXhColumn(); | |
| 87 | 121 | var userInfo = await _userManager.GetUserInfo(); |
| 88 | 122 | var entity = input.Adapt<WtPpEntity>(); |
| 89 | 123 | entity.Id = YitIdHelper.NextId().ToString(); |
| ... | ... | @@ -100,9 +134,16 @@ namespace NCC.Extend.WtPp |
| 100 | 134 | [HttpPut("{id}")] |
| 101 | 135 | public async Task Update(string id, [FromBody] WtPpUpInput input) |
| 102 | 136 | { |
| 137 | + EnsureXhColumn(); | |
| 103 | 138 | var entity = input.Adapt<WtPpEntity>(); |
| 139 | + // xh 允许清空为 null,这里单独显式更新 Xh 字段(IgnoreColumns:ignoreAllNullColumns 会跳过 null, | |
| 140 | + // 所以主更新仍保留忽略 null 行为,Xh 通过独立更新语句处理) | |
| 104 | 141 | var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); |
| 105 | 142 | if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); |
| 143 | + await _db.Updateable<WtPpEntity>() | |
| 144 | + .SetColumns(it => it.Xh == input.xh) | |
| 145 | + .Where(it => it.Id == id) | |
| 146 | + .ExecuteCommandAsync(); | |
| 106 | 147 | } |
| 107 | 148 | |
| 108 | 149 | /// <summary> | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtSkfkChannelStatService.cs
| ... | ... | @@ -40,6 +40,201 @@ namespace NCC.Extend.WtSkfkChannelStat |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | 42 | /// <summary> |
| 43 | + /// 账户对账汇总:按账户分类分组,输出「上期余额/今日收入/今日支出/本期余额/实时余额」。 | |
| 44 | + /// </summary> | |
| 45 | + /// <param name="input">支持 date(单日)与 accountId/accountKeyword 筛选</param> | |
| 46 | + [HttpGet("Actions/GetAccountReconcileSummary")] | |
| 47 | + public async Task<dynamic> GetAccountReconcileSummary([FromQuery] WtSkfkChannelStatQueryInput input) | |
| 48 | + { | |
| 49 | + input ??= new WtSkfkChannelStatQueryInput(); | |
| 50 | + if (!TryParseReconcileDate(input, out var statDate, out var err)) | |
| 51 | + throw NCCException.Oh(err); | |
| 52 | + | |
| 53 | + var dayStart = statDate.Date; | |
| 54 | + var dayEnd = dayStart.AddDays(1); | |
| 55 | + var nowEndExclusive = DateTime.Now.Date.AddDays(1); | |
| 56 | + | |
| 57 | + var accountQuery = _db.Queryable<WtAccountEntity>().Where(a => a.Status == 1); | |
| 58 | + if (!string.IsNullOrWhiteSpace(input.accountId)) | |
| 59 | + { | |
| 60 | + var accountId = input.accountId.Trim(); | |
| 61 | + accountQuery = accountQuery.Where(a => a.Id == accountId); | |
| 62 | + } | |
| 63 | + if (!string.IsNullOrWhiteSpace(input.accountKeyword)) | |
| 64 | + { | |
| 65 | + var kw = input.accountKeyword.Trim(); | |
| 66 | + accountQuery = accountQuery.Where(a => | |
| 67 | + a.AccountName.Contains(kw) || | |
| 68 | + a.AccountCode.Contains(kw) || | |
| 69 | + a.Category.Contains(kw)); | |
| 70 | + } | |
| 71 | + | |
| 72 | + var accounts = await accountQuery | |
| 73 | + .OrderBy(a => a.Category) | |
| 74 | + .OrderBy(a => a.SortCode) | |
| 75 | + .OrderBy(a => a.AccountCode) | |
| 76 | + .Select(a => new | |
| 77 | + { | |
| 78 | + a.Id, | |
| 79 | + a.AccountName, | |
| 80 | + a.AccountCode, | |
| 81 | + a.Category, | |
| 82 | + a.SortCode | |
| 83 | + }) | |
| 84 | + .ToListAsync(); | |
| 85 | + | |
| 86 | + var openingLines = await BuildLedgerLinesAsync(DateTime.MinValue, dayStart); | |
| 87 | + var dayLines = await BuildLedgerLinesAsync(dayStart, dayEnd); | |
| 88 | + var realtimeLines = await BuildLedgerLinesAsync(DateTime.MinValue, nowEndExclusive); | |
| 89 | + | |
| 90 | + decimal Signed(InternalLine x) => string.Equals(x.FxCode, "sk", StringComparison.Ordinal) ? x.Je : -x.Je; | |
| 91 | + | |
| 92 | + var openingMap = openingLines | |
| 93 | + .Where(x => !string.IsNullOrWhiteSpace(x.SkzhId)) | |
| 94 | + .GroupBy(x => x.SkzhId.Trim(), StringComparer.Ordinal) | |
| 95 | + .ToDictionary(g => g.Key, g => g.Sum(Signed), StringComparer.Ordinal); | |
| 96 | + | |
| 97 | + var dayIncomeMap = dayLines | |
| 98 | + .Where(x => !string.IsNullOrWhiteSpace(x.SkzhId) && x.FxCode == "sk") | |
| 99 | + .GroupBy(x => x.SkzhId.Trim(), StringComparer.Ordinal) | |
| 100 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.Je), StringComparer.Ordinal); | |
| 101 | + | |
| 102 | + var dayExpenseMap = dayLines | |
| 103 | + .Where(x => !string.IsNullOrWhiteSpace(x.SkzhId) && x.FxCode == "fk") | |
| 104 | + .GroupBy(x => x.SkzhId.Trim(), StringComparer.Ordinal) | |
| 105 | + .ToDictionary(g => g.Key, g => g.Sum(x => x.Je), StringComparer.Ordinal); | |
| 106 | + | |
| 107 | + var realtimeMap = realtimeLines | |
| 108 | + .Where(x => !string.IsNullOrWhiteSpace(x.SkzhId)) | |
| 109 | + .GroupBy(x => x.SkzhId.Trim(), StringComparer.Ordinal) | |
| 110 | + .ToDictionary(g => g.Key, g => g.Sum(Signed), StringComparer.Ordinal); | |
| 111 | + | |
| 112 | + var rows = new List<dynamic>(); | |
| 113 | + foreach (var a in accounts) | |
| 114 | + { | |
| 115 | + var aid = a.Id ?? string.Empty; | |
| 116 | + openingMap.TryGetValue(aid, out var openingBalance); | |
| 117 | + dayIncomeMap.TryGetValue(aid, out var todayIncome); | |
| 118 | + dayExpenseMap.TryGetValue(aid, out var todayExpense); | |
| 119 | + realtimeMap.TryGetValue(aid, out var realtimeBalance); | |
| 120 | + var periodBalance = openingBalance + todayIncome - todayExpense; | |
| 121 | + | |
| 122 | + rows.Add(new | |
| 123 | + { | |
| 124 | + date = statDate.ToString("yyyy-MM-dd"), | |
| 125 | + accountId = aid, | |
| 126 | + account = a.AccountName ?? "", | |
| 127 | + accountCode = a.AccountCode ?? "", | |
| 128 | + category = string.IsNullOrWhiteSpace(a.Category) ? "未分类" : a.Category.Trim(), | |
| 129 | + openingBalance, | |
| 130 | + todayIncome, | |
| 131 | + todayExpense, | |
| 132 | + periodBalance, | |
| 133 | + realtimeBalance | |
| 134 | + }); | |
| 135 | + } | |
| 136 | + | |
| 137 | + var categoryGroups = rows | |
| 138 | + .GroupBy(x => (string)x.category, StringComparer.Ordinal) | |
| 139 | + .Select(g => new | |
| 140 | + { | |
| 141 | + category = g.Key, | |
| 142 | + totalOpeningBalance = g.Sum(x => (decimal)x.openingBalance), | |
| 143 | + totalTodayIncome = g.Sum(x => (decimal)x.todayIncome), | |
| 144 | + totalTodayExpense = g.Sum(x => (decimal)x.todayExpense), | |
| 145 | + totalPeriodBalance = g.Sum(x => (decimal)x.periodBalance), | |
| 146 | + totalRealtimeBalance = g.Sum(x => (decimal)x.realtimeBalance), | |
| 147 | + rows = g.OrderBy(x => (string)x.accountCode).ThenBy(x => (string)x.account).ToList() | |
| 148 | + }) | |
| 149 | + .OrderBy(x => x.category) | |
| 150 | + .ToList(); | |
| 151 | + | |
| 152 | + return new | |
| 153 | + { | |
| 154 | + date = statDate.ToString("yyyy-MM-dd"), | |
| 155 | + categoryGroups, | |
| 156 | + totalOpeningBalance = categoryGroups.Sum(x => x.totalOpeningBalance), | |
| 157 | + totalTodayIncome = categoryGroups.Sum(x => x.totalTodayIncome), | |
| 158 | + totalTodayExpense = categoryGroups.Sum(x => x.totalTodayExpense), | |
| 159 | + totalPeriodBalance = categoryGroups.Sum(x => x.totalPeriodBalance), | |
| 160 | + totalRealtimeBalance = categoryGroups.Sum(x => x.totalRealtimeBalance) | |
| 161 | + }; | |
| 162 | + } | |
| 163 | + | |
| 164 | + /// <summary> | |
| 165 | + /// 账户对账明细:按日期、账户筛选收支明细。 | |
| 166 | + /// </summary> | |
| 167 | + [HttpGet("Actions/GetAccountReconcileLedger")] | |
| 168 | + public async Task<dynamic> GetAccountReconcileLedger([FromQuery] WtSkfkChannelStatQueryInput input) | |
| 169 | + { | |
| 170 | + input ??= new WtSkfkChannelStatQueryInput(); | |
| 171 | + if (!TryParseRange(input, out var start, out var endEx, out var err)) | |
| 172 | + throw NCCException.Oh(err); | |
| 173 | + | |
| 174 | + var page = input.currentPage.GetValueOrDefault(1); | |
| 175 | + if (page < 1) page = 1; | |
| 176 | + var size = input.pageSize.GetValueOrDefault(100); | |
| 177 | + if (size < 1) size = 100; | |
| 178 | + if (size > 500) size = 500; | |
| 179 | + | |
| 180 | + var lines = await BuildLedgerLinesAsync(start, endEx); | |
| 181 | + IEnumerable<InternalLine> q = lines; | |
| 182 | + | |
| 183 | + if (!string.IsNullOrWhiteSpace(input.accountId)) | |
| 184 | + { | |
| 185 | + var accountId = input.accountId.Trim(); | |
| 186 | + var account = await _db.Queryable<WtAccountEntity>() | |
| 187 | + .Where(a => a.Id == accountId) | |
| 188 | + .Select(a => new { a.Id, a.AccountName, a.AccountCode }) | |
| 189 | + .FirstAsync(); | |
| 190 | + var accountName = account?.AccountName?.Trim(); | |
| 191 | + q = q.Where(x => | |
| 192 | + (!string.IsNullOrWhiteSpace(x.SkzhId) && string.Equals(x.SkzhId.Trim(), accountId, StringComparison.Ordinal)) | |
| 193 | + || (!string.IsNullOrWhiteSpace(accountName) && string.Equals((x.SkzhMc ?? "").Trim(), accountName, StringComparison.Ordinal)) | |
| 194 | + || ((x.SkzhMc ?? "").Contains("(" + accountId + ")"))); | |
| 195 | + } | |
| 196 | + if (!string.IsNullOrWhiteSpace(input.accountKeyword)) | |
| 197 | + { | |
| 198 | + var kw = input.accountKeyword.Trim(); | |
| 199 | + q = q.Where(x => (x.SkzhMc != null && x.SkzhMc.Contains(kw, StringComparison.Ordinal)) | |
| 200 | + || (x.Fffs != null && x.Fffs.Contains(kw, StringComparison.Ordinal)) | |
| 201 | + || (x.Djlx != null && x.Djlx.Contains(kw, StringComparison.Ordinal))); | |
| 202 | + } | |
| 203 | + if (!string.IsNullOrWhiteSpace(input.fx)) | |
| 204 | + { | |
| 205 | + var f = input.fx.Trim().ToLowerInvariant(); | |
| 206 | + if (f == "sk" || f == "收款") | |
| 207 | + q = q.Where(x => x.FxCode == "sk"); | |
| 208 | + else if (f == "fk" || f == "付款") | |
| 209 | + q = q.Where(x => x.FxCode == "fk"); | |
| 210 | + } | |
| 211 | + | |
| 212 | + var ordered = q.OrderByDescending(x => x.Dt).ThenByDescending(x => x.Djbh).ToList(); | |
| 213 | + var total = ordered.Count; | |
| 214 | + var slice = ordered.Skip((page - 1) * size).Take(size).ToList(); | |
| 215 | + | |
| 216 | + var userIds = slice.Where(x => !string.IsNullOrEmpty(x.JsrId)).Select(x => x.JsrId).Distinct().ToList(); | |
| 217 | + var userMap = await BuildUserNameMapAsync(userIds); | |
| 218 | + | |
| 219 | + var list = slice.Select(x => new WtSkfkChannelStatLedgerRowOutput | |
| 220 | + { | |
| 221 | + djrq = x.Dt.ToString("yyyy-MM-dd"), | |
| 222 | + djbh = x.Djbh ?? "", | |
| 223 | + djlx = string.IsNullOrEmpty(x.Djlx) ? "" : x.Djlx, | |
| 224 | + fx = x.FxCode == "sk" ? "收款" : "付款", | |
| 225 | + fffs = string.IsNullOrEmpty(x.Fffs) ? "" : x.Fffs, | |
| 226 | + skzhMc = string.IsNullOrEmpty(x.SkzhMc) ? "" : x.SkzhMc, | |
| 227 | + je = x.Je, | |
| 228 | + ly = x.Ly ?? "", | |
| 229 | + ycddh = string.IsNullOrEmpty(x.Ycddh) ? "" : x.Ycddh, | |
| 230 | + zy = string.IsNullOrWhiteSpace(x.Zy) ? "" : x.Zy.Trim(), | |
| 231 | + jsr = userMap.TryGetValue(x.JsrId ?? "", out var nm) && !string.IsNullOrEmpty(nm) ? nm : "" | |
| 232 | + }).ToList(); | |
| 233 | + | |
| 234 | + return new { list, total }; | |
| 235 | + } | |
| 236 | + | |
| 237 | + /// <summary> | |
| 43 | 238 | /// 汇总:按收付方向拆分。收款侧仅含 Fx=收款 的流水;付款侧仅含 Fx=付款 的流水,避免采购付款计入「入账」。 |
| 44 | 239 | /// </summary> |
| 45 | 240 | /// <param name="input">开始/结束日期</param> |
| ... | ... | @@ -172,6 +367,8 @@ namespace NCC.Extend.WtSkfkChannelStat |
| 172 | 367 | public string FxCode { get; set; } |
| 173 | 368 | /// <summary>买方付款方式(微信、余额等)</summary> |
| 174 | 369 | public string Fffs { get; set; } |
| 370 | + /// <summary>账户主键(wt_account.F_Id)</summary> | |
| 371 | + public string SkzhId { get; set; } | |
| 175 | 372 | /// <summary>收款账户入账端(字典名)</summary> |
| 176 | 373 | public string SkzhMc { get; set; } |
| 177 | 374 | public decimal Je { get; set; } |
| ... | ... | @@ -213,6 +410,20 @@ namespace NCC.Extend.WtSkfkChannelStat |
| 213 | 410 | return true; |
| 214 | 411 | } |
| 215 | 412 | |
| 413 | + private static bool TryParseReconcileDate(WtSkfkChannelStatQueryInput input, out DateTime day, out string err) | |
| 414 | + { | |
| 415 | + day = default; | |
| 416 | + err = null; | |
| 417 | + var s = string.IsNullOrWhiteSpace(input.date) ? DateTime.Now.ToString("yyyy-MM-dd") : input.date.Trim(); | |
| 418 | + if (!DateTime.TryParse(s, out day)) | |
| 419 | + { | |
| 420 | + err = "日期无效"; | |
| 421 | + return false; | |
| 422 | + } | |
| 423 | + day = day.Date; | |
| 424 | + return true; | |
| 425 | + } | |
| 426 | + | |
| 216 | 427 | private async Task<List<InternalLine>> BuildLedgerLinesAsync(DateTime start, DateTime endExclusive) |
| 217 | 428 | { |
| 218 | 429 | var dictIds = new HashSet<string>(StringComparer.Ordinal); |
| ... | ... | @@ -272,6 +483,7 @@ namespace NCC.Extend.WtSkfkChannelStat |
| 272 | 483 | Djlx = string.IsNullOrEmpty(s.Djlx) ? "收付款单" : s.Djlx, |
| 273 | 484 | FxCode = fxCode, |
| 274 | 485 | Fffs = fffs, |
| 486 | + SkzhId = string.IsNullOrWhiteSpace(s.Jszh) ? null : s.Jszh.Trim(), | |
| 275 | 487 | SkzhMc = zhMc, |
| 276 | 488 | Je = Math.Abs(s.Je), |
| 277 | 489 | Ly = "收付款单", |
| ... | ... | @@ -301,6 +513,9 @@ namespace NCC.Extend.WtSkfkChannelStat |
| 301 | 513 | Djlx = string.IsNullOrEmpty(c.Djlx) ? "财务单据" : c.Djlx, |
| 302 | 514 | FxCode = fxCode, |
| 303 | 515 | Fffs = fffs, |
| 516 | + SkzhId = isPay | |
| 517 | + ? (string.IsNullOrWhiteSpace(c.Fkzh) ? null : c.Fkzh.Trim()) | |
| 518 | + : (string.IsNullOrWhiteSpace(c.Skzh) ? null : c.Skzh.Trim()), | |
| 304 | 519 | SkzhMc = zhMc, |
| 305 | 520 | Je = Math.Abs(c.Fsje), |
| 306 | 521 | Ly = "财务单据", |
| ... | ... | @@ -423,6 +638,7 @@ namespace NCC.Extend.WtSkfkChannelStat |
| 423 | 638 | Djlx = x.Djlx, |
| 424 | 639 | FxCode = fxCode, |
| 425 | 640 | Fffs = fffs, |
| 641 | + SkzhId = skzhId, | |
| 426 | 642 | SkzhMc = skzhMc, |
| 427 | 643 | Je = Math.Abs(je), |
| 428 | 644 | Ly = "进销存单据", |
| ... | ... | @@ -446,6 +662,7 @@ namespace NCC.Extend.WtSkfkChannelStat |
| 446 | 662 | Djlx = x.Djlx, |
| 447 | 663 | FxCode = fxCode, |
| 448 | 664 | Fffs = "其他", |
| 665 | + SkzhId = mainSkzh, | |
| 449 | 666 | SkzhMc = ResolveDictLabel(mainSkzh, dictMap, accountFallback), |
| 450 | 667 | Je = Math.Abs(x.Skje), |
| 451 | 668 | Ly = "进销存单据", | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtSpService.cs
| ... | ... | @@ -561,6 +561,72 @@ CREATE TABLE `wt_sp_dysku` ( |
| 561 | 561 | } |
| 562 | 562 | |
| 563 | 563 | /// <summary> |
| 564 | + /// 构造商品档案筛选查询(与列表筛选口径一致,不含分页)。 | |
| 565 | + /// </summary> | |
| 566 | + private async Task<ISugarQueryable<WtSpEntity>> BuildSpFilterQueryAsync(WtSpListQueryInput input) | |
| 567 | + { | |
| 568 | + var safeInput = input ?? new WtSpListQueryInput(); | |
| 569 | + var kw = safeInput.keyword?.Trim(); | |
| 570 | + var dyFilter = safeInput.dyspid?.Trim(); | |
| 571 | + List<object> queryLsj = safeInput.lsj != null ? safeInput.lsj.Split(',').ToObeject<List<object>>() : null; | |
| 572 | + var startLsj = safeInput.lsj != null && !string.IsNullOrEmpty(queryLsj.First().ToString()) ? queryLsj.First() : decimal.MinValue; | |
| 573 | + var endLsj = safeInput.lsj != null && !string.IsNullOrEmpty(queryLsj.Last().ToString()) ? queryLsj.Last() : decimal.MaxValue; | |
| 574 | + List<object> queryZg = safeInput.zg != null ? safeInput.zg.Split(',').ToObeject<List<object>>() : null; | |
| 575 | + var startZg = safeInput.zg != null && !string.IsNullOrEmpty(queryZg.First().ToString()) ? queryZg.First() : decimal.MinValue; | |
| 576 | + var endZg = safeInput.zg != null && !string.IsNullOrEmpty(queryZg.Last().ToString()) ? queryZg.Last() : decimal.MaxValue; | |
| 577 | + List<object> queryKc = safeInput.kc != null ? safeInput.kc.Split(',').ToObeject<List<object>>() : null; | |
| 578 | + var startKc = safeInput.kc != null && !string.IsNullOrEmpty(queryKc.First().ToString()) ? queryKc.First() : decimal.MinValue; | |
| 579 | + var endKc = safeInput.kc != null && !string.IsNullOrEmpty(queryKc.Last().ToString()) ? queryKc.Last() : decimal.MaxValue; | |
| 580 | + var q0 = WherePlCategoryIfAny(_db.Queryable<WtSpEntity>() | |
| 581 | + .WhereIF(!string.IsNullOrEmpty(safeInput.spmc), p => p.Spmc.Contains(safeInput.spmc)), safeInput.pl) | |
| 582 | + .WhereIF( | |
| 583 | + !string.IsNullOrEmpty(kw), | |
| 584 | + p => p.Spmc.Contains(kw) | |
| 585 | + || p.Spbm.Contains(kw) | |
| 586 | + || p.Id == kw | |
| 587 | + || (!SqlFunc.IsNullOrEmpty(p.Dyspid) && p.Dyspid.Contains(kw)) | |
| 588 | + || SqlFunc.Subqueryable<WtSpDyskuEntity>() | |
| 589 | + .Where(m => m.SpId == p.Id && m.SkuId.Contains(kw)) | |
| 590 | + .Any()) | |
| 591 | + .WhereIF(!string.IsNullOrEmpty(safeInput.pp), p => p.Pp.Equals(safeInput.pp)) | |
| 592 | + .WhereIF(!string.IsNullOrEmpty(safeInput.spbm), p => p.Spbm.Contains(safeInput.spbm)) | |
| 593 | + .WhereIF(!string.IsNullOrEmpty(safeInput.spxlhType), p => p.SpxlhType.Equals(safeInput.spxlhType)) | |
| 594 | + .WhereIF( | |
| 595 | + !string.IsNullOrEmpty(dyFilter), | |
| 596 | + " (F_Dyspid=@dyf OR FIND_IN_SET(@dyf,F_Dyspid)>0 OR EXISTS (SELECT 1 FROM wt_sp_dysku __d WHERE __d.F_SpId=F_Id AND __d.F_SkuId=@dyf)) ", | |
| 597 | + new { dyf = dyFilter }) | |
| 598 | + .WhereIF(queryLsj != null, p => SqlFunc.Between(p.Lsj, startLsj, endLsj)) | |
| 599 | + .WhereIF(queryZg != null, p => SqlFunc.Between(p.Zg, startZg, endZg)) | |
| 600 | + .WhereIF(queryKc != null, p => SqlFunc.Between(p.Kc, startKc, endKc)) | |
| 601 | + .WhereIF(!string.IsNullOrEmpty(safeInput.shgz), p => p.Shgz.Contains(safeInput.shgz)) | |
| 602 | + .WhereIF(!string.IsNullOrEmpty(safeInput.yfgz), p => p.Yfgz.Contains(safeInput.yfgz)) | |
| 603 | + .WhereIF(!string.IsNullOrEmpty(safeInput.xsqd), p => p.Xsqd.Equals(safeInput.xsqd)); | |
| 604 | + return await ApplyXsmdFilterAsync(q0, safeInput.xsmd); | |
| 605 | + } | |
| 606 | + | |
| 607 | + /// <summary> | |
| 608 | + /// 判断是否包含任一筛选条件。 | |
| 609 | + /// </summary> | |
| 610 | + private static bool HasAnySpFilterCondition(WtSpListQueryInput input) | |
| 611 | + { | |
| 612 | + if (input == null) return false; | |
| 613 | + return !string.IsNullOrWhiteSpace(input.spmc) | |
| 614 | + || !string.IsNullOrWhiteSpace(input.pl) | |
| 615 | + || !string.IsNullOrWhiteSpace(input.pp) | |
| 616 | + || !string.IsNullOrWhiteSpace(input.spbm) | |
| 617 | + || !string.IsNullOrWhiteSpace(input.spxlhType) | |
| 618 | + || !string.IsNullOrWhiteSpace(input.lsj) | |
| 619 | + || !string.IsNullOrWhiteSpace(input.zg) | |
| 620 | + || !string.IsNullOrWhiteSpace(input.kc) | |
| 621 | + || !string.IsNullOrWhiteSpace(input.shgz) | |
| 622 | + || !string.IsNullOrWhiteSpace(input.yfgz) | |
| 623 | + || !string.IsNullOrWhiteSpace(input.xsqd) | |
| 624 | + || !string.IsNullOrWhiteSpace(input.xsmd) | |
| 625 | + || !string.IsNullOrWhiteSpace(input.keyword) | |
| 626 | + || !string.IsNullOrWhiteSpace(input.dyspid); | |
| 627 | + } | |
| 628 | + | |
| 629 | + /// <summary> | |
| 564 | 630 | /// 序列号是否计入门店库存:<c>in_warehouse</c> 与仓库主键比对时 Trim+忽略大小写;兼容历史数据中 <c>in_warehouse</c> 误存为门店 ID 的情况。 |
| 565 | 631 | /// </summary> |
| 566 | 632 | private static bool SerialMatchesStoreWarehouses(string inWarehouse, string storeIdTrimmed, |
| ... | ... | @@ -936,7 +1002,7 @@ CREATE TABLE `wt_sp_dysku` ( |
| 936 | 1002 | .WhereIF( |
| 937 | 1003 | !string.IsNullOrEmpty(dyFilter), |
| 938 | 1004 | " (F_Dyspid=@dyf OR FIND_IN_SET(@dyf,F_Dyspid)>0 OR EXISTS (SELECT 1 FROM wt_sp_dysku __d WHERE __d.F_SpId=F_Id AND __d.F_SkuId=@dyf)) ", |
| 939 | - new SugarParameter("@dyf", dyFilter)) | |
| 1005 | + new { dyf = dyFilter }) | |
| 940 | 1006 | .WhereIF(queryLsj != null, p => SqlFunc.Between(p.Lsj, startLsj, endLsj)) |
| 941 | 1007 | .WhereIF(queryZg != null, p => SqlFunc.Between(p.Zg, startZg, endZg)) |
| 942 | 1008 | .WhereIF(queryKc != null, p => SqlFunc.Between(p.Kc, startKc, endKc)) |
| ... | ... | @@ -1030,7 +1096,7 @@ CREATE TABLE `wt_sp_dysku` ( |
| 1030 | 1096 | .WhereIF( |
| 1031 | 1097 | !string.IsNullOrEmpty(dyFilter), |
| 1032 | 1098 | " (F_Dyspid=@dyf OR FIND_IN_SET(@dyf,F_Dyspid)>0 OR EXISTS (SELECT 1 FROM wt_sp_dysku __d WHERE __d.F_SpId=F_Id AND __d.F_SkuId=@dyf)) ", |
| 1033 | - new SugarParameter("@dyf", dyFilter)); | |
| 1099 | + new { dyf = dyFilter }); | |
| 1034 | 1100 | var qk1 = await ApplyXsmdFilterAsync(qk0, input.xsmd); |
| 1035 | 1101 | var data = await qk1 |
| 1036 | 1102 | // SqlSugar 在包含 Subqueryable/WhereIF 时对 lambda 参数名较敏感 |
| ... | ... | @@ -1398,6 +1464,154 @@ CREATE TABLE `wt_sp_dysku` ( |
| 1398 | 1464 | } |
| 1399 | 1465 | |
| 1400 | 1466 | /// <summary> |
| 1467 | + /// 按筛选条件批量设置商品关联权益卡(覆盖写入 <c>F_Hyxz</c>)。 | |
| 1468 | + /// </summary> | |
| 1469 | + [HttpPost("BatchSetHyxzByFilter")] | |
| 1470 | + public async Task<dynamic> BatchSetHyxzByFilter([FromBody] WtSpBatchHyxzByFilterInput input) | |
| 1471 | + { | |
| 1472 | + var req = input ?? new WtSpBatchHyxzByFilterInput(); | |
| 1473 | + var filter = req.query ?? new WtSpListQueryInput(); | |
| 1474 | + var hasFilter = HasAnySpFilterCondition(filter); | |
| 1475 | + if (!hasFilter && !req.allowAll) | |
| 1476 | + throw NCCException.Oh("未设置筛选条件。若需作用于全部商品,请确认后传 allowAll=true"); | |
| 1477 | + | |
| 1478 | + var cardIds = (req.hyxzCardIds ?? new List<string>()) | |
| 1479 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 1480 | + .Select(x => x.Trim()) | |
| 1481 | + .Distinct(StringComparer.Ordinal) | |
| 1482 | + .ToList(); | |
| 1483 | + if (cardIds.Count == 0) | |
| 1484 | + throw NCCException.Oh("请至少选择一张权益卡"); | |
| 1485 | + | |
| 1486 | + var validCards = await _db.Queryable<WtHyKjqyEntity>() | |
| 1487 | + .Where(c => cardIds.Contains(c.Id)) | |
| 1488 | + .Select(c => c.Id) | |
| 1489 | + .ToListAsync(); | |
| 1490 | + var finalCardIds = validCards | |
| 1491 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 1492 | + .Select(x => x.Trim()) | |
| 1493 | + .Distinct(StringComparer.Ordinal) | |
| 1494 | + .ToList(); | |
| 1495 | + if (finalCardIds.Count == 0) | |
| 1496 | + throw NCCException.Oh("所选权益卡不存在或已失效"); | |
| 1497 | + | |
| 1498 | + var query = await BuildSpFilterQueryAsync(filter); | |
| 1499 | + var ids = await query.Select(p => p.Id).ToListAsync(); | |
| 1500 | + if (ids.Count == 0) | |
| 1501 | + return new { success = true, affected = 0, message = "未命中任何商品" }; | |
| 1502 | + | |
| 1503 | + var hyxz = string.Join(",", finalCardIds); | |
| 1504 | + var affected = await _db.Updateable<WtSpEntity>() | |
| 1505 | + .SetColumns(p => new WtSpEntity { Hyxz = hyxz }) | |
| 1506 | + .Where(p => ids.Contains(p.Id)) | |
| 1507 | + .ExecuteCommandAsync(); | |
| 1508 | + return new { success = true, affected, message = $"批量设置完成,共影响 {affected} 条商品" }; | |
| 1509 | + } | |
| 1510 | + | |
| 1511 | + /// <summary> | |
| 1512 | + /// 按筛选条件批量取消商品关联权益卡(清空 <c>F_Hyxz</c>)。 | |
| 1513 | + /// </summary> | |
| 1514 | + [HttpPost("BatchClearHyxzByFilter")] | |
| 1515 | + public async Task<dynamic> BatchClearHyxzByFilter([FromBody] WtSpBatchHyxzByFilterInput input) | |
| 1516 | + { | |
| 1517 | + var req = input ?? new WtSpBatchHyxzByFilterInput(); | |
| 1518 | + var filter = req.query ?? new WtSpListQueryInput(); | |
| 1519 | + var hasFilter = HasAnySpFilterCondition(filter); | |
| 1520 | + if (!hasFilter && !req.allowAll) | |
| 1521 | + throw NCCException.Oh("未设置筛选条件。若需作用于全部商品,请确认后传 allowAll=true"); | |
| 1522 | + | |
| 1523 | + var query = await BuildSpFilterQueryAsync(filter); | |
| 1524 | + var ids = await query.Select(p => p.Id).ToListAsync(); | |
| 1525 | + if (ids.Count == 0) | |
| 1526 | + return new { success = true, affected = 0, message = "未命中任何商品" }; | |
| 1527 | + | |
| 1528 | + var affected = await _db.Updateable<WtSpEntity>() | |
| 1529 | + .SetColumns(p => new WtSpEntity { Hyxz = "" }) | |
| 1530 | + .Where(p => ids.Contains(p.Id)) | |
| 1531 | + .ExecuteCommandAsync(); | |
| 1532 | + return new { success = true, affected, message = $"批量取消完成,共影响 {affected} 条商品" }; | |
| 1533 | + } | |
| 1534 | + | |
| 1535 | + /// <summary> | |
| 1536 | + /// 按勾选商品批量设置关联权益卡(覆盖写入 <c>F_Hyxz</c>)。 | |
| 1537 | + /// </summary> | |
| 1538 | + [HttpPost("BatchSetHyxz")] | |
| 1539 | + public async Task<dynamic> BatchSetHyxz([FromBody] WtSpBatchHyxzInput input) | |
| 1540 | + { | |
| 1541 | + var req = input ?? new WtSpBatchHyxzInput(); | |
| 1542 | + var ids = (req.ids ?? new List<string>()) | |
| 1543 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 1544 | + .Select(x => x.Trim()) | |
| 1545 | + .Distinct(StringComparer.Ordinal) | |
| 1546 | + .ToList(); | |
| 1547 | + if (ids.Count == 0) | |
| 1548 | + throw NCCException.Oh("请先勾选商品"); | |
| 1549 | + | |
| 1550 | + var cardIds = (req.hyxzCardIds ?? new List<string>()) | |
| 1551 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 1552 | + .Select(x => x.Trim()) | |
| 1553 | + .Distinct(StringComparer.Ordinal) | |
| 1554 | + .ToList(); | |
| 1555 | + if (cardIds.Count == 0) | |
| 1556 | + throw NCCException.Oh("请至少选择一张权益卡"); | |
| 1557 | + | |
| 1558 | + var validCards = await _db.Queryable<WtHyKjqyEntity>() | |
| 1559 | + .Where(c => cardIds.Contains(c.Id)) | |
| 1560 | + .Select(c => c.Id) | |
| 1561 | + .ToListAsync(); | |
| 1562 | + var finalCardIds = validCards | |
| 1563 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 1564 | + .Select(x => x.Trim()) | |
| 1565 | + .Distinct(StringComparer.Ordinal) | |
| 1566 | + .ToList(); | |
| 1567 | + if (finalCardIds.Count == 0) | |
| 1568 | + throw NCCException.Oh("所选权益卡不存在或已失效"); | |
| 1569 | + | |
| 1570 | + var hitIds = await _db.Queryable<WtSpEntity>() | |
| 1571 | + .Where(p => ids.Contains(p.Id)) | |
| 1572 | + .Select(p => p.Id) | |
| 1573 | + .ToListAsync(); | |
| 1574 | + if (hitIds.Count == 0) | |
| 1575 | + return new { success = true, affected = 0, message = "未命中任何商品" }; | |
| 1576 | + | |
| 1577 | + var hyxz = string.Join(",", finalCardIds); | |
| 1578 | + var affected = await _db.Updateable<WtSpEntity>() | |
| 1579 | + .SetColumns(p => new WtSpEntity { Hyxz = hyxz }) | |
| 1580 | + .Where(p => hitIds.Contains(p.Id)) | |
| 1581 | + .ExecuteCommandAsync(); | |
| 1582 | + return new { success = true, affected, message = $"批量设置完成,共影响 {affected} 条商品" }; | |
| 1583 | + } | |
| 1584 | + | |
| 1585 | + /// <summary> | |
| 1586 | + /// 按勾选商品批量取消关联权益卡(清空 <c>F_Hyxz</c>)。 | |
| 1587 | + /// </summary> | |
| 1588 | + [HttpPost("BatchClearHyxz")] | |
| 1589 | + public async Task<dynamic> BatchClearHyxz([FromBody] WtSpBatchHyxzInput input) | |
| 1590 | + { | |
| 1591 | + var req = input ?? new WtSpBatchHyxzInput(); | |
| 1592 | + var ids = (req.ids ?? new List<string>()) | |
| 1593 | + .Where(x => !string.IsNullOrWhiteSpace(x)) | |
| 1594 | + .Select(x => x.Trim()) | |
| 1595 | + .Distinct(StringComparer.Ordinal) | |
| 1596 | + .ToList(); | |
| 1597 | + if (ids.Count == 0) | |
| 1598 | + throw NCCException.Oh("请先勾选商品"); | |
| 1599 | + | |
| 1600 | + var hitIds = await _db.Queryable<WtSpEntity>() | |
| 1601 | + .Where(p => ids.Contains(p.Id)) | |
| 1602 | + .Select(p => p.Id) | |
| 1603 | + .ToListAsync(); | |
| 1604 | + if (hitIds.Count == 0) | |
| 1605 | + return new { success = true, affected = 0, message = "未命中任何商品" }; | |
| 1606 | + | |
| 1607 | + var affected = await _db.Updateable<WtSpEntity>() | |
| 1608 | + .SetColumns(p => new WtSpEntity { Hyxz = "" }) | |
| 1609 | + .Where(p => hitIds.Contains(p.Id)) | |
| 1610 | + .ExecuteCommandAsync(); | |
| 1611 | + return new { success = true, affected, message = $"批量取消完成,共影响 {affected} 条商品" }; | |
| 1612 | + } | |
| 1613 | + | |
| 1614 | + /// <summary> | |
| 1401 | 1615 | /// 更新商品档案 |
| 1402 | 1616 | /// </summary> |
| 1403 | 1617 | /// <param name="id">主键</param> |
| ... | ... | @@ -1767,4 +1981,17 @@ CREATE TABLE `wt_sp_dysku` ( |
| 1767 | 1981 | public string Spbm { get; set; } |
| 1768 | 1982 | public int Quantity { get; set; } |
| 1769 | 1983 | } |
| 1984 | + | |
| 1985 | + public class WtSpBatchHyxzByFilterInput | |
| 1986 | + { | |
| 1987 | + public WtSpListQueryInput query { get; set; } | |
| 1988 | + public List<string> hyxzCardIds { get; set; } | |
| 1989 | + public bool allowAll { get; set; } | |
| 1990 | + } | |
| 1991 | + | |
| 1992 | + public class WtSpBatchHyxzInput | |
| 1993 | + { | |
| 1994 | + public List<string> ids { get; set; } | |
| 1995 | + public List<string> hyxzCardIds { get; set; } | |
| 1996 | + } | |
| 1770 | 1997 | } | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtXsckdService.cs
| ... | ... | @@ -60,6 +60,7 @@ namespace NCC.Extend.WtXsckd |
| 60 | 60 | private static bool _ytwtfhdColumnChecked; |
| 61 | 61 | private static bool _bjsxColumnChecked; |
| 62 | 62 | private static bool _bjhcbColumnChecked; |
| 63 | + private static bool _mxShgzColumnChecked; | |
| 63 | 64 | private static bool _fkmxColumnChecked; |
| 64 | 65 | private static bool _syPchColumnChecked; |
| 65 | 66 | private static bool _yddhColumnChecked; |
| ... | ... | @@ -1140,6 +1141,70 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 1140 | 1141 | } |
| 1141 | 1142 | |
| 1142 | 1143 | /// <summary> |
| 1144 | + /// 确保售后规则快照列存在(销售出库单明细)。 | |
| 1145 | + /// 业务动机:商品档案的售后规则会变(例如平时 1 年质保、大促 2 年),历史单据需保留下单时点的规则,因此需要在明细落库快照。 | |
| 1146 | + /// </summary> | |
| 1147 | + private void EnsureMxShgzColumn() | |
| 1148 | + { | |
| 1149 | + if (_mxShgzColumnChecked) return; | |
| 1150 | + lock (typeof(WtXsckdService)) | |
| 1151 | + { | |
| 1152 | + if (_mxShgzColumnChecked) return; | |
| 1153 | + try | |
| 1154 | + { | |
| 1155 | + if (!_db.DbMaintenance.IsAnyTable("wt_xsckd_mx")) { _mxShgzColumnChecked = true; return; } | |
| 1156 | + var columns = _db.DbMaintenance.GetColumnInfosByTableName("wt_xsckd_mx"); | |
| 1157 | + var columnNames = columns.Select(c => c.DbColumnName.ToLower()).ToHashSet(); | |
| 1158 | + if (!columnNames.Contains("shgz")) | |
| 1159 | + { | |
| 1160 | + _db.Ado.ExecuteCommand("ALTER TABLE `wt_xsckd_mx` ADD COLUMN `shgz` text NULL COMMENT '售后规则快照(出库时从wt_sp.F_Shgz抓取,商品档案后续变更不影响历史)'"); | |
| 1161 | + Console.WriteLine("✅ 已添加字段: wt_xsckd_mx.shgz (售后规则快照)"); | |
| 1162 | + } | |
| 1163 | + } | |
| 1164 | + catch (Exception ex) { Console.WriteLine($"EnsureMxShgzColumn: {ex.Message}"); } | |
| 1165 | + _mxShgzColumnChecked = true; | |
| 1166 | + } | |
| 1167 | + } | |
| 1168 | + | |
| 1169 | + /// <summary> | |
| 1170 | + /// 批量按 <paramref name="mxList"/> 的 <c>Spbh</c> 从 <c>wt_sp.F_Shgz</c> 拉取售后规则,落成明细快照。 | |
| 1171 | + /// 若明细已经带了非空 <c>Shgz</c>(前端收银台下单时可能直接传当时的规则),则保留前端值不被覆盖。 | |
| 1172 | + /// </summary> | |
| 1173 | + private async Task FillMxShgzSnapshotAsync(List<WtXsckdMxEntity> mxList) | |
| 1174 | + { | |
| 1175 | + if (mxList == null || mxList.Count == 0) return; | |
| 1176 | + var needLookup = mxList | |
| 1177 | + .Where(m => m != null && string.IsNullOrWhiteSpace(m.Shgz) && !string.IsNullOrWhiteSpace(m.Spbh)) | |
| 1178 | + .ToList(); | |
| 1179 | + if (needLookup.Count == 0) return; | |
| 1180 | + | |
| 1181 | + var spbhs = needLookup | |
| 1182 | + .Select(m => m.Spbh.Trim()) | |
| 1183 | + .Where(bh => !string.IsNullOrWhiteSpace(bh)) | |
| 1184 | + .Distinct(StringComparer.Ordinal) | |
| 1185 | + .ToList(); | |
| 1186 | + if (spbhs.Count == 0) return; | |
| 1187 | + | |
| 1188 | + var rows = await _db.Queryable<WtSpEntity>() | |
| 1189 | + .Where(s => spbhs.Contains(s.Id)) | |
| 1190 | + .Select(s => new { s.Id, s.Shgz }) | |
| 1191 | + .ToListAsync(); | |
| 1192 | + var dict = rows | |
| 1193 | + .Where(r => !string.IsNullOrWhiteSpace(r.Id)) | |
| 1194 | + .GroupBy(r => r.Id.Trim(), StringComparer.Ordinal) | |
| 1195 | + .ToDictionary(g => g.Key, g => g.First().Shgz, StringComparer.Ordinal); | |
| 1196 | + | |
| 1197 | + foreach (var mx in needLookup) | |
| 1198 | + { | |
| 1199 | + var key = mx.Spbh?.Trim(); | |
| 1200 | + if (!string.IsNullOrEmpty(key) && dict.TryGetValue(key, out var v)) | |
| 1201 | + { | |
| 1202 | + mx.Shgz = v; | |
| 1203 | + } | |
| 1204 | + } | |
| 1205 | + } | |
| 1206 | + | |
| 1207 | + /// <summary> | |
| 1143 | 1208 | /// 从备注中解析原销售出库单号(如:原订单号:CHD202603300001) |
| 1144 | 1209 | /// </summary> |
| 1145 | 1210 | private static string TryParseYcddhFromRemark(string bz) |
| ... | ... | @@ -1501,6 +1566,31 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 1501 | 1566 | } |
| 1502 | 1567 | |
| 1503 | 1568 | /// <summary> |
| 1569 | + /// 委托代销退货单、委托代销结算单:若关联原委托代销发货单,则该原单必须已审核(过账)。 | |
| 1570 | + /// </summary> | |
| 1571 | + private async Task EnsureConsignmentSourceBillApprovedAsync(WtXsckdEntity entity) | |
| 1572 | + { | |
| 1573 | + if (entity == null) return; | |
| 1574 | + var lx = entity.Djlx ?? string.Empty; | |
| 1575 | + if (!lx.Contains("委托代销退货单") && !lx.Contains("委托代销结算单")) return; | |
| 1576 | + | |
| 1577 | + var srcId = entity.YtWtfhd?.Trim(); | |
| 1578 | + if (string.IsNullOrWhiteSpace(srcId)) return; | |
| 1579 | + | |
| 1580 | + var src = await _db.Queryable<WtXsckdEntity>() | |
| 1581 | + .Where(x => x.Id == srcId && x.Djlx == "委托代销发货单") | |
| 1582 | + .Select(x => new { x.Id, x.Djzt }) | |
| 1583 | + .FirstAsync(); | |
| 1584 | + | |
| 1585 | + if (src == null) | |
| 1586 | + throw NCCException.Bah($"原委托代销发货单不存在:{srcId}"); | |
| 1587 | + | |
| 1588 | + var zt = (src.Djzt ?? string.Empty).Trim(); | |
| 1589 | + if (!string.Equals(zt, "已审核", StringComparison.Ordinal) && !string.Equals(zt, "已过账", StringComparison.Ordinal)) | |
| 1590 | + throw NCCException.Bah($"原委托代销发货单【{srcId}】当前状态为「{zt}」,仅已审核(过账)状态允许调原"); | |
| 1591 | + } | |
| 1592 | + | |
| 1593 | + /// <summary> | |
| 1504 | 1594 | /// 退货类主表「入库仓库」:前端常用 cjck 存仓库,成本/序列号逻辑读 rkck,创建/更新时对齐。 |
| 1505 | 1595 | /// </summary> |
| 1506 | 1596 | private static void NormalizeReturnOrderWarehouseFields(WtXsckdEntity entity) |
| ... | ... | @@ -2027,12 +2117,14 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 2027 | 2117 | { |
| 2028 | 2118 | var bh = mxItem.spbh?.Trim(); |
| 2029 | 2119 | var hasPositiveQty = decimal.TryParse(mxItem.sl, out var slVal) && slVal > 0; |
| 2030 | - if (string.IsNullOrEmpty(bh) || noSerialSpbhSet.Contains(bh) || !hasPositiveQty) | |
| 2120 | + // 关键修复:优先返回“单据实际已出库”的序列号,不再被 spxlhType=3 误清空。 | |
| 2121 | + // 历史数据中存在商品档案序列号类型与实际序列号池不一致的情况(例如档案标记为 3,但单据确有 out_djbh 记录)。 | |
| 2122 | + if (!string.IsNullOrEmpty(bh) && serialBySpbh.TryGetValue(bh, out var snList)) | |
| 2123 | + mxItem.selectedSerialNumbers = snList; | |
| 2124 | + else if (string.IsNullOrEmpty(bh) || noSerialSpbhSet.Contains(bh) || !hasPositiveQty) | |
| 2031 | 2125 | { |
| 2032 | 2126 | mxItem.selectedSerialNumbers = new List<string>(); |
| 2033 | 2127 | } |
| 2034 | - else if (serialBySpbh.TryGetValue(bh, out var snList)) | |
| 2035 | - mxItem.selectedSerialNumbers = snList; | |
| 2036 | 2128 | else |
| 2037 | 2129 | mxItem.selectedSerialNumbers = new List<string>(); |
| 2038 | 2130 | } |
| ... | ... | @@ -2305,13 +2397,20 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 2305 | 2397 | } |
| 2306 | 2398 | |
| 2307 | 2399 | /// <summary> |
| 2308 | - /// 批量按明细 <c>spbh</c> 从 <c>wt_sp.F_Shgz</c> 填入售后规则(质保时间等),供前端在收银台及出库单据明细行提示。 | |
| 2400 | + /// 补齐明细售后规则: | |
| 2401 | + /// 1) 优先保留 <c>wt_xsckd_mx.shgz</c> 快照(下单 / 保存时锁定); | |
| 2402 | + /// 2) 仅当明细快照为空(历史旧数据)时,才按 <c>spbh</c> 回退取当前的 <c>wt_sp.F_Shgz</c>。 | |
| 2403 | + /// 这样商品档案后续调整(例如大促期间把 1 年改为 2 年质保)不会污染已下单据的展示。 | |
| 2309 | 2404 | /// </summary> |
| 2310 | 2405 | private async Task EnrichMxShgzAsync(List<WtXsckdMxInfoOutput> mxList) |
| 2311 | 2406 | { |
| 2312 | 2407 | if (mxList == null || mxList.Count == 0) return; |
| 2313 | - var spbhs = mxList | |
| 2314 | - .Where(m => !string.IsNullOrWhiteSpace(m.spbh)) | |
| 2408 | + var needFallback = mxList | |
| 2409 | + .Where(m => m != null && string.IsNullOrWhiteSpace(m.shgz) && !string.IsNullOrWhiteSpace(m.spbh)) | |
| 2410 | + .ToList(); | |
| 2411 | + if (needFallback.Count == 0) return; | |
| 2412 | + | |
| 2413 | + var spbhs = needFallback | |
| 2315 | 2414 | .Select(m => m.spbh.Trim()) |
| 2316 | 2415 | .Distinct(StringComparer.Ordinal) |
| 2317 | 2416 | .ToList(); |
| ... | ... | @@ -2324,7 +2423,7 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 2324 | 2423 | .Where(r => !string.IsNullOrWhiteSpace(r.Id)) |
| 2325 | 2424 | .GroupBy(r => r.Id.Trim(), StringComparer.Ordinal) |
| 2326 | 2425 | .ToDictionary(g => g.Key, g => g.First().Shgz, StringComparer.Ordinal); |
| 2327 | - foreach (var mx in mxList) | |
| 2426 | + foreach (var mx in needFallback) | |
| 2328 | 2427 | { |
| 2329 | 2428 | var bh = mx.spbh?.Trim(); |
| 2330 | 2429 | if (!string.IsNullOrEmpty(bh) && dict.TryGetValue(bh, out var v)) |
| ... | ... | @@ -2852,6 +2951,7 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 2852 | 2951 | entity.Hysjh = input.hysjh ?? ""; |
| 2853 | 2952 | ApplyYcddhForReturnOrder(entity, input); |
| 2854 | 2953 | ApplyYtWtfhdForConsignmentDocs(entity, input); |
| 2954 | + await EnsureConsignmentSourceBillApprovedAsync(entity); | |
| 2855 | 2955 | NormalizeReturnOrderWarehouseFields(entity); |
| 2856 | 2956 | entity.SyPch = NormalizeSyPch(input.sy_pch); |
| 2857 | 2957 | Console.WriteLine($"[Create] 会员手机号码: input.hysjh={input.hysjh}, entity.Hysjh={entity.Hysjh}"); |
| ... | ... | @@ -2884,7 +2984,7 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 2884 | 2984 | .Where(x => (x.Id.StartsWith("CHD") || x.Id.StartsWith("YC") || x.Id.StartsWith("CT") || |
| 2885 | 2985 | x.Id.StartsWith("RK") || x.Id.StartsWith("WF") || x.Id.StartsWith("WT") || |
| 2886 | 2986 | x.Id.StartsWith("WJ") || x.Id.StartsWith("XF") || x.Id.StartsWith("QT") || |
| 2887 | - x.Id.StartsWith("PDD") || x.Id.StartsWith("BSD") || x.Id.StartsWith("TJD") || | |
| 2987 | + x.Id.StartsWith("PDD") || x.Id.StartsWith("BSD") || x.Id.StartsWith("ZSD") || x.Id.StartsWith("TJD") || | |
| 2888 | 2988 | x.Id.StartsWith("BJD") || x.Id.StartsWith("HZD") || x.Id.StartsWith("BYD")) && |
| 2889 | 2989 | x.Id.Contains(today)) // 包含今天的日期 |
| 2890 | 2990 | .Select(x => x.Id) |
| ... | ... | @@ -2941,6 +3041,17 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 2941 | 3041 | { |
| 2942 | 3042 | entity.Djzt = "待审核"; |
| 2943 | 3043 | } |
| 3044 | + // ✅ 赠送单/获赠单:草稿保存 / 正式提交(沿用 wt_xsckd.djzt) | |
| 3045 | + if (IsGiftAuditDocument(input.djlx)) | |
| 3046 | + { | |
| 3047 | + if (input.isDraft || string.Equals(input.djzt, "草稿", StringComparison.Ordinal)) | |
| 3048 | + { | |
| 3049 | + if (string.IsNullOrEmpty(entity.Djzt)) | |
| 3050 | + entity.Djzt = "草稿"; | |
| 3051 | + } | |
| 3052 | + else if (string.IsNullOrEmpty(entity.Djzt)) | |
| 3053 | + entity.Djzt = "待审核"; | |
| 3054 | + } | |
| 2944 | 3055 | await NormalizeSalesOrPresaleReturnJsrToOperatorAsync(entity, userId); |
| 2945 | 3056 | // ✅ 确保会员手机号码被保存(即使为空字符串也要保存) |
| 2946 | 3057 | // 尝试插入主表 |
| ... | ... | @@ -2955,6 +3066,9 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 2955 | 3066 | item.Djbh = newEntity.Id; |
| 2956 | 3067 | item.Djlx = input.djlx; |
| 2957 | 3068 | } |
| 3069 | + // 售后规则快照:没有前端值时按 spbh 从商品档案抓取,锁定当时的质保规则 | |
| 3070 | + EnsureMxShgzColumn(); | |
| 3071 | + await FillMxShgzSnapshotAsync(wtXsckdMxEntityList); | |
| 2958 | 3072 | await _db.Insertable(wtXsckdMxEntityList).ExecuteCommandAsync(); |
| 2959 | 3073 | } |
| 2960 | 3074 | |
| ... | ... | @@ -3541,6 +3655,26 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 3541 | 3655 | var entitys = await _db.Queryable<WtXsckdEntity>().In(it => it.Id, ids).ToListAsync(); |
| 3542 | 3656 | foreach (var entity in entitys) |
| 3543 | 3657 | { |
| 3658 | + if (string.Equals(entity.Djlx, "采购入库单", StringComparison.Ordinal)) | |
| 3659 | + { | |
| 3660 | + var z = entity.Djzt?.Trim() ?? ""; | |
| 3661 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal)) | |
| 3662 | + throw NCCException.Bah($"采购入库单 {entity.Id} 在「{entity.Djzt}」状态下不可删除,仅草稿状态可删除"); | |
| 3663 | + } | |
| 3664 | + if (string.Equals(entity.Djlx, "采购退货单", StringComparison.Ordinal)) | |
| 3665 | + { | |
| 3666 | + var z = entity.Djzt?.Trim() ?? ""; | |
| 3667 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal)) | |
| 3668 | + throw NCCException.Bah($"采购退货单 {entity.Id} 在「{entity.Djzt}」状态下不可删除,仅草稿状态可删除"); | |
| 3669 | + } | |
| 3670 | + if (IsGiftAuditDocument(entity.Djlx)) | |
| 3671 | + { | |
| 3672 | + var z = entity.Djzt?.Trim() ?? ""; | |
| 3673 | + if (string.Equals(z, "待审核", StringComparison.Ordinal) | |
| 3674 | + || string.Equals(z, "已审核", StringComparison.Ordinal) | |
| 3675 | + || z == "一级已审" || z == "一级已审/待二级" || z == "待二级") | |
| 3676 | + throw NCCException.Bah($"赠送/获赠单 {entity.Id} 在「{entity.Djzt}」状态下不可删除,请先反审回草稿或处理为审核不通过后再删"); | |
| 3677 | + } | |
| 3544 | 3678 | var thCsv = await GetLinkedReturnBillIdsCsvAsync(entity.Id, entity.Djlx); |
| 3545 | 3679 | if (!string.IsNullOrEmpty(thCsv)) |
| 3546 | 3680 | throw NCCException.Bah($"单据 {entity.Id} 已存在关联退货单({thCsv}),无法删除"); |
| ... | ... | @@ -3619,7 +3753,12 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 3619 | 3753 | var oldMxList = await _db.Queryable<WtXsckdMxEntity>().Where(u => u.Djbh == id).ToListAsync(); |
| 3620 | 3754 | await EnsureXsckdWarehousesNotLockedForUpdateAsync(oldHeader, input.cjck, input.rkck); |
| 3621 | 3755 | if (IsSalesOutboundSkipErpAudit(oldHeader)) |
| 3622 | - throw NCCException.Bah("非后台来源的销售出库单不允许在此修改"); | |
| 3756 | + { | |
| 3757 | + var z = oldHeader.Djzt?.Trim() ?? ""; | |
| 3758 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal) | |
| 3759 | + && !string.Equals(z, "审核不通过", StringComparison.Ordinal)) | |
| 3760 | + throw NCCException.Bah($"来源为「{oldHeader.Ly ?? "非后台"}」的销售出库单当前状态为「{oldHeader.Djzt}」,仅草稿或审核不通过时可编辑"); | |
| 3761 | + } | |
| 3623 | 3762 | if (string.Equals(oldHeader.Djlx, "同价调拨单", StringComparison.Ordinal)) |
| 3624 | 3763 | { |
| 3625 | 3764 | var z = oldHeader.Djzt?.Trim() ?? ""; |
| ... | ... | @@ -3630,10 +3769,21 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 3630 | 3769 | if (string.Equals(oldHeader.Djlx, "采购入库单", StringComparison.Ordinal)) |
| 3631 | 3770 | { |
| 3632 | 3771 | var z = oldHeader.Djzt?.Trim() ?? ""; |
| 3633 | - if (!string.IsNullOrEmpty(z) | |
| 3634 | - && !string.Equals(z, "草稿", StringComparison.Ordinal) | |
| 3772 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal)) | |
| 3773 | + throw NCCException.Bah($"采购入库单当前状态为「{oldHeader.Djzt}」,仅草稿状态可编辑"); | |
| 3774 | + } | |
| 3775 | + if (string.Equals(oldHeader.Djlx, "采购退货单", StringComparison.Ordinal)) | |
| 3776 | + { | |
| 3777 | + var z = oldHeader.Djzt?.Trim() ?? ""; | |
| 3778 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal)) | |
| 3779 | + throw NCCException.Bah($"采购退货单当前状态为「{oldHeader.Djzt}」,仅草稿状态可编辑"); | |
| 3780 | + } | |
| 3781 | + if (IsGiftAuditDocument(oldHeader.Djlx)) | |
| 3782 | + { | |
| 3783 | + var z = oldHeader.Djzt?.Trim() ?? ""; | |
| 3784 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal) | |
| 3635 | 3785 | && !string.Equals(z, "审核不通过", StringComparison.Ordinal)) |
| 3636 | - throw NCCException.Bah($"采购入库单当前状态为「{oldHeader.Djzt}」,仅草稿或审核不通过时可编辑"); | |
| 3786 | + throw NCCException.Bah($"赠送/获赠单当前状态为「{oldHeader.Djzt}」,仅草稿或审核不通过时可编辑"); | |
| 3637 | 3787 | } |
| 3638 | 3788 | var entity = input.Adapt<WtXsckdEntity>(); |
| 3639 | 3789 | entity.Bjsx = input.bjsx; |
| ... | ... | @@ -3645,6 +3795,7 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 3645 | 3795 | entity.Dyddh = input.dyddh; |
| 3646 | 3796 | ApplyYcddhForReturnOrder(entity, input); |
| 3647 | 3797 | ApplyYtWtfhdForConsignmentDocs(entity, input); |
| 3798 | + await EnsureConsignmentSourceBillApprovedAsync(entity); | |
| 3648 | 3799 | NormalizeReturnOrderWarehouseFields(entity); |
| 3649 | 3800 | if (input.sy_pch != null) |
| 3650 | 3801 | entity.SyPch = string.IsNullOrWhiteSpace(input.sy_pch) ? null : NormalizeSyPch(input.sy_pch); |
| ... | ... | @@ -3748,6 +3899,9 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 3748 | 3899 | item.Djbh = id; |
| 3749 | 3900 | item.Djlx = entity.Djlx; |
| 3750 | 3901 | } |
| 3902 | + // 售后规则快照:没有前端值时按 spbh 从商品档案抓取,锁定当时的质保规则 | |
| 3903 | + EnsureMxShgzColumn(); | |
| 3904 | + await FillMxShgzSnapshotAsync(wtXsckdMxEntityList); | |
| 3751 | 3905 | await _db.Insertable(wtXsckdMxEntityList).ExecuteCommandAsync(); |
| 3752 | 3906 | } |
| 3753 | 3907 | |
| ... | ... | @@ -3811,6 +3965,26 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 3811 | 3965 | { |
| 3812 | 3966 | var entity = await _db.Queryable<WtXsckdEntity>().FirstAsync(p => p.Id == id); |
| 3813 | 3967 | _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); |
| 3968 | + if (string.Equals(entity.Djlx, "采购入库单", StringComparison.Ordinal)) | |
| 3969 | + { | |
| 3970 | + var z = entity.Djzt?.Trim() ?? ""; | |
| 3971 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal)) | |
| 3972 | + throw NCCException.Bah($"采购入库单在「{entity.Djzt}」状态下不可删除,仅草稿状态可删除"); | |
| 3973 | + } | |
| 3974 | + if (string.Equals(entity.Djlx, "采购退货单", StringComparison.Ordinal)) | |
| 3975 | + { | |
| 3976 | + var z = entity.Djzt?.Trim() ?? ""; | |
| 3977 | + if (!string.Equals(z, "草稿", StringComparison.Ordinal)) | |
| 3978 | + throw NCCException.Bah($"采购退货单在「{entity.Djzt}」状态下不可删除,仅草稿状态可删除"); | |
| 3979 | + } | |
| 3980 | + if (IsGiftAuditDocument(entity.Djlx)) | |
| 3981 | + { | |
| 3982 | + var z = entity.Djzt?.Trim() ?? ""; | |
| 3983 | + if (string.Equals(z, "待审核", StringComparison.Ordinal) | |
| 3984 | + || string.Equals(z, "已审核", StringComparison.Ordinal) | |
| 3985 | + || z == "一级已审" || z == "一级已审/待二级" || z == "待二级") | |
| 3986 | + throw NCCException.Bah($"赠送/获赠单在「{entity.Djzt}」状态下不可删除,请先反审回草稿或处理为审核不通过后再删"); | |
| 3987 | + } | |
| 3814 | 3988 | await EnsureXsckdWarehousesNotLockedForHeaderAsync(entity); |
| 3815 | 3989 | var thCsv = await GetLinkedReturnBillIdsCsvAsync(entity.Id, entity.Djlx); |
| 3816 | 3990 | if (!string.IsNullOrEmpty(thCsv)) |
| ... | ... | @@ -4096,8 +4270,20 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 4096 | 4270 | ["销售"] = "t.`销售`", |
| 4097 | 4271 | ["salesAmount"] = "t.`销售金额`", |
| 4098 | 4272 | ["销售金额"] = "t.`销售金额`", |
| 4273 | + ["unitPrice"] = "t.`单价`", | |
| 4274 | + ["单价"] = "t.`单价`", | |
| 4275 | + ["costUnitPrice"] = "t.`成本单价`", | |
| 4276 | + ["成本单价"] = "t.`成本单价`", | |
| 4277 | + ["costAmount"] = "t.`成本金额`", | |
| 4278 | + ["成本金额"] = "t.`成本金额`", | |
| 4279 | + ["preSalesAmount"] = "t.`折前销售金额`", | |
| 4280 | + ["折前销售金额"] = "t.`折前销售金额`", | |
| 4099 | 4281 | ["grossProfit"] = "t.`毛利`", |
| 4100 | - ["毛利"] = "t.`毛利`" | |
| 4282 | + ["毛利"] = "t.`毛利`", | |
| 4283 | + ["grossMarginPct"] = "t.`毛利率(%)`", | |
| 4284 | + ["毛利率(%)"] = "t.`毛利率(%)`", | |
| 4285 | + ["zeroPriceQty"] = "t.`0单价数量`", | |
| 4286 | + ["0单价数量"] = "t.`0单价数量`" | |
| 4101 | 4287 | }; |
| 4102 | 4288 | var orderCol = "t.`销售金额`"; |
| 4103 | 4289 | var orderDir = "DESC"; |
| ... | ... | @@ -4121,8 +4307,20 @@ WHERE d.djlx IN ('销售退货单','预售退货单','委托代销退货单') |
| 4121 | 4307 | ["销售"] = "t.`销售`", |
| 4122 | 4308 | ["salesAmount"] = "t.`销售金额`", |
| 4123 | 4309 | ["销售金额"] = "t.`销售金额`", |
| 4310 | + ["unitPrice"] = "t.`单价`", | |
| 4311 | + ["单价"] = "t.`单价`", | |
| 4312 | + ["costUnitPrice"] = "t.`成本单价`", | |
| 4313 | + ["成本单价"] = "t.`成本单价`", | |
| 4314 | + ["costAmount"] = "t.`成本金额`", | |
| 4315 | + ["成本金额"] = "t.`成本金额`", | |
| 4316 | + ["preSalesAmount"] = "t.`折前销售金额`", | |
| 4317 | + ["折前销售金额"] = "t.`折前销售金额`", | |
| 4124 | 4318 | ["grossProfit"] = "t.`毛利`", |
| 4125 | - ["毛利"] = "t.`毛利`" | |
| 4319 | + ["毛利"] = "t.`毛利`", | |
| 4320 | + ["grossMarginPct"] = "t.`毛利率(%)`", | |
| 4321 | + ["毛利率(%)"] = "t.`毛利率(%)`", | |
| 4322 | + ["zeroPriceQty"] = "t.`0单价数量`", | |
| 4323 | + ["0单价数量"] = "t.`0单价数量`" | |
| 4126 | 4324 | }; |
| 4127 | 4325 | var orderColB = "t.`销售金额`"; |
| 4128 | 4326 | var orderDirB = "DESC"; |
| ... | ... | @@ -4308,6 +4506,7 @@ ORDER BY {orderBy}"; |
| 4308 | 4506 | [FromQuery] string groupLevel, |
| 4309 | 4507 | [FromQuery] string categoryId = null, |
| 4310 | 4508 | [FromQuery] string brandId = null, |
| 4509 | + [FromQuery] bool includeAllProducts = false, | |
| 4311 | 4510 | string productName = null, |
| 4312 | 4511 | string settleUnit = null, |
| 4313 | 4512 | string contactUnit = null, |
| ... | ... | @@ -4324,7 +4523,8 @@ ORDER BY {orderBy}"; |
| 4324 | 4523 | if (gl != "category" && gl != "brand" && gl != "product") |
| 4325 | 4524 | throw NCCException.Oh("groupLevel 须为 category、brand 或 product"); |
| 4326 | 4525 | |
| 4327 | - if ((gl == "brand" || gl == "product") && string.IsNullOrWhiteSpace(categoryId)) | |
| 4526 | + var hasCategoryIdQuery = App.HttpContext?.Request?.Query.ContainsKey("categoryId") == true; | |
| 4527 | + if ((gl == "brand" || gl == "product") && !hasCategoryIdQuery) | |
| 4328 | 4528 | throw NCCException.Oh("brand / product 层级须提供 categoryId"); |
| 4329 | 4529 | |
| 4330 | 4530 | List<string> extras = null; |
| ... | ... | @@ -4334,15 +4534,15 @@ ORDER BY {orderBy}"; |
| 4334 | 4534 | extras = new List<string>(); |
| 4335 | 4535 | extraParams = new List<SugarParameter> |
| 4336 | 4536 | { |
| 4337 | - new SugarParameter("@hierCat", categoryId.Trim()) | |
| 4537 | + new SugarParameter("@hierCat", (categoryId ?? string.Empty).Trim()) | |
| 4338 | 4538 | }; |
| 4339 | 4539 | if (!string.IsNullOrWhiteSpace(brandId)) |
| 4340 | 4540 | { |
| 4341 | - extras.Add("mx.spbh IN (SELECT s2.F_Id FROM wt_sp s2 WHERE s2.F_Pl = @hierCat AND s2.F_Pp = @hierBrand)"); | |
| 4541 | + extras.Add("mx.spbh IN (SELECT s2.F_Id FROM wt_sp s2 WHERE IFNULL(s2.F_Pl,'') = @hierCat AND s2.F_Pp = @hierBrand)"); | |
| 4342 | 4542 | extraParams.Add(new SugarParameter("@hierBrand", brandId.Trim())); |
| 4343 | 4543 | } |
| 4344 | 4544 | else |
| 4345 | - extras.Add("mx.spbh IN (SELECT s2.F_Id FROM wt_sp s2 WHERE s2.F_Pl = @hierCat)"); | |
| 4545 | + extras.Add("mx.spbh IN (SELECT s2.F_Id FROM wt_sp s2 WHERE IFNULL(s2.F_Pl,'') = @hierCat)"); | |
| 4346 | 4546 | } |
| 4347 | 4547 | |
| 4348 | 4548 | var (whereSql, paramList, posInSql) = BuildProductSummaryWhere( |
| ... | ... | @@ -4367,6 +4567,106 @@ ORDER BY {orderBy}"; |
| 4367 | 4567 | |
| 4368 | 4568 | if (gl == "product") |
| 4369 | 4569 | { |
| 4570 | + if (includeAllProducts) | |
| 4571 | + { | |
| 4572 | + var whereSqlP = whereSql; | |
| 4573 | + var paramListP = paramList; | |
| 4574 | + var salesAggSql = $@" | |
| 4575 | +SELECT | |
| 4576 | + mx.spbh AS spbh, | |
| 4577 | + MAX(mx.spmc) AS spmc, | |
| 4578 | + MAX(mx.dw) AS dw, | |
| 4579 | + {sumNetSl} AS salesQty, | |
| 4580 | + {sumNetJe} AS salesAmount, | |
| 4581 | + {sumNetLineCost} AS costAmount, | |
| 4582 | + {sumPreSl} AS preSalesAmount, | |
| 4583 | + {sumZeroSl} AS zeroPriceQty | |
| 4584 | +FROM wt_xsckd_mx mx | |
| 4585 | +INNER JOIN wt_xsckd d ON d.F_Id = mx.djbh | |
| 4586 | +LEFT JOIN wt_ck ck ON ck.F_Id = COALESCE(NULLIF(TRIM(mx.ckck), ''), NULLIF(TRIM(d.cjck), '')) | |
| 4587 | +LEFT JOIN wt_sp s ON s.F_Id = mx.spbh | |
| 4588 | +LEFT JOIN wt_pl pl ON pl.F_Id = s.F_Pl | |
| 4589 | +LEFT JOIN wt_pp pp ON pp.F_Id = s.F_Pp | |
| 4590 | +{whereSqlP} | |
| 4591 | +GROUP BY mx.spbh"; | |
| 4592 | + | |
| 4593 | + var productFilterSql = ""; | |
| 4594 | + if (!string.IsNullOrWhiteSpace(productName)) | |
| 4595 | + { | |
| 4596 | + productFilterSql = " AND (s.F_Spmc LIKE @hierProductName OR s.F_Spbm LIKE @hierProductName)"; | |
| 4597 | + paramListP.Add(new SugarParameter("@hierProductName", $"%{productName.Trim()}%")); | |
| 4598 | + } | |
| 4599 | + | |
| 4600 | + var categoryFilterSql = "IFNULL(s.F_Pl,'') = @hierCat"; | |
| 4601 | + if (!string.IsNullOrWhiteSpace(brandId)) | |
| 4602 | + categoryFilterSql += " AND s.F_Pp = @hierBrand"; | |
| 4603 | + | |
| 4604 | + var productOuterSql = $@" | |
| 4605 | + SELECT | |
| 4606 | + s.F_Id AS `商品编号`, | |
| 4607 | + s.F_Spmc AS `商品名称`, | |
| 4608 | + IFNULL(NULLIF(TRIM(pl.F_Plmc), ''), '无') AS `分类名称`, | |
| 4609 | + IFNULL(NULLIF(TRIM(pp.F_Ppmc), ''), '无') AS `品牌名称`, | |
| 4610 | + IFNULL(sa.salesQty, 0) AS `销售`, | |
| 4611 | + IFNULL(sa.dw, '') AS `单位`, | |
| 4612 | + CASE WHEN IFNULL(sa.salesQty, 0) = 0 THEN 0 ELSE IFNULL(sa.salesAmount, 0) / IFNULL(sa.salesQty, 1) END AS `单价`, | |
| 4613 | + IFNULL(sa.salesAmount, 0) AS `销售金额`, | |
| 4614 | + CASE WHEN IFNULL(sa.salesQty, 0) = 0 THEN 0 ELSE IFNULL(sa.costAmount, 0) / IFNULL(sa.salesQty, 1) END AS `成本单价`, | |
| 4615 | + IFNULL(sa.costAmount, 0) AS `成本金额`, | |
| 4616 | + IFNULL(sa.preSalesAmount, 0) AS `折前销售金额`, | |
| 4617 | + IFNULL(sa.salesAmount, 0) * 1.13 AS `价税合计`, | |
| 4618 | + IFNULL(sa.salesAmount, 0) * 0.13 AS `税额`, | |
| 4619 | + 0 AS `费用分摊金额`, | |
| 4620 | + (IFNULL(sa.salesAmount, 0) - IFNULL(sa.costAmount, 0)) AS `毛利`, | |
| 4621 | + CASE WHEN IFNULL(sa.salesAmount, 0) = 0 THEN 0 ELSE (IFNULL(sa.salesAmount, 0) - IFNULL(sa.costAmount, 0)) / IFNULL(sa.salesAmount, 1) * 100 END AS `毛利率(%)`, | |
| 4622 | + IFNULL(sa.zeroPriceQty, 0) AS `0单价数量`, | |
| 4623 | + (IFNULL(sa.salesAmount, 0) - IFNULL(sa.costAmount, 0)) AS `平均利润` | |
| 4624 | + FROM wt_sp s | |
| 4625 | + LEFT JOIN wt_pl pl ON pl.F_Id = s.F_Pl | |
| 4626 | + LEFT JOIN wt_pp pp ON pp.F_Id = s.F_Pp | |
| 4627 | + LEFT JOIN ({salesAggSql}) sa ON sa.spbh = s.F_Id | |
| 4628 | + WHERE {categoryFilterSql}{productFilterSql}"; | |
| 4629 | + | |
| 4630 | + if (!paramListP.Any(p => p.ParameterName == "@hierCat")) | |
| 4631 | + paramListP.Add(new SugarParameter("@hierCat", (categoryId ?? string.Empty).Trim())); | |
| 4632 | + if (!string.IsNullOrWhiteSpace(brandId) && !paramListP.Any(p => p.ParameterName == "@hierBrand")) | |
| 4633 | + paramListP.Add(new SugarParameter("@hierBrand", brandId.Trim())); | |
| 4634 | + | |
| 4635 | + var orderByAll = BuildProductSummaryOrderBy(sortField, sortOrder, "product"); | |
| 4636 | + var sqlAll = $@"SELECT | |
| 4637 | + t.`商品编号`, | |
| 4638 | + t.`商品名称`, | |
| 4639 | + t.`分类名称`, | |
| 4640 | + t.`品牌名称`, | |
| 4641 | + t.`销售`, | |
| 4642 | + t.`单位`, | |
| 4643 | + t.`单价`, | |
| 4644 | + t.`销售金额`, | |
| 4645 | + t.`成本单价`, | |
| 4646 | + t.`成本金额`, | |
| 4647 | + t.`折前销售金额`, | |
| 4648 | + t.`价税合计`, | |
| 4649 | + t.`税额`, | |
| 4650 | + t.`费用分摊金额`, | |
| 4651 | + t.`毛利`, | |
| 4652 | + t.`毛利率(%)`, | |
| 4653 | + t.`0单价数量`, | |
| 4654 | + t.`平均利润`, | |
| 4655 | + CASE WHEN agg.totalSalesQty = 0 THEN 0 ELSE t.`销售` / agg.totalSalesQty * 100 END AS `销售权重(%)`, | |
| 4656 | + CASE WHEN agg.totalSalesAmount = 0 THEN 0 ELSE t.`销售金额` / agg.totalSalesAmount * 100 END AS `金额权重(%)`, | |
| 4657 | + CASE WHEN agg.totalProfit = 0 THEN 0 ELSE t.`毛利` / agg.totalProfit * 100 END AS `利润权重(%)` | |
| 4658 | +FROM ({productOuterSql}) t | |
| 4659 | +CROSS JOIN ( | |
| 4660 | + SELECT | |
| 4661 | + SUM(t2.`销售`) AS totalSalesQty, | |
| 4662 | + SUM(t2.`销售金额`) AS totalSalesAmount, | |
| 4663 | + SUM(t2.`毛利`) AS totalProfit | |
| 4664 | + FROM ({productOuterSql}) t2 | |
| 4665 | +) agg | |
| 4666 | +ORDER BY {orderByAll}"; | |
| 4667 | + return Task.FromResult<dynamic>(_db.Ado.GetDataTable(sqlAll, paramListP)); | |
| 4668 | + } | |
| 4669 | + | |
| 4370 | 4670 | var fromSql = $@" |
| 4371 | 4671 | FROM wt_xsckd_mx mx |
| 4372 | 4672 | INNER JOIN wt_xsckd d ON d.F_Id = mx.djbh |
| ... | ... | @@ -4470,8 +4770,8 @@ LEFT JOIN wt_pp pp ON pp.F_Id = s.F_Pp |
| 4470 | 4770 | } |
| 4471 | 4771 | |
| 4472 | 4772 | // brand |
| 4473 | - var brandExtras = new List<string> { "s.F_Pl = @hierBrandCat" }; | |
| 4474 | - var brandParams = new List<SugarParameter> { new SugarParameter("@hierBrandCat", categoryId.Trim()) }; | |
| 4773 | + var brandExtras = new List<string> { "IFNULL(s.F_Pl,'') = @hierBrandCat" }; | |
| 4774 | + var brandParams = new List<SugarParameter> { new SugarParameter("@hierBrandCat", (categoryId ?? string.Empty).Trim()) }; | |
| 4475 | 4775 | var (whereSqlB, paramListB, posInSqlB) = BuildProductSummaryWhere( |
| 4476 | 4776 | productName, settleUnit, contactUnit, agent, warehouse, billType, startDate, endDate, |
| 4477 | 4777 | brandExtras, |
| ... | ... | @@ -4870,7 +5170,30 @@ ORDER BY SUM(CAST(c.sl AS DECIMAL(18,4)) * c.cbj) DESC"; |
| 4870 | 5170 | var pid = productId.Trim(); |
| 4871 | 5171 | var wid = warehouseId.Trim(); |
| 4872 | 5172 | |
| 5173 | + // 兼容前端传入「仓库/门店名称」(详情接口会把 ckck 替换为展示名称,编辑页无法反查时会直接带名称过来) | |
| 5174 | + // 先尝试按 ID 解析;若按 ID 解析不出仓库集合,再按名称(Mdmc) 反查 ID 再解析一次 | |
| 4873 | 5175 | var warehouseIds = await ResolveWarehouseIdsForSpCostFromStoreOrCkAsync(wid); |
| 5176 | + if (warehouseIds.Count == 0) | |
| 5177 | + { | |
| 5178 | + string resolvedId = null; | |
| 5179 | + var mdByName = await _db.Queryable<WtMdEntity>() | |
| 5180 | + .Where(m => m.Mdmc == wid) | |
| 5181 | + .Select(m => m.Id) | |
| 5182 | + .FirstAsync(); | |
| 5183 | + if (!string.IsNullOrWhiteSpace(mdByName)) resolvedId = mdByName; | |
| 5184 | + if (string.IsNullOrWhiteSpace(resolvedId)) | |
| 5185 | + { | |
| 5186 | + var ckByName = await _db.Queryable<WtCkEntity>() | |
| 5187 | + .Where(c => c.Mdmc == wid) | |
| 5188 | + .Select(c => c.Id) | |
| 5189 | + .FirstAsync(); | |
| 5190 | + if (!string.IsNullOrWhiteSpace(ckByName)) resolvedId = ckByName; | |
| 5191 | + } | |
| 5192 | + if (!string.IsNullOrWhiteSpace(resolvedId)) | |
| 5193 | + { | |
| 5194 | + warehouseIds = await ResolveWarehouseIdsForSpCostFromStoreOrCkAsync(resolvedId); | |
| 5195 | + } | |
| 5196 | + } | |
| 4874 | 5197 | if (warehouseIds.Count == 0) warehouseIds = new List<string> { wid }; |
| 4875 | 5198 | |
| 4876 | 5199 | var whSet = new HashSet<string>( |
| ... | ... | @@ -4962,6 +5285,49 @@ ORDER BY SUM(CAST(c.sl AS DECIMAL(18,4)) * c.cbj) DESC"; |
| 4962 | 5285 | } |
| 4963 | 5286 | } |
| 4964 | 5287 | |
| 5288 | + /// <summary> | |
| 5289 | + /// 获赠单成本参考:按 <c>wt_sp_cost</c> 在指定门店/仓库范围内汇总在库数量,仅当在库数量合计 > 0 时返回加权平均成本单价(Σ(sl×cbj)/Σ(sl));无在库则 <c>data</c> 为 null,由用户手工录入。 | |
| 5290 | + /// </summary> | |
| 5291 | + /// <param name="spbh">商品主键 <c>wt_sp.F_Id</c></param> | |
| 5292 | + /// <param name="ckOrMdId">入库仓库或门店主键(与 <see cref="GetStockQuantity"/> 展开规则一致)</param> | |
| 5293 | + /// <returns>success、data(可空)、qty(在库数量合计)、msg</returns> | |
| 5294 | + [HttpGet("GetWarehouseWeightedAvgCost")] | |
| 5295 | + public async Task<dynamic> GetWarehouseWeightedAvgCost(string spbh, string ckOrMdId) | |
| 5296 | + { | |
| 5297 | + if (string.IsNullOrWhiteSpace(spbh) || string.IsNullOrWhiteSpace(ckOrMdId)) | |
| 5298 | + return new { success = false, msg = "商品编号与仓库不能为空", data = (decimal?)null, qty = 0m }; | |
| 5299 | + | |
| 5300 | + try | |
| 5301 | + { | |
| 5302 | + var pid = spbh.Trim(); | |
| 5303 | + var warehouseIds = await ResolveWarehouseIdsForSpCostFromStoreOrCkAsync(ckOrMdId); | |
| 5304 | + if (warehouseIds.Count == 0) warehouseIds = new List<string> { ckOrMdId.Trim() }; | |
| 5305 | + var whSet = new HashSet<string>( | |
| 5306 | + warehouseIds.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()), | |
| 5307 | + StringComparer.OrdinalIgnoreCase); | |
| 5308 | + | |
| 5309 | + var rows = await _db.Queryable<WtSpCostEntity>() | |
| 5310 | + .Where(c => c.Spbh == pid && whSet.Contains(c.Ck)) | |
| 5311 | + .Select(c => new { c.Sl, c.Cbj }) | |
| 5312 | + .ToListAsync(); | |
| 5313 | + | |
| 5314 | + var totalQty = rows.Sum(c => (decimal)c.Sl); | |
| 5315 | + if (totalQty <= 0) | |
| 5316 | + return new { success = true, data = (decimal?)null, qty = totalQty, msg = "无在库数量,请手动填写成本" }; | |
| 5317 | + | |
| 5318 | + var totalAmt = rows.Sum(c => (decimal)c.Sl * c.Cbj); | |
| 5319 | + var avg = Math.Round(totalAmt / totalQty, 4); | |
| 5320 | + if (avg <= 0) | |
| 5321 | + return new { success = true, data = (decimal?)null, qty = totalQty, msg = "在库但加权成本为0,请手动填写成本" }; | |
| 5322 | + | |
| 5323 | + return new { success = true, data = avg, qty = totalQty, msg = "操作成功" }; | |
| 5324 | + } | |
| 5325 | + catch (Exception ex) | |
| 5326 | + { | |
| 5327 | + return new { success = false, msg = "查询失败: " + ex.Message, data = (decimal?)null, qty = 0m }; | |
| 5328 | + } | |
| 5329 | + } | |
| 5330 | + | |
| 4965 | 5331 | private const string PurchaseSummaryJoinFromSql = @" |
| 4966 | 5332 | FROM wt_xsckd_mx mx |
| 4967 | 5333 | INNER JOIN wt_xsckd d ON d.F_Id = mx.djbh |
| ... | ... | @@ -6075,6 +6441,25 @@ LIMIT {offset}, {pageSize}"; |
| 6075 | 6441 | } |
| 6076 | 6442 | |
| 6077 | 6443 | /// <summary> |
| 6444 | + /// 是否赠送单(仅赠送单)。 | |
| 6445 | + /// </summary> | |
| 6446 | + private static bool IsZsdDocument(string? djlx) => | |
| 6447 | + !string.IsNullOrEmpty(djlx) | |
| 6448 | + && djlx.Contains("赠送单", StringComparison.Ordinal); | |
| 6449 | + | |
| 6450 | + /// <summary> | |
| 6451 | + /// 是否获赠单(仅获赠单)。 | |
| 6452 | + /// </summary> | |
| 6453 | + private static bool IsHzdDocument(string? djlx) => | |
| 6454 | + !string.IsNullOrEmpty(djlx) | |
| 6455 | + && djlx.Contains("获赠单", StringComparison.Ordinal); | |
| 6456 | + | |
| 6457 | + /// <summary> | |
| 6458 | + /// 是否纳入草稿/审核/反审编辑管控的赠送相关单据(赠送单、获赠单)。 | |
| 6459 | + /// </summary> | |
| 6460 | + private static bool IsGiftAuditDocument(string? djlx) => IsZsdDocument(djlx) || IsHzdDocument(djlx); | |
| 6461 | + | |
| 6462 | + /// <summary> | |
| 6078 | 6463 | /// 将审批备注追加写入 spbz(不写业务备注 bz) |
| 6079 | 6464 | /// </summary> |
| 6080 | 6465 | [NonAction] |
| ... | ... | @@ -6120,12 +6505,6 @@ LIMIT {offset}, {pageSize}"; |
| 6120 | 6505 | return new { success = false, message = $"该单据不是{expectedDjlx},无法审核" }; |
| 6121 | 6506 | } |
| 6122 | 6507 | |
| 6123 | - if (entity.Djlx == "销售出库单" && IsSalesOutboundSkipErpAudit(entity)) | |
| 6124 | - { | |
| 6125 | - _db.RollbackTran(); | |
| 6126 | - return new { success = false, message = "非后台来源的销售出库单无需在此审核" }; | |
| 6127 | - } | |
| 6128 | - | |
| 6129 | 6508 | var actualDjlx = entity.Djlx; |
| 6130 | 6509 | |
| 6131 | 6510 | if (entity.Djzt == "已审核") |
| ... | ... | @@ -6170,7 +6549,11 @@ LIMIT {offset}, {pageSize}"; |
| 6170 | 6549 | bool hasTwoLevel = !string.IsNullOrWhiteSpace(lvl2.users) || !string.IsNullOrWhiteSpace(lvl2.roles); |
| 6171 | 6550 | bool forceTwoLevelForSamePriceTransfer = entity.Djlx == "同价调拨单"; |
| 6172 | 6551 | |
| 6173 | - if (entity.Djzt == "待审核" || string.IsNullOrEmpty(entity.Djzt)) | |
| 6552 | + // 赠送单/获赠单:仅「待审核」可进一级审核(与历史单兼容:其它类型仍允许空状态等同待审) | |
| 6553 | + var canEnterLevel1 = string.Equals(entity.Djzt, "待审核", StringComparison.Ordinal) | |
| 6554 | + || (string.IsNullOrEmpty(entity.Djzt) && !IsGiftAuditDocument(entity.Djlx)); | |
| 6555 | + | |
| 6556 | + if (canEnterLevel1) | |
| 6174 | 6557 | { |
| 6175 | 6558 | // 一级审核 |
| 6176 | 6559 | if (entity.Djlx == "同价调拨单") |
| ... | ... | @@ -6373,9 +6756,6 @@ LIMIT {offset}, {pageSize}"; |
| 6373 | 6756 | [HttpPost("ApproveSalesOutbound/{id}")] |
| 6374 | 6757 | public async Task<dynamic> ApproveSalesOutbound(string id, [FromBody] WtApprovalRemarkInput input) |
| 6375 | 6758 | { |
| 6376 | - var header = await _db.Queryable<WtXsckdEntity>().FirstAsync(p => p.Id == id); | |
| 6377 | - if (header != null && IsSalesOutboundSkipErpAudit(header)) | |
| 6378 | - return new { success = false, message = "非后台来源的销售出库单无需在此审核" }; | |
| 6379 | 6759 | return await ApproveDocument(id, "销售出库单", input?.remark); |
| 6380 | 6760 | } |
| 6381 | 6761 | |
| ... | ... | @@ -6437,9 +6817,6 @@ LIMIT {offset}, {pageSize}"; |
| 6437 | 6817 | if (!string.IsNullOrEmpty(expectedDjlx) && entity.Djlx != expectedDjlx) |
| 6438 | 6818 | return new { success = false, message = $"该单据不是{expectedDjlx},无法操作" }; |
| 6439 | 6819 | |
| 6440 | - if (entity.Djlx == "销售出库单" && IsSalesOutboundSkipErpAudit(entity)) | |
| 6441 | - return new { success = false, message = "非后台来源的销售出库单无需在此审核" }; | |
| 6442 | - | |
| 6443 | 6820 | if (entity.Djzt == "已审核") |
| 6444 | 6821 | return new { success = false, message = "该单据已审核通过,如需修改请使用反审" }; |
| 6445 | 6822 | |
| ... | ... | @@ -6577,10 +6954,11 @@ LIMIT {offset}, {pageSize}"; |
| 6577 | 6954 | } |
| 6578 | 6955 | } |
| 6579 | 6956 | |
| 6580 | - // ★ 更新状态为审核不通过 | |
| 6957 | + // ★ 销售出库单:审核不通过直接退回草稿,便于二次编辑并再次提交审核 | |
| 6958 | + var isSalesOutbound = string.Equals(row.Djlx, "销售出库单", StringComparison.Ordinal); | |
| 6581 | 6959 | AppendApprovalSpbzLine(row, "审核不通过", approvalRemark); |
| 6582 | - row.Djzt = "审核不通过"; | |
| 6583 | - row.Shr = userId; | |
| 6960 | + row.Djzt = isSalesOutbound ? "草稿" : "审核不通过"; | |
| 6961 | + row.Shr = isSalesOutbound ? null : userId; | |
| 6584 | 6962 | row.Shr1 = null; |
| 6585 | 6963 | row.Shr2 = null; |
| 6586 | 6964 | await _db.Updateable(row) |
| ... | ... | @@ -6588,7 +6966,11 @@ LIMIT {offset}, {pageSize}"; |
| 6588 | 6966 | .ExecuteCommandAsync(); |
| 6589 | 6967 | |
| 6590 | 6968 | _db.CommitTran(); |
| 6591 | - return new { success = true, message = "已标记为审核不通过" }; | |
| 6969 | + return new | |
| 6970 | + { | |
| 6971 | + success = true, | |
| 6972 | + message = isSalesOutbound ? "审核不通过,已退回草稿,可编辑后再次提交审核" : "已标记为审核不通过" | |
| 6973 | + }; | |
| 6592 | 6974 | } |
| 6593 | 6975 | catch (AppFriendlyException) |
| 6594 | 6976 | { |
| ... | ... | @@ -6628,8 +7010,15 @@ LIMIT {offset}, {pageSize}"; |
| 6628 | 7010 | if (entity == null) |
| 6629 | 7011 | return new { success = false, message = "单据不存在" }; |
| 6630 | 7012 | |
| 6631 | - if (entity.Djzt != "已审核" && entity.Djzt != "一级已审" && entity.Djzt != "一级已审/待二级") | |
| 7013 | + if (IsGiftAuditDocument(entity.Djlx)) | |
| 7014 | + { | |
| 7015 | + if (!string.Equals(entity.Djzt, "已审核", StringComparison.Ordinal)) | |
| 7016 | + return new { success = false, message = "赠送/获赠单仅「已审核」状态可反审" }; | |
| 7017 | + } | |
| 7018 | + else if (entity.Djzt != "已审核" && entity.Djzt != "一级已审" && entity.Djzt != "一级已审/待二级") | |
| 7019 | + { | |
| 6632 | 7020 | return new { success = false, message = "当前单据状态不允许反审" }; |
| 7021 | + } | |
| 6633 | 7022 | |
| 6634 | 7023 | var userInfo = await _userManager.GetUserInfo(); |
| 6635 | 7024 | var userId = userInfo?.userId ?? ""; |
| ... | ... | @@ -6680,11 +7069,21 @@ LIMIT {offset}, {pageSize}"; |
| 6680 | 7069 | if (mxList.Count > 0) |
| 6681 | 7070 | await RollbackSamePriceTransferStock(entity, mxList); |
| 6682 | 7071 | } |
| 7072 | + else if (IsGiftAuditDocument(entity.Djlx) && string.Equals(entity.Djzt, "已审核", StringComparison.Ordinal)) | |
| 7073 | + { | |
| 7074 | + var zsdMx = await _db.Queryable<WtXsckdMxEntity>() | |
| 7075 | + .Where(d => d.Djbh == id) | |
| 7076 | + .ToListAsync(); | |
| 7077 | + if (zsdMx.Count > 0) | |
| 7078 | + await RollbackSpCostOnDelete(entity); | |
| 7079 | + } | |
| 6683 | 7080 | |
| 6684 | - entity.Djzt = (string.Equals(entity.Djlx, "同价调拨单", StringComparison.Ordinal) | |
| 6685 | - || string.Equals(entity.Djlx, "采购入库单", StringComparison.Ordinal)) | |
| 6686 | - ? "草稿" | |
| 6687 | - : "待审核"; | |
| 7081 | + var backToDraft = string.Equals(entity.Djlx, "同价调拨单", StringComparison.Ordinal) | |
| 7082 | + || string.Equals(entity.Djlx, "采购入库单", StringComparison.Ordinal) | |
| 7083 | + || string.Equals(entity.Djlx, "采购退货单", StringComparison.Ordinal) | |
| 7084 | + || IsGiftAuditDocument(entity.Djlx) | |
| 7085 | + || (string.Equals(entity.Djlx, "销售出库单", StringComparison.Ordinal) && IsSalesOutboundSkipErpAudit(entity)); | |
| 7086 | + entity.Djzt = backToDraft ? "草稿" : "待审核"; | |
| 6688 | 7087 | entity.Shr = null; |
| 6689 | 7088 | entity.Shr1 = null; |
| 6690 | 7089 | entity.Shr2 = null; |
| ... | ... | @@ -6693,8 +7092,7 @@ LIMIT {offset}, {pageSize}"; |
| 6693 | 7092 | .ExecuteCommandAsync(); |
| 6694 | 7093 | |
| 6695 | 7094 | _db.CommitTran(); |
| 6696 | - var reverseMsg = (string.Equals(entity.Djlx, "同价调拨单", StringComparison.Ordinal) | |
| 6697 | - || string.Equals(entity.Djlx, "采购入库单", StringComparison.Ordinal)) | |
| 7095 | + var reverseMsg = backToDraft | |
| 6698 | 7096 | ? "反审成功,单据已恢复为草稿" |
| 6699 | 7097 | : "反审成功,单据已恢复为待审核状态"; |
| 6700 | 7098 | return new { success = true, message = reverseMsg }; |
| ... | ... | @@ -7833,6 +8231,16 @@ LIMIT {offset}, {pageSize}"; |
| 7833 | 8231 | "UPDATE wt_xsckd_mx SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id", |
| 7834 | 8232 | new { cbdj = snapCbdj, cbje = Math.Round(snapCbdj * qty, 2), id = detail.Id }); |
| 7835 | 8233 | } |
| 8234 | + // 报溢单:用户录入的成本单价作为 cbdj 快照,避免「审核后列表金额显示 0」。若用户未填 Dj,则回退到候选仓 wt_sp_cost 历史成本 | |
| 8235 | + else if (djlx.Contains("报溢单")) | |
| 8236 | + { | |
| 8237 | + decimal byCbdj = detail.Dj > 0 | |
| 8238 | + ? detail.Dj | |
| 8239 | + : await QueryWtSpCostCbjForWarehouseCandidatesAsync(detail.Spbh, warehouseIds); | |
| 8240 | + await _db.Ado.ExecuteCommandAsync( | |
| 8241 | + "UPDATE wt_xsckd_mx SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id", | |
| 8242 | + new { cbdj = byCbdj, cbje = Math.Round(byCbdj * qty, 2), id = detail.Id }); | |
| 8243 | + } | |
| 7836 | 8244 | |
| 7837 | 8245 | var strictSerial = await IsProductStrictSerialOutboundAsync(detail.Spbh); |
| 7838 | 8246 | if (strictSerial) |
| ... | ... | @@ -8652,15 +9060,15 @@ LIMIT {offset}, {pageSize}"; |
| 8652 | 9060 | } |
| 8653 | 9061 | |
| 8654 | 9062 | /// <summary> |
| 8655 | - /// 变价调拨单过账:调出按快照原成本扣减,调入按变价后单价增加,并回写明细 cbdj/bjhcb/cbje | |
| 9063 | + /// 变价调拨单过账:调出仓按调出仓加权平均成本扣减对应数量(部分数量变价亦按加权平均,不区分个别品), | |
| 9064 | + /// 调入仓按「明细变价后单价(bjhcb)」加权增加。若未提供 bjhcb 则按表头变价系数(bjsx)折算; | |
| 9065 | + /// 两者均未提供则抛错。回写明细 cbdj/bjhcb/cbje。 | |
| 8656 | 9066 | /// </summary> |
| 8657 | 9067 | private async Task ApplyVariablePriceTransferCost(WtXsckdEntity entity, List<WtXsckdMxEntity> mxList) |
| 8658 | 9068 | { |
| 8659 | 9069 | if (string.Equals((entity.Cjck ?? "").Trim(), (entity.Rkck ?? "").Trim(), StringComparison.Ordinal)) |
| 8660 | 9070 | throw new Exception("变价调拨单出库仓与入库仓不能相同"); |
| 8661 | 9071 | var coef = entity.Bjsx ?? 0; |
| 8662 | - if (coef <= 0) | |
| 8663 | - throw new Exception("变价系数必须大于0"); | |
| 8664 | 9072 | |
| 8665 | 9073 | foreach (var detail in mxList) |
| 8666 | 9074 | { |
| ... | ... | @@ -8672,15 +9080,33 @@ LIMIT {offset}, {pageSize}"; |
| 8672 | 9080 | if (string.IsNullOrEmpty(outRef) || string.IsNullOrEmpty(inRef)) |
| 8673 | 9081 | throw new Exception($"商品 {detail.Spmc ?? detail.Spbh} 未指定完整的出库/入库仓库"); |
| 8674 | 9082 | |
| 9083 | + // 调出仓选取 wt_sp_cost(该仓加权平均成本,部分变价亦按此单价扣减,保证该仓保持平均成本) | |
| 8675 | 9084 | var outIds = await ResolveWarehouseIdListAsync(outRef); |
| 8676 | 9085 | var (outCk, originalUnit, _) = await PickOutboundCostRowForTransferAsync(detail.Spbh, outIds, qty); |
| 8677 | - var adjustedUnit = ComputeVariableAdjustedUnitCost(originalUnit, coef); | |
| 9086 | + | |
| 9087 | + // 优先使用明细录入的 bjhcb;否则用表头变价系数换算;两者均无则报错 | |
| 9088 | + decimal adjustedUnit = 0; | |
| 9089 | + if (detail.Bjhcb.HasValue && detail.Bjhcb.Value > 0) | |
| 9090 | + { | |
| 9091 | + adjustedUnit = Math.Round(detail.Bjhcb.Value, 4); | |
| 9092 | + } | |
| 9093 | + else if (coef > 0) | |
| 9094 | + { | |
| 9095 | + adjustedUnit = ComputeVariableAdjustedUnitCost(originalUnit, coef); | |
| 9096 | + } | |
| 9097 | + else | |
| 9098 | + { | |
| 9099 | + throw new Exception($"商品 {detail.Spmc ?? detail.Spbh} 未填写变价后成本,且未设置变价系数%"); | |
| 9100 | + } | |
| 9101 | + | |
| 8678 | 9102 | var inIds = await ResolveWarehouseIdListAsync(inRef); |
| 8679 | 9103 | if (inIds == null || inIds.Count == 0) |
| 8680 | 9104 | throw new Exception($"商品 {detail.Spbh} 未解析到入库仓库"); |
| 8681 | 9105 | var inCk = inIds[0]; |
| 8682 | 9106 | |
| 9107 | + // 调出仓按原加权平均成本扣减(保持该仓平均成本不变) | |
| 8683 | 9108 | await SpCostWeightedRemoveAsync(detail.Spbh, outCk, qty, originalUnit); |
| 9109 | + // 调入仓按变价后单价加权增加(与该仓其他库存进一步加权平均) | |
| 8684 | 9110 | await SpCostWeightedAddAsync(detail.Spbh, inCk, qty, adjustedUnit); |
| 8685 | 9111 | |
| 8686 | 9112 | await _db.Ado.ExecuteCommandAsync( | ... | ... |
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtYskzjjsService.cs
| ... | ... | @@ -482,7 +482,10 @@ namespace NCC.Extend.WtYskzjjs |
| 482 | 482 | if (entitys.Count > 0) |
| 483 | 483 | { |
| 484 | 484 | foreach (var e in entitys) |
| 485 | + { | |
| 485 | 486 | ThrowIfTransferBillCannotDelete(e); |
| 487 | + ThrowIfCashExpenseBillCannotDelete(e); | |
| 488 | + } | |
| 486 | 489 | try |
| 487 | 490 | { |
| 488 | 491 | //开启事务 |
| ... | ... | @@ -518,6 +521,7 @@ namespace NCC.Extend.WtYskzjjs |
| 518 | 521 | var existingHead = await _db.Queryable<WtYskzjjsEntity>().FirstAsync(p => p.Id == id); |
| 519 | 522 | _ = existingHead ?? throw NCCException.Oh(ErrorCode.COM1005); |
| 520 | 523 | ThrowIfTransferBillLockedForEdit(existingHead); |
| 524 | + ThrowIfCashExpenseBillLockedForEdit(existingHead); | |
| 521 | 525 | |
| 522 | 526 | NormalizeDjlxForQt(input); |
| 523 | 527 | NormalizeDjlxForTransfer(input); |
| ... | ... | @@ -589,6 +593,7 @@ namespace NCC.Extend.WtYskzjjs |
| 589 | 593 | var entity = await _db.Queryable<WtYskzjjsEntity>().FirstAsync(p => p.Id == id); |
| 590 | 594 | _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); |
| 591 | 595 | ThrowIfTransferBillCannotDelete(entity); |
| 596 | + ThrowIfCashExpenseBillCannotDelete(entity); | |
| 592 | 597 | try |
| 593 | 598 | { |
| 594 | 599 | //开启事务 |
| ... | ... | @@ -677,6 +682,11 @@ namespace NCC.Extend.WtYskzjjs |
| 677 | 682 | return !string.IsNullOrWhiteSpace(djlx) && djlx.Trim().Contains("转款", StringComparison.Ordinal); |
| 678 | 683 | } |
| 679 | 684 | |
| 685 | + private static bool IsCashExpenseDjlx(string? djlx) | |
| 686 | + { | |
| 687 | + return !string.IsNullOrWhiteSpace(djlx) && djlx.Trim().Contains("现金费用单", StringComparison.Ordinal); | |
| 688 | + } | |
| 689 | + | |
| 680 | 690 | private static void ThrowIfTransferBillLockedForEdit(WtYskzjjsEntity existing) |
| 681 | 691 | { |
| 682 | 692 | if (existing == null || !IsTransferDjlx(existing.Djlx)) return; |
| ... | ... | @@ -685,6 +695,14 @@ namespace NCC.Extend.WtYskzjjs |
| 685 | 695 | throw NCCException.Oh($"转款单当前状态「{existing.Djzt}」不可编辑,请查看或反审后再编辑"); |
| 686 | 696 | } |
| 687 | 697 | |
| 698 | + private static void ThrowIfCashExpenseBillLockedForEdit(WtYskzjjsEntity existing) | |
| 699 | + { | |
| 700 | + if (existing == null || !IsCashExpenseDjlx(existing.Djlx)) return; | |
| 701 | + var z = (existing.Djzt ?? string.Empty).Trim(); | |
| 702 | + if (z == "草稿") return; | |
| 703 | + throw NCCException.Oh($"现金费用单当前状态「{existing.Djzt}」不可编辑,仅草稿状态可编辑"); | |
| 704 | + } | |
| 705 | + | |
| 688 | 706 | private static void ThrowIfTransferBillCannotDelete(WtYskzjjsEntity existing) |
| 689 | 707 | { |
| 690 | 708 | if (existing == null || !IsTransferDjlx(existing.Djlx)) return; |
| ... | ... | @@ -693,6 +711,14 @@ namespace NCC.Extend.WtYskzjjs |
| 693 | 711 | throw NCCException.Oh($"转款单当前状态「{existing.Djzt}」不可删除"); |
| 694 | 712 | } |
| 695 | 713 | |
| 714 | + private static void ThrowIfCashExpenseBillCannotDelete(WtYskzjjsEntity existing) | |
| 715 | + { | |
| 716 | + if (existing == null || !IsCashExpenseDjlx(existing.Djlx)) return; | |
| 717 | + var z = (existing.Djzt ?? string.Empty).Trim(); | |
| 718 | + if (z == "草稿") return; | |
| 719 | + throw NCCException.Oh($"现金费用单当前状态「{existing.Djzt}」不可删除,仅草稿状态可删除"); | |
| 720 | + } | |
| 721 | + | |
| 696 | 722 | /// <summary> |
| 697 | 723 | /// 审核状态字符串去空白,便于前端与 <c>待审核</c> 等字面量一致匹配。 |
| 698 | 724 | /// </summary> | ... | ... |
Antis.Erp.Plat/sy/css/pos-unified.css
| ... | ... | @@ -1255,6 +1255,39 @@ body.pos-page--embed .pos-embed-select { |
| 1255 | 1255 | box-sizing: border-box; |
| 1256 | 1256 | } |
| 1257 | 1257 | |
| 1258 | +/* 购物车行:售后规则提示(质保时间等,来自商品档案;下单时落成明细快照) */ | |
| 1259 | +.pos-cart-shgz-tip { | |
| 1260 | + margin-top: 8px; | |
| 1261 | + padding: 6px 10px; | |
| 1262 | + display: flex; | |
| 1263 | + align-items: flex-start; | |
| 1264 | + gap: 8px; | |
| 1265 | + background: #fff1f0; | |
| 1266 | + border: 1px solid #ffccc7; | |
| 1267 | + border-radius: 8px; | |
| 1268 | + color: #cf1322; | |
| 1269 | + font-size: 12px; | |
| 1270 | + line-height: 1.5; | |
| 1271 | + word-break: break-all; | |
| 1272 | +} | |
| 1273 | + | |
| 1274 | +.pos-cart-shgz-tip__label { | |
| 1275 | + flex: 0 0 auto; | |
| 1276 | + padding: 1px 6px; | |
| 1277 | + background: #cf1322; | |
| 1278 | + color: #fff; | |
| 1279 | + border-radius: 4px; | |
| 1280 | + font-size: 11px; | |
| 1281 | + font-weight: 600; | |
| 1282 | + letter-spacing: 0.5px; | |
| 1283 | +} | |
| 1284 | + | |
| 1285 | +.pos-cart-shgz-tip__text { | |
| 1286 | + flex: 1 1 auto; | |
| 1287 | + min-width: 0; | |
| 1288 | + font-weight: 500; | |
| 1289 | +} | |
| 1290 | + | |
| 1258 | 1291 | /* 分类 + 分段切换 */ |
| 1259 | 1292 | .pos-page .pos-home-category-row { |
| 1260 | 1293 | display: flex; | ... | ... |
Antis.Erp.Plat/sy/home.html
| ... | ... | @@ -589,6 +589,11 @@ |
| 589 | 589 | </div> |
| 590 | 590 | </div> |
| 591 | 591 | </div> |
| 592 | + <!-- 售后规则(质保等):来自商品档案,下单时随快照带到出库单 --> | |
| 593 | + <div v-if="item.shgz" class="pos-cart-shgz-tip" :title="item.shgz"> | |
| 594 | + <span class="pos-cart-shgz-tip__label">售后规则</span> | |
| 595 | + <span class="pos-cart-shgz-tip__text">{{ item.shgz }}</span> | |
| 596 | + </div> | |
| 592 | 597 | <!-- 序列号选择区域:预售商品不卡序列号,不显示 --> |
| 593 | 598 | <div v-if="item.spbm && !item.isPresale" class="pos-cart-serial-box"> |
| 594 | 599 | <div v-if="item.selectedSerialNumbers && item.selectedSerialNumbers.length > 0" style="margin-bottom: 5px;"> | ... | ... |
Antis.Erp.Plat/sy/settlement.html
| ... | ... | @@ -3852,6 +3852,11 @@ |
| 3852 | 3852 | <div>单品改价后: ¥{{parseFloat(item.modifiedPrice || 0).toFixed(2)}}</div> |
| 3853 | 3853 | </div> |
| 3854 | 3854 | </div> |
| 3855 | + <!-- 售后规则(质保等):来自商品档案,下单时会随快照写入 wt_xsckd_mx.shgz,后续变更不影响历史单 --> | |
| 3856 | + <div v-if="getItemShgz(item)" class="pos-cart-shgz-tip" :title="getItemShgz(item)"> | |
| 3857 | + <span class="pos-cart-shgz-tip__label">售后规则</span> | |
| 3858 | + <span class="pos-cart-shgz-tip__text">{{ getItemShgz(item) }}</span> | |
| 3859 | + </div> | |
| 3855 | 3860 | <!-- ✅ 套餐商品:显示套餐内商品列表 --> |
| 3856 | 3861 | <div v-if="item.isPackageItem && item.packageItems && item.packageItems.length > 0" class="pos-cart-package-inner"> |
| 3857 | 3862 | <div class="pos-cart-package-inner-title">套餐内商品</div> |
| ... | ... | @@ -5969,6 +5974,8 @@ |
| 5969 | 5974 | "sl": actualQuantity, |
| 5970 | 5975 | "dj": itemPrice, |
| 5971 | 5976 | "je": itemPrice * actualQuantity, |
| 5977 | + // 售后规则快照:锁定下单当时的规则;缺失时后端会按 spbh 从商品档案再兜底 | |
| 5978 | + "shgz": (pkgItem && pkgItem.shgz) || '', | |
| 5972 | 5979 | }); |
| 5973 | 5980 | }); |
| 5974 | 5981 | } else { |
| ... | ... | @@ -5985,6 +5992,8 @@ |
| 5985 | 5992 | "sl": packageQuantity, |
| 5986 | 5993 | "dj": packagePrice, |
| 5987 | 5994 | "je": packagePrice * packageQuantity, |
| 5995 | + // 售后规则快照:套餐行本身没有 shgz,交由后端按 spbh 自动兜底 | |
| 5996 | + "shgz": (item && item.shgz) || '', | |
| 5988 | 5997 | }); |
| 5989 | 5998 | } |
| 5990 | 5999 | } else { |
| ... | ... | @@ -6001,6 +6010,8 @@ |
| 6001 | 6010 | "sl": item.quantity, |
| 6002 | 6011 | "dj": itemPrice, |
| 6003 | 6012 | "je": itemPrice * item.quantity, |
| 6013 | + // 售后规则快照:锁定下单当时的质保等规则;缺失时后端会按 spbh 从商品档案再兜底 | |
| 6014 | + "shgz": (item && item.shgz) || '', | |
| 6004 | 6015 | }); |
| 6005 | 6016 | } |
| 6006 | 6017 | } |
| ... | ... | @@ -6533,6 +6544,27 @@ |
| 6533 | 6544 | const price = this.getSettlementLineUnitPrice(item); |
| 6534 | 6545 | return price * item.quantity; |
| 6535 | 6546 | }, |
| 6547 | + /** | |
| 6548 | + * 售后规则展示: | |
| 6549 | + * 单品直接取 item.shgz; | |
| 6550 | + * 套装取套装内各子商品 shgz,非空去重后以「、」拼接(常见场景是套装内各子商品规则一致)。 | |
| 6551 | + * 为空返回 '',模板上层 v-if 不渲染提示条。 | |
| 6552 | + */ | |
| 6553 | + getItemShgz(item) { | |
| 6554 | + if (!item) return ''; | |
| 6555 | + if (item.isPackageItem) { | |
| 6556 | + const pkgList = Array.isArray(item.packageItems) ? item.packageItems : []; | |
| 6557 | + if (pkgList.length > 0) { | |
| 6558 | + const set = new Set(); | |
| 6559 | + pkgList.forEach(p => { | |
| 6560 | + const s = (p && typeof p.shgz === 'string') ? p.shgz.trim() : ''; | |
| 6561 | + if (s) set.add(s); | |
| 6562 | + }); | |
| 6563 | + if (set.size > 0) return Array.from(set).join('、'); | |
| 6564 | + } | |
| 6565 | + } | |
| 6566 | + return (item.shgz || '').toString().trim(); | |
| 6567 | + }, | |
| 6536 | 6568 | // ✅ 打开改价模态框 |
| 6537 | 6569 | async openPriceEditModal(item, index) { |
| 6538 | 6570 | // ✅ 如果是套餐商品,找到实际的第一个子商品(用于改价) | ... | ... |