Commit 60226f1594559ffdd5fd714f4109f0bd497098cc

Authored by “wangming”
1 parent 03c75022

修改调价调拨单

Antis.Erp.Plat/antis-ncc-admin/src/views/wtBjdbd/Form.vue
1 -<template> 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="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%">
3 <el-row :gutter="15" class="" > 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="!!isDetail" :rules="rules">
@@ -41,11 +41,16 @@ @@ -41,11 +41,16 @@
41 </el-col> 41 </el-col>
42 <el-col :span="12"> 42 <el-col :span="12">
43 <el-form-item label="变价系数%" prop="bjsx"> 43 <el-form-item label="变价系数%" prop="bjsx">
44 - <el-input v-model="dataForm.bjsx" placeholder="请输入变价系数" clearable :style='{"width":"100%"}' @input="handlePriceAdjustmentChange"> 44 + <el-input v-model="dataForm.bjsx" placeholder="如 95 表示 95%" clearable :style='{"width":"100%"}' @input="handlePriceAdjustmentChange" @change="handlePriceAdjustmentChange">
45 <template slot="append">%</template> 45 <template slot="append">%</template>
46 </el-input> 46 </el-input>
47 </el-form-item> 47 </el-form-item>
48 </el-col> 48 </el-col>
  49 + <el-col :span="12">
  50 + <el-form-item label="变价成本合计">
  51 + <el-input :value="cbjeDisplay" placeholder="保存后由系统按变价后金额汇总" readonly :style='{"width":"100%"}' />
  52 + </el-form-item>
  53 + </el-col>
49 <el-col :span="12" v-if="false"> 54 <el-col :span="12" v-if="false">
50 <el-form-item label="供应商" prop="gys"> 55 <el-form-item label="供应商" prop="gys">
51 <el-select v-model="dataForm.gys" placeholder="请选择" clearable :style='{"width":"100%"}' filterable > 56 <el-select v-model="dataForm.gys" placeholder="请选择" clearable :style='{"width":"100%"}' filterable >
@@ -134,17 +139,18 @@ @@ -134,17 +139,18 @@
134 ></el-input> 139 ></el-input>
135 </template> 140 </template>
136 </el-table-column> 141 </el-table-column>
137 - <el-table-column prop="dj" label="成本单价"> 142 + <el-table-column prop="dj" label="成本单价" width="110">
138 <template slot-scope="scope"> 143 <template slot-scope="scope">
139 <el-input 144 <el-input
140 v-model="scope.row.dj" 145 v-model="scope.row.dj"
141 - placeholder="请输入" 146 + placeholder="参考成本,过账以服务端为准"
142 clearable 147 clearable
  148 + :readonly="!!isDetail"
143 @input="handleAmountChange(scope.row)" 149 @input="handleAmountChange(scope.row)"
144 ></el-input> 150 ></el-input>
145 </template> 151 </template>
146 </el-table-column> 152 </el-table-column>
147 - <el-table-column prop="bjhcb" label="变价后成本" width="120"> 153 + <el-table-column prop="bjhcb" label="变价后单价" width="120">
148 <template slot-scope="scope"> 154 <template slot-scope="scope">
149 <el-input 155 <el-input
150 v-model="scope.row.bjhcb" 156 v-model="scope.row.bjhcb"
@@ -155,6 +161,11 @@ @@ -155,6 +161,11 @@
155 ></el-input> 161 ></el-input>
156 </template> 162 </template>
157 </el-table-column> 163 </el-table-column>
  164 + <el-table-column prop="cbje" label="变价后金额" width="100" show-overflow-tooltip>
  165 + <template slot-scope="scope">
  166 + <span>{{ formatMxCbje(scope.row) }}</span>
  167 + </template>
  168 + </el-table-column>
158 <el-table-column prop="je" label="销售总额" v-if="false"> 169 <el-table-column prop="je" label="销售总额" v-if="false">
159 <template slot-scope="scope"> 170 <template slot-scope="scope">
160 <el-input v-model="scope.row.je" placeholder="请输入" clearable readonly></el-input> 171 <el-input v-model="scope.row.je" placeholder="请输入" clearable readonly></el-input>
@@ -300,6 +311,7 @@ @@ -300,6 +311,7 @@
300 kh:undefined, 311 kh:undefined,
301 gys:undefined, 312 gys:undefined,
302 bjsx:undefined, // 变价系数% 313 bjsx:undefined, // 变价系数%
  314 + cbje: undefined, // 主表变价成本合计(服务端汇总)
303 wtXsckdMxList:[], 315 wtXsckdMxList:[],
304 ysje:undefined, 316 ysje:undefined,
305 skzh:undefined, 317 skzh:undefined,
@@ -336,6 +348,12 @@ @@ -336,6 +348,12 @@
336 totalJe() { 348 totalJe() {
337 return (this.dataForm.wtXsckdMxList || []).reduce((sum, row) => sum + (parseFloat(row.je) || 0), 0) 349 return (this.dataForm.wtXsckdMxList || []).reduce((sum, row) => sum + (parseFloat(row.je) || 0), 0)
338 }, 350 },
  351 + cbjeDisplay() {
  352 + const v = this.dataForm.cbje
  353 + if (v == null || v === '') return '无'
  354 + const n = Number(v)
  355 + return isNaN(n) ? '无' : n.toFixed(2)
  356 + },
339 357
340 }, 358 },
341 watch: { 359 watch: {
@@ -371,6 +389,11 @@ @@ -371,6 +389,11 @@
371 }, 389 },
372 deep: true, 390 deep: true,
373 immediate: false 391 immediate: false
  392 + },
  393 + 'dataForm.bjsx'() {
  394 + this.$nextTick(() => {
  395 + this.calculateAllAdjustedCosts();
  396 + });
374 } 397 }
375 }, 398 },
376 created() { 399 created() {
@@ -641,6 +664,88 @@ @@ -641,6 +664,88 @@
641 goBack() { 664 goBack() {
642 this.$emit('refresh') 665 this.$emit('refresh')
643 }, 666 },
  667 + /** 明细变价后金额:已过账用服务端 cbje;未过账用数量×变价后单价预览 */
  668 + formatMxCbje(row) {
  669 + if (!row) return '无'
  670 + if (row.cbje != null && row.cbje !== '') {
  671 + const n = Number(row.cbje)
  672 + return isNaN(n) ? '无' : n.toFixed(2)
  673 + }
  674 + const sl = parseInt(row.sl, 10)
  675 + const bjh = parseFloat(row.bjhcb)
  676 + if (sl > 0 && !isNaN(bjh) && bjh > 0) {
  677 + return (sl * bjh).toFixed(2)
  678 + }
  679 + return '无'
  680 + },
  681 + /**
  682 + * 从 wt_sp_cost 拉取调出方成本单价并回填 dj,再算变价后单价
  683 + * @param {boolean} silent 无成本记录时不弹警告(避免刷屏)
  684 + */
  685 + async applyOutboundCostPrice(row, silent = false) {
  686 + if (!row || !row.spbh || !row.ckck) return
  687 + try {
  688 + const spbh = encodeURIComponent(String(row.spbh))
  689 + const ck = encodeURIComponent(String(row.ckck))
  690 + const res = await request({
  691 + url: `/api/Extend/WtXsckd/GetOutboundCostPrice?spbh=${spbh}&ckOrMdId=${ck}`,
  692 + method: 'get'
  693 + })
  694 + const payload = res.data
  695 + if (payload && payload.success && payload.data != null) {
  696 + const p = Number(payload.data)
  697 + if (!isNaN(p) && p > 0) {
  698 + this.$set(row, 'dj', p.toFixed(4))
  699 + this.calculateRowAdjustedCost(row)
  700 + this.$forceUpdate()
  701 + }
  702 + } else if (!silent && payload && payload.msg) {
  703 + this.$message.warning(payload.msg)
  704 + }
  705 + } catch (e) {
  706 + if (!silent) console.error(e)
  707 + }
  708 + },
  709 + /** 组装提交体:变价系数转数值、显式 isDraft、去掉前端临时字段 */
  710 + buildPayload(isDraft) {
  711 + const bjsxNum = parseFloat(this.dataForm.bjsx)
  712 + const mx = (this.dataForm.wtXsckdMxList || []).map(r => {
  713 + const { productQuery, spxlhLoaded, loadingStock, ...rest } = r
  714 + return rest
  715 + })
  716 + return {
  717 + ...this.dataForm,
  718 + bjsx: isNaN(bjsxNum) ? null : bjsxNum,
  719 + isDraft: !!isDraft,
  720 + djlx: '变价调拨单',
  721 + // 正式保存一律待审核(含从草稿转正);草稿仅保存草稿
  722 + djzt: isDraft ? '草稿' : '待审核',
  723 + wtXsckdMxList: mx
  724 + }
  725 + },
  726 + /** 正式保存前业务校验(草稿不走此函数) */
  727 + validateBjdFormalBasics() {
  728 + if (!this.dataForm.cjck || !this.dataForm.rkck)
  729 + return '请选择出库仓库与入库仓库'
  730 + if (this.dataForm.cjck === this.dataForm.rkck)
  731 + return '出库仓库与入库仓库不能相同'
  732 + const coef = parseFloat(this.dataForm.bjsx)
  733 + if (this.dataForm.bjsx === '' || this.dataForm.bjsx === undefined || isNaN(coef) || coef <= 0)
  734 + return '请输入大于 0 的变价系数(%)'
  735 + if (!this.dataForm.wtXsckdMxList || !this.dataForm.wtXsckdMxList.length)
  736 + return '请添加至少一条明细'
  737 + let hasSp = false
  738 + for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) {
  739 + const row = this.dataForm.wtXsckdMxList[i]
  740 + if (!row.spbh) continue
  741 + hasSp = true
  742 + const q = parseInt(row.sl, 10)
  743 + if (!q || q <= 0)
  744 + return `第${i + 1}行请填写有效数量或选择序列号`
  745 + }
  746 + if (!hasSp) return '请至少选择一条商品明细'
  747 + return ''
  748 + },
644 init(id, isDetail) { 749 init(id, isDetail) {
645 var _this = this; 750 var _this = this;
646 console.log('id',id,'detail',isDetail) 751 console.log('id',id,'detail',isDetail)
@@ -666,6 +771,8 @@ @@ -666,6 +771,8 @@
666 _this.dataForm = res.data; 771 _this.dataForm = res.data;
667 console.log('编辑时加载的数据:', _this.dataForm); 772 console.log('编辑时加载的数据:', _this.dataForm);
668 console.log('明细数据:', _this.dataForm.wtXsckdMxList); 773 console.log('明细数据:', _this.dataForm.wtXsckdMxList);
  774 + if (_this.dataForm.bjsx != null && _this.dataForm.bjsx !== '')
  775 + _this.$set(_this.dataForm, 'bjsx', String(_this.dataForm.bjsx))
669 776
670 // 为每个明细项添加productQuery字段 777 // 为每个明细项添加productQuery字段
671 if (_this.dataForm.wtXsckdMxList) { 778 if (_this.dataForm.wtXsckdMxList) {
@@ -673,6 +780,13 @@ @@ -673,6 +780,13 @@
673 if (!item.hasOwnProperty('productQuery')) { 780 if (!item.hasOwnProperty('productQuery')) {
674 _this.$set(item, 'productQuery', ''); 781 _this.$set(item, 'productQuery', '');
675 } 782 }
  783 + // 服务端过账快照成本 → 与「成本单价」展示一致
  784 + if (item.cbdj != null && item.cbdj !== '') {
  785 + _this.$set(item, 'dj', Number(item.cbdj))
  786 + }
  787 + if (item.bjhcb != null && item.bjhcb !== '') {
  788 + _this.$set(item, 'bjhcb', Number(item.bjhcb).toFixed(4))
  789 + }
676 }); 790 });
677 } 791 }
678 792
@@ -700,9 +814,7 @@ @@ -700,9 +814,7 @@
700 this.$refs['elForm'].validate(async (valid) => { 814 this.$refs['elForm'].validate(async (valid) => {
701 if (valid) { 815 if (valid) {
702 try { 816 try {
703 - // 标记为草稿  
704 - this.dataForm.djzt = '草稿';  
705 - const draftData = { ...this.dataForm, isDraft: true, djzt: '草稿' }; 817 + const draftData = this.buildPayload(true);
706 let res; 818 let res;
707 if (!this.dataForm.id) { 819 if (!this.dataForm.id) {
708 res = await request({ 820 res = await request({
@@ -734,6 +846,11 @@ @@ -734,6 +846,11 @@
734 }); 846 });
735 }, 847 },
736 async dataFormSubmit() { 848 async dataFormSubmit() {
  849 + const basicErr = this.validateBjdFormalBasics()
  850 + if (basicErr) {
  851 + this.$message.error(basicErr)
  852 + return
  853 + }
737 // 1. 明细校验:序列号数量与销售数量一致性 854 // 1. 明细校验:序列号数量与销售数量一致性
738 let validationErrors = []; 855 let validationErrors = [];
739 for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) { 856 for (let i = 0; i < this.dataForm.wtXsckdMxList.length; i++) {
@@ -878,7 +995,7 @@ @@ -878,7 +995,7 @@
878 const res = await request({ 995 const res = await request({
879 url: `/api/Extend/WtXsckd`, 996 url: `/api/Extend/WtXsckd`,
880 method: 'post', 997 method: 'post',
881 - data: this.dataForm, 998 + data: this.buildPayload(false),
882 }) 999 })
883 1000
884 // 更新序列号状态 1001 // 更新序列号状态
@@ -891,7 +1008,7 @@ @@ -891,7 +1008,7 @@
891 const res = await request({ 1008 const res = await request({
892 url: '/api/Extend/WtXsckd/' + this.dataForm.id, 1009 url: '/api/Extend/WtXsckd/' + this.dataForm.id,
893 method: 'PUT', 1010 method: 'PUT',
894 - data: this.dataForm 1011 + data: this.buildPayload(false)
895 }) 1012 })
896 1013
897 // 更新序列号状态 1014 // 更新序列号状态
@@ -947,7 +1064,7 @@ @@ -947,7 +1064,7 @@
947 const res = await request({ 1064 const res = await request({
948 url: `/api/Extend/WtXsckd`, 1065 url: `/api/Extend/WtXsckd`,
949 method: 'post', 1066 method: 'post',
950 - data: this.dataForm, 1067 + data: this.buildPayload(false),
951 }) 1068 })
952 1069
953 // 更新序列号状态 1070 // 更新序列号状态
@@ -960,7 +1077,7 @@ @@ -960,7 +1077,7 @@
960 const res = await request({ 1077 const res = await request({
961 url: '/api/Extend/WtXsckd/' + this.dataForm.id, 1078 url: '/api/Extend/WtXsckd/' + this.dataForm.id,
962 method: 'PUT', 1079 method: 'PUT',
963 - data: this.dataForm 1080 + data: this.buildPayload(false)
964 }) 1081 })
965 1082
966 // 更新序列号状态 1083 // 更新序列号状态
@@ -1109,7 +1226,7 @@ @@ -1109,7 +1226,7 @@
1109 handleMainWarehouseChange(value) { 1226 handleMainWarehouseChange(value) {
1110 // 同步更新所有明细行的出库仓库 1227 // 同步更新所有明细行的出库仓库
1111 this.syncDetailWarehouses(); 1228 this.syncDetailWarehouses();
1112 - // 更新所有明细行的库存 1229 + // 更新所有明细行的库存与成本参考价
1113 this.dataForm.wtXsckdMxList.forEach(row => { 1230 this.dataForm.wtXsckdMxList.forEach(row => {
1114 if (row.spbh) { 1231 if (row.spbh) {
1115 this.getStockQuantity(row); 1232 this.getStockQuantity(row);
@@ -1232,6 +1349,10 @@ @@ -1232,6 +1349,10 @@
1232 row.loadingStock = false; 1349 row.loadingStock = false;
1233 console.log('=== 库存查询完成 ==='); 1350 console.log('=== 库存查询完成 ===');
1234 } 1351 }
  1352 + // 无论库存接口是否成功,都尝试拉取成本单价(与过账取价一致)
  1353 + if (row.spbh && row.ckck) {
  1354 + await this.applyOutboundCostPrice(row, true);
  1355 + }
1235 }, 1356 },
1236 1357
1237 // 计算总金额 1358 // 计算总金额
@@ -1493,11 +1614,13 @@ @@ -1493,11 +1614,13 @@
1493 sums[index] = '合计'; 1614 sums[index] = '合计';
1494 return; 1615 return;
1495 } 1616 }
1496 - if (['kucun', 'sl', 'je'].includes(column.property)) { 1617 + if (['kucun', 'sl', 'je', 'cbje', 'dj', 'bjhcb'].includes(column.property)) {
1497 sums[index] = data.reduce((total, row) => { 1618 sums[index] = data.reduce((total, row) => {
1498 const value = parseFloat(row[column.property]); 1619 const value = parseFloat(row[column.property]);
1499 return total + (isNaN(value) ? 0 : value); 1620 return total + (isNaN(value) ? 0 : value);
1500 }, 0); 1621 }, 0);
  1622 + if (['cbje', 'dj', 'bjhcb'].includes(column.property) && sums[index] !== '')
  1623 + sums[index] = Number(sums[index]).toFixed(2)
1501 } else { 1624 } else {
1502 sums[index] = ''; 1625 sums[index] = '';
1503 } 1626 }
@@ -1509,7 +1632,7 @@ @@ -1509,7 +1632,7 @@
1509 const label = (option.label || '').toLowerCase(); 1632 const label = (option.label || '').toLowerCase();
1510 return label.includes(query.toLowerCase()); 1633 return label.includes(query.toLowerCase());
1511 }, 1634 },
1512 - handleProductChange(row) { 1635 + async handleProductChange(row) {
1513 // 选中商品后可自动回填商品名称等信息 1636 // 选中商品后可自动回填商品名称等信息
1514 const product = this.spbhOptions.find(item => item.F_Id === row.spbh); 1637 const product = this.spbhOptions.find(item => item.F_Id === row.spbh);
1515 if (product) { 1638 if (product) {
@@ -1540,14 +1663,9 @@ @@ -1540,14 +1663,9 @@
1540 return; 1663 return;
1541 } 1664 }
1542 1665
1543 - // 如果已选择出库仓库,自动获取库存 1666 + // 如果已选择出库仓库,自动获取库存(内部会再拉成本单价)
1544 console.log('开始获取库存...'); 1667 console.log('开始获取库存...');
1545 - this.getStockQuantity(row);  
1546 -  
1547 - // 如果有变价系数,计算该行的变价后成本  
1548 - if (this.dataForm.bjsx && this.dataForm.bjsx !== '') {  
1549 - this.calculateRowAdjustedCost(row);  
1550 - } 1668 + await this.getStockQuantity(row);
1551 } 1669 }
1552 }, 1670 },
1553 handleProductQuery(val, scope) { 1671 handleProductQuery(val, scope) {
@@ -1567,7 +1685,10 @@ @@ -1567,7 +1685,10 @@
1567 }, 1685 },
1568 // 处理变价系数变化,重新计算所有明细的变价后成本 1686 // 处理变价系数变化,重新计算所有明细的变价后成本
1569 handlePriceAdjustmentChange() { 1687 handlePriceAdjustmentChange() {
1570 - this.calculateAllAdjustedCosts(); 1688 + this.$nextTick(() => {
  1689 + this.calculateAllAdjustedCosts();
  1690 + this.$forceUpdate();
  1691 + });
1571 }, 1692 },
1572 1693
1573 // 计算所有明细的变价后成本 1694 // 计算所有明细的变价后成本
@@ -1575,7 +1696,7 @@ @@ -1575,7 +1696,7 @@
1575 if (!this.dataForm.bjsx || this.dataForm.bjsx === '') { 1696 if (!this.dataForm.bjsx || this.dataForm.bjsx === '') {
1576 // 如果没有输入变价系数,清空所有变价后成本 1697 // 如果没有输入变价系数,清空所有变价后成本
1577 this.dataForm.wtXsckdMxList.forEach(row => { 1698 this.dataForm.wtXsckdMxList.forEach(row => {
1578 - row.bjhcb = ''; 1699 + this.$set(row, 'bjhcb', '');
1579 }); 1700 });
1580 return; 1701 return;
1581 } 1702 }
@@ -1593,24 +1714,25 @@ @@ -1593,24 +1714,25 @@
1593 // 例如:成本100,系数95,则变价后成本 = 100 / (95/100) = 100 / 0.95 = 105.26 1714 // 例如:成本100,系数95,则变价后成本 = 100 / (95/100) = 100 / 0.95 = 105.26
1594 1715
1595 this.dataForm.wtXsckdMxList.forEach(row => { 1716 this.dataForm.wtXsckdMxList.forEach(row => {
1596 - if (row.dj && row.dj !== '') { 1717 + if (row.dj !== undefined && row.dj !== null && row.dj !== '') {
1597 const cost = parseFloat(row.dj); 1718 const cost = parseFloat(row.dj);
1598 if (!isNaN(cost) && cost > 0) { 1719 if (!isNaN(cost) && cost > 0) {
1599 const adjustedCost = cost / (coefficient / 100); 1720 const adjustedCost = cost / (coefficient / 100);
1600 - row.bjhcb = adjustedCost.toFixed(2); 1721 + this.$set(row, 'bjhcb', adjustedCost.toFixed(4));
1601 } else { 1722 } else {
1602 - row.bjhcb = ''; 1723 + this.$set(row, 'bjhcb', '');
1603 } 1724 }
1604 } else { 1725 } else {
1605 - row.bjhcb = ''; 1726 + this.$set(row, 'bjhcb', '');
1606 } 1727 }
1607 }); 1728 });
  1729 + this.$forceUpdate();
1608 }, 1730 },
1609 1731
1610 // 计算单行的变价后成本 1732 // 计算单行的变价后成本
1611 calculateRowAdjustedCost(row) { 1733 calculateRowAdjustedCost(row) {
1612 if (!this.dataForm.bjsx || this.dataForm.bjsx === '' || !row.dj || row.dj === '') { 1734 if (!this.dataForm.bjsx || this.dataForm.bjsx === '' || !row.dj || row.dj === '') {
1613 - row.bjhcb = ''; 1735 + this.$set(row, 'bjhcb', '');
1614 return; 1736 return;
1615 } 1737 }
1616 1738
@@ -1618,17 +1740,15 @@ @@ -1618,17 +1740,15 @@
1618 const cost = parseFloat(row.dj); 1740 const cost = parseFloat(row.dj);
1619 1741
1620 if (isNaN(coefficient) || isNaN(cost) || cost <= 0) { 1742 if (isNaN(coefficient) || isNaN(cost) || cost <= 0) {
1621 - row.bjhcb = ''; 1743 + this.$set(row, 'bjhcb', '');
1622 return; 1744 return;
1623 } 1745 }
1624 1746
1625 // 按照公式计算:变价后成本 = 成本 / (系数/100) 1747 // 按照公式计算:变价后成本 = 成本 / (系数/100)
1626 // 例如:成本100,系数95,则变价后成本 = 100 / (95/100) = 100 / 0.95 = 105.26 1748 // 例如:成本100,系数95,则变价后成本 = 100 / (95/100) = 100 / 0.95 = 105.26
1627 const adjustedCost = cost / (coefficient / 100); 1749 const adjustedCost = cost / (coefficient / 100);
1628 - row.bjhcb = adjustedCost.toFixed(2);  
1629 - },  
1630 -  
1631 - // 处理变价系数变化,重新计算所有明细的变价后成本 1750 + this.$set(row, 'bjhcb', adjustedCost.toFixed(4));
  1751 + }
1632 } 1752 }
1633 } 1753 }
1634 </script> 1754 </script>
Antis.Erp.Plat/antis-ncc-admin/src/views/wtBjdbd/index.vue
1 -<template> 1 +<template>
2 <div class="NCC-common-layout"> 2 <div class="NCC-common-layout">
3 <div class="NCC-common-layout-center"> 3 <div class="NCC-common-layout-center">
4 <el-row class="NCC-common-search-box" :gutter="16"> 4 <el-row class="NCC-common-search-box" :gutter="16">
@@ -106,10 +106,30 @@ @@ -106,10 +106,30 @@
106 </div> 106 </div>
107 </div> 107 </div>
108 <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange"> 108 <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange">
109 - <el-table-column prop="id" label="单据编号" align="left" /> 109 + <el-table-column prop="id" label="单据编号" align="left" width="160">
  110 + <template slot-scope="scope">
  111 + <i class="el-icon-document" style="color:#409EFF;margin-right:4px" />
  112 + {{ scope.row.id || '无' }}
  113 + </template>
  114 + </el-table-column>
110 <el-table-column prop="djrq" label="单据日期" align="left" :formatter="ncc.tableDateFormat" /> 115 <el-table-column prop="djrq" label="单据日期" align="left" :formatter="ncc.tableDateFormat" />
111 - <el-table-column label="出库仓库" prop="cjck" align="left">  
112 - <template slot-scope="scope">{{ scope.row.cjck | dynamicText(cjckOptions) }}</template> 116 + <el-table-column label="出库仓库" prop="cjck" align="left" min-width="100" show-overflow-tooltip>
  117 + <template slot-scope="scope">
  118 + <i class="el-icon-office-building" style="color:#909399;margin-right:4px" />
  119 + {{ scope.row.cjck | dynamicText(cjckOptions) }}
  120 + </template>
  121 + </el-table-column>
  122 + <el-table-column prop="bjsx" label="变价系数%" align="right" width="100">
  123 + <template slot-scope="scope">
  124 + <i class="el-icon-sort" style="color:#E6A23C;margin-right:4px" />
  125 + {{ scope.row.bjsx != null && scope.row.bjsx !== '' ? scope.row.bjsx + '%' : '无' }}
  126 + </template>
  127 + </el-table-column>
  128 + <el-table-column prop="cbje" label="变价成本合计" align="right" width="120">
  129 + <template slot-scope="scope">
  130 + <i class="el-icon-coin" style="color:#67C23A;margin-right:4px" />
  131 + {{ scope.row.cbje != null && scope.row.cbje !== '' ? scope.row.cbje : '无' }}
  132 + </template>
113 </el-table-column> 133 </el-table-column>
114 <el-table-column prop="jsr" label="经手人" align="left" /> 134 <el-table-column prop="jsr" label="经手人" align="left" />
115 <el-table-column prop="ysje" label="优惠金额" align="left" /> 135 <el-table-column prop="ysje" label="优惠金额" align="left" />
@@ -122,7 +142,14 @@ @@ -122,7 +142,14 @@
122 <el-table-column prop="gzr" label="过账人" align="left" /> 142 <el-table-column prop="gzr" label="过账人" align="left" />
123 <el-table-column prop="bz" label="备注" align="left" /> 143 <el-table-column prop="bz" label="备注" align="left" />
124 <el-table-column prop="djlx" label="单据类型" align="left" /> 144 <el-table-column prop="djlx" label="单据类型" align="left" />
125 - <el-table-column prop="djzt" label="单据状态" align="left" /> 145 + <el-table-column prop="djzt" label="单据状态" align="left" width="100">
  146 + <template slot-scope="scope">
  147 + <el-tag v-if="scope.row.djzt === '草稿'" type="info" size="mini">草稿</el-tag>
  148 + <el-tag v-else-if="scope.row.djzt === '待审核'" type="warning" size="mini">待审核</el-tag>
  149 + <el-tag v-else-if="scope.row.djzt === '已审核'" type="success" size="mini">已审核</el-tag>
  150 + <el-tag v-else type="warning" size="mini">{{ scope.row.djzt || '无' }}</el-tag>
  151 + </template>
  152 + </el-table-column>
126 <el-table-column label="操作" fixed="right" width="100"> 153 <el-table-column label="操作" fixed="right" width="100">
127 <template slot-scope="scope"> 154 <template slot-scope="scope">
128 <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button> 155 <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button>
@@ -174,10 +201,12 @@ @@ -174,10 +201,12 @@
174 }, 201 },
175 formVisible: false, 202 formVisible: false,
176 exportBoxVisible: false, 203 exportBoxVisible: false,
177 - columnList: [ 204 + columnList: [
178 { prop: 'id', label: '单据编号' }, 205 { prop: 'id', label: '单据编号' },
179 { prop: 'djrq', label: '单据日期' }, 206 { prop: 'djrq', label: '单据日期' },
180 { prop: 'cjck', label: '出库仓库' }, 207 { prop: 'cjck', label: '出库仓库' },
  208 + { prop: 'bjsx', label: '变价系数%' },
  209 + { prop: 'cbje', label: '变价成本合计' },
181 { prop: 'jsr', label: '经手人' }, 210 { prop: 'jsr', label: '经手人' },
182 { prop: 'ysje', label: '优惠金额' }, 211 { prop: 'ysje', label: '优惠金额' },
183 { prop: 'skzh', label: '收款账户' }, 212 { prop: 'skzh', label: '收款账户' },
@@ -187,6 +216,7 @@ @@ -187,6 +216,7 @@
187 { prop: 'gzr', label: '过账人' }, 216 { prop: 'gzr', label: '过账人' },
188 { prop: 'bz', label: '备注' }, 217 { prop: 'bz', label: '备注' },
189 { prop: 'djlx', label: '单据类型' }, 218 { prop: 'djlx', label: '单据类型' },
  219 + { prop: 'djzt', label: '单据状态' },
190 ], 220 ],
191 cjckOptions : [], 221 cjckOptions : [],
192 rkckOptions : [], 222 rkckOptions : [],
@@ -335,6 +365,7 @@ @@ -335,6 +365,7 @@
335 for (let key in this.query) { 365 for (let key in this.query) {
336 this.query[key] = undefined 366 this.query[key] = undefined
337 } 367 }
  368 + this.query.djlx = '变价调拨单'
338 this.listQuery = { 369 this.listQuery = {
339 currentPage: 1, 370 currentPage: 1,
340 pageSize: 20, 371 pageSize: 20,
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/Form.vue
@@ -83,7 +83,7 @@ @@ -83,7 +83,7 @@
83 <el-option 83 <el-option
84 v-for="item in getFilteredSpbhOptions(scope.row.productQuery)" 84 v-for="item in getFilteredSpbhOptions(scope.row.productQuery)"
85 :key="item.F_Id" 85 :key="item.F_Id"
86 - :label="item.spbm + ' ' + (item.F_Spmc || '')" 86 + :label="getSpOptionLabel(item)"
87 :value="item.F_Id" 87 :value="item.F_Id"
88 /> 88 />
89 </el-select> 89 </el-select>
@@ -147,6 +147,16 @@ @@ -147,6 +147,16 @@
147 <el-input v-model="scope.row.je" placeholder="请输入" clearable readonly></el-input> 147 <el-input v-model="scope.row.je" placeholder="请输入" clearable readonly></el-input>
148 </template> 148 </template>
149 </el-table-column> 149 </el-table-column>
  150 + <el-table-column prop="cbdj" label="成本单价" width="100" align="right">
  151 + <template slot-scope="scope">
  152 + <span class="cost-cell">{{ formatCostCell(scope.row.cbdj) }}</span>
  153 + </template>
  154 + </el-table-column>
  155 + <el-table-column prop="cbje" label="成本金额" width="100" align="right">
  156 + <template slot-scope="scope">
  157 + <span class="cost-cell">{{ formatCostCell(scope.row.cbje) }}</span>
  158 + </template>
  159 + </el-table-column>
150 <el-table-column prop="description" label="备注"> 160 <el-table-column prop="description" label="备注">
151 <template slot-scope="scope"> 161 <template slot-scope="scope">
152 <el-input v-model="scope.row.description" placeholder="请输入备注" clearable></el-input> 162 <el-input v-model="scope.row.description" placeholder="请输入备注" clearable></el-input>
@@ -218,6 +228,11 @@ @@ -218,6 +228,11 @@
218 </el-form-item> 228 </el-form-item>
219 </el-col> 229 </el-col>
220 <el-col :span="8"> 230 <el-col :span="8">
  231 + <el-form-item label="出库成本合计">
  232 + <el-input :value="displayOutboundCostTotalText" placeholder="按明细成本汇总" readonly :style='{"width":"100%"}' />
  233 + </el-form-item>
  234 + </el-col>
  235 + <el-col :span="8">
221 <el-form-item label="收款金额" prop="skje"> 236 <el-form-item label="收款金额" prop="skje">
222 <el-input v-model="dataForm.skje" placeholder="自动计算" clearable :style='{"width":"100%"}' readonly> 237 <el-input v-model="dataForm.skje" placeholder="自动计算" clearable :style='{"width":"100%"}' readonly>
223 </el-input> 238 </el-input>
@@ -295,6 +310,7 @@ @@ -295,6 +310,7 @@
295 id:'', 310 id:'',
296 djrq:undefined, 311 djrq:undefined,
297 cjck:undefined, 312 cjck:undefined,
  313 + cjckId: undefined,
298 rkck:undefined, 314 rkck:undefined,
299 jsr:undefined, 315 jsr:undefined,
300 kh:undefined, 316 kh:undefined,
@@ -312,9 +328,12 @@ @@ -312,9 +328,12 @@
312 bz:undefined, 328 bz:undefined,
313 djlx:undefined, 329 djlx:undefined,
314 djzt: '', // 单据状态 330 djzt: '', // 单据状态
  331 + cbje: undefined, // 主表出库成本合计(详情/已保存单据由后端返回)
315 }, 332 },
316 rules: { 333 rules: {
317 }, 334 },
  335 + // 当前出库门店下各商品成本预览(下拉展示用,key=商品F_Id)
  336 + productCostPreviewMap: {},
318 cjckOptions : [], 337 cjckOptions : [],
319 rkckOptions : [], 338 rkckOptions : [],
320 khOptions : [], 339 khOptions : [],
@@ -338,6 +357,18 @@ @@ -338,6 +357,18 @@
338 totalJe() { 357 totalJe() {
339 return (this.dataForm.wtXsckdMxList || []).reduce((sum, row) => sum + (parseFloat(row.je) || 0), 0) 358 return (this.dataForm.wtXsckdMxList || []).reduce((sum, row) => sum + (parseFloat(row.je) || 0), 0)
340 }, 359 },
  360 + // 详情页优先用主表 cbje;新建/编辑按明细成本金额汇总
  361 + displayOutboundCostTotal() {
  362 + if (this.isDetail && this.dataForm.cbje != null && this.dataForm.cbje !== '') {
  363 + const m = parseFloat(this.dataForm.cbje)
  364 + if (!isNaN(m)) return m
  365 + }
  366 + return (this.dataForm.wtXsckdMxList || []).reduce((s, r) => s + (parseFloat(r.cbje) || 0), 0)
  367 + },
  368 + displayOutboundCostTotalText() {
  369 + const v = this.displayOutboundCostTotal
  370 + return (typeof v === 'number' && !isNaN(v)) ? v.toFixed(2) : '0.00'
  371 + },
341 372
342 }, 373 },
343 watch: {}, 374 watch: {},
@@ -633,6 +664,13 @@ @@ -633,6 +664,13 @@
633 method: 'get' 664 method: 'get'
634 }).then(res =>{ 665 }).then(res =>{
635 _this.dataForm = res.data; 666 _this.dataForm = res.data;
  667 + // 详情接口 cjck 可能为门店名称,下拉需绑定原始ID
  668 + if (res.data.cjckId) {
  669 + _this.dataForm.cjck = res.data.cjckId;
  670 + _this.dataForm.cjckId = res.data.cjckId;
  671 + } else if (res.data.cjck) {
  672 + _this.dataForm.cjckId = res.data.cjck;
  673 + }
636 console.log('编辑时加载的数据:', _this.dataForm); 674 console.log('编辑时加载的数据:', _this.dataForm);
637 console.log('明细数据:', _this.dataForm.wtXsckdMxList); 675 console.log('明细数据:', _this.dataForm.wtXsckdMxList);
638 676
@@ -672,6 +710,7 @@ @@ -672,6 +710,7 @@
672 _this.restoreSerialNumbers(); 710 _this.restoreSerialNumbers();
673 // 编辑时自动获取所有明细行的账面库存 711 // 编辑时自动获取所有明细行的账面库存
674 _this.$nextTick(() => { 712 _this.$nextTick(() => {
  713 + _this.loadProductCostPreviewMap();
675 _this.dataForm.wtXsckdMxList.forEach(row => { 714 _this.dataForm.wtXsckdMxList.forEach(row => {
676 if (row.spbh && row.ckck) { 715 if (row.spbh && row.ckck) {
677 _this.getStockQuantity(row); 716 _this.getStockQuantity(row);
@@ -684,10 +723,14 @@ @@ -684,10 +723,14 @@
684 // 新建时加载默认选项设置 723 // 新建时加载默认选项设置
685 request({ url: '/api/Extend/WtMrsz', method: 'get' }).then(res => { 724 request({ url: '/api/Extend/WtMrsz', method: 'get' }).then(res => {
686 if (res.data) { 725 if (res.data) {
687 - if (res.data.mrck) _this.dataForm.cjck = res.data.mrck 726 + if (res.data.mrck) {
  727 + _this.dataForm.cjck = res.data.mrck
  728 + _this.dataForm.cjckId = res.data.mrck
  729 + }
688 if (res.data.mrwldw) _this.dataForm.kh = res.data.mrwldw 730 if (res.data.mrwldw) _this.dataForm.kh = res.data.mrwldw
689 if (res.data.mrskzh) _this.dataForm.skzh = res.data.mrskzh 731 if (res.data.mrskzh) _this.dataForm.skzh = res.data.mrskzh
690 } 732 }
  733 + _this.loadProductCostPreviewMap();
691 }).catch(() => {}) 734 }).catch(() => {})
692 } 735 }
693 }) 736 })
@@ -1030,6 +1073,8 @@ @@ -1030,6 +1073,8 @@
1030 sl:undefined, 1073 sl:undefined,
1031 dj:undefined, 1074 dj:undefined,
1032 je:undefined, 1075 je:undefined,
  1076 + cbdj: undefined,
  1077 + cbje: undefined,
1033 selectedSerialNumbers: [], // 添加序列号数组 1078 selectedSerialNumbers: [], // 添加序列号数组
1034 loadingStock: false, // 库存加载状态 1079 loadingStock: false, // 库存加载状态
1035 productQuery: '', // 为每一行添加独立的查询条件 1080 productQuery: '', // 为每一行添加独立的查询条件
@@ -1050,7 +1095,10 @@ @@ -1050,7 +1095,10 @@
1050 this.currentBarcodeRow.sptm = barcodeData.barcode 1095 this.currentBarcodeRow.sptm = barcodeData.barcode
1051 this.currentBarcodeRow.spmc = barcodeData.productName 1096 this.currentBarcodeRow.spmc = barcodeData.productName
1052 this.currentBarcodeRow.spbh = barcodeData.productCode 1097 this.currentBarcodeRow.spbh = barcodeData.productCode
1053 - // 可根据需要回填其他字段 1098 + if (!this.currentBarcodeRow.ckck && this.dataForm.cjck) {
  1099 + this.currentBarcodeRow.ckck = this.dataForm.cjck
  1100 + }
  1101 + this.fetchRowCost(this.currentBarcodeRow)
1054 } 1102 }
1055 }, 1103 },
1056 1104
@@ -1087,6 +1135,7 @@ @@ -1087,6 +1135,7 @@
1087 if (currentRow.dj) { 1135 if (currentRow.dj) {
1088 currentRow.je = (parseFloat(currentRow.sl) * parseFloat(currentRow.dj)).toFixed(2) 1136 currentRow.je = (parseFloat(currentRow.sl) * parseFloat(currentRow.dj)).toFixed(2)
1089 } 1137 }
  1138 + this.updateRowCostAmount(currentRow)
1090 // 更新总收款金额 1139 // 更新总收款金额
1091 this.calculateTotalAmount(); 1140 this.calculateTotalAmount();
1092 1141
@@ -1097,6 +1146,7 @@ @@ -1097,6 +1146,7 @@
1097 const sl = parseFloat(row.sl) || 0; 1146 const sl = parseFloat(row.sl) || 0;
1098 const dj = parseFloat(row.dj) || 0; 1147 const dj = parseFloat(row.dj) || 0;
1099 row.je = (sl * dj).toFixed(2); 1148 row.je = (sl * dj).toFixed(2);
  1149 + this.updateRowCostAmount(row)
1100 // 自动计算总收款金额 1150 // 自动计算总收款金额
1101 this.calculateTotalAmount(); 1151 this.calculateTotalAmount();
1102 // 检查数量与序列号数量是否一致 1152 // 检查数量与序列号数量是否一致
@@ -1113,12 +1163,17 @@ @@ -1113,12 +1163,17 @@
1113 1163
1114 // 处理主表出库仓库变化 1164 // 处理主表出库仓库变化
1115 handleMainWarehouseChange(value) { 1165 handleMainWarehouseChange(value) {
  1166 + if (value != null && value !== '') {
  1167 + this.dataForm.cjckId = value
  1168 + }
1116 // 同步更新所有明细行的出库仓库 1169 // 同步更新所有明细行的出库仓库
1117 this.syncDetailWarehouses(); 1170 this.syncDetailWarehouses();
1118 - // 更新所有明细行的库存 1171 + this.loadProductCostPreviewMap()
  1172 + // 更新所有明细行的库存与成本预览
1119 this.dataForm.wtXsckdMxList.forEach(row => { 1173 this.dataForm.wtXsckdMxList.forEach(row => {
1120 if (row.spbh) { 1174 if (row.spbh) {
1121 this.getStockQuantity(row); 1175 this.getStockQuantity(row);
  1176 + this.fetchRowCost(row);
1122 } 1177 }
1123 }); 1178 });
1124 }, 1179 },
@@ -1147,7 +1202,7 @@ @@ -1147,7 +1202,7 @@
1147 try { 1202 try {
1148 // 确保参数不为空且转换为字符串 1203 // 确保参数不为空且转换为字符串
1149 const productId = String(row.spbh || ''); 1204 const productId = String(row.spbh || '');
1150 - const warehouseId = String(row.ckck || ''); 1205 + const warehouseId = String(row.ckck || this.dataForm.cjckId || this.dataForm.cjck || '');
1151 1206
1152 // 构建URL参数 1207 // 构建URL参数
1153 const url = `/api/Extend/WtXsckd/GetStockQuantity?productId=${encodeURIComponent(productId)}&warehouseId=${encodeURIComponent(warehouseId)}`; 1208 const url = `/api/Extend/WtXsckd/GetStockQuantity?productId=${encodeURIComponent(productId)}&warehouseId=${encodeURIComponent(warehouseId)}`;
@@ -1487,11 +1542,12 @@ @@ -1487,11 +1542,12 @@
1487 sums[index] = '合计'; 1542 sums[index] = '合计';
1488 return; 1543 return;
1489 } 1544 }
1490 - if (['kucun', 'sl', 'je'].includes(column.property)) {  
1491 - sums[index] = data.reduce((total, row) => { 1545 + if (['kucun', 'sl', 'je', 'cbje'].includes(column.property)) {
  1546 + const t = data.reduce((total, row) => {
1492 const value = parseFloat(row[column.property]); 1547 const value = parseFloat(row[column.property]);
1493 return total + (isNaN(value) ? 0 : value); 1548 return total + (isNaN(value) ? 0 : value);
1494 }, 0); 1549 }, 0);
  1550 + sums[index] = (column.property === 'cbje' || column.property === 'je') ? t.toFixed(2) : t;
1495 } else { 1551 } else {
1496 sums[index] = ''; 1552 sums[index] = '';
1497 } 1553 }
@@ -1503,7 +1559,93 @@ @@ -1503,7 +1559,93 @@
1503 const label = (option.label || '').toLowerCase(); 1559 const label = (option.label || '').toLowerCase();
1504 return label.includes(query.toLowerCase()); 1560 return label.includes(query.toLowerCase());
1505 }, 1561 },
  1562 + // 成本/库存接口使用的门店或仓库ID
  1563 + costContextCk() {
  1564 + if (this.dataForm.cjckId != null && this.dataForm.cjckId !== '') {
  1565 + return this.dataForm.cjckId
  1566 + }
  1567 + return this.dataForm.cjck
  1568 + },
  1569 + formatCostCell(val) {
  1570 + if (val == null || val === '') return '无'
  1571 + const n = parseFloat(val)
  1572 + if (isNaN(n)) return '无'
  1573 + return n.toFixed(2)
  1574 + },
  1575 + getSpOptionLabel(item) {
  1576 + const base = ((item.spbm || '') + ' ' + (item.F_Spmc || '')).trim()
  1577 + const c = this.productCostPreviewMap[item.F_Id]
  1578 + if (c != null && c !== '' && !isNaN(parseFloat(c))) {
  1579 + return base + ' (成本¥' + parseFloat(c).toFixed(2) + ')'
  1580 + }
  1581 + if (!this.costContextCk()) {
  1582 + return base + ' (选仓库后显示成本)'
  1583 + }
  1584 + return base
  1585 + },
  1586 + async loadProductCostPreviewMap() {
  1587 + const ck = this.costContextCk()
  1588 + if (!ck) {
  1589 + this.productCostPreviewMap = {}
  1590 + return
  1591 + }
  1592 + try {
  1593 + const res = await request({
  1594 + url: '/api/Extend/WtTjd/Actions/GetCostPreviewForStore',
  1595 + method: 'get',
  1596 + data: { ck }
  1597 + })
  1598 + let items = res.data
  1599 + if (items && items.items) items = items.items
  1600 + else if (res.items) items = res.items
  1601 + const map = {}
  1602 + ;(Array.isArray(items) ? items : []).forEach(x => {
  1603 + if (x && x.spbh != null && map[x.spbh] == null) {
  1604 + map[x.spbh] = parseFloat(x.cbj) || 0
  1605 + }
  1606 + })
  1607 + this.productCostPreviewMap = map
  1608 + } catch (e) {
  1609 + console.warn('loadProductCostPreviewMap failed', e)
  1610 + this.productCostPreviewMap = {}
  1611 + }
  1612 + },
  1613 + async fetchRowCost(row) {
  1614 + const ck = row.ckck || this.costContextCk()
  1615 + if (!row.spbh || !ck) {
  1616 + this.$set(row, 'cbdj', undefined)
  1617 + this.$set(row, 'cbje', undefined)
  1618 + return
  1619 + }
  1620 + try {
  1621 + const res = await request({
  1622 + url: '/api/Extend/WtTjd/Actions/GetCost',
  1623 + method: 'get',
  1624 + data: { spbh: row.spbh, ck }
  1625 + })
  1626 + let payload = res.data
  1627 + if (payload && typeof payload.cbj === 'undefined' && res.data && res.data.data) {
  1628 + payload = res.data.data
  1629 + }
  1630 + const cbj = parseFloat(payload && payload.cbj != null ? payload.cbj : 0) || 0
  1631 + this.$set(row, 'cbdj', cbj)
  1632 + this.updateRowCostAmount(row)
  1633 + } catch (e) {
  1634 + this.$set(row, 'cbdj', 0)
  1635 + this.updateRowCostAmount(row)
  1636 + }
  1637 + },
  1638 + updateRowCostAmount(row) {
  1639 + const sl = parseFloat(row.sl) || 0
  1640 + const cbdj = parseFloat(row.cbdj) || 0
  1641 + this.$set(row, 'cbje', (cbdj * sl).toFixed(2))
  1642 + },
1506 handleProductChange(row) { 1643 handleProductChange(row) {
  1644 + if (!row.spbh) {
  1645 + this.$set(row, 'cbdj', undefined)
  1646 + this.$set(row, 'cbje', undefined)
  1647 + return
  1648 + }
1507 // 选中商品后可自动回填商品名称等信息 1649 // 选中商品后可自动回填商品名称等信息
1508 const product = this.spbhOptions.find(item => item.F_Id === row.spbh); 1650 const product = this.spbhOptions.find(item => item.F_Id === row.spbh);
1509 if (product) { 1651 if (product) {
@@ -1536,9 +1678,11 @@ @@ -1536,9 +1678,11 @@
1536 return; 1678 return;
1537 } 1679 }
1538 1680
1539 - // 如果已选择出库仓库,自动获取库存 1681 + // 如果已选择出库仓库,自动获取库存与成本
1540 console.log('开始获取库存...'); 1682 console.log('开始获取库存...');
1541 - this.getStockQuantity(row); 1683 + this.getStockQuantity(row).finally(() => {
  1684 + this.fetchRowCost(row)
  1685 + });
1542 } 1686 }
1543 }, 1687 },
1544 handleProductQuery(val, scope) { 1688 handleProductQuery(val, scope) {
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/index.vue
@@ -127,6 +127,9 @@ @@ -127,6 +127,9 @@
127 <el-table-column label="优惠金额" align="left" min-width="90"> 127 <el-table-column label="优惠金额" align="left" min-width="90">
128 <template slot-scope="scope">{{ getDisplayDiscountAmount(scope.row) }}</template> 128 <template slot-scope="scope">{{ getDisplayDiscountAmount(scope.row) }}</template>
129 </el-table-column> 129 </el-table-column>
  130 + <el-table-column label="出库成本" align="left" min-width="90">
  131 + <template slot-scope="scope">{{ getDisplayOutboundCost(scope.row) }}</template>
  132 + </el-table-column>
130 <el-table-column label="收款账户" prop="skzh" align="left"> 133 <el-table-column label="收款账户" prop="skzh" align="left">
131 <template slot-scope="scope">{{ scope.row.skzh | dynamicText(skzhOptions) }}</template> 134 <template slot-scope="scope">{{ scope.row.skzh | dynamicText(skzhOptions) }}</template>
132 </el-table-column> 135 </el-table-column>
@@ -259,6 +262,11 @@ @@ -259,6 +262,11 @@
259 } 262 }
260 return (row.skje != null && row.skje !== '') ? parseFloat(row.skje).toFixed(2) : '0.00'; 263 return (row.skje != null && row.skje !== '') ? parseFloat(row.skje).toFixed(2) : '0.00';
261 }, 264 },
  265 + // ✅ 出库成本:使用主表字段 cbje,空值按金额风格显示为 0.00
  266 + getDisplayOutboundCost(row) {
  267 + const val = parseFloat(row.cbje);
  268 + return isNaN(val) ? '0.00' : val.toFixed(2);
  269 + },
262 getcjckOptions(){ 270 getcjckOptions(){
263 previewDataInterface('681758216954053893').then(res => { 271 previewDataInterface('681758216954053893').then(res => {
264 this.cjckOptions = res.data 272 this.cjckOptions = res.data
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdCrInput.cs
@@ -39,6 +39,21 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd @@ -39,6 +39,21 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
39 public decimal? ydje { get; set; } 39 public decimal? ydje { get; set; }
40 40
41 /// <summary> 41 /// <summary>
  42 + /// 成本金额(主表汇总)
  43 + /// </summary>
  44 + public decimal? cbje { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 变价系数%(变价调拨单)
  48 + /// </summary>
  49 + public decimal? bjsx { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 是否草稿保存(草稿不触发变价调拨成本过账)
  53 + /// </summary>
  54 + public bool isDraft { get; set; }
  55 +
  56 + /// <summary>
42 /// 优惠金额 57 /// 优惠金额
43 /// </summary> 58 /// </summary>
44 public decimal ysje { get; set; } 59 public decimal ysje { get; set; }
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdInfoOutput.cs
@@ -24,6 +24,11 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd @@ -24,6 +24,11 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
24 public string cjck { get; set; } 24 public string cjck { get; set; }
25 25
26 /// <summary> 26 /// <summary>
  27 + /// 出库仓库/门店原始ID(cjck 可能为展示名称,成本与库存接口请用此字段)
  28 + /// </summary>
  29 + public string cjckId { get; set; }
  30 +
  31 + /// <summary>
27 /// 入库仓库 32 /// 入库仓库
28 /// </summary> 33 /// </summary>
29 public string rkck { get; set; } 34 public string rkck { get; set; }
@@ -39,6 +44,16 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd @@ -39,6 +44,16 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
39 public decimal? ydje { get; set; } 44 public decimal? ydje { get; set; }
40 45
41 /// <summary> 46 /// <summary>
  47 + /// 出库成本合计(主表汇总)
  48 + /// </summary>
  49 + public decimal? cbje { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 变价系数%(变价调拨单)
  53 + /// </summary>
  54 + public decimal? bjsx { get; set; }
  55 +
  56 + /// <summary>
42 /// 优惠金额 57 /// 优惠金额
43 /// </summary> 58 /// </summary>
44 public decimal ysje { get; set; } 59 public decimal ysje { get; set; }
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdListOutput.cs
@@ -33,6 +33,16 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd @@ -33,6 +33,16 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
33 public decimal? ydje { get; set; } 33 public decimal? ydje { get; set; }
34 34
35 /// <summary> 35 /// <summary>
  36 + /// 成本金额(主表汇总)
  37 + /// </summary>
  38 + public decimal? cbje { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 变价系数%(变价调拨单)
  42 + /// </summary>
  43 + public decimal? bjsx { get; set; }
  44 +
  45 + /// <summary>
36 /// 优惠金额 46 /// 优惠金额
37 /// </summary> 47 /// </summary>
38 public decimal ysje { get; set; } 48 public decimal ysje { get; set; }
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdMxCrInput.cs
@@ -64,6 +64,11 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd @@ -64,6 +64,11 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
64 public decimal je { get; set; } 64 public decimal je { get; set; }
65 65
66 /// <summary> 66 /// <summary>
  67 + /// 变价后成本单价(变价调拨单,服务端会按系数重算覆盖)
  68 + /// </summary>
  69 + public decimal? bjhcb { get; set; }
  70 +
  71 + /// <summary>
67 /// 单据类型 72 /// 单据类型
68 /// </summary> 73 /// </summary>
69 public string djlx { get; set; } 74 public string djlx { get; set; }
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdMxInfoOutput.cs
@@ -67,6 +67,22 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd @@ -67,6 +67,22 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
67 /// 金额 67 /// 金额
68 /// </summary> 68 /// </summary>
69 public decimal je { get; set; } 69 public decimal je { get; set; }
  70 +
  71 + /// <summary>
  72 + /// 成本单价(出库快照)
  73 + /// </summary>
  74 + public decimal? cbdj { get; set; }
  75 +
  76 + /// <summary>
  77 + /// 成本金额(出库快照)
  78 + /// </summary>
  79 + public decimal? cbje { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 变价后成本单价(变价调拨单)
  83 + /// </summary>
  84 + public decimal? bjhcb { get; set; }
  85 +
70 /// <summary> 86 /// <summary>
71 /// 单据类型 87 /// 单据类型
72 /// </summary> 88 /// </summary>
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtXsckdEntity.cs
@@ -48,6 +48,18 @@ namespace NCC.Extend.Entitys @@ -48,6 +48,18 @@ namespace NCC.Extend.Entitys
48 public decimal? Ydje { get; set; } 48 public decimal? Ydje { get; set; }
49 49
50 /// <summary> 50 /// <summary>
  51 + /// 成本金额(主表汇总)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "cbje", IsNullable = true)]
  54 + public decimal? Cbje { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 变价系数%(变价调拨单:统一应用到所有明细,变价后单价=原成本/(系数/100))
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "bjsx", IsNullable = true)]
  60 + public decimal? Bjsx { get; set; }
  61 +
  62 + /// <summary>
51 /// 优惠金额 63 /// 优惠金额
52 /// </summary> 64 /// </summary>
53 [SugarColumn(ColumnName = "ysje")] 65 [SugarColumn(ColumnName = "ysje")]
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtXsckdMxEntity.cs
@@ -107,5 +107,11 @@ namespace NCC.Extend.Entitys @@ -107,5 +107,11 @@ namespace NCC.Extend.Entitys
107 [SugarColumn(ColumnName = "cbje")] 107 [SugarColumn(ColumnName = "cbje")]
108 public decimal? Cbje { get; set; } 108 public decimal? Cbje { get; set; }
109 109
  110 + /// <summary>
  111 + /// 变价后成本单价(变价调拨单入库计价)
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "bjhcb", IsNullable = true)]
  114 + public decimal? Bjhcb { get; set; }
  115 +
110 } 116 }
111 } 117 }
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtTjdService.cs
@@ -426,18 +426,79 @@ namespace NCC.Extend.WtTjd @@ -426,18 +426,79 @@ namespace NCC.Extend.WtTjd
426 /// 查询指定商品在指定仓库的成本价 426 /// 查询指定商品在指定仓库的成本价
427 /// </summary> 427 /// </summary>
428 /// <param name="spbh">商品编号</param> 428 /// <param name="spbh">商品编号</param>
429 - /// <param name="ck">仓库ID</param>  
430 - /// <returns>成本价</returns> 429 + /// <param name="ck">门店ID或仓库ID(与销售出库单主表 cjck / 明细 ckck 一致)</param>
  430 + /// <returns>成本价与在库数量</returns>
431 [HttpGet("Actions/GetCost")] 431 [HttpGet("Actions/GetCost")]
432 public async Task<dynamic> GetCost([FromQuery] string spbh, [FromQuery] string ck) 432 public async Task<dynamic> GetCost([FromQuery] string spbh, [FromQuery] string ck)
433 { 433 {
434 if (string.IsNullOrEmpty(spbh) || string.IsNullOrEmpty(ck)) 434 if (string.IsNullOrEmpty(spbh) || string.IsNullOrEmpty(ck))
435 - return new { cbj = 0m }; 435 + return new { cbj = 0m, sl = 0 };
  436 +
  437 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  438 + .Where(c => c.Ssmd == ck || c.Id == ck)
  439 + .Select(c => c.Id)
  440 + .ToListAsync();
  441 + if (warehouseIds == null || warehouseIds.Count == 0)
  442 + warehouseIds = new List<string> { ck };
  443 +
  444 + var inClause = string.Join(",", warehouseIds.Select((_, i) => $"@ck{i}"));
  445 + var parms = warehouseIds
  446 + .Select((id, i) => new SugarParameter($"@ck{i}", id))
  447 + .Concat(new[] { new SugarParameter("@spbh", spbh) })
  448 + .ToArray();
  449 +
  450 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(
  451 + $"SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck IN ({inClause}) AND cbj > 0 LIMIT 1",
  452 + parms);
  453 +
  454 + if (rows != null && rows.Count > 0)
  455 + {
  456 + return new
  457 + {
  458 + cbj = Convert.ToDecimal(rows[0].cbj),
  459 + sl = Convert.ToInt32(rows[0].sl)
  460 + };
  461 + }
  462 +
  463 + return new { cbj = 0m, sl = 0 };
  464 + }
  465 +
  466 + /// <summary>
  467 + /// 按出库门店/仓库批量查询成本价(用于销售出库单商品下拉展示,口径与 GetCost 一致)
  468 + /// </summary>
  469 + /// <param name="ck">门店ID或仓库ID</param>
  470 + [HttpGet("Actions/GetCostPreviewForStore")]
  471 + public async Task<dynamic> GetCostPreviewForStore([FromQuery] string ck)
  472 + {
  473 + if (string.IsNullOrEmpty(ck))
  474 + return new { items = Array.Empty<object>() };
  475 +
  476 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  477 + .Where(c => c.Ssmd == ck || c.Id == ck)
  478 + .Select(c => c.Id)
  479 + .ToListAsync();
  480 + if (warehouseIds == null || warehouseIds.Count == 0)
  481 + warehouseIds = new List<string> { ck };
436 482
437 - var cost = await _db.Queryable<WtSpCostEntity>()  
438 - .FirstAsync(c => c.Spbh == spbh && c.Ck == ck); 483 + var inClause = string.Join(",", warehouseIds.Select((_, i) => $"@ck{i}"));
  484 + var parms = warehouseIds.Select((id, i) => new SugarParameter($"@ck{i}", id)).ToArray();
  485 +
  486 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(
  487 + $"SELECT spbh, cbj FROM wt_sp_cost WHERE ck IN ({inClause}) AND cbj > 0",
  488 + parms);
  489 +
  490 + var list = new List<object>();
  491 + var seen = new HashSet<string>();
  492 + foreach (var r in rows ?? new List<dynamic>())
  493 + {
  494 + var spbh = r.spbh?.ToString();
  495 + if (string.IsNullOrEmpty(spbh) || seen.Contains(spbh))
  496 + continue;
  497 + seen.Add(spbh);
  498 + list.Add(new { spbh, cbj = Convert.ToDecimal(r.cbj) });
  499 + }
439 500
440 - return new { cbj = cost?.Cbj ?? 0m, sl = cost?.Sl ?? 0 }; 501 + return new { items = list };
441 } 502 }
442 503
443 /// <summary> 504 /// <summary>
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtXsckdService.cs
@@ -47,6 +47,9 @@ namespace NCC.Extend.WtXsckd @@ -47,6 +47,9 @@ namespace NCC.Extend.WtXsckd
47 // 序列号生成信号量,确保线程安全 47 // 序列号生成信号量,确保线程安全
48 private static readonly SemaphoreSlim _serialNumberSemaphore = new SemaphoreSlim(1, 1); 48 private static readonly SemaphoreSlim _serialNumberSemaphore = new SemaphoreSlim(1, 1);
49 private static bool _ydjeColumnChecked; 49 private static bool _ydjeColumnChecked;
  50 + private static bool _cbjeColumnChecked;
  51 + private static bool _bjsxColumnChecked;
  52 + private static bool _bjhcbColumnChecked;
50 53
51 /// <summary> 54 /// <summary>
52 /// 确保原价金额列存在(迁移) 55 /// 确保原价金额列存在(迁移)
@@ -74,6 +77,81 @@ namespace NCC.Extend.WtXsckd @@ -74,6 +77,81 @@ namespace NCC.Extend.WtXsckd
74 } 77 }
75 78
76 /// <summary> 79 /// <summary>
  80 + /// 确保成本金额列存在(迁移)
  81 + /// </summary>
  82 + private void EnsureCbjeColumn()
  83 + {
  84 + if (_cbjeColumnChecked) return;
  85 + lock (typeof(WtXsckdService))
  86 + {
  87 + if (_cbjeColumnChecked) return;
  88 + try
  89 + {
  90 + if (!_db.DbMaintenance.IsAnyTable("wt_xsckd")) { _cbjeColumnChecked = true; return; }
  91 + var columns = _db.DbMaintenance.GetColumnInfosByTableName("wt_xsckd");
  92 + var columnNames = columns.Select(c => c.DbColumnName.ToLower()).ToHashSet();
  93 + if (!columnNames.Contains("cbje"))
  94 + {
  95 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_xsckd` ADD COLUMN `cbje` decimal(18,2) NULL COMMENT '成本金额'");
  96 + Console.WriteLine("✅ 已添加字段: cbje (成本金额)");
  97 + }
  98 + }
  99 + catch (Exception ex) { Console.WriteLine($"EnsureCbjeColumn: {ex.Message}"); }
  100 + _cbjeColumnChecked = true;
  101 + }
  102 + }
  103 +
  104 + /// <summary>
  105 + /// 确保变价系数列存在(变价调拨单主表)
  106 + /// </summary>
  107 + private void EnsureBjsxColumn()
  108 + {
  109 + if (_bjsxColumnChecked) return;
  110 + lock (typeof(WtXsckdService))
  111 + {
  112 + if (_bjsxColumnChecked) return;
  113 + try
  114 + {
  115 + if (!_db.DbMaintenance.IsAnyTable("wt_xsckd")) { _bjsxColumnChecked = true; return; }
  116 + var columns = _db.DbMaintenance.GetColumnInfosByTableName("wt_xsckd");
  117 + var columnNames = columns.Select(c => c.DbColumnName.ToLower()).ToHashSet();
  118 + if (!columnNames.Contains("bjsx"))
  119 + {
  120 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_xsckd` ADD COLUMN `bjsx` decimal(18,4) NULL COMMENT '变价系数%'");
  121 + Console.WriteLine("✅ 已添加字段: bjsx (变价系数%)");
  122 + }
  123 + }
  124 + catch (Exception ex) { Console.WriteLine($"EnsureBjsxColumn: {ex.Message}"); }
  125 + _bjsxColumnChecked = true;
  126 + }
  127 + }
  128 +
  129 + /// <summary>
  130 + /// 确保变价后成本单价列存在(变价调拨单明细)
  131 + /// </summary>
  132 + private void EnsureBjhcbColumn()
  133 + {
  134 + if (_bjhcbColumnChecked) return;
  135 + lock (typeof(WtXsckdService))
  136 + {
  137 + if (_bjhcbColumnChecked) return;
  138 + try
  139 + {
  140 + if (!_db.DbMaintenance.IsAnyTable("wt_xsckd_mx")) { _bjhcbColumnChecked = true; return; }
  141 + var columns = _db.DbMaintenance.GetColumnInfosByTableName("wt_xsckd_mx");
  142 + var columnNames = columns.Select(c => c.DbColumnName.ToLower()).ToHashSet();
  143 + if (!columnNames.Contains("bjhcb"))
  144 + {
  145 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_xsckd_mx` ADD COLUMN `bjhcb` decimal(18,4) NULL COMMENT '变价后成本单价'");
  146 + Console.WriteLine("✅ 已添加字段: bjhcb (变价后成本单价)");
  147 + }
  148 + }
  149 + catch (Exception ex) { Console.WriteLine($"EnsureBjhcbColumn: {ex.Message}"); }
  150 + _bjhcbColumnChecked = true;
  151 + }
  152 + }
  153 +
  154 + /// <summary>
77 /// 初始化一个<see cref="WtXsckdService"/>类型的新实例 155 /// 初始化一个<see cref="WtXsckdService"/>类型的新实例
78 /// </summary> 156 /// </summary>
79 public WtXsckdService( 157 public WtXsckdService(
@@ -95,8 +173,11 @@ namespace NCC.Extend.WtXsckd @@ -95,8 +173,11 @@ namespace NCC.Extend.WtXsckd
95 [HttpGet("{id}")] 173 [HttpGet("{id}")]
96 public async Task<dynamic> GetInfo(string id) 174 public async Task<dynamic> GetInfo(string id)
97 { 175 {
  176 + EnsureBjsxColumn();
  177 + EnsureBjhcbColumn();
98 var entity = await _db.Queryable<WtXsckdEntity>().FirstAsync(p => p.Id == id); 178 var entity = await _db.Queryable<WtXsckdEntity>().FirstAsync(p => p.Id == id);
99 var output = entity.Adapt<WtXsckdInfoOutput>(); 179 var output = entity.Adapt<WtXsckdInfoOutput>();
  180 + output.cjckId = entity.Cjck;
100 181
101 // ✅ 将出库仓库ID转换为门店名称 182 // ✅ 将出库仓库ID转换为门店名称
102 if (!string.IsNullOrEmpty(entity.Cjck)) 183 if (!string.IsNullOrEmpty(entity.Cjck))
@@ -193,6 +274,8 @@ namespace NCC.Extend.WtXsckd @@ -193,6 +274,8 @@ namespace NCC.Extend.WtXsckd
193 public async Task<dynamic> GetList([FromQuery] WtXsckdListQueryInput input) 274 public async Task<dynamic> GetList([FromQuery] WtXsckdListQueryInput input)
194 { 275 {
195 EnsureYdjeColumn(); 276 EnsureYdjeColumn();
  277 + EnsureCbjeColumn();
  278 + EnsureBjsxColumn();
196 var sidx = input.sidx == null ? "id" : input.sidx; 279 var sidx = input.sidx == null ? "id" : input.sidx;
197 List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null; 280 List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null;
198 DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null; 281 DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null;
@@ -230,6 +313,8 @@ namespace NCC.Extend.WtXsckd @@ -230,6 +313,8 @@ namespace NCC.Extend.WtXsckd
230 // jsr=xsckd.Jsr, 313 // jsr=xsckd.Jsr,
231 jsr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == xsckd.Jsr).Select(u => u.RealName), 314 jsr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == xsckd.Jsr).Select(u => u.RealName),
232 ydje = xsckd.Ydje, 315 ydje = xsckd.Ydje,
  316 + cbje = xsckd.Cbje,
  317 + bjsx = xsckd.Bjsx,
233 ysje = xsckd.Ysje, 318 ysje = xsckd.Ysje,
234 skzh = xsckd.Skzh, 319 skzh = xsckd.Skzh,
235 skje = xsckd.Skje, 320 skje = xsckd.Skje,
@@ -262,10 +347,15 @@ namespace NCC.Extend.WtXsckd @@ -262,10 +347,15 @@ namespace NCC.Extend.WtXsckd
262 [AllowAnonymous] // 允许抖音物流系统匿名调用 347 [AllowAnonymous] // 允许抖音物流系统匿名调用
263 public async Task<dynamic> Create([FromBody] WtXsckdCrInput input) 348 public async Task<dynamic> Create([FromBody] WtXsckdCrInput input)
264 { 349 {
  350 + EnsureCbjeColumn();
  351 + EnsureBjsxColumn();
  352 + EnsureBjhcbColumn();
265 // 匿名访问时,userInfo可能为null,需要安全处理 353 // 匿名访问时,userInfo可能为null,需要安全处理
266 var userInfo = await _userManager.GetUserInfo(); 354 var userInfo = await _userManager.GetUserInfo();
267 var entity = input.Adapt<WtXsckdEntity>(); 355 var entity = input.Adapt<WtXsckdEntity>();
268 entity.Ydje = input.ydje; 356 entity.Ydje = input.ydje;
  357 + entity.Cbje = input.cbje;
  358 + entity.Bjsx = input.bjsx;
269 359
270 // 获取用户ID(匿名访问时为null或空字符串) 360 // 获取用户ID(匿名访问时为null或空字符串)
271 string? userId = null; 361 string? userId = null;
@@ -675,7 +765,9 @@ namespace NCC.Extend.WtXsckd @@ -675,7 +765,9 @@ namespace NCC.Extend.WtXsckd
675 } 765 }
676 766
677 // ========== P4: 自动维护 wt_sp_cost 成本表 ========== 767 // ========== P4: 自动维护 wt_sp_cost 成本表 ==========
678 - await UpdateSpCostOnCreate(input.djlx, entity, wtXsckdMxEntityList); 768 + await UpdateSpCostOnCreate(input.djlx, entity, wtXsckdMxEntityList, input.isDraft);
  769 + await RecalculateMainCbje(newEntity.Id, input.djlx,
  770 + input.isDraft || string.Equals(entity.Djzt, "草稿", StringComparison.Ordinal));
679 771
680 //关闭事务 772 //关闭事务
681 _db.CommitTran(); 773 _db.CommitTran();
@@ -898,7 +990,7 @@ namespace NCC.Extend.WtXsckd @@ -898,7 +990,7 @@ namespace NCC.Extend.WtXsckd
898 { 990 {
899 exportData = await this.GetNoPagingList(input); 991 exportData = await this.GetNoPagingList(input);
900 } 992 }
901 - List<ParamsModel> paramList = "[{\"value\":\"单据编号\",\"field\":\"id\"},{\"value\":\"单据日期\",\"field\":\"djrq\"},{\"value\":\"出库仓库\",\"field\":\"cjck\"},{\"value\":\"经手人\",\"field\":\"jsr\"},{\"value\":\"优惠金额\",\"field\":\"ysje\"},{\"value\":\"收款账户\",\"field\":\"skzh\"},{\"value\":\"收款金额\",\"field\":\"skje\"},{\"value\":\"制单人\",\"field\":\"zdr\"},{\"value\":\"审核人\",\"field\":\"shr\"},{\"value\":\"过账人\",\"field\":\"gzr\"},{\"value\":\"备注\",\"field\":\"bz\"},{\"value\":\"单据类型\",\"field\":\"djlx\"},]".ToList<ParamsModel>(); 993 + List<ParamsModel> paramList = "[{\"value\":\"单据编号\",\"field\":\"id\"},{\"value\":\"单据日期\",\"field\":\"djrq\"},{\"value\":\"出库仓库\",\"field\":\"cjck\"},{\"value\":\"变价系数%\",\"field\":\"bjsx\"},{\"value\":\"成本金额\",\"field\":\"cbje\"},{\"value\":\"经手人\",\"field\":\"jsr\"},{\"value\":\"优惠金额\",\"field\":\"ysje\"},{\"value\":\"收款账户\",\"field\":\"skzh\"},{\"value\":\"收款金额\",\"field\":\"skje\"},{\"value\":\"制单人\",\"field\":\"zdr\"},{\"value\":\"审核人\",\"field\":\"shr\"},{\"value\":\"过账人\",\"field\":\"gzr\"},{\"value\":\"备注\",\"field\":\"bz\"},{\"value\":\"单据类型\",\"field\":\"djlx\"},{\"value\":\"单据状态\",\"field\":\"djzt\"},]".ToList<ParamsModel>();
902 ExcelConfig excelconfig = new ExcelConfig(); 994 ExcelConfig excelconfig = new ExcelConfig();
903 excelconfig.FileName = "销售出库单.xls"; 995 excelconfig.FileName = "销售出库单.xls";
904 excelconfig.HeadFont = "微软雅黑"; 996 excelconfig.HeadFont = "微软雅黑";
@@ -968,7 +1060,13 @@ namespace NCC.Extend.WtXsckd @@ -968,7 +1060,13 @@ namespace NCC.Extend.WtXsckd
968 [HttpPut("{id}")] 1060 [HttpPut("{id}")]
969 public async Task Update(string id, [FromBody] WtXsckdUpInput input) 1061 public async Task Update(string id, [FromBody] WtXsckdUpInput input)
970 { 1062 {
  1063 + EnsureCbjeColumn();
  1064 + EnsureBjsxColumn();
  1065 + EnsureBjhcbColumn();
  1066 + var oldHeader = await _db.Queryable<WtXsckdEntity>().FirstAsync(p => p.Id == id);
  1067 + var oldMxList = await _db.Queryable<WtXsckdMxEntity>().Where(u => u.Djbh == id).ToListAsync();
971 var entity = input.Adapt<WtXsckdEntity>(); 1068 var entity = input.Adapt<WtXsckdEntity>();
  1069 + entity.Bjsx = input.bjsx;
972 1070
973 // ✅ 确保会员手机号码字段被正确映射和保存 1071 // ✅ 确保会员手机号码字段被正确映射和保存
974 entity.Hysjh = input.hysjh ?? ""; 1072 entity.Hysjh = input.hysjh ?? "";
@@ -978,6 +1076,14 @@ namespace NCC.Extend.WtXsckd @@ -978,6 +1076,14 @@ namespace NCC.Extend.WtXsckd
978 { 1076 {
979 //开启事务 1077 //开启事务
980 _db.BeginTran(); 1078 _db.BeginTran();
  1079 +
  1080 + // 变价调拨单:编辑前先按库中旧单回滚成本,避免重复过账
  1081 + if (oldHeader.Djlx == "变价调拨单"
  1082 + && !string.Equals(oldHeader.Djzt, "草稿", StringComparison.Ordinal)
  1083 + && oldMxList.Count > 0)
  1084 + {
  1085 + await RollbackVariablePriceTransferCost(oldHeader, oldMxList);
  1086 + }
981 1087
982 //更新销售出库单记录 1088 //更新销售出库单记录
983 await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); 1089 await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
@@ -997,6 +1103,10 @@ namespace NCC.Extend.WtXsckd @@ -997,6 +1103,10 @@ namespace NCC.Extend.WtXsckd
997 await _db.Insertable(wtXsckdMxEntityList).ExecuteCommandAsync(); 1103 await _db.Insertable(wtXsckdMxEntityList).ExecuteCommandAsync();
998 } 1104 }
999 1105
  1106 + await UpdateSpCostOnCreate(entity.Djlx, entity, wtXsckdMxEntityList, input.isDraft);
  1107 + await RecalculateMainCbje(id, entity.Djlx,
  1108 + input.isDraft || string.Equals(entity.Djzt, "草稿", StringComparison.Ordinal));
  1109 +
1000 // 自动生成导购提成记录 1110 // 自动生成导购提成记录
1001 if (entity.Djlx == "销售出库单") 1111 if (entity.Djlx == "销售出库单")
1002 { 1112 {
@@ -1286,6 +1396,53 @@ ORDER BY t.`商品编号`&quot;; @@ -1286,6 +1396,53 @@ ORDER BY t.`商品编号`&quot;;
1286 } 1396 }
1287 } 1397 }
1288 1398
  1399 + /// <summary>
  1400 + /// 按商品与出库门店/仓库查询加权成本单价(与销售出库、变价调拨过账取价逻辑一致,供前端录入参考)
  1401 + /// </summary>
  1402 + /// <param name="spbh">商品主键 F_Id</param>
  1403 + /// <param name="ckOrMdId">明细 ckck 或主表 cjck(可为门店ID或仓库ID)</param>
  1404 + [HttpGet("GetOutboundCostPrice")]
  1405 + public async Task<dynamic> GetOutboundCostPrice(string spbh, string ckOrMdId)
  1406 + {
  1407 + if (string.IsNullOrWhiteSpace(spbh) || string.IsNullOrWhiteSpace(ckOrMdId))
  1408 + {
  1409 + return new { success = false, msg = "商品编号与仓库不能为空", data = (decimal?)null };
  1410 + }
  1411 +
  1412 + try
  1413 + {
  1414 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  1415 + .Where(c => c.Ssmd == ckOrMdId || c.Id == ckOrMdId)
  1416 + .Select(c => c.Id)
  1417 + .ToListAsync();
  1418 + if (warehouseIds == null || warehouseIds.Count == 0)
  1419 + {
  1420 + warehouseIds = new List<string> { ckOrMdId.Trim() };
  1421 + }
  1422 +
  1423 + var ps = new List<SugarParameter> { new SugarParameter("@spbh", spbh.Trim()) };
  1424 + for (var i = 0; i < warehouseIds.Count; i++)
  1425 + ps.Add(new SugarParameter($"@ck{i}", warehouseIds[i]));
  1426 + var inSql = string.Join(",", warehouseIds.Select((_, i) => $"@ck{i}"));
  1427 +
  1428 + var rows = await _db.Ado.SqlQueryAsync<decimal?>(
  1429 + $"SELECT cbj FROM wt_sp_cost WHERE spbh = @spbh AND ck IN ({inSql}) AND cbj > 0 ORDER BY sl DESC LIMIT 1",
  1430 + ps.ToArray());
  1431 +
  1432 + var price = rows?.FirstOrDefault();
  1433 + if (price == null || price <= 0)
  1434 + {
  1435 + return new { success = false, msg = "未查询到成本单价,请先维护采购/入库成本", data = (decimal?)null };
  1436 + }
  1437 +
  1438 + return new { success = true, data = Math.Round(price.Value, 4), msg = "操作成功" };
  1439 + }
  1440 + catch (Exception ex)
  1441 + {
  1442 + return new { success = false, msg = "查询成本失败: " + ex.Message, data = (decimal?)null };
  1443 + }
  1444 + }
  1445 +
1289 [HttpGet("GetSaleStatistics")] 1446 [HttpGet("GetSaleStatistics")]
1290 public async Task<dynamic> GetSaleStatistics() 1447 public async Task<dynamic> GetSaleStatistics()
1291 { 1448 {
@@ -2820,6 +2977,30 @@ ORDER BY t.`商品编号`;&quot;; @@ -2820,6 +2977,30 @@ ORDER BY t.`商品编号`;&quot;;
2820 } 2977 }
2821 } 2978 }
2822 2979
  2980 + /// <summary>
  2981 + /// 销售/预售/变价调拨单重算并回写主表成本金额(变价调拨为明细变价后成本金额合计)
  2982 + /// </summary>
  2983 + /// <param name="skipForBjdDraft">变价调拨草稿时不回写主表 cbje(明细未过账)</param>
  2984 + private async Task RecalculateMainCbje(string djbh, string djlx, bool skipForBjdDraft = false)
  2985 + {
  2986 + if (string.IsNullOrEmpty(djbh)) return;
  2987 + if (djlx == "变价调拨单" && skipForBjdDraft) return;
  2988 + if (djlx != "销售出库单" && djlx != "预售出库单" && djlx != "变价调拨单") return;
  2989 +
  2990 + decimal cbje = 0;
  2991 + var result = await _db.Ado.SqlQueryAsync<decimal?>(
  2992 + "SELECT IFNULL(SUM(IFNULL(cbje, 0)), 0) FROM wt_xsckd_mx WHERE djbh = @djbh",
  2993 + new { djbh });
  2994 + if (result != null && result.Count > 0)
  2995 + {
  2996 + cbje = result[0] ?? 0;
  2997 + }
  2998 +
  2999 + await _db.Ado.ExecuteCommandAsync(
  3000 + "UPDATE wt_xsckd SET cbje = @cbje WHERE F_Id = @id",
  3001 + new { cbje, id = djbh });
  3002 + }
  3003 +
2823 #region P4: wt_sp_cost 成本表自动维护 3004 #region P4: wt_sp_cost 成本表自动维护
2824 3005
2825 /// <summary> 3006 /// <summary>
@@ -2880,10 +3061,29 @@ ORDER BY t.`商品编号`;&quot;; @@ -2880,10 +3061,29 @@ ORDER BY t.`商品编号`;&quot;;
2880 /// 采购入库 → 审核通过后才更新(见 UpdateSpCostOnPurchaseApproval) 3061 /// 采购入库 → 审核通过后才更新(见 UpdateSpCostOnPurchaseApproval)
2881 /// 销售/预售出库 → 快照成本到明细 + 减少数量 3062 /// 销售/预售出库 → 快照成本到明细 + 减少数量
2882 /// 销售退货 → 恢复数量(成本不变) 3063 /// 销售退货 → 恢复数量(成本不变)
  3064 + /// 变价调拨单 → 调出仓按原成本加权减少,调入仓按变价后单价加权增加(草稿/ isDraft 不过账)
2883 /// </summary> 3065 /// </summary>
2884 - private async Task UpdateSpCostOnCreate(string djlx, WtXsckdEntity entity, List<WtXsckdMxEntity> mxList) 3066 + private async Task UpdateSpCostOnCreate(string djlx, WtXsckdEntity entity, List<WtXsckdMxEntity> mxList, bool isDraft = false)
2885 { 3067 {
2886 if (mxList == null || mxList.Count == 0) return; 3068 if (mxList == null || mxList.Count == 0) return;
  3069 +
  3070 + if (djlx == "变价调拨单")
  3071 + {
  3072 + EnsureBjsxColumn();
  3073 + EnsureBjhcbColumn();
  3074 + if (isDraft || string.Equals(entity.Djzt, "草稿", StringComparison.Ordinal))
  3075 + return;
  3076 + try
  3077 + {
  3078 + await ApplyVariablePriceTransferCost(entity, mxList);
  3079 + }
  3080 + catch (Exception ex)
  3081 + {
  3082 + Console.WriteLine($"变价调拨成本过账失败: {ex.Message}");
  3083 + throw;
  3084 + }
  3085 + return;
  3086 + }
2887 3087
2888 try 3088 try
2889 { 3089 {
@@ -2891,17 +3091,22 @@ ORDER BY t.`商品编号`;&quot;; @@ -2891,17 +3091,22 @@ ORDER BY t.`商品编号`;&quot;;
2891 { 3091 {
2892 var outStoreId = entity.Cjck; 3092 var outStoreId = entity.Cjck;
2893 if (string.IsNullOrEmpty(outStoreId)) return; 3093 if (string.IsNullOrEmpty(outStoreId)) return;
2894 -  
2895 - var warehouseIds = await _db.Queryable<WtCkEntity>()  
2896 - .Where(c => c.Ssmd == outStoreId)  
2897 - .Select(c => c.Id)  
2898 - .ToListAsync();  
2899 - if (warehouseIds == null || warehouseIds.Count == 0) return;  
2900 - 3094 +
2901 foreach (var detail in mxList) 3095 foreach (var detail in mxList)
2902 { 3096 {
2903 if (string.IsNullOrEmpty(detail.Spbh)) continue; 3097 if (string.IsNullOrEmpty(detail.Spbh)) continue;
2904 if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue; 3098 if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  3099 +
  3100 + // 兼容前端传门店ID或仓库ID:按明细仓库优先,其次主表出库仓库
  3101 + var detailStoreOrWarehouseId = !string.IsNullOrEmpty(detail.Ckck) ? detail.Ckck : outStoreId;
  3102 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  3103 + .Where(c => c.Ssmd == detailStoreOrWarehouseId || c.Id == detailStoreOrWarehouseId)
  3104 + .Select(c => c.Id)
  3105 + .ToListAsync();
  3106 + if (warehouseIds == null || warehouseIds.Count == 0)
  3107 + {
  3108 + warehouseIds = new List<string> { detailStoreOrWarehouseId };
  3109 + }
2905 3110
2906 var costResult = await _db.Ado.SqlQueryAsync<decimal?>( 3111 var costResult = await _db.Ado.SqlQueryAsync<decimal?>(
2907 $"SELECT cbj FROM wt_sp_cost WHERE spbh = @spbh AND ck IN ({string.Join(",", warehouseIds.Select((_, i) => $"@ck{i}"))}) AND cbj > 0 LIMIT 1", 3112 $"SELECT cbj FROM wt_sp_cost WHERE spbh = @spbh AND ck IN ({string.Join(",", warehouseIds.Select((_, i) => $"@ck{i}"))}) AND cbj > 0 LIMIT 1",
@@ -2910,12 +3115,9 @@ ORDER BY t.`商品编号`;&quot;; @@ -2910,12 +3115,9 @@ ORDER BY t.`商品编号`;&quot;;
2910 3115
2911 decimal costPrice = costResult?.FirstOrDefault() ?? 0; 3116 decimal costPrice = costResult?.FirstOrDefault() ?? 0;
2912 3117
2913 - if (costPrice > 0)  
2914 - {  
2915 - await _db.Ado.ExecuteCommandAsync(  
2916 - "UPDATE wt_xsckd_mx SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id",  
2917 - new { cbdj = costPrice, cbje = Math.Round(costPrice * qty, 2), id = detail.Id });  
2918 - } 3118 + await _db.Ado.ExecuteCommandAsync(
  3119 + "UPDATE wt_xsckd_mx SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id",
  3120 + new { cbdj = costPrice, cbje = Math.Round(costPrice * qty, 2), id = detail.Id });
2919 3121
2920 foreach (var whId in warehouseIds) 3122 foreach (var whId in warehouseIds)
2921 { 3123 {
@@ -3209,6 +3411,243 @@ ORDER BY t.`商品编号`;&quot;; @@ -3209,6 +3411,243 @@ ORDER BY t.`商品编号`;&quot;;
3209 } 3411 }
3210 } 3412 }
3211 } 3413 }
  3414 + else if (djlx == "变价调拨单")
  3415 + {
  3416 + if (string.Equals(entity.Djzt, "草稿", StringComparison.Ordinal)) return;
  3417 + EnsureBjsxColumn();
  3418 + EnsureBjhcbColumn();
  3419 + var mxList = await _db.Queryable<WtXsckdMxEntity>().Where(d => d.Djbh == id).ToListAsync();
  3420 + if (mxList.Count > 0)
  3421 + await RollbackVariablePriceTransferCost(entity, mxList);
  3422 + }
  3423 + }
  3424 +
  3425 + /// <summary>
  3426 + /// 将门店ID或仓库ID解析为仓库ID列表(与同价/销售成本逻辑一致)
  3427 + /// </summary>
  3428 + private async Task<List<string>> ResolveWarehouseIdListAsync(string storeOrWarehouseId)
  3429 + {
  3430 + if (string.IsNullOrEmpty(storeOrWarehouseId)) return new List<string>();
  3431 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  3432 + .Where(c => c.Ssmd == storeOrWarehouseId || c.Id == storeOrWarehouseId)
  3433 + .Select(c => c.Id)
  3434 + .ToListAsync();
  3435 + if (warehouseIds == null || warehouseIds.Count == 0)
  3436 + warehouseIds = new List<string> { storeOrWarehouseId };
  3437 + return warehouseIds;
  3438 + }
  3439 +
  3440 + /// <summary>
  3441 + /// 变价后单价 = 原成本单价 / (系数% / 100),如 100 与 95% → 105.26
  3442 + /// </summary>
  3443 + private static decimal ComputeVariableAdjustedUnitCost(decimal originalUnit, decimal bjsxPercent)
  3444 + {
  3445 + if (bjsxPercent <= 0) throw new Exception("变价系数必须大于0");
  3446 + return Math.Round(originalUnit / (bjsxPercent / 100m), 4);
  3447 + }
  3448 +
  3449 + /// <summary>
  3450 + /// 调出仓选取 wt_sp_cost 行:优先库存不少于数量且成本&gt;0,否则取有库存且成本&gt;0的第一条
  3451 + /// </summary>
  3452 + private async Task<(string ck, decimal unitCost, int sl)> PickOutboundCostRowForTransferAsync(string spbh, List<string> outWarehouseIds, int qty)
  3453 + {
  3454 + if (outWarehouseIds == null || outWarehouseIds.Count == 0)
  3455 + throw new Exception($"商品 {spbh} 未解析到调出仓库");
  3456 +
  3457 + var ps = new List<SugarParameter> { new SugarParameter("@spbh", spbh) };
  3458 + for (var i = 0; i < outWarehouseIds.Count; i++)
  3459 + ps.Add(new SugarParameter($"@ck{i}", outWarehouseIds[i]));
  3460 + var inSql = string.Join(",", outWarehouseIds.Select((_, i) => $"@ck{i}"));
  3461 +
  3462 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(
  3463 + $"SELECT ck, cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck IN ({inSql}) ORDER BY sl DESC",
  3464 + ps.ToArray());
  3465 +
  3466 + if (rows == null || rows.Count == 0)
  3467 + throw new Exception($"商品 {spbh} 在调出方无成本数据,请先维护采购/入库成本");
  3468 +
  3469 + foreach (var r in rows)
  3470 + {
  3471 + int sl = Convert.ToInt32(r.sl);
  3472 + decimal cbj = Convert.ToDecimal(r.cbj);
  3473 + if (sl >= qty && cbj > 0)
  3474 + return (r.ck.ToString(), cbj, sl);
  3475 + }
  3476 + foreach (var r in rows)
  3477 + {
  3478 + int sl = Convert.ToInt32(r.sl);
  3479 + decimal cbj = Convert.ToDecimal(r.cbj);
  3480 + if (sl > 0 && cbj > 0)
  3481 + return (r.ck.ToString(), cbj, sl);
  3482 + }
  3483 + throw new Exception($"商品 {spbh} 在调出仓可用数量不足或成本为0,无法变价调拨");
  3484 + }
  3485 +
  3486 + /// <summary>
  3487 + /// 回滚时按原成本加回调出仓:优先匹配与过账时快照成本单价一致的仓库行
  3488 + /// </summary>
  3489 + private async Task<string> PickOutboundCkForRollbackAddAsync(string spbh, List<string> outWarehouseIds, decimal snapshotOriginalCbj, int qty)
  3490 + {
  3491 + if (outWarehouseIds == null || outWarehouseIds.Count == 0)
  3492 + throw new Exception($"商品 {spbh} 未解析到调出仓库");
  3493 + var ps = new List<SugarParameter> { new SugarParameter("@spbh", spbh) };
  3494 + for (var i = 0; i < outWarehouseIds.Count; i++)
  3495 + ps.Add(new SugarParameter($"@ck{i}", outWarehouseIds[i]));
  3496 + var inSql = string.Join(",", outWarehouseIds.Select((_, i) => $"@ck{i}"));
  3497 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(
  3498 + $"SELECT ck, cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck IN ({inSql}) ORDER BY sl ASC, cbj ASC",
  3499 + ps.ToArray());
  3500 + if (rows != null && rows.Count > 0)
  3501 + {
  3502 + foreach (var r in rows)
  3503 + {
  3504 + decimal cbj = Convert.ToDecimal(r.cbj);
  3505 + if (Math.Abs(cbj - snapshotOriginalCbj) < 0.02m)
  3506 + return r.ck.ToString();
  3507 + }
  3508 + }
  3509 + try
  3510 + {
  3511 + var picked = await PickOutboundCostRowForTransferAsync(spbh, outWarehouseIds, Math.Max(1, qty));
  3512 + return picked.ck;
  3513 + }
  3514 + catch
  3515 + {
  3516 + return outWarehouseIds[0];
  3517 + }
  3518 + }
  3519 +
  3520 + /// <summary>
  3521 + /// 加权减少库存与成本(与采购退货扣减一致)
  3522 + /// </summary>
  3523 + private async Task SpCostWeightedRemoveAsync(string spbh, string ck, int qty, decimal unitPrice)
  3524 + {
  3525 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  3526 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  3527 + new { spbh, ck });
  3528 + if (existing == null || existing.Count == 0)
  3529 + throw new Exception($"成本记录不存在: {spbh} 仓库 {ck}");
  3530 + decimal currentCbj = Convert.ToDecimal(existing[0].cbj);
  3531 + int currentSl = Convert.ToInt32(existing[0].sl);
  3532 + if (currentSl < qty)
  3533 + throw new Exception($"库存不足: {spbh} 仓库 {ck} 当前{currentSl} 需要{qty}");
  3534 + int newSl = currentSl - qty;
  3535 + decimal newCbj = 0;
  3536 + if (newSl > 0)
  3537 + newCbj = Math.Round((currentCbj * currentSl - unitPrice * qty) / newSl, 2);
  3538 + await _db.Ado.ExecuteCommandAsync(
  3539 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  3540 + new { cbj = newCbj, sl = newSl, spbh, ck });
  3541 + }
  3542 +
  3543 + /// <summary>
  3544 + /// 加权增加库存与成本(与采购入库增加一致)
  3545 + /// </summary>
  3546 + private async Task SpCostWeightedAddAsync(string spbh, string ck, int qty, decimal unitPrice)
  3547 + {
  3548 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  3549 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  3550 + new { spbh, ck });
  3551 + if (existing != null && existing.Count > 0)
  3552 + {
  3553 + decimal oldCbj = Convert.ToDecimal(existing[0].cbj);
  3554 + int oldSl = Convert.ToInt32(existing[0].sl);
  3555 + int newSl = oldSl + qty;
  3556 + decimal newCbj = newSl > 0
  3557 + ? Math.Round((oldCbj * oldSl + unitPrice * qty) / newSl, 2)
  3558 + : unitPrice;
  3559 + await _db.Ado.ExecuteCommandAsync(
  3560 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  3561 + new { cbj = newCbj, sl = newSl, spbh, ck });
  3562 + }
  3563 + else
  3564 + {
  3565 + var rowId = $"{spbh}_{ck}";
  3566 + await _db.Ado.ExecuteCommandAsync(
  3567 + "INSERT INTO wt_sp_cost (F_Id, spbh, ck, cbj, sl, update_time) VALUES (@id, @spbh, @ck, @cbj, @sl, NOW())",
  3568 + new { id = rowId, spbh, ck, cbj = unitPrice, sl = qty });
  3569 + }
  3570 + }
  3571 +
  3572 + /// <summary>
  3573 + /// 变价调拨单过账:调出按快照原成本扣减,调入按变价后单价增加,并回写明细 cbdj/bjhcb/cbje
  3574 + /// </summary>
  3575 + private async Task ApplyVariablePriceTransferCost(WtXsckdEntity entity, List<WtXsckdMxEntity> mxList)
  3576 + {
  3577 + if (string.Equals((entity.Cjck ?? "").Trim(), (entity.Rkck ?? "").Trim(), StringComparison.Ordinal))
  3578 + throw new Exception("变价调拨单出库仓与入库仓不能相同");
  3579 + var coef = entity.Bjsx ?? 0;
  3580 + if (coef <= 0)
  3581 + throw new Exception("变价系数必须大于0");
  3582 +
  3583 + foreach (var detail in mxList)
  3584 + {
  3585 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  3586 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  3587 +
  3588 + var outRef = !string.IsNullOrEmpty(detail.Ckck) ? detail.Ckck : entity.Cjck;
  3589 + var inRef = !string.IsNullOrEmpty(detail.Rkck) ? detail.Rkck : entity.Rkck;
  3590 + if (string.IsNullOrEmpty(outRef) || string.IsNullOrEmpty(inRef))
  3591 + throw new Exception($"商品 {detail.Spmc ?? detail.Spbh} 未指定完整的出库/入库仓库");
  3592 +
  3593 + var outIds = await ResolveWarehouseIdListAsync(outRef);
  3594 + var (outCk, originalUnit, _) = await PickOutboundCostRowForTransferAsync(detail.Spbh, outIds, qty);
  3595 + var adjustedUnit = ComputeVariableAdjustedUnitCost(originalUnit, coef);
  3596 + var inIds = await ResolveWarehouseIdListAsync(inRef);
  3597 + if (inIds == null || inIds.Count == 0)
  3598 + throw new Exception($"商品 {detail.Spbh} 未解析到入库仓库");
  3599 + var inCk = inIds[0];
  3600 +
  3601 + await SpCostWeightedRemoveAsync(detail.Spbh, outCk, qty, originalUnit);
  3602 + await SpCostWeightedAddAsync(detail.Spbh, inCk, qty, adjustedUnit);
  3603 +
  3604 + await _db.Ado.ExecuteCommandAsync(
  3605 + "UPDATE wt_xsckd_mx SET cbdj = @cbdj, bjhcb = @bjhcb, cbje = @cbje WHERE F_Id = @id",
  3606 + new
  3607 + {
  3608 + cbdj = originalUnit,
  3609 + bjhcb = adjustedUnit,
  3610 + cbje = Math.Round(adjustedUnit * qty, 2),
  3611 + id = detail.Id
  3612 + });
  3613 + }
  3614 + }
  3615 +
  3616 + /// <summary>
  3617 + /// 变价调拨单成本回滚:调入按变价后单价扣回,调出按原成本加回(删除/编辑前调用)
  3618 + /// </summary>
  3619 + private async Task RollbackVariablePriceTransferCost(WtXsckdEntity entity, List<WtXsckdMxEntity> mxList)
  3620 + {
  3621 + foreach (var detail in mxList)
  3622 + {
  3623 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  3624 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  3625 +
  3626 + var outRef = !string.IsNullOrEmpty(detail.Ckck) ? detail.Ckck : entity.Cjck;
  3627 + var inRef = !string.IsNullOrEmpty(detail.Rkck) ? detail.Rkck : entity.Rkck;
  3628 + if (string.IsNullOrEmpty(outRef) || string.IsNullOrEmpty(inRef)) continue;
  3629 +
  3630 + decimal originalUnit = detail.Cbdj ?? detail.Dj;
  3631 + if (originalUnit <= 0) continue;
  3632 +
  3633 + decimal adjustedUnit = 0;
  3634 + if (detail.Bjhcb.HasValue && detail.Bjhcb.Value > 0)
  3635 + adjustedUnit = detail.Bjhcb.Value;
  3636 + else if (entity.Bjsx.HasValue && entity.Bjsx.Value > 0)
  3637 + adjustedUnit = ComputeVariableAdjustedUnitCost(originalUnit, entity.Bjsx.Value);
  3638 + if (adjustedUnit <= 0) continue;
  3639 +
  3640 + var inIds = await ResolveWarehouseIdListAsync(inRef);
  3641 + var outIds = await ResolveWarehouseIdListAsync(outRef);
  3642 + if (inIds == null || inIds.Count == 0 || outIds == null || outIds.Count == 0) continue;
  3643 + var inCk = inIds[0];
  3644 +
  3645 + // 调入仓按变价后单价扣减(与过账时增加互逆)
  3646 + await SpCostWeightedRemoveAsync(detail.Spbh, inCk, qty, adjustedUnit);
  3647 +
  3648 + var outCk = await PickOutboundCkForRollbackAddAsync(detail.Spbh, outIds, originalUnit, qty);
  3649 + await SpCostWeightedAddAsync(detail.Spbh, outCk, qty, originalUnit);
  3650 + }
3212 } 3651 }
3213 3652
3214 #endregion 3653 #endregion