Commit dfbda420886155d80732a83291d4268813e4b1c8

Authored by “wangming”
1 parent b14267c9

更改了一堆东西

Showing 20 changed files with 3333 additions and 967 deletions
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgthd/Form.vue
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%"> 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%">
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">
5 - <el-col :span="12">  
6 - <el-form-item label="单据编号" prop="id">  
7 - <el-input v-model="dataForm.id" placeholder="请输入" clearable readonly :style='{"width":"100%"}' >  
8 - </el-input>  
9 - </el-form-item>  
10 - </el-col> 5 + <el-col :span="12">
  6 + <el-form-item label="单据编号" prop="id">
  7 + <el-input v-model="dataForm.id" placeholder="保存后自动生成" readonly :style='{"width":"100%"}' />
  8 + </el-form-item>
  9 + </el-col>
11 <el-col :span="12"> 10 <el-col :span="12">
12 <el-form-item label="单据日期" prop="djrq"> 11 <el-form-item label="单据日期" prop="djrq">
13 <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 >
@@ -61,12 +60,19 @@ @@ -61,12 +60,19 @@
61 </el-select> 60 </el-select>
62 </template> 61 </template>
63 </el-table-column> 62 </el-table-column>
64 - <el-table-column prop="spmc" label="商品名称">  
65 - <template slot-scope="scope">  
66 - <el-input v-model="scope.row.spmc" placeholder="请输入" clearable ></el-input>  
67 - </template>  
68 - </el-table-column>  
69 - <!-- 明细表商品条码字段 --> 63 + <el-table-column prop="spmc" label="商品名称">
  64 + <template slot-scope="scope">
  65 + <el-input v-model="scope.row.spmc" placeholder="请输入" clearable ></el-input>
  66 + </template>
  67 + </el-table-column>
  68 + <el-table-column prop="dyddbh" label="入库编号" width="200">
  69 + <template slot-scope="scope">
  70 + <el-select v-model="scope.row.dyddbh" placeholder="请选择入库单" clearable filterable @change="handleInboundOrderChange(scope.row, scope.$index)" :disabled="!scope.row.spbh">
  71 + <el-option v-for="item in (inboundOrdersMap[scope.$index] || [])" :key="item.orderid" :label="item.orderid + ' (¥' + item.dj + ' × ' + item.sl + ')'" :value="item.orderid"></el-option>
  72 + </el-select>
  73 + </template>
  74 + </el-table-column>
  75 + <!-- 明细表商品条码字段 -->
70 <!-- <el-table-column prop="sptm" label="商品条码" v-if="false"> 76 <!-- <el-table-column prop="sptm" label="商品条码" v-if="false">
71 <template slot-scope="scope"> 77 <template slot-scope="scope">
72 <el-input v-model="scope.row.sptm" placeholder="请输入" clearable ></el-input> 78 <el-input v-model="scope.row.sptm" placeholder="请输入" clearable ></el-input>
@@ -88,11 +94,11 @@ @@ -88,11 +94,11 @@
88 ></el-input> 94 ></el-input>
89 </template> 95 </template>
90 </el-table-column> 96 </el-table-column>
91 - <el-table-column prop="dj" label="单价">  
92 - <template slot-scope="scope">  
93 - <el-input v-model="scope.row.dj" placeholder="请输入" clearable @input="handlePriceChange(scope.row)"></el-input>  
94 - </template>  
95 - </el-table-column> 97 + <el-table-column prop="dj" label="单价">
  98 + <template slot-scope="scope">
  99 + <el-input v-model="scope.row.dj" placeholder="选择入库编号自动填入" readonly :style="{ background: '#f5f7fa' }"></el-input>
  100 + </template>
  101 + </el-table-column>
96 <el-table-column prop="je" label="金额"> 102 <el-table-column prop="je" label="金额">
97 <template slot-scope="scope"> 103 <template slot-scope="scope">
98 <el-input v-model="scope.row.je" placeholder="请输入" clearable @input="handleAmountFieldChange(scope.row)"></el-input> 104 <el-input v-model="scope.row.je" placeholder="请输入" clearable @input="handleAmountFieldChange(scope.row)"></el-input>
@@ -234,6 +240,7 @@ @@ -234,6 +240,7 @@
234 loading: false, 240 loading: false,
235 visible: false, 241 visible: false,
236 isDetail: false, 242 isDetail: false,
  243 + isNew: false,
237 dataForm: { 244 dataForm: {
238 id:'', 245 id:'',
239 id:undefined, 246 id:undefined,
@@ -259,7 +266,8 @@ @@ -259,7 +266,8 @@
259 ckckOptions : [], 266 ckckOptions : [],
260 spbhOptions : [], 267 spbhOptions : [],
261 skzhOptions : [], 268 skzhOptions : [],
262 - productCache: new Map(), // 商品信息缓存 269 + productCache: new Map(),
  270 + inboundOrdersMap: {},
263 } 271 }
264 }, 272 },
265 computed: { 273 computed: {
@@ -326,45 +334,53 @@ @@ -326,45 +334,53 @@
326 }, 334 },
327 init(id, isDetail) { 335 init(id, isDetail) {
328 this.dataForm.id = id || 0; 336 this.dataForm.id = id || 0;
  337 + this.isNew = !id;
329 this.visible = true; 338 this.visible = true;
330 this.isDetail = isDetail || false; 339 this.isDetail = isDetail || false;
331 this.$nextTick(() => { 340 this.$nextTick(() => {
332 this.$refs['elForm'].resetFields(); 341 this.$refs['elForm'].resetFields();
333 - if (this.dataForm.id) {  
334 - request({  
335 - url: '/api/Extend/WtXsckd/' + this.dataForm.id,  
336 - method: 'get'  
337 - }).then(res =>{  
338 - this.dataForm = res.data;  
339 - // 确保wtXsckdMxList存在  
340 - if (!this.dataForm.wtXsckdMxList) {  
341 - this.dataForm.wtXsckdMxList = []; 342 + if (this.dataForm.id) {
  343 + request({
  344 + url: '/api/Extend/WtXsckd/' + this.dataForm.id,
  345 + method: 'get'
  346 + }).then(res =>{
  347 + this.dataForm = res.data;
  348 + if (!this.dataForm.wtXsckdMxList) {
  349 + this.dataForm.wtXsckdMxList = [];
  350 + }
  351 + this.dataForm.wtXsckdMxList.forEach(item => {
  352 + if (!item.selectedSerialNumbers) {
  353 + this.$set(item, 'selectedSerialNumbers', []);
342 } 354 }
343 - // 为每个明细项添加序列号相关字段  
344 - this.dataForm.wtXsckdMxList.forEach(item => {  
345 - if (!item.selectedSerialNumbers) {  
346 - this.$set(item, 'selectedSerialNumbers', []);  
347 - }  
348 - if (!item.hasOwnProperty('spxlhLoaded')) {  
349 - this.$set(item, 'spxlhLoaded', false);  
350 - }  
351 - if (!item.hasOwnProperty('description')) {  
352 - this.$set(item, 'description', undefined);  
353 - }  
354 - if (!item.hasOwnProperty('productQuery')) {  
355 - this.$set(item, 'productQuery', '');  
356 - }  
357 - });  
358 -  
359 - // 同步明细表的出库仓库与主表保持一致  
360 - this.syncDetailWarehouses();  
361 - // 计算总金额  
362 - this.updateTotalAmount();  
363 - })  
364 - } else {  
365 - // 新建时确保wtXsckdMxList为空数组  
366 - this.dataForm.wtXsckdMxList = [];  
367 - } 355 + if (!item.hasOwnProperty('spxlhLoaded')) {
  356 + this.$set(item, 'spxlhLoaded', false);
  357 + }
  358 + if (!item.hasOwnProperty('description')) {
  359 + this.$set(item, 'description', undefined);
  360 + }
  361 + if (!item.hasOwnProperty('productQuery')) {
  362 + this.$set(item, 'productQuery', '');
  363 + }
  364 + if (!item.hasOwnProperty('dyddbh')) {
  365 + this.$set(item, 'dyddbh', item.dyddbh || undefined);
  366 + }
  367 + });
  368 + this.syncDetailWarehouses();
  369 + this.updateTotalAmount();
  370 + })
  371 + } else {
  372 + this.dataForm.wtXsckdMxList = [];
  373 + // 预生成单据编号
  374 + request({
  375 + url: '/api/Extend/WtXsckd/Actions/GenerateBillNo',
  376 + method: 'GET',
  377 + data: { djlx: this.dataForm.djlx }
  378 + }).then(res => {
  379 + if (res.data && res.data.billNo) {
  380 + this.dataForm.id = res.data.billNo;
  381 + }
  382 + }).catch(() => {});
  383 + }
368 }) 384 })
369 }, 385 },
370 dataFormSubmit() { 386 dataFormSubmit() {
@@ -445,7 +461,7 @@ @@ -445,7 +461,7 @@
445 461
446 this.$refs['elForm'].validate((valid) => { 462 this.$refs['elForm'].validate((valid) => {
447 if (valid) { 463 if (valid) {
448 - if (!this.dataForm.id) { 464 + if (this.isNew) {
449 request({ 465 request({
450 url: `/api/Extend/WtXsckd`, 466 url: `/api/Extend/WtXsckd`,
451 method: 'post', 467 method: 'post',
@@ -487,7 +503,7 @@ @@ -487,7 +503,7 @@
487 503
488 let item = { 504 let item = {
489 rkck:undefined, 505 rkck:undefined,
490 - ckck:this.dataForm.cjck, // 自动使用主表的出库仓库 506 + ckck:this.dataForm.cjck,
491 spbh:undefined, 507 spbh:undefined,
492 spmc:undefined, 508 spmc:undefined,
493 sptm:undefined, 509 sptm:undefined,
@@ -495,10 +511,11 @@ @@ -495,10 +511,11 @@
495 sl:undefined, 511 sl:undefined,
496 dj:undefined, 512 dj:undefined,
497 je:undefined, 513 je:undefined,
  514 + dyddbh: undefined,
498 description: undefined, 515 description: undefined,
499 selectedSerialNumbers: [], 516 selectedSerialNumbers: [],
500 spxlhLoaded: false, 517 spxlhLoaded: false,
501 - productQuery: '', // 为每一行添加独立的查询条件 518 + productQuery: '',
502 } 519 }
503 520
504 if (!this.dataForm.wtXsckdMxList) { 521 if (!this.dataForm.wtXsckdMxList) {
@@ -515,42 +532,86 @@ @@ -515,42 +532,86 @@
515 }, 532 },
516 async handleProductChange(row) { 533 async handleProductChange(row) {
517 const product = this.spbhOptions.find(item => item.F_Id === row.spbh); 534 const product = this.spbhOptions.find(item => item.F_Id === row.spbh);
518 - console.log('选中商品', row.spbh, product);  
519 if (product) { 535 if (product) {
520 row.spmc = product.F_Spmc || ''; 536 row.spmc = product.F_Spmc || '';
521 - // 请求后端接口获取该商品最近一次采购入库单价  
522 - try {  
523 - const res = await request({ url: '/api/Extend/WtXsckd/GetAllPurchaseInboundDetails', method: 'get' });  
524 - console.log('采购入库明细接口返回', res);  
525 - const arr = res && res.data && res.data.data ? res.data.data : [];  
526 - const details = arr.filter(x => x.Spbh === row.spbh);  
527 - console.log('该商品所有入库明细', details);  
528 - let latest = null;  
529 - if (details.length > 0) {  
530 - latest = details.sort((a, b) => (b.Djbh || b.Id || 0) < (a.Djbh || a.Id || 0) ? 1 : -1)[0];  
531 - }  
532 - console.log('最新一条明细', latest);  
533 - row.dj = latest ? latest.Dj : '';  
534 - } catch (e) {  
535 - row.dj = '';  
536 - }  
537 537
538 - // 获取商品序列号类型信息 538 + this.$set(row, 'dyddbh', undefined);
  539 + this.$set(row, 'dj', '');
  540 + this.$set(row, 'je', '');
  541 +
  542 + await this.loadInboundOrders(row);
  543 +
539 this.getProductInfo(row.spbh).then(productInfo => { 544 this.getProductInfo(row.spbh).then(productInfo => {
540 this.$set(row, 'spxlhLoaded', true); 545 this.$set(row, 'spxlhLoaded', true);
541 this.$forceUpdate(); 546 this.$forceUpdate();
542 - if (!(productInfo && productInfo.spxlhType)) {  
543 - this.$message.warning('无法获取序列号类型信息');  
544 - } else {  
545 - // 如果商品需要序列号,给出提示 547 + if (productInfo && productInfo.spxlhType) {
546 const spxlhType = productInfo.spxlhType; 548 const spxlhType = productInfo.spxlhType;
547 if (spxlhType === '1' || spxlhType === '2') { 549 if (spxlhType === '1' || spxlhType === '2') {
548 - this.$message.info(`商品"${row.spmc}"需要选择序列号,请在明细中选择序列号`); 550 + this.$message.info(`商品"${row.spmc}"需要选择序列号`);
549 } 551 }
550 } 552 }
551 }); 553 });
552 } 554 }
553 }, 555 },
  556 + async loadInboundOrders(row) {
  557 + const warehouse = row.ckck || this.dataForm.cjck;
  558 + if (!row.spbh || !warehouse) return;
  559 +
  560 + try {
  561 + const res = await request({
  562 + url: '/api/Extend/WtXsckd/GetPurchaseInboundOrders',
  563 + method: 'get',
  564 + params: { spbh: row.spbh, ck: warehouse }
  565 + });
  566 + let rawList = [];
  567 + if (Array.isArray(res)) {
  568 + rawList = res;
  569 + } else if (Array.isArray(res.data)) {
  570 + rawList = res.data;
  571 + } else if (res.data && Array.isArray(res.data.data)) {
  572 + rawList = res.data.data;
  573 + } else if (res.data && res.data.data && Array.isArray(res.data.data.data)) {
  574 + rawList = res.data.data.data;
  575 + }
  576 + const list = (rawList || []).map(item => this.normalizeInboundOrder(item));
  577 + const idx = this.dataForm.wtXsckdMxList.indexOf(row);
  578 + if (idx >= 0) {
  579 + this.$set(this.inboundOrdersMap, idx, list);
  580 + }
  581 + } catch (e) {
  582 + console.error('加载入库单列表失败', e);
  583 + }
  584 + },
  585 + normalizeInboundOrder(item) {
  586 + const getVal = (obj, keys) => {
  587 + for (const key of keys) {
  588 + if (obj && obj[key] !== undefined && obj[key] !== null && obj[key] !== '') {
  589 + return obj[key];
  590 + }
  591 + }
  592 + return '';
  593 + };
  594 + return {
  595 + orderid: getVal(item, ['orderid', 'orderId', 'OrderId', 'ORDERID', 'f_id', 'F_Id']),
  596 + dj: getVal(item, ['dj', 'Dj', 'DJ']),
  597 + sl: getVal(item, ['sl', 'Sl', 'SL']),
  598 + spmc: getVal(item, ['spmc', 'Spmc', 'Spmc', 'SPMC']),
  599 + djrq: getVal(item, ['djrq', 'Djrq', 'DJRQ'])
  600 + };
  601 + },
  602 + handleInboundOrderChange(row, index) {
  603 + const orders = this.inboundOrdersMap[index] || [];
  604 + const selected = orders.find(o => o.orderid === row.dyddbh);
  605 + if (selected) {
  606 + this.$set(row, 'dj', selected.dj);
  607 + const sl = parseFloat(row.sl) || 0;
  608 + if (sl > 0) {
  609 + this.$set(row, 'je', (sl * parseFloat(selected.dj)).toFixed(2));
  610 + }
  611 + this.updateTotalAmount();
  612 + this.$message.success(`已自动填入采购单价: ¥${selected.dj}`);
  613 + }
  614 + },
554 handleProductQuery(val, scope) { 615 handleProductQuery(val, scope) {
555 // 为每一行设置独立的查询条件 616 // 为每一行设置独立的查询条件
556 this.$set(scope.row, 'productQuery', val); 617 this.$set(scope.row, 'productQuery', val);
@@ -569,9 +630,14 @@ @@ -569,9 +630,14 @@
569 630
570 // 处理主表出库仓库变化,同步更新所有明细行的出库仓库 631 // 处理主表出库仓库变化,同步更新所有明细行的出库仓库
571 handleMainWarehouseChange(value) { 632 handleMainWarehouseChange(value) {
572 - console.log('主表出库仓库变化:', value);  
573 - // 同步更新所有明细行的出库仓库  
574 this.syncDetailWarehouses(); 633 this.syncDetailWarehouses();
  634 + this.inboundOrdersMap = {};
  635 + this.dataForm.wtXsckdMxList.forEach(row => {
  636 + this.$set(row, 'dyddbh', undefined);
  637 + this.$set(row, 'dj', '');
  638 + this.$set(row, 'je', '');
  639 + });
  640 + this.updateTotalAmount();
575 }, 641 },
576 642
577 // 同步明细表的出库仓库与主表保持一致 643 // 同步明细表的出库仓库与主表保持一致
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCzd/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="90%"> 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%">
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">
@@ -42,16 +42,6 @@ @@ -42,16 +42,6 @@
42 </el-col> 42 </el-col>
43 <el-col :span="24"> 43 <el-col :span="24">
44 <el-form-item label-width="0"> 44 <el-form-item label-width="0">
45 - <el-button type="info" size="mini" @click="debugFrontendData" style="margin-bottom: 10px;">  
46 - 调试数据状态  
47 - </el-button>  
48 - <el-button type="warning" size="mini" @click="quickDiagnosis" style="margin-bottom: 10px;">  
49 - 快速诊断  
50 - </el-button>  
51 - </el-form-item>  
52 - </el-col>  
53 - <el-col :span="24">  
54 - <el-form-item label-width="0">  
55 <el-table :data="dataForm.wtCzdmxList" size='mini'> 45 <el-table :data="dataForm.wtCzdmxList" size='mini'>
56 <el-table-column type="index" width="50" label="序号" align="center" /> 46 <el-table-column type="index" width="50" label="序号" align="center" />
57 <el-table-column prop="chck" label="出货仓库"> 47 <el-table-column prop="chck" label="出货仓库">
@@ -290,22 +280,16 @@ @@ -290,22 +280,16 @@
290 newVal.forEach((row, index) => { 280 newVal.forEach((row, index) => {
291 // 确保行数据有效且仓库和商品编号都有值 281 // 确保行数据有效且仓库和商品编号都有值
292 if (row && typeof row === 'object' && 282 if (row && typeof row === 'object' &&
293 - row.chck && row.chck !== '' &&  
294 - row.spbh && row.spbh !== '' &&  
295 - !row.zmkc && !row.stockLoading && !row.hasQueriedStock) {  
296 - // 标记已查询,避免重复查询  
297 - this.$set(row, 'hasQueriedStock', true);  
298 - try {  
299 - console.log('自动触发库存查询:', {  
300 - row: row,  
301 - chck: row.chck,  
302 - spbh: row.spbh  
303 - });  
304 - this.queryStock(row, 'wtCzdmxList');  
305 - } catch (err) {  
306 - console.warn('自动查询库存失败:', err, row);  
307 - } 283 + row.chck && row.chck !== '' &&
  284 + row.spbh && row.spbh !== '' &&
  285 + !row.zmkc && !row.stockLoading && !row.hasQueriedStock) {
  286 + this.$set(row, 'hasQueriedStock', true);
  287 + try {
  288 + this.queryStock(row, 'wtCzdmxList');
  289 + } catch (err) {
  290 + console.warn('自动查询库存失败:', err);
308 } 291 }
  292 + }
309 }); 293 });
310 } 294 }
311 } 295 }
@@ -321,22 +305,16 @@ @@ -321,22 +305,16 @@
321 newVal.forEach((row, index) => { 305 newVal.forEach((row, index) => {
322 // 确保行数据有效且仓库和商品编号都有值 306 // 确保行数据有效且仓库和商品编号都有值
323 if (row && typeof row === 'object' && 307 if (row && typeof row === 'object' &&
324 - row.chck && row.chck !== '' &&  
325 - row.spbh && row.spbh !== '' &&  
326 - !row.zmkc && !row.stockLoading && !row.hasQueriedStock) {  
327 - // 标记已查询,避免重复查询  
328 - this.$set(row, 'hasQueriedStock', true);  
329 - try {  
330 - console.log('自动触发库存查询:', {  
331 - row: row,  
332 - chck: row.chck,  
333 - spbh: row.spbh  
334 - });  
335 - this.queryStock(row, 'wtCzdmx2List');  
336 - } catch (err) {  
337 - console.warn('自动查询库存失败:', err, row);  
338 - } 308 + row.chck && row.chck !== '' &&
  309 + row.spbh && row.spbh !== '' &&
  310 + !row.zmkc && !row.stockLoading && !row.hasQueriedStock) {
  311 + this.$set(row, 'hasQueriedStock', true);
  312 + try {
  313 + this.queryStock(row, 'wtCzdmx2List');
  314 + } catch (err) {
  315 + console.warn('自动查询库存失败:', err);
339 } 316 }
  317 + }
340 }); 318 });
341 } 319 }
342 } 320 }
@@ -353,207 +331,131 @@ @@ -353,207 +331,131 @@
353 mounted() { 331 mounted() {
354 }, 332 },
355 methods: { 333 methods: {
356 - getchckOptions(){  
357 - previewDataInterface('681758216954053893').then(res => {  
358 - this.chckOptions = res.data  
359 - console.log('出库仓库选项数据:', this.chckOptions);  
360 -  
361 - // 检查仓库数据是否正确获取  
362 - if (this.chckOptions && this.chckOptions.length > 0) {  
363 - console.log('成功获取出库仓库数据,数量:', this.chckOptions.length);  
364 - console.log('第一个仓库示例:', this.chckOptions[0]);  
365 - } else {  
366 - console.warn('出库仓库数据为空,请检查接口配置');  
367 - // 尝试备用方法  
368 - this.getWarehouseOptionsFallback();  
369 - }  
370 - }).catch(err => {  
371 - console.error('获取出库仓库数据失败:', err);  
372 - this.$message.error('获取出库仓库数据失败,请检查网络连接');  
373 - // 尝试备用方法 334 + getchckOptions(){
  335 + previewDataInterface('681758216954053893').then(res => {
  336 + this.chckOptions = res.data
  337 + if (!this.chckOptions || this.chckOptions.length === 0) {
374 this.getWarehouseOptionsFallback(); 338 this.getWarehouseOptionsFallback();
375 - });  
376 - }, 339 + }
  340 + }).catch(err => {
  341 + console.error('获取出库仓库数据失败:', err);
  342 + this.$message.error('获取出库仓库数据失败,请检查网络连接');
  343 + this.getWarehouseOptionsFallback();
  344 + });
  345 + },
377 getdwOptions(){ 346 getdwOptions(){
378 getDictionaryDataSelector('681805755032012037').then(res => { 347 getDictionaryDataSelector('681805755032012037').then(res => {
379 this.dwOptions = res.data.list 348 this.dwOptions = res.data.list
380 }); 349 });
381 }, 350 },
382 - getspbhOptions(){  
383 - previewDataInterface('675937572047815941').then(res => {  
384 - this.spbhOptions = res.data  
385 - console.log('商品选项数据:', this.spbhOptions);  
386 -  
387 - // 检查商品数据是否正确获取  
388 - if (this.spbhOptions && this.spbhOptions.length > 0) {  
389 - console.log('成功获取商品数据,数量:', this.spbhOptions.length);  
390 - console.log('第一个商品示例:', this.spbhOptions[0]);  
391 - } else {  
392 - console.warn('商品数据为空,请检查接口配置');  
393 - }  
394 - }).catch(err => {  
395 - console.error('获取商品数据失败:', err);  
396 - this.$message.error('获取商品数据失败,请检查网络连接');  
397 - });  
398 - },  
399 - getrhckOptions() {  
400 - previewDataInterface('681758216954053893').then(res => {  
401 - this.rhckOptions = res.data  
402 - console.log('入货仓库选项数据:', this.rhckOptions);  
403 -  
404 - // 检查仓库数据是否正确获取  
405 - if (this.rhckOptions && this.rhckOptions.length > 0) {  
406 - console.log('成功获取入货仓库数据,数量:', this.rhckOptions.length);  
407 - console.log('第一个仓库示例:', this.rhckOptions[0]);  
408 - } else {  
409 - console.warn('入货仓库数据为空,请检查接口配置');  
410 - // 尝试备用方法  
411 - this.getWarehouseOptionsFallback();  
412 - }  
413 - }).catch(err => {  
414 - console.error('获取入货仓库数据失败:', err);  
415 - this.$message.error('获取入货仓库数据失败,请检查网络连接');  
416 - // 尝试备用方法 351 + getspbhOptions(){
  352 + previewDataInterface('675937572047815941').then(res => {
  353 + this.spbhOptions = res.data
  354 + }).catch(err => {
  355 + console.error('获取商品数据失败:', err);
  356 + this.$message.error('获取商品数据失败,请检查网络连接');
  357 + });
  358 + },
  359 + getrhckOptions() {
  360 + previewDataInterface('681758216954053893').then(res => {
  361 + this.rhckOptions = res.data
  362 + if (!this.rhckOptions || this.rhckOptions.length === 0) {
417 this.getWarehouseOptionsFallback(); 363 this.getWarehouseOptionsFallback();
418 - });  
419 - },  
420 - // 备用仓库获取方法  
421 - getWarehouseOptionsFallback() {  
422 - console.log('尝试使用备用方法获取仓库数据...');  
423 - // 尝试使用字典数据接口  
424 - getDictionaryDataSelector('681758216954053893').then(res => {  
425 - if (res.data && res.data.list && res.data.list.length > 0) {  
426 - console.log('备用方法成功获取仓库数据:', res.data.list);  
427 - this.chckOptions = res.data.list;  
428 - this.rhckOptions = res.data.list;  
429 - } else {  
430 - console.warn('备用方法也无法获取仓库数据');  
431 - // 设置一些默认的仓库选项用于测试  
432 - this.setDefaultWarehouseOptions();  
433 - }  
434 - }).catch(err => {  
435 - console.error('备用方法获取仓库数据失败:', err);  
436 - // 设置一些默认的仓库选项用于测试 364 + }
  365 + }).catch(err => {
  366 + console.error('获取入货仓库数据失败:', err);
  367 + this.$message.error('获取入货仓库数据失败,请检查网络连接');
  368 + this.getWarehouseOptionsFallback();
  369 + });
  370 + },
  371 + getWarehouseOptionsFallback() {
  372 + getDictionaryDataSelector('681758216954053893').then(res => {
  373 + if (res.data && res.data.list && res.data.list.length > 0) {
  374 + this.chckOptions = res.data.list;
  375 + this.rhckOptions = res.data.list;
  376 + } else {
437 this.setDefaultWarehouseOptions(); 377 this.setDefaultWarehouseOptions();
438 - });  
439 - },  
440 - // 设置默认仓库选项(用于测试)  
441 - setDefaultWarehouseOptions() {  
442 - console.log('设置默认仓库选项用于测试...');  
443 - const defaultWarehouses = [  
444 - { F_Id: '1', F_mdmc: '主仓库' },  
445 - { F_Id: '2', F_mdmc: '分仓库A' },  
446 - { F_Id: '3', F_mdmc: '分仓库B' }  
447 - ];  
448 - this.chckOptions = defaultWarehouses;  
449 - this.rhckOptions = defaultWarehouses;  
450 - console.log('已设置默认仓库选项:', defaultWarehouses);  
451 - },  
452 - handleProductChange(row) {  
453 - // 增强参数安全检查  
454 - if (!row || typeof row !== 'object') {  
455 - console.warn('handleProductChange: row参数无效', row);  
456 - return;  
457 } 378 }
  379 + }).catch(() => {
  380 + this.setDefaultWarehouseOptions();
  381 + });
  382 + },
  383 + setDefaultWarehouseOptions() {
  384 + const defaultWarehouses = [
  385 + { F_Id: '1', F_mdmc: '主仓库' },
  386 + { F_Id: '2', F_mdmc: '分仓库A' },
  387 + { F_Id: '3', F_mdmc: '分仓库B' }
  388 + ];
  389 + this.chckOptions = defaultWarehouses;
  390 + this.rhckOptions = defaultWarehouses;
  391 + },
  392 + handleProductChange(row) {
  393 + if (!row || typeof row !== 'object') return;
  394 +
  395 + const product = this.spbhOptions.find(item => item.F_Id === row.spbh);
  396 + if (product) {
  397 + this.$set(row, 'spmc', product.F_Spmc || '');
458 398
459 - // 选中商品后可自动回填商品名称等信息  
460 - const product = this.spbhOptions.find(item => item.F_Id === row.spbh);  
461 - if (product) {  
462 - row.spmc = product.F_Spmc || '';  
463 -  
464 - // 如果已选择仓库,自动获取成本价格和库存  
465 - if (row.chck) {  
466 - // 自动获取成本价格  
467 - this.getProductCost(row);  
468 -  
469 - // 判断当前行属于哪个明细表  
470 - try {  
471 - const isInWtCzdmxList = this.dataForm.wtCzdmxList && Array.isArray(this.dataForm.wtCzdmxList) && this.dataForm.wtCzdmxList.includes(row);  
472 - const isInWtCzdmx2List = this.dataForm.wtCzdmx2List && Array.isArray(this.dataForm.wtCzdmx2List) && this.dataForm.wtCzdmx2List.includes(row);  
473 -  
474 - if (isInWtCzdmxList) {  
475 - this.queryStock(row, 'wtCzdmxList');  
476 - } else if (isInWtCzdmx2List) {  
477 - this.queryStock(row, 'wtCzdmx2List');  
478 - }  
479 - } catch (err) {  
480 - console.warn('判断明细表归属失败:', err, row);  
481 - }  
482 - } 399 + const isInList1 = this.dataForm.wtCzdmxList && this.dataForm.wtCzdmxList.includes(row);
  400 + const listName = isInList1 ? 'wtCzdmxList' : 'wtCzdmx2List';
  401 +
  402 + // 获取成本价格(getProductCost 内部会自动从主表取仓库)
  403 + this.getProductCost(row);
  404 +
  405 + // 自动查询库存(需要确保仓库有值)
  406 + const warehouseId = row.chck || (isInList1 ? this.dataForm.chck : this.dataForm.rhck);
  407 + if (warehouseId) {
  408 + if (!row.chck) this.$set(row, 'chck', warehouseId);
  409 + this.queryStock(row, listName);
483 } 410 }
484 - }, 411 + }
  412 + },
485 413
486 - // 获取商品成本价格  
487 - async getProductCost(row) {  
488 - if (!row.spbh || !row.chck) {  
489 - console.warn('getProductCost: 商品编号或仓库不能为空', {  
490 - spbh: row.spbh,  
491 - chck: row.chck,  
492 - row: row  
493 - });  
494 - return;  
495 - }  
496 -  
497 - console.log('开始获取商品成本价格:', {  
498 - productId: row.spbh,  
499 - warehouseId: row.chck,  
500 - productName: row.spmc 414 + async getProductCost(row) {
  415 + if (!row.spbh) return;
  416 +
  417 + const isInList1 = this.dataForm.wtCzdmxList && this.dataForm.wtCzdmxList.includes(row);
  418 + const warehouseId = row.chck || (isInList1 ? this.dataForm.chck : this.dataForm.rhck) || '';
  419 +
  420 + try {
  421 + const res = await request({
  422 + url: '/api/Extend/WtCzd/GetProductCost',
  423 + method: 'post',
  424 + data: {
  425 + ProductId: row.spbh,
  426 + WarehouseId: warehouseId
  427 + }
501 }); 428 });
502 429
503 - try {  
504 - const res = await request({  
505 - url: '/api/Extend/WtCzd/GetProductCost',  
506 - method: 'post',  
507 - data: {  
508 - ProductId: row.spbh,  
509 - WarehouseId: row.chck  
510 - }  
511 - });  
512 -  
513 - console.log('获取商品成本价格接口响应:', res);  
514 -  
515 - if (res.data && res.data.success) {  
516 - const costPrice = res.data.data;  
517 - console.log('成本价格数据:', {  
518 - costPrice: costPrice,  
519 - source: res.data.source,  
520 - type: typeof costPrice  
521 - });  
522 -  
523 - if (costPrice > 0) {  
524 - row.cbdj = costPrice;  
525 - console.log(`商品 ${row.spmc} 成本价格获取成功:`, costPrice, '来源:', res.data.source);  
526 -  
527 - // 如果数量已填写,自动计算成本金额  
528 - if (row.sl && row.sl > 0) {  
529 - row.cbje = (parseFloat(row.sl) * costPrice).toFixed(2);  
530 - }  
531 -  
532 - // 强制更新视图  
533 - this.$forceUpdate();  
534 - } else {  
535 - console.log(`商品 ${row.spmc} 未找到成本价格信息`);  
536 - row.cbdj = '';  
537 - }  
538 - } else {  
539 - console.warn('获取商品成本价格失败:', (res.data && res.data.msg) || '未知错误');  
540 - console.warn('完整响应数据:', res); 430 + let costPrice = 0;
  431 + if (res.data !== undefined && res.data !== null) {
  432 + if (typeof res.data === 'object' && res.data.success) {
  433 + costPrice = parseFloat(res.data.data) || 0;
  434 + } else if (typeof res.data === 'number') {
  435 + costPrice = res.data;
  436 + } else if (typeof res.data === 'object') {
  437 + costPrice = parseFloat(res.data.cbdj || res.data.data || res.data.price || 0);
541 } 438 }
542 - } catch (error) {  
543 - console.error('获取商品成本价格异常:', error);  
544 } 439 }
545 - }, 440 +
  441 + this.$set(row, 'cbdj', costPrice || '');
  442 + if (costPrice > 0 && row.sl && parseFloat(row.sl) > 0) {
  443 + this.$set(row, 'cbje', (parseFloat(row.sl) * costPrice).toFixed(2));
  444 + }
  445 + } catch (error) {
  446 + console.error('获取商品成本价格异常:', error);
  447 + }
  448 + },
546 449
547 - // 处理数量变化,自动计算成本金额  
548 - handleQuantityChange(row) {  
549 - if (row.sl && row.cbdj) {  
550 - const quantity = parseFloat(row.sl);  
551 - const costPrice = parseFloat(row.cbdj);  
552 - if (!isNaN(quantity) && !isNaN(costPrice)) {  
553 - row.cbje = (quantity * costPrice).toFixed(2);  
554 - } 450 + handleQuantityChange(row) {
  451 + if (row.sl && row.cbdj) {
  452 + const quantity = parseFloat(row.sl);
  453 + const costPrice = parseFloat(row.cbdj);
  454 + if (!isNaN(quantity) && !isNaN(costPrice)) {
  455 + this.$set(row, 'cbje', (quantity * costPrice).toFixed(2));
555 } 456 }
556 - }, 457 + }
  458 + },
557 handleProductQuery(val, scope) { 459 handleProductQuery(val, scope) {
558 // 增强参数安全检查 460 // 增强参数安全检查
559 if (!scope || !scope.row || typeof scope.row !== 'object') { 461 if (!scope || !scope.row || typeof scope.row !== 'object') {
@@ -661,63 +563,10 @@ @@ -661,63 +563,10 @@
661 } 563 }
662 }); 564 });
663 }, 565 },
664 - dataFormSubmit() {  
665 - this.$refs['elForm'].validate((valid) => {  
666 - if (valid) {  
667 - // 数据提交前的调试信息  
668 - console.log('=== 保存拆装单前的数据检查 ===');  
669 - console.log('出货明细表:', this.dataForm.wtCzdmxList);  
670 - console.log('入货明细表:', this.dataForm.wtCzdmx2List);  
671 -  
672 - // 检查出货明细的序列号信息  
673 - if (this.dataForm.wtCzdmxList && this.dataForm.wtCzdmxList.length > 0) {  
674 - this.dataForm.wtCzdmxList.forEach((item, index) => {  
675 - console.log(`出货明细${index + 1}:`, {  
676 - 商品: item.spmc,  
677 - 序列号字段: item.xlh,  
678 - 选择序列号: item.selectedSerialNumbers,  
679 - 选择序列号数量: item.selectedSerialNumbers ? item.selectedSerialNumbers.length : 0  
680 - });  
681 - });  
682 - }  
683 -  
684 - // 检查入货明细的序列号信息  
685 - if (this.dataForm.wtCzdmx2List && this.dataForm.wtCzdmx2List.length > 0) {  
686 - this.dataForm.wtCzdmx2List.forEach((item, index) => {  
687 - console.log(`入货明细${index + 1}:`, {  
688 - 商品: item.spmc,  
689 - 序列号字段: item.xlh,  
690 - 选择序列号: item.selectedSerialNumbers,  
691 - 选择序列号数量: item.selectedSerialNumbers ? item.selectedSerialNumbers.length : 0  
692 - });  
693 - });  
694 - }  
695 -  
696 - // 检查序列化后的数据  
697 - console.log('=== 序列化后的数据检查 ===');  
698 - console.log('原始数据:', JSON.stringify(this.dataForm, null, 2));  
699 -  
700 - // 检查是否有循环引用或其他问题  
701 - try {  
702 - const serializedData = JSON.parse(JSON.stringify(this.dataForm));  
703 - console.log('序列化成功,数据:', serializedData);  
704 -  
705 - // 检查序列化后的序列号数据  
706 - if (serializedData.wtCzdmxList && serializedData.wtCzdmxList.length > 0) {  
707 - serializedData.wtCzdmxList.forEach((item, index) => {  
708 - console.log(`序列化后出货明细${index + 1}:`, {  
709 - 商品: item.spmc,  
710 - 序列号字段: item.xlh,  
711 - 选择序列号: item.selectedSerialNumbers,  
712 - 选择序列号数量: item.selectedSerialNumbers ? item.selectedSerialNumbers.length : 0  
713 - });  
714 - });  
715 - }  
716 - } catch (error) {  
717 - console.error('数据序列化失败:', error);  
718 - }  
719 -  
720 - this.loading = true; 566 + dataFormSubmit() {
  567 + this.$refs['elForm'].validate((valid) => {
  568 + if (valid) {
  569 + this.loading = true;
721 let method = 'post'; 570 let method = 'post';
722 let url = '/api/Extend/WtCzd'; 571 let url = '/api/Extend/WtCzd';
723 572
@@ -774,17 +623,11 @@ @@ -774,17 +623,11 @@
774 }); 623 });
775 } 624 }
776 625
777 - console.log('=== 提交的干净数据 ===');  
778 - console.log('cleanData:', cleanData);  
779 - console.log('出货明细selectedSerialNumbers:', cleanData.wtCzdmxList.map(item => item.selectedSerialNumbers));  
780 -  
781 - request({ 626 + request({
782 url: url, 627 url: url,
783 method: method, 628 method: method,
784 data: cleanData 629 data: cleanData
785 - }).then((res) => {  
786 - console.log('后端响应:', res);  
787 - console.log('响应数据:', res.data); 630 + }).then((res) => {
788 631
789 // 检查响应格式 632 // 检查响应格式
790 if (res.data && res.data.success) { 633 if (res.data && res.data.success) {
@@ -807,17 +650,8 @@ @@ -807,17 +650,8 @@
807 this.visible = false; 650 this.visible = false;
808 this.$emit('refresh', true); 651 this.$emit('refresh', true);
809 } 652 }
810 - }).catch(err => {  
811 - console.error('保存拆装单失败:', err);  
812 - console.error('错误详情:', {  
813 - message: err.message,  
814 - response: err.response,  
815 - status: (err.response && err.response.status),  
816 - data: (err.response && err.response.data),  
817 - stack: err.stack  
818 - });  
819 -  
820 - // 显示具体的错误信息 653 + }).catch(err => {
  654 + console.error('保存拆装单失败:', err);
821 let errorMessage = '保存拆装单失败'; 655 let errorMessage = '保存拆装单失败';
822 if (err.response && err.response.data) { 656 if (err.response && err.response.data) {
823 if (err.response.data.msg) { 657 if (err.response.data.msg) {
@@ -843,14 +677,14 @@ @@ -843,14 +677,14 @@
843 spmc: '', 677 spmc: '',
844 xlh: '', 678 xlh: '',
845 dw: '', 679 dw: '',
846 - zmkc: '', 680 + zmkc: 0,
847 stockLoading: false, 681 stockLoading: false,
848 productQuery: '', 682 productQuery: '',
849 - sl: '', 683 + sl: 1,
850 cbdj: '', 684 cbdj: '',
851 cbje: '', 685 cbje: '',
852 hasQueriedStock: false, 686 hasQueriedStock: false,
853 - selectedSerialNumbers: [], // 初始化序列号数组 687 + selectedSerialNumbers: [],
854 }; 688 };
855 this.dataForm.wtCzdmxList.push(item); 689 this.dataForm.wtCzdmxList.push(item);
856 }, 690 },
@@ -866,14 +700,14 @@ @@ -866,14 +700,14 @@
866 spmc: '', 700 spmc: '',
867 xlh: '', 701 xlh: '',
868 dw: '', 702 dw: '',
869 - zmkc: '', 703 + zmkc: 0,
870 stockLoading: false, 704 stockLoading: false,
871 productQuery: '', 705 productQuery: '',
872 - sl: '', 706 + sl: 1,
873 cbdj: '', 707 cbdj: '',
874 cbje: '', 708 cbje: '',
875 hasQueriedStock: false, 709 hasQueriedStock: false,
876 - selectedSerialNumbers: [], // 初始化序列号数组 710 + selectedSerialNumbers: [],
877 }; 711 };
878 this.dataForm.wtCzdmx2List.push(item); 712 this.dataForm.wtCzdmx2List.push(item);
879 }, 713 },
@@ -882,261 +716,48 @@ @@ -882,261 +716,48 @@
882 this.dataForm.wtCzdmx2List.splice(index, 1); 716 this.dataForm.wtCzdmx2List.splice(index, 1);
883 } 717 }
884 }, 718 },
885 - queryStock(row, listName) {  
886 - // 增强参数安全检查  
887 - if (!row || typeof row !== 'object') {  
888 - console.warn('queryStock: row参数无效', row);  
889 - return;  
890 - }  
891 -  
892 - // 检查仓库和商品编号是否有效(不能为空字符串或undefined)  
893 - if (!row.chck || row.chck === '' || !row.spbh || row.spbh === '') {  
894 - console.warn('queryStock: 仓库或商品编号无效', {  
895 - chck: row.chck,  
896 - spbh: row.spbh,  
897 - row: row  
898 - });  
899 - this.$message.warning('请先选择仓库和商品编号');  
900 - return;  
901 - }  
902 -  
903 - // 防抖处理,避免频繁查询  
904 - if (row.stockLoading) return;  
905 -  
906 - row.stockLoading = true;  
907 -  
908 - // 添加调试信息  
909 - console.log('查询库存参数:', {  
910 - warehouseId: row.chck,  
911 - productId: row.spbh,  
912 - serialNumber: row.xlh || '',  
913 - listName: listName  
914 - });  
915 -  
916 - // 先尝试调试接口  
917 - console.log('开始请求库存接口...');  
918 - console.log('请求参数详情:', {  
919 - warehouseId: row.chck,  
920 - productId: row.spbh,  
921 - serialNumber: row.xlh || '',  
922 - warehouseIdType: typeof row.chck,  
923 - productIdType: typeof row.spbh,  
924 - warehouseIdLength: row.chck ? row.chck.length : 0,  
925 - productIdLength: row.spbh ? row.spbh.length : 0  
926 - });  
927 -  
928 - // 构建完整的URL用于调试  
929 - const fullUrl = `/api/Extend/WtCzd/QueryStock?warehouseId=${encodeURIComponent(row.chck)}&productId=${encodeURIComponent(row.spbh)}&serialNumber=${encodeURIComponent(row.xlh || '')}`;  
930 - console.log('完整请求URL:', fullUrl);  
931 -  
932 - request({  
933 - url: `/api/Extend/WtCzd/QueryStock`,  
934 - method: 'post',  
935 - data: {  
936 - warehouseId: row.chck,  
937 - productId: row.spbh,  
938 - serialNumber: row.xlh || ''  
939 - }  
940 - }).then(res => {  
941 - console.log('库存查询响应:', res);  
942 - console.log('响应数据结构:', {  
943 - status: res.status,  
944 - statusText: res.statusText,  
945 - data: res.data,  
946 - dataType: typeof res.data,  
947 - hasData: !!res.data,  
948 - hasStock: res.data && res.data.success && res.data.data !== undefined  
949 - });  
950 -  
951 - // 检查响应数据的原始结构  
952 - console.log('原始响应对象:', {  
953 - keys: Object.keys(res),  
954 - values: Object.values(res),  
955 - entries: Object.entries(res)  
956 - });  
957 -  
958 - // 处理新的响应格式:{ success: true, data: 数量 }  
959 - let stockData = null;  
960 -  
961 - // 优先处理新的响应格式  
962 - if (res.data && res.data.success && res.data.data !== undefined) {  
963 - stockData = res.data.data;  
964 - console.log('从新格式获取库存:', stockData);  
965 - } else if (res.data && res.data.stock !== undefined) {  
966 - stockData = res.data.stock;  
967 - console.log('从旧格式获取库存:', stockData);  
968 - } else if (res.success && res.data !== undefined) {  
969 - stockData = res.data;  
970 - console.log('从根级新格式获取库存:', stockData);  
971 - } else if (res.stock !== undefined) {  
972 - stockData = res.stock;  
973 - console.log('从根级旧格式获取库存:', stockData);  
974 - }  
975 -  
976 - // 调试信息:显示实际的数据路径  
977 - console.log('数据访问路径检查:', {  
978 - 'res.data.success': res.data && res.data.success,  
979 - 'res.data.data': res.data && res.data.data,  
980 - 'res.data.stock': res.data && res.data.stock,  
981 - 'res.success': res.success,  
982 - 'res.data': res.data,  
983 - 'res.stock': res.stock  
984 - });  
985 -  
986 - if (stockData !== null) {  
987 - row.zmkc = stockData;  
988 - console.log('库存查询成功,设置库存值:', stockData);  
989 - this.$message.success(`库存查询成功: ${stockData}`);  
990 -  
991 - // 如果库存为0,自动调用调试接口诊断问题  
992 - if (stockData === 0) {  
993 - console.log('库存为0,自动调用调试接口诊断问题...');  
994 - this.debugStockQuery(row, listName);  
995 - }  
996 - } else {  
997 - row.zmkc = 0;  
998 - console.log('库存查询响应无效,设置默认值0');  
999 - if (res.data && res.data.msg) {  
1000 - console.log('响应消息:', res.data.msg);  
1001 - this.$message.info(res.data.msg);  
1002 - } else if (res.msg) {  
1003 - console.log('响应消息:', res.msg);  
1004 - this.$message.info(res.msg);  
1005 - } else {  
1006 - this.$message.info('该商品在当前仓库无库存');  
1007 - }  
1008 - }  
1009 - }).catch(err => {  
1010 - console.error('查询库存失败:', err);  
1011 - console.error('错误详情:', {  
1012 - message: err.message,  
1013 - response: err.response,  
1014 - status: err.response && err.response.status,  
1015 - data: err.response && err.response.data  
1016 - });  
1017 -  
1018 - // 如果库存查询失败,尝试调试接口  
1019 - this.debugStockQuery(row, listName);  
1020 -  
1021 - row.zmkc = 0;  
1022 -  
1023 - // 根据错误类型显示不同的提示  
1024 - if (err.response && err.response.status === 404) {  
1025 - this.$message.error('库存查询接口不存在,请联系管理员');  
1026 - } else if (err.response && err.response.status === 500) {  
1027 - this.$message.error('服务器内部错误,请联系管理员');  
1028 - } else if (err.code === 'NETWORK_ERROR') {  
1029 - this.$message.error('网络连接失败,请检查网络设置');  
1030 - } else {  
1031 - this.$message.error(`查询库存失败: ${err.message || '未知错误'}`);  
1032 - }  
1033 - }).finally(() => {  
1034 - row.stockLoading = false;  
1035 - });  
1036 - }, 719 + queryStock(row, listName) {
  720 + if (!row || typeof row !== 'object') return;
  721 + if (!row.chck || !row.spbh) {
  722 + this.$message.warning('请先选择仓库和商品编号');
  723 + return;
  724 + }
  725 + if (row.stockLoading) return;
1037 726
1038 - // 调试库存查询问题  
1039 - debugStockQuery(row, listName) {  
1040 - console.log('=== 开始调试库存查询问题 ===');  
1041 - console.log('调试参数:', {  
1042 - warehouse: row.chck,  
1043 - productCode: row.spbh,  
1044 - listName: listName  
1045 - });  
1046 -  
1047 - request({  
1048 - url: `/api/Extend/WtCzd/DebugStock`,  
1049 - method: 'get',  
1050 - params: {  
1051 - warehouseId: row.chck,  
1052 - productId: row.spbh  
1053 - }  
1054 - }).then(res => {  
1055 - console.log('调试库存查询响应:', res);  
1056 - console.log('调试响应数据结构:', {  
1057 - 'res.data': res.data,  
1058 - 'res.data.data': res.data && res.data.data,  
1059 - 'res.data.data.checks': res.data && res.data.data && res.data.data.checks  
1060 - });  
1061 -  
1062 - if (res.data && res.data.data) {  
1063 - console.log('=== 调试信息详情 ===');  
1064 - console.log('仓库信息:', res.data.data.warehouse);  
1065 - console.log('商品信息:', res.data.data.productCode);  
1066 - console.log('时间戳:', res.data.data.timestamp);  
1067 -  
1068 - if (res.data.data.checks && Array.isArray(res.data.data.checks)) {  
1069 - console.log('=== 数据表检查结果 ===');  
1070 - res.data.data.checks.forEach((check, index) => {  
1071 - console.log(`检查项 ${index + 1}:`, check);  
1072 - });  
1073 - }  
1074 -  
1075 - // 分析库存为0的原因  
1076 - this.analyzeStockZeroReason(res.data.data);  
1077 - } else if (res.data) {  
1078 - console.log('调试信息:', res.data);  
1079 - }  
1080 -  
1081 - console.log('=== 调试库存查询问题结束 ===');  
1082 - }).catch(err => {  
1083 - console.error('调试库存查询失败:', err);  
1084 - });  
1085 - }, 727 + row.stockLoading = true;
1086 728
1087 - // 分析库存为0的原因  
1088 - analyzeStockZeroReason(debugData) {  
1089 - console.log('=== 分析库存为0的原因 ===');  
1090 -  
1091 - if (debugData.checks && Array.isArray(debugData.checks)) {  
1092 - debugData.checks.forEach((check, index) => {  
1093 - if (check.table === 'WtSerialNumberEntity') {  
1094 - console.log('序列号表分析:');  
1095 - console.log(`- 总记录数: ${check.totalCount}`);  
1096 - console.log(`- 按商品查询: ${check.byProduct}`);  
1097 - console.log(`- 按仓库查询: ${check.byWarehouse}`);  
1098 - console.log(`- 按商品+仓库查询: ${check.byBoth}`);  
1099 - console.log(`- 在库状态: ${check.inStock}`);  
1100 -  
1101 - if (check.totalCount === 0) {  
1102 - console.log('❌ 问题: 序列号表为空,没有商品库存记录');  
1103 - } else if (check.byProduct === 0) {  
1104 - console.log('❌ 问题: 该商品在序列号表中不存在');  
1105 - } else if (check.byWarehouse === 0) {  
1106 - console.log('❌ 问题: 该仓库在序列号表中不存在');  
1107 - } else if (check.byBoth === 0) {  
1108 - console.log('❌ 问题: 该商品在该仓库中不存在');  
1109 - } else if (check.inStock === 0) {  
1110 - console.log('❌ 问题: 该商品在该仓库中无在库状态的记录');  
1111 - }  
1112 - }  
1113 -  
1114 - if (check.table === 'WtSpEntity') {  
1115 - console.log('商品档案表分析:');  
1116 - console.log(`- 总商品数: ${check.totalCount}`);  
1117 - if (check.product) {  
1118 - console.log(`- 商品信息:`, check.product);  
1119 - if (check.product.Kc === 0 || check.product.Kc === null) {  
1120 - console.log('❌ 问题: 商品档案表中的库存字段为0或空');  
1121 - }  
1122 - } else {  
1123 - console.log('❌ 问题: 商品档案表中找不到该商品');  
1124 - }  
1125 - }  
1126 -  
1127 - if (check.table === 'WtCkEntity') {  
1128 - console.log('仓库表分析:');  
1129 - if (check.warehouse) {  
1130 - console.log(`- 仓库信息:`, check.warehouse);  
1131 - } else {  
1132 - console.log('❌ 问题: 仓库表中找不到该仓库');  
1133 - }  
1134 - }  
1135 - }); 729 + request({
  730 + url: `/api/Extend/WtCzd/QueryStock`,
  731 + method: 'post',
  732 + data: {
  733 + warehouseId: row.chck,
  734 + productId: row.spbh,
  735 + serialNumber: row.xlh || ''
  736 + }
  737 + }).then(res => {
  738 + let stockData = null;
  739 + if (res.data && res.data.success && res.data.data !== undefined) {
  740 + stockData = res.data.data;
  741 + } else if (res.data && res.data.stock !== undefined) {
  742 + stockData = res.data.stock;
  743 + } else if (res.success && res.data !== undefined) {
  744 + stockData = res.data;
  745 + } else if (res.stock !== undefined) {
  746 + stockData = res.stock;
1136 } 747 }
1137 748
1138 - console.log('=== 库存为0原因分析结束 ===');  
1139 - }, 749 + if (stockData !== null) {
  750 + this.$set(row, 'zmkc', stockData);
  751 + } else {
  752 + this.$set(row, 'zmkc', 0);
  753 + }
  754 + }).catch(err => {
  755 + console.error('查询库存失败:', err);
  756 + this.$set(row, 'zmkc', 0);
  757 + }).finally(() => {
  758 + row.stockLoading = false;
  759 + });
  760 + },
1140 761
1141 // 调试前端数据状态 762 // 调试前端数据状态
1142 debugFrontendData() { 763 debugFrontendData() {
@@ -1256,74 +877,43 @@ @@ -1256,74 +877,43 @@
1256 console.log('商品选项:', this.spbhOptions); 877 console.log('商品选项:', this.spbhOptions);
1257 console.log('=== 快速诊断结束 ==='); 878 console.log('=== 快速诊断结束 ===');
1258 }, 879 },
1259 - handleCostPriceChange(row) {  
1260 - // 当成本单价变化时,自动计算成本金额  
1261 - if (row.sl && row.cbdj) {  
1262 - const quantity = parseFloat(row.sl);  
1263 - const costPrice = parseFloat(row.cbdj);  
1264 - if (!isNaN(quantity) && !isNaN(costPrice)) {  
1265 - row.cbje = (quantity * costPrice).toFixed(2);  
1266 - } 880 + handleCostPriceChange(row) {
  881 + if (row.sl && row.cbdj) {
  882 + const quantity = parseFloat(row.sl);
  883 + const costPrice = parseFloat(row.cbdj);
  884 + if (!isNaN(quantity) && !isNaN(costPrice)) {
  885 + this.$set(row, 'cbje', (quantity * costPrice).toFixed(2));
1267 } 886 }
1268 - }, 887 + }
  888 + },
1269 889
1270 - // 打开序列号选择弹窗  
1271 - openSerialNumberSelect(row, listName) {  
1272 - console.log('=== 序列号选择按钮被点击 ===');  
1273 - console.log('行数据:', row);  
1274 - console.log('明细表类型:', listName);  
1275 - console.log('商品编号:', row.spbh);  
1276 - console.log('仓库:', row.chck);  
1277 - console.log('当前行对象:', JSON.stringify(row, null, 2));  
1278 -  
1279 - // 检查基本参数  
1280 - if (!row) {  
1281 - console.error('行数据为空');  
1282 - this.$message.error('行数据为空');  
1283 - return;  
1284 - }  
1285 -  
1286 - if (!row.spbh || !row.chck) {  
1287 - console.warn('缺少商品或仓库信息');  
1288 - this.$message.warning('请先选择商品和仓库');  
1289 - return;  
1290 - }  
1291 -  
1292 - // 检查组件引用  
1293 - console.log('检查组件引用...');  
1294 - console.log('this.$refs:', this.$refs);  
1295 - console.log('this.$refs.serialNumberSelect:', this.$refs.serialNumberSelect);  
1296 -  
1297 - if (!this.$refs.serialNumberSelect) {  
1298 - console.error('序列号选择组件引用不存在');  
1299 - this.$message.error('序列号选择组件加载失败');  
1300 - return;  
1301 - }  
1302 -  
1303 - // 根据明细表类型选择对应的仓库选项  
1304 - const warehouseOptions = listName === 'wtCzdmxList' ? this.chckOptions : this.rhckOptions;  
1305 - console.log('仓库选项:', warehouseOptions);  
1306 -  
1307 - console.log('准备打开序列号选择弹窗...');  
1308 -  
1309 - // 打开序列号选择弹窗  
1310 - try {  
1311 - // 确保当前行有selectedSerialNumbers数组  
1312 - if (!row.selectedSerialNumbers) {  
1313 - console.log('初始化selectedSerialNumbers数组');  
1314 - this.$set(row, 'selectedSerialNumbers', []);  
1315 - }  
1316 -  
1317 - console.log('调用组件open方法...');  
1318 - this.$refs.serialNumberSelect.open(row, listName, warehouseOptions);  
1319 - console.log('序列号选择弹窗打开成功');  
1320 - } catch (error) {  
1321 - console.error('打开序列号选择弹窗失败:', error);  
1322 - console.error('错误堆栈:', error.stack);  
1323 - this.$message.error('打开序列号选择弹窗失败: ' + error.message); 890 + openSerialNumberSelect(row, listName) {
  891 + if (!row) {
  892 + this.$message.error('行数据为空');
  893 + return;
  894 + }
  895 + if (!row.spbh || !row.chck) {
  896 + this.$message.warning('请先选择商品和仓库');
  897 + return;
  898 + }
  899 + if (!this.$refs.serialNumberSelect) {
  900 + this.$message.error('序列号选择组件加载失败');
  901 + return;
  902 + }
  903 +
  904 + const warehouseOptions = listName === 'wtCzdmxList' ? this.chckOptions : this.rhckOptions;
  905 +
  906 + try {
  907 + if (!row.selectedSerialNumbers) {
  908 + this.$set(row, 'selectedSerialNumbers', []);
1324 } 909 }
  910 + this.$refs.serialNumberSelect.open(row, listName, warehouseOptions);
  911 + } catch (error) {
  912 + console.error('打开序列号选择弹窗失败:', error);
  913 + this.$message.error('打开序列号选择弹窗失败: ' + error.message);
1325 } 914 }
1326 } 915 }
  916 + }
1327 } 917 }
1328 </script> 918 </script>
1329 <style scoped> 919 <style scoped>
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPriceAdjust/Form.vue
1 <template> 1 <template>
2 - <transition name="el-zoom-in-center">  
3 - <div class="NCC-preview-main">  
4 - <div class="NCC-common-page-header">  
5 - <el-page-header @back="goBack" :content="!dataForm.id ? '新建调价单' : isDetail ? '调价单详情' : '编辑调价单'" />  
6 - <div class="options">  
7 - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button>  
8 - <el-button @click="goBack">取 消</el-button>  
9 - </div>  
10 - </div>  
11 - <el-row :gutter="15" class="main" style="margin: 0 auto; width: 100%">  
12 - <el-form ref="elForm" :model="dataForm" size="medium" label-width="110px" label-position="right" :disabled="!!isDetail" :rules="rules">  
13 - <el-col :span="24">  
14 - <el-form-item label="商品名称" prop="productName">  
15 - <el-input v-model="dataForm.productName" placeholder="商品名称" clearable required style="width:100%" />  
16 - </el-form-item>  
17 - </el-col>  
18 - <el-col :span="12">  
19 - <el-form-item label="原价" prop="oldPrice">  
20 - <el-input v-model="dataForm.oldPrice" placeholder="原价" clearable required style="width:100%" />  
21 - </el-form-item>  
22 - </el-col>  
23 - <el-col :span="12">  
24 - <el-form-item label="新价" prop="newPrice">  
25 - <el-input v-model="dataForm.newPrice" placeholder="新价" clearable required style="width:100%" />  
26 - </el-form-item>  
27 - </el-col>  
28 - <el-col :span="12">  
29 - <el-form-item label="调价日期" prop="adjustDate">  
30 - <el-date-picker v-model="dataForm.adjustDate" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width:100%" />  
31 - </el-form-item>  
32 - </el-col>  
33 - <el-col :span="12">  
34 - <el-form-item label="状态" prop="status">  
35 - <el-select v-model="dataForm.status" placeholder="请选择状态" clearable style="width:100%">  
36 - <el-option label="草稿" value="draft" />  
37 - <el-option label="已生效" value="active" />  
38 - <el-option label="已作废" value="void" />  
39 - </el-select>  
40 - </el-form-item>  
41 - </el-col>  
42 - </el-form>  
43 - </el-row>  
44 - </div>  
45 - </transition>  
46 -</template> 2 + <el-dialog :title="dialogTitle" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center tjd-dialog" lock-scroll width="1200px" top="5vh">
  3 + <el-form ref="elForm" :model="dataForm" size="small" label-width="80px" label-position="right" :disabled="!!isDetail" :rules="rules">
  4 + <div class="tjd-section">
  5 + <div class="tjd-section__header">
  6 + <span class="tjd-section__title">基本信息</span>
  7 + <el-tag v-if="dataForm.djzt === '已审核'" type="success" size="small" effect="dark">已审核</el-tag>
  8 + <el-tag v-else-if="dataForm.id" type="info" size="small">草稿</el-tag>
  9 + </div>
  10 + <el-row :gutter="16">
  11 + <el-col :span="8">
  12 + <el-form-item label="单据编号">
  13 + <el-input v-model="dataForm.id" placeholder="自动生成" readonly />
  14 + </el-form-item>
  15 + </el-col>
  16 + <el-col :span="8">
  17 + <el-form-item label="单据日期" prop="djrq">
  18 + <el-date-picker v-model="dataForm.djrq" placeholder="请选择" style="width:100%" type="date" format="yyyy-MM-dd" value-format="timestamp" />
  19 + </el-form-item>
  20 + </el-col>
  21 + <el-col :span="8">
  22 + <el-form-item label="仓库" prop="ck">
  23 + <el-select v-model="dataForm.ck" placeholder="请选择仓库" clearable filterable style="width:100%" @change="handleWarehouseChange">
  24 + <el-option v-for="item in ckOptions" :key="item.F_Id" :label="item.F_mdmc" :value="item.F_Id" />
  25 + </el-select>
  26 + </el-form-item>
  27 + </el-col>
  28 + <el-col :span="8">
  29 + <el-form-item label="经手人">
  30 + <user-select v-model="dataForm.jsr" placeholder="自动设置" disabled />
  31 + </el-form-item>
  32 + </el-col>
  33 + <el-col :span="16">
  34 + <el-form-item label="备注">
  35 + <el-input v-model="dataForm.bz" placeholder="请输入备注" />
  36 + </el-form-item>
  37 + </el-col>
  38 + </el-row>
  39 + </div>
  40 +
  41 + <div class="tjd-section">
  42 + <div class="tjd-section__header">
  43 + <span class="tjd-section__title">商品明细</span>
  44 + <span class="tjd-section__count" v-if="dataForm.wtTjdMxList.length">{{ dataForm.wtTjdMxList.length }} 条</span>
  45 + </div>
  46 +
  47 + <div class="tjd-toolbar" v-if="!isDetail">
  48 + <div class="tjd-toolbar__left">
  49 + <el-button type="primary" size="small" icon="el-icon-plus" :disabled="!dataForm.ck" @click="addMxRow">添加商品</el-button>
  50 + <el-divider direction="vertical" />
  51 + <el-checkbox v-model="loadAllFlag" :disabled="!dataForm.ck" @change="handleLoadAllChange">
  52 + 加载全部商品
  53 + </el-checkbox>
  54 + <span class="tjd-toolbar__hint" v-if="!dataForm.ck">请先选择仓库</span>
  55 + </div>
  56 + <div class="tjd-toolbar__right">
  57 + <el-button size="small" icon="el-icon-delete" :disabled="!dataForm.wtTjdMxList.length" @click="clearAllMx">清空</el-button>
  58 + </div>
  59 + </div>
  60 +
  61 + <div class="tjd-empty" v-if="!dataForm.wtTjdMxList.length && !productsLoading">
  62 + <i class="el-icon-box" style="font-size:36px;color:#C0C4CC" />
  63 + <p>暂无商品明细</p>
  64 + <p class="tjd-empty__hint" v-if="!isDetail">选择仓库后勾选「加载全部商品」或点击「添加商品」</p>
  65 + </div>
  66 +
  67 + <el-table
  68 + v-show="dataForm.wtTjdMxList.length || productsLoading"
  69 + :data="dataForm.wtTjdMxList"
  70 + size="mini"
  71 + show-summary
  72 + :summary-method="getSummaries"
  73 + border
  74 + stripe
  75 + v-loading="productsLoading"
  76 + element-loading-text="正在加载..."
  77 + max-height="380"
  78 + class="tjd-table"
  79 + >
  80 + <el-table-column type="index" width="40" label="#" align="center" />
  81 + <el-table-column prop="spbh" label="商品" min-width="200">
  82 + <template slot-scope="scope">
  83 + <el-select
  84 + v-if="scope.row._manual && !isDetail"
  85 + v-model="scope.row.spbh"
  86 + placeholder="搜索商品"
  87 + filterable
  88 + clearable
  89 + size="mini"
  90 + style="width:100%"
  91 + @change="handleProductSelect(scope.row)"
  92 + >
  93 + <el-option
  94 + v-for="item in warehouseProducts"
  95 + :key="item.spbh"
  96 + :label="(item.spbm || '') + ' ' + (item.spmc || '')"
  97 + :value="item.spbh"
  98 + />
  99 + </el-select>
  100 + <span v-else>{{ scope.row.spmc || scope.row.spbh || '-' }}</span>
  101 + </template>
  102 + </el-table-column>
  103 + <el-table-column prop="spbm" label="编码" width="120">
  104 + <template slot-scope="scope">
  105 + <span class="tjd-code">{{ scope.row.spbm || '-' }}</span>
  106 + </template>
  107 + </el-table-column>
  108 + <el-table-column prop="sl" label="库存" width="70" align="right">
  109 + <template slot-scope="scope">
  110 + <span :class="{ 'text-warning': scope.row.sl === 0 }">{{ scope.row.sl }}</span>
  111 + </template>
  112 + </el-table-column>
  113 + <el-table-column prop="tqdj" label="调前单价" width="90" align="right">
  114 + <template slot-scope="scope">
  115 + <span :class="{ 'text-warning': !(parseFloat(scope.row.tqdj) > 0) }">{{ formatPrice(scope.row.tqdj) }}</span>
  116 + </template>
  117 + </el-table-column>
  118 + <el-table-column prop="tjbl" label="比率%" width="100" class-name="tjd-editable-col">
  119 + <template slot-scope="scope">
  120 + <el-input v-model="scope.row.tjbl" size="mini" placeholder="如10" :disabled="!!isDetail" @input="handleRatioChange(scope.row)">
  121 + <template slot="append">%</template>
  122 + </el-input>
  123 + </template>
  124 + </el-table-column>
  125 + <el-table-column prop="thdj" label="调后单价" width="100" class-name="tjd-editable-col">
  126 + <template slot-scope="scope">
  127 + <el-input v-model="scope.row.thdj" size="mini" placeholder="价格" :disabled="!!isDetail" @input="handleNewPriceChange(scope.row)" />
  128 + </template>
  129 + </el-table-column>
  130 + <el-table-column prop="tqje" label="调前金额" width="90" align="right">
  131 + <template slot-scope="scope">{{ formatPrice(scope.row.tqje) }}</template>
  132 + </el-table-column>
  133 + <el-table-column prop="thje" label="调后金额" width="90" align="right">
  134 + <template slot-scope="scope">
  135 + <span class="text-primary" v-if="scope.row.thje">{{ formatPrice(scope.row.thje) }}</span>
  136 + <span v-else>-</span>
  137 + </template>
  138 + </el-table-column>
  139 + <el-table-column prop="bz" label="备注" min-width="80">
  140 + <template slot-scope="scope">
  141 + <el-input v-model="scope.row.bz" size="mini" placeholder="备注" :disabled="!!isDetail" />
  142 + </template>
  143 + </el-table-column>
  144 + <el-table-column label="" width="45" align="center" v-if="!isDetail">
  145 + <template slot-scope="scope">
  146 + <el-button size="mini" type="text" icon="el-icon-delete" class="NCC-table-delBtn" @click="handleDelMx(scope.$index)" />
  147 + </template>
  148 + </el-table-column>
  149 + </el-table>
  150 + </div>
  151 + </el-form>
47 152
  153 + <span slot="footer" class="dialog-footer">
  154 + <el-button @click="visible = false">取 消</el-button>
  155 + <el-button type="warning" icon="el-icon-check" @click="handleAudit()" v-if="dataForm.id && (!dataForm.djzt || dataForm.djzt === '草稿') && !isDetail">审 核</el-button>
  156 + <el-button type="primary" icon="el-icon-document-checked" @click="dataFormSubmit()" :loading="submitLoading" v-if="!isDetail">保 存</el-button>
  157 + </span>
  158 + </el-dialog>
  159 +</template>
48 <script> 160 <script>
49 -export default {  
50 - name: 'PriceAdjustForm',  
51 - props: {  
52 - id: String  
53 - },  
54 - data() {  
55 - return {  
56 - isDetail: false,  
57 - dataForm: {  
58 - id: '',  
59 - productName: '',  
60 - oldPrice: '',  
61 - newPrice: '',  
62 - adjustDate: '',  
63 - status: 'draft'  
64 - },  
65 - rules: {  
66 - productName: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],  
67 - oldPrice: [{ required: true, message: '请输入原价', trigger: 'blur' }],  
68 - newPrice: [{ required: true, message: '请输入新价', trigger: 'blur' }],  
69 - adjustDate: [{ required: true, message: '请选择调价日期', trigger: 'change' }],  
70 - status: [{ required: true, message: '请选择状态', trigger: 'change' }]  
71 - }  
72 - }  
73 - },  
74 - watch: {  
75 - id: {  
76 - immediate: true,  
77 - handler(val) {  
78 - if (val) {  
79 - // TODO: 获取调价单详情,填充 dataForm  
80 - this.dataForm.id = val  
81 - } else {  
82 - this.dataForm = {  
83 - id: '',  
84 - productName: '',  
85 - oldPrice: '',  
86 - newPrice: '',  
87 - adjustDate: '',  
88 - status: 'draft'  
89 - }  
90 - }  
91 - }  
92 - }  
93 - },  
94 - methods: {  
95 - dataFormSubmit() {  
96 - this.$refs.elForm.validate(valid => {  
97 - if (!valid) return  
98 - // TODO: 新增或编辑调价单  
99 - this.$emit('refresh')  
100 - })  
101 - },  
102 - goBack() {  
103 - this.$emit('close')  
104 - }  
105 - }  
106 -} 161 + import request from '@/utils/request'
  162 + import { previewDataInterface } from '@/api/systemData/dataInterface'
  163 + export default {
  164 + props: [],
  165 + data() {
  166 + return {
  167 + visible: false,
  168 + isDetail: false,
  169 + submitLoading: false,
  170 + productsLoading: false,
  171 + loadAllFlag: false,
  172 + dataForm: {
  173 + id: '',
  174 + djrq: undefined,
  175 + ck: undefined,
  176 + jsr: undefined,
  177 + zdr: undefined,
  178 + djzt: '草稿',
  179 + bz: undefined,
  180 + wtTjdMxList: [],
  181 + },
  182 + rules: {
  183 + ck: [{ required: true, message: '请选择仓库', trigger: 'change' }],
  184 + },
  185 + ckOptions: [],
  186 + warehouseProducts: [],
  187 + }
  188 + },
  189 + computed: {
  190 + dialogTitle() {
  191 + if (!this.dataForm.id) return '新建调价单'
  192 + if (this.isDetail) return '调价单详情'
  193 + return '编辑调价单'
  194 + }
  195 + },
  196 + created() {
  197 + this.getCkOptions()
  198 + },
  199 + methods: {
  200 + getCkOptions() {
  201 + previewDataInterface('681758216954053893').then(res => {
  202 + this.ckOptions = res.data
  203 + })
  204 + },
  205 + formatPrice(val) {
  206 + const n = parseFloat(val)
  207 + if (isNaN(n)) return '-'
  208 + return n.toFixed(2)
  209 + },
  210 + init(id, isDetail) {
  211 + this.visible = true
  212 + this.isDetail = isDetail || false
  213 + this.loadAllFlag = false
  214 + this.warehouseProducts = []
  215 + this.$nextTick(() => {
  216 + if (this.$refs['elForm']) this.$refs['elForm'].resetFields()
  217 + if (id) {
  218 + request({ url: '/api/Extend/WtTjd/' + id, method: 'get' }).then(res => {
  219 + this.dataForm = { ...res.data, wtTjdMxList: (res.data.wtTjdMxList || []).map(item => ({ ...item, _manual: false })) }
  220 + if (this.dataForm.ck) this.loadWarehouseProducts(this.dataForm.ck)
  221 + })
  222 + } else {
  223 + this.dataForm = {
  224 + id: '',
  225 + djrq: Date.now(),
  226 + ck: undefined,
  227 + jsr: undefined,
  228 + zdr: undefined,
  229 + djzt: '草稿',
  230 + bz: undefined,
  231 + wtTjdMxList: [],
  232 + }
  233 + var user = (this.$store && this.$store.getters && this.$store.getters.userInfo) ? this.$store.getters.userInfo : {}
  234 + this.dataForm.jsr = user.userId || user.id || ''
  235 + }
  236 + })
  237 + },
  238 + loadWarehouseProducts(ck) {
  239 + if (!ck) { this.warehouseProducts = []; return }
  240 + request({
  241 + url: '/api/Extend/WtTjd/Actions/LoadAllProducts',
  242 + method: 'GET',
  243 + data: { ck }
  244 + }).then(res => {
  245 + this.warehouseProducts = (res.data && Array.isArray(res.data)) ? res.data : []
  246 + }).catch(() => { this.warehouseProducts = [] })
  247 + },
  248 + handleWarehouseChange(val) {
  249 + if (!val) {
  250 + this.dataForm.wtTjdMxList = []
  251 + this.loadAllFlag = false
  252 + this.warehouseProducts = []
  253 + return
  254 + }
  255 + this.loadWarehouseProducts(val)
  256 + this.dataForm.wtTjdMxList.forEach(row => {
  257 + this.$set(row, 'ck', val)
  258 + })
  259 + if (this.loadAllFlag) {
  260 + this.loadProducts()
  261 + }
  262 + },
  263 + handleLoadAllChange(checked) {
  264 + if (checked && this.dataForm.ck) {
  265 + this.loadProducts()
  266 + } else if (!checked) {
  267 + this.dataForm.wtTjdMxList = []
  268 + }
  269 + },
  270 + loadProducts() {
  271 + const ck = this.dataForm.ck
  272 + if (!ck) { this.$message.warning('请先选择仓库'); return }
  273 + this.productsLoading = true
  274 + request({
  275 + url: '/api/Extend/WtTjd/Actions/LoadAllProducts',
  276 + method: 'GET',
  277 + data: { ck }
  278 + }).then(res => {
  279 + if (res.data && Array.isArray(res.data)) {
  280 + this.dataForm.wtTjdMxList = res.data.map(item => {
  281 + const sl = parseFloat(item.sl) || 0
  282 + const tqdj = parseFloat(item.tqdj) || 0
  283 + return {
  284 + ck: this.dataForm.ck, spbh: item.spbh, spbm: item.spbm || '',
  285 + spmc: item.spmc, sl, tqdj, tjbl: '', thdj: '',
  286 + tqje: (sl * tqdj).toFixed(2), thje: '', bz: '', _manual: false,
  287 + }
  288 + })
  289 + if (!this.dataForm.wtTjdMxList.length) this.$message.info('该仓库暂无商品')
  290 + } else {
  291 + this.dataForm.wtTjdMxList = []
  292 + this.$message.info('该仓库暂无商品')
  293 + }
  294 + }).catch(() => {
  295 + this.$message.error('加载商品失败')
  296 + }).finally(() => { this.productsLoading = false })
  297 + },
  298 + handleProductSelect(row) {
  299 + const product = this.warehouseProducts.find(item => item.spbh === row.spbh)
  300 + if (product) {
  301 + this.$set(row, 'spmc', product.spmc || '')
  302 + this.$set(row, 'spbm', product.spbm || '')
  303 + this.$set(row, 'sl', parseFloat(product.sl) || 0)
  304 + this.$set(row, 'tqdj', parseFloat(product.tqdj) || 0)
  305 + const sl = parseFloat(product.sl) || 0
  306 + const tqdj = parseFloat(product.tqdj) || 0
  307 + this.$set(row, 'tqje', (sl * tqdj).toFixed(2))
  308 + }
  309 + this.$set(row, 'ck', this.dataForm.ck || '')
  310 + },
  311 + handleRatioChange(row) {
  312 + const tqdj = parseFloat(row.tqdj) || 0
  313 + const tjbl = parseFloat(row.tjbl)
  314 + if (!isNaN(tjbl) && tqdj > 0) {
  315 + const thdj = tqdj * (1 + tjbl / 100)
  316 + this.$set(row, 'thdj', thdj.toFixed(2))
  317 + const sl = parseFloat(row.sl) || 0
  318 + this.$set(row, 'thje', (sl * thdj).toFixed(2))
  319 + }
  320 + },
  321 + handleNewPriceChange(row) {
  322 + const tqdj = parseFloat(row.tqdj) || 0
  323 + const thdj = parseFloat(row.thdj)
  324 + if (!isNaN(thdj)) {
  325 + if (tqdj > 0) {
  326 + this.$set(row, 'tjbl', (((thdj - tqdj) / tqdj) * 100).toFixed(2))
  327 + }
  328 + const sl = parseFloat(row.sl) || 0
  329 + this.$set(row, 'thje', (sl * thdj).toFixed(2))
  330 + }
  331 + },
  332 + addMxRow() {
  333 + if (!this.dataForm.ck) { this.$message.warning('请先选择仓库'); return }
  334 + this.dataForm.wtTjdMxList.push({
  335 + ck: this.dataForm.ck, spbh: '', spbm: '', spmc: '',
  336 + sl: 0, tqdj: 0, tjbl: '', thdj: '',
  337 + tqje: '0.00', thje: '', bz: '', _manual: true,
  338 + })
  339 + },
  340 + handleDelMx(index) {
  341 + this.dataForm.wtTjdMxList.splice(index, 1)
  342 + },
  343 + clearAllMx() {
  344 + this.$confirm('确认清空所有商品明细?', '提示', { type: 'warning' }).then(() => {
  345 + this.dataForm.wtTjdMxList = []
  346 + this.loadAllFlag = false
  347 + }).catch(() => {})
  348 + },
  349 + getSummaries(param) {
  350 + const { columns, data } = param
  351 + const sums = []
  352 + columns.forEach((col, i) => {
  353 + if (i === 0) { sums[i] = '合计'; return }
  354 + if (col.property === 'sl') sums[i] = data.reduce((t, r) => t + (parseFloat(r.sl) || 0), 0)
  355 + else if (col.property === 'tqje') sums[i] = data.reduce((t, r) => t + (parseFloat(r.tqje) || 0), 0).toFixed(2)
  356 + else if (col.property === 'thje') sums[i] = data.reduce((t, r) => t + (parseFloat(r.thje) || 0), 0).toFixed(2)
  357 + else sums[i] = ''
  358 + })
  359 + return sums
  360 + },
  361 + dataFormSubmit() {
  362 + this.$refs['elForm'].validate((valid) => {
  363 + if (!valid) return
  364 + if (!this.dataForm.wtTjdMxList || this.dataForm.wtTjdMxList.length === 0) {
  365 + this.$message.warning('请至少添加一条商品明细'); return
  366 + }
  367 + this.submitLoading = true
  368 + const isNew = !this.dataForm.id
  369 + const method = isNew ? 'post' : 'put'
  370 + const url = isNew ? '/api/Extend/WtTjd' : `/api/Extend/WtTjd/${this.dataForm.id}`
  371 + request({ url, method, data: this.dataForm }).then((res) => {
  372 + this.$message({ type: 'success', message: res.msg || '保存成功', duration: 1000, onClose: () => { this.visible = false; this.$emit('refresh', true) } })
  373 + }).finally(() => { this.submitLoading = false })
  374 + })
  375 + },
  376 + handleAudit() {
  377 + if (!this.dataForm.id) return
  378 + this.$confirm('确认审核?审核后不可修改,调后价格将写入成本价表。', '审核确认', { type: 'warning' }).then(() => {
  379 + request({ url: `/api/Extend/WtTjd/Actions/Audit/${this.dataForm.id}`, method: 'POST' }).then(res => {
  380 + this.$message({ type: 'success', message: res.msg || '审核成功', duration: 1000, onClose: () => { this.visible = false; this.$emit('refresh', true) } })
  381 + })
  382 + }).catch(() => {})
  383 + }
  384 + }
  385 + }
107 </script> 386 </script>
108 -  
109 <style scoped> 387 <style scoped>
110 -</style>  
111 \ No newline at end of file 388 \ No newline at end of file
  389 +.tjd-section { margin-bottom: 14px; }
  390 +.tjd-section__header {
  391 + display: flex; align-items: center; gap: 10px;
  392 + margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid #EBEEF5;
  393 +}
  394 +.tjd-section__title { font-size: 14px; font-weight: 600; color: #303133; }
  395 +.tjd-section__count { font-size: 12px; color: #909399; margin-left: auto; }
  396 +.tjd-toolbar {
  397 + display: flex; align-items: center; justify-content: space-between;
  398 + padding: 6px 12px; background: #F5F7FA; border-radius: 4px; margin-bottom: 8px;
  399 +}
  400 +.tjd-toolbar__left { display: flex; align-items: center; gap: 8px; }
  401 +.tjd-toolbar__hint { font-size: 12px; color: #C0C4CC; }
  402 +.tjd-empty { text-align: center; padding: 30px 0; color: #909399; }
  403 +.tjd-empty p { margin: 6px 0 0; font-size: 13px; }
  404 +.tjd-empty__hint { font-size: 12px !important; color: #C0C4CC; }
  405 +.tjd-code { font-family: 'SFMono-Regular', Consolas, monospace; font-size: 12px; color: #606266; }
  406 +.text-warning { color: #E6A23C; }
  407 +.text-primary { color: #409EFF; font-weight: 500; }
  408 +</style>
  409 +<style>
  410 +.tjd-dialog .el-dialog__body { padding: 14px 20px; max-height: calc(100vh - 200px); overflow-y: auto; }
  411 +.tjd-table .tjd-editable-col { background-color: #FFF9EF !important; }
  412 +.tjd-table .el-input-group__append { padding: 0 5px; }
  413 +</style>
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPriceAdjust/index.vue
1 <template> 1 <template>
2 - <div class="NCC-common-layout">  
3 - <div class="NCC-common-layout-center">  
4 - <el-row class="NCC-common-search-box" :gutter="16">  
5 - <el-form @submit.native.prevent>  
6 - <el-col :span="6">  
7 - <el-form-item label="调价单编号">  
8 - <el-input v-model="query.code" placeholder="调价单编号" clearable />  
9 - </el-form-item>  
10 - </el-col>  
11 - <el-col :span="6">  
12 - <el-form-item label="商品名称">  
13 - <el-input v-model="query.productName" placeholder="商品名称" clearable />  
14 - </el-form-item>  
15 - </el-col>  
16 - <el-col :span="6">  
17 - <el-form-item label="调价日期">  
18 - <el-date-picker v-model="query.adjustDate" type="daterange" value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" />  
19 - </el-form-item>  
20 - </el-col>  
21 - <el-col :span="6">  
22 - <el-form-item>  
23 - <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>  
24 - <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>  
25 - </el-form-item>  
26 - </el-col>  
27 - </el-form>  
28 - </el-row>  
29 - <div class="NCC-common-layout-main NCC-flex-main">  
30 - <div class="NCC-common-head">  
31 - <div>  
32 - <el-button type="primary" icon="el-icon-plus" @click="addOrUpdateHandle()">新增</el-button>  
33 - </div>  
34 - <div class="NCC-common-head-right">  
35 - <el-tooltip effect="dark" content="刷新" placement="top">  
36 - <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="reset()" />  
37 - </el-tooltip>  
38 - <screenfull isContainer />  
39 - </div>  
40 - </div>  
41 - <NCC-table v-loading="listLoading" :data="list">  
42 - <el-table-column prop="code" label="调价单编号" align="left" />  
43 - <el-table-column prop="productName" label="商品名称" align="left" />  
44 - <el-table-column prop="oldPrice" label="原价" align="left" />  
45 - <el-table-column prop="newPrice" label="新价" align="left" />  
46 - <el-table-column prop="adjustDate" label="调价日期" align="left" />  
47 - <el-table-column prop="status" label="状态" align="left" />  
48 - <el-table-column label="操作" fixed="right" width="120">  
49 - <template slot-scope="scope">  
50 - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button>  
51 - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button>  
52 - </template>  
53 - </el-table-column>  
54 - </NCC-table>  
55 - <pagination :total="total" :page.sync="listQuery.currentPage" :limit.sync="listQuery.pageSize" @pagination="initData" />  
56 - </div>  
57 - <PriceAdjustForm v-if="formVisible" :id="editId" @refresh="refresh" @close="formVisible=false" />  
58 - </div>  
59 - </div> 2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <el-row class="NCC-common-search-box" :gutter="16">
  5 + <el-form @submit.native.prevent>
  6 + <el-col :span="6">
  7 + <el-form-item label="调价单编号">
  8 + <el-input v-model="query.keyword" placeholder="编号或商品名" clearable />
  9 + </el-form-item>
  10 + </el-col>
  11 + <el-col :span="6">
  12 + <el-form-item label="仓库">
  13 + <el-select v-model="query.ck" placeholder="全部仓库" clearable>
  14 + <el-option v-for="(item, index) in ckOptions" :key="index" :label="item.F_mdmc" :value="item.F_Id" />
  15 + </el-select>
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="6">
  19 + <el-form-item label="状态">
  20 + <el-select v-model="query.djzt" placeholder="全部" clearable>
  21 + <el-option label="草稿" value="草稿" />
  22 + <el-option label="已审核" value="已审核" />
  23 + </el-select>
  24 + </el-form-item>
  25 + </el-col>
  26 + <el-col :span="6">
  27 + <el-form-item label="日期范围">
  28 + <el-date-picker v-model="query.dateRange" type="daterange" value-format="timestamp" format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" />
  29 + </el-form-item>
  30 + </el-col>
  31 + <el-col :span="6">
  32 + <el-form-item>
  33 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  34 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  35 + </el-form-item>
  36 + </el-col>
  37 + </el-form>
  38 + </el-row>
  39 + <div class="NCC-common-layout-main NCC-flex-main">
  40 + <div class="NCC-common-head">
  41 + <div>
  42 + <el-button type="primary" icon="el-icon-plus" @click="addOrUpdateHandle()">新增</el-button>
  43 + </div>
  44 + <div class="NCC-common-head-right">
  45 + <el-tooltip effect="dark" content="刷新" placement="top">
  46 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="reset()" />
  47 + </el-tooltip>
  48 + <screenfull isContainer />
  49 + </div>
  50 + </div>
  51 + <NCC-table v-loading="listLoading" :data="list">
  52 + <el-table-column prop="id" label="单据编号" align="left" />
  53 + <el-table-column prop="djrq" label="单据日期" align="left" :formatter="ncc.tableDateFormat" />
  54 + <el-table-column label="仓库" prop="ck" align="left">
  55 + <template slot-scope="scope">
  56 + {{ getWarehouseName(scope.row.ck) }}
  57 + </template>
  58 + </el-table-column>
  59 + <el-table-column prop="djzt" label="状态" align="left" width="100">
  60 + <template slot-scope="scope">
  61 + <el-tag v-if="scope.row.djzt === '已审核'" type="success" size="mini">已审核</el-tag>
  62 + <el-tag v-else type="info" size="mini">{{ scope.row.djzt || '草稿' }}</el-tag>
  63 + </template>
  64 + </el-table-column>
  65 + <el-table-column prop="zdr" label="制单人" align="left" />
  66 + <el-table-column label="操作" fixed="right" width="220">
  67 + <template slot-scope="scope">
  68 + <el-button type="text" @click="addOrUpdateHandle(scope.row.id, true)">详情</el-button>
  69 + <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" v-if="!scope.row.djzt || scope.row.djzt === '草稿'">编辑</el-button>
  70 + <el-button type="text" @click="handleAudit(scope.row.id)" v-if="!scope.row.djzt || scope.row.djzt === '草稿'" style="color:#E6A23C">审核</el-button>
  71 + <el-button type="text" @click="handleDel(scope.row.id)" v-if="!scope.row.djzt || scope.row.djzt === '草稿'" class="NCC-table-delBtn">删除</el-button>
  72 + </template>
  73 + </el-table-column>
  74 + </NCC-table>
  75 + <pagination :total="total" :page.sync="listQuery.currentPage" :limit.sync="listQuery.pageSize" @pagination="initData" />
  76 + </div>
  77 + </div>
  78 + <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
  79 + </div>
60 </template> 80 </template>
61 -  
62 <script> 81 <script>
63 -import NCCtable from '@/components/NCC-table'  
64 -import pagination from '@/components/Pagination'  
65 -import screenfull from '@/components/Screenfull'  
66 -import PriceAdjustForm from './Form.vue'  
67 -  
68 -export default {  
69 - name: 'PriceAdjustIndex',  
70 - components: { NCCtable, pagination, screenfull, PriceAdjustForm },  
71 - data() {  
72 - return {  
73 - query: {  
74 - code: '',  
75 - productName: '',  
76 - adjustDate: []  
77 - },  
78 - list: [],  
79 - listLoading: false,  
80 - total: 0,  
81 - listQuery: {  
82 - currentPage: 1,  
83 - pageSize: 20  
84 - },  
85 - formVisible: false,  
86 - editId: null  
87 - }  
88 - },  
89 - methods: {  
90 - search() {  
91 - this.listQuery.currentPage = 1  
92 - this.initData()  
93 - },  
94 - reset() {  
95 - this.query = { code: '', productName: '', adjustDate: [] }  
96 - this.search()  
97 - },  
98 - initData() {  
99 - // TODO: 获取调价单列表数据  
100 - },  
101 - addOrUpdateHandle(id) {  
102 - this.editId = id || null  
103 - this.formVisible = true  
104 - },  
105 - handleDel(id) {  
106 - // TODO: 删除调价单  
107 - },  
108 - refresh() {  
109 - this.initData()  
110 - this.formVisible = false  
111 - }  
112 - }  
113 -} 82 + import request from '@/utils/request'
  83 + import NCCForm from './Form'
  84 + import { previewDataInterface } from '@/api/systemData/dataInterface'
  85 + export default {
  86 + components: { NCCForm },
  87 + data() {
  88 + return {
  89 + query: {
  90 + keyword: undefined,
  91 + ck: undefined,
  92 + djzt: undefined,
  93 + dateRange: undefined,
  94 + },
  95 + list: [],
  96 + listLoading: true,
  97 + total: 0,
  98 + listQuery: {
  99 + currentPage: 1,
  100 + pageSize: 20,
  101 + sort: "desc",
  102 + sidx: "",
  103 + },
  104 + formVisible: false,
  105 + ckOptions: [],
  106 + }
  107 + },
  108 + created() {
  109 + this.initData()
  110 + this.getCkOptions()
  111 + },
  112 + methods: {
  113 + getCkOptions() {
  114 + previewDataInterface('681758216954053893').then(res => {
  115 + this.ckOptions = res.data
  116 + })
  117 + },
  118 + getWarehouseName(warehouseId) {
  119 + if (!warehouseId || !this.ckOptions || !Array.isArray(this.ckOptions)) return warehouseId || '无'
  120 + const warehouse = this.ckOptions.find(item => item.F_Id === warehouseId)
  121 + return warehouse ? warehouse.F_mdmc : warehouseId
  122 + },
  123 + initData() {
  124 + this.listLoading = true
  125 + let query = {
  126 + ...this.listQuery,
  127 + keyword: this.query.keyword,
  128 + ck: this.query.ck,
  129 + djzt: this.query.djzt,
  130 + }
  131 + if (this.query.dateRange && this.query.dateRange.length === 2) {
  132 + query.startDate = this.query.dateRange[0]
  133 + query.endDate = this.query.dateRange[1]
  134 + }
  135 + let cleanQuery = {}
  136 + for (let key in query) {
  137 + if (query[key] !== undefined && query[key] !== null && query[key] !== '') {
  138 + cleanQuery[key] = query[key]
  139 + }
  140 + }
  141 + request({
  142 + url: '/api/Extend/WtTjd',
  143 + method: 'GET',
  144 + data: cleanQuery
  145 + }).then(res => {
  146 + this.list = res.data.list
  147 + this.total = res.data.pagination.total
  148 + this.listLoading = false
  149 + }).catch(() => {
  150 + this.listLoading = false
  151 + })
  152 + },
  153 + handleDel(id) {
  154 + this.$confirm('此操作将永久删除该调价单, 是否继续?', '提示', {
  155 + type: 'warning'
  156 + }).then(() => {
  157 + request({
  158 + url: `/api/Extend/WtTjd/${id}`,
  159 + method: 'DELETE'
  160 + }).then(res => {
  161 + this.$message({
  162 + type: 'success',
  163 + message: res.msg,
  164 + onClose: () => {
  165 + this.initData()
  166 + }
  167 + })
  168 + })
  169 + }).catch(() => {})
  170 + },
  171 + handleAudit(id) {
  172 + this.$confirm('确认审核该调价单?审核后将不可修改。', '审核确认', {
  173 + type: 'warning'
  174 + }).then(() => {
  175 + request({
  176 + url: `/api/Extend/WtTjd/Actions/Audit/${id}`,
  177 + method: 'POST'
  178 + }).then(res => {
  179 + this.$message({
  180 + type: 'success',
  181 + message: res.msg || '审核成功',
  182 + onClose: () => {
  183 + this.initData()
  184 + }
  185 + })
  186 + })
  187 + }).catch(() => {})
  188 + },
  189 + addOrUpdateHandle(id, isDetail) {
  190 + this.formVisible = true
  191 + this.$nextTick(() => {
  192 + if (this.$refs.NCCForm) {
  193 + this.$refs.NCCForm.init(id, isDetail)
  194 + } else {
  195 + setTimeout(() => {
  196 + if (this.$refs.NCCForm) {
  197 + this.$refs.NCCForm.init(id, isDetail)
  198 + }
  199 + }, 100)
  200 + }
  201 + })
  202 + },
  203 + search() {
  204 + this.listQuery = {
  205 + currentPage: 1,
  206 + pageSize: 20,
  207 + sort: "desc",
  208 + sidx: "",
  209 + }
  210 + this.initData()
  211 + },
  212 + refresh(isRefresh) {
  213 + this.formVisible = false
  214 + if (isRefresh) this.reset()
  215 + },
  216 + reset() {
  217 + for (let key in this.query) {
  218 + this.query[key] = undefined
  219 + }
  220 + this.listQuery = {
  221 + currentPage: 1,
  222 + pageSize: 20,
  223 + sort: "desc",
  224 + sidx: "",
  225 + }
  226 + this.initData()
  227 + }
  228 + }
  229 + }
114 </script> 230 </script>
115 -  
116 -<style scoped>  
117 -</style>  
118 \ No newline at end of file 231 \ No newline at end of file
Antis.Erp.Plat/antis-ncc-admin/src/views/wtSpCost/index.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <el-row class="NCC-common-search-box" :gutter="16">
  5 + <el-form @submit.native.prevent>
  6 + <el-col :span="6">
  7 + <el-form-item label="商品搜索">
  8 + <el-input v-model="query.keyword" placeholder="商品名称或编码" clearable @keyup.enter.native="search" />
  9 + </el-form-item>
  10 + </el-col>
  11 + <el-col :span="6">
  12 + <el-form-item label="仓库">
  13 + <el-select v-model="query.ck" placeholder="全部仓库" clearable filterable>
  14 + <el-option v-for="item in ckOptions" :key="item.F_Id" :label="item.F_mdmc" :value="item.F_Id" />
  15 + </el-select>
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="6">
  19 + <el-form-item>
  20 + <el-button type="primary" icon="el-icon-search" @click="search">查询</el-button>
  21 + <el-button icon="el-icon-refresh-right" @click="reset">重置</el-button>
  22 + </el-form-item>
  23 + </el-col>
  24 + </el-form>
  25 + </el-row>
  26 + <div class="NCC-common-layout-main NCC-flex-main">
  27 + <div class="NCC-common-head">
  28 + <div>
  29 + <span style="font-size:14px;font-weight:bold;color:#303133">商品成本价表</span>
  30 + <el-tag type="info" size="mini" style="margin-left:8px">共 {{ total }} 条</el-tag>
  31 + </div>
  32 + <div class="NCC-common-head-right">
  33 + <el-tooltip effect="dark" content="刷新" placement="top">
  34 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="reset" />
  35 + </el-tooltip>
  36 + <screenfull isContainer />
  37 + </div>
  38 + </div>
  39 + <NCC-table v-loading="listLoading" :data="list" show-summary :summary-method="getSummaries">
  40 + <el-table-column type="index" width="50" label="#" align="center" />
  41 + <el-table-column prop="ckmc" label="仓库" min-width="130">
  42 + <template slot-scope="scope">{{ scope.row.ckmc || scope.row.ck || '-' }}</template>
  43 + </el-table-column>
  44 + <el-table-column prop="spbm" label="商品编码" width="150">
  45 + <template slot-scope="scope">{{ scope.row.spbm || '-' }}</template>
  46 + </el-table-column>
  47 + <el-table-column prop="spmc" label="商品名称" min-width="180">
  48 + <template slot-scope="scope">{{ scope.row.spmc || '-' }}</template>
  49 + </el-table-column>
  50 + <el-table-column prop="sl" label="在库数量" width="100" align="right">
  51 + <template slot-scope="scope">{{ scope.row.sl }}</template>
  52 + </el-table-column>
  53 + <el-table-column prop="cbj" label="成本单价" width="120" align="right">
  54 + <template slot-scope="scope">
  55 + <span :style="{ color: parseFloat(scope.row.cbj) > 0 ? '#303133' : '#E6A23C' }">{{ formatPrice(scope.row.cbj) }}</span>
  56 + </template>
  57 + </el-table-column>
  58 + <el-table-column prop="cbje" label="成本金额" width="130" align="right">
  59 + <template slot-scope="scope">
  60 + <span style="font-weight:bold">{{ formatPrice(scope.row.cbje) }}</span>
  61 + </template>
  62 + </el-table-column>
  63 + <el-table-column prop="updateTime" label="最后更新" width="170" align="center">
  64 + <template slot-scope="scope">{{ formatTime(scope.row.updateTime) }}</template>
  65 + </el-table-column>
  66 + </NCC-table>
  67 + <pagination :total="total" :page.sync="listQuery.currentPage" :limit.sync="listQuery.pageSize" @pagination="initData" />
  68 + </div>
  69 + </div>
  70 + </div>
  71 +</template>
  72 +<script>
  73 + import request from '@/utils/request'
  74 + import { previewDataInterface } from '@/api/systemData/dataInterface'
  75 + export default {
  76 + data() {
  77 + return {
  78 + query: {
  79 + keyword: undefined,
  80 + ck: undefined,
  81 + },
  82 + list: [],
  83 + listLoading: true,
  84 + total: 0,
  85 + listQuery: {
  86 + currentPage: 1,
  87 + pageSize: 20,
  88 + },
  89 + ckOptions: [],
  90 + }
  91 + },
  92 + created() {
  93 + this.initData()
  94 + this.getCkOptions()
  95 + },
  96 + methods: {
  97 + getCkOptions() {
  98 + previewDataInterface('681758216954053893').then(res => {
  99 + this.ckOptions = res.data
  100 + })
  101 + },
  102 + formatPrice(val) {
  103 + const n = parseFloat(val)
  104 + if (isNaN(n)) return '-'
  105 + return n.toFixed(2)
  106 + },
  107 + formatTime(val) {
  108 + if (!val) return '-'
  109 + const d = new Date(val)
  110 + if (isNaN(d.getTime())) return val
  111 + const pad = n => String(n).padStart(2, '0')
  112 + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
  113 + },
  114 + initData() {
  115 + this.listLoading = true
  116 + const params = {
  117 + currentPage: this.listQuery.currentPage,
  118 + pageSize: this.listQuery.pageSize,
  119 + }
  120 + if (this.query.keyword) params.keyword = this.query.keyword
  121 + if (this.query.ck) params.ck = this.query.ck
  122 + request({
  123 + url: '/api/Extend/WtTjd/Actions/GetCostList',
  124 + method: 'GET',
  125 + data: params
  126 + }).then(res => {
  127 + this.list = res.data.list || []
  128 + this.total = (res.data.pagination && res.data.pagination.total) || 0
  129 + this.listLoading = false
  130 + }).catch(() => {
  131 + this.listLoading = false
  132 + })
  133 + },
  134 + getSummaries(param) {
  135 + const { columns, data } = param
  136 + const sums = []
  137 + columns.forEach((col, idx) => {
  138 + if (idx === 0) { sums[idx] = '合计'; return }
  139 + if (col.property === 'sl') {
  140 + sums[idx] = data.reduce((t, r) => t + (parseFloat(r.sl) || 0), 0)
  141 + } else if (col.property === 'cbje') {
  142 + sums[idx] = data.reduce((t, r) => t + (parseFloat(r.cbje) || 0), 0).toFixed(2)
  143 + } else {
  144 + sums[idx] = ''
  145 + }
  146 + })
  147 + return sums
  148 + },
  149 + search() {
  150 + this.listQuery.currentPage = 1
  151 + this.initData()
  152 + },
  153 + reset() {
  154 + this.query = { keyword: undefined, ck: undefined }
  155 + this.listQuery = { currentPage: 1, pageSize: 20 }
  156 + this.initData()
  157 + }
  158 + }
  159 + }
  160 +</script>
Antis.Erp.Plat/docs/商品成本价与调价单开发计划.md 0 → 100644
  1 +# 商品成本价体系与调价单开发计划
  2 +
  3 +> **现有基础**:前端 `wtPriceAdjust/index.vue` + `Form.vue`(骨架页面),后端和数据库均不存在
  4 +
  5 +## 一、背景与目标
  6 +
  7 +### 现状问题
  8 +1. **没有统一的成本价存储**:当前成本价只能从最近一次采购入库单的单价推算,不准确
  9 +2. **同一商品不同仓库成本不同**:如"卡带 开心镇"在莱迪仓平均成本 42.92,景枫仓 111.00
  10 +3. **出库时不记录成本价**:`wt_xsckd_mx` 表只有售价(dj),无法计算毛利
  11 +4. **拆装单成本价精度丢失**:`wt_czdmx.cbdj` 字段是 `decimal(50,0)`,没有小数位
  12 +5. **缺少调价单功能**:无法对在库商品进行成本调整
  13 +
  14 +### 目标
  15 +建立完整的**加权平均成本价体系**,支持:采购入库自动算成本 → 调价单手动调整 → 出库时记录快照 → 拆装单/调拨单联动
  16 +
  17 +---
  18 +
  19 +## 二、数据库变更
  20 +
  21 +### 2.1 新建表:`wt_sp_cost`(商品仓库成本表)
  22 +
  23 +> 核心表,按「商品+仓库」维度存储当前加权平均成本价
  24 +
  25 +```sql
  26 +CREATE TABLE wt_sp_cost (
  27 + F_Id VARCHAR(50) NOT NULL COMMENT '主键',
  28 + spbh VARCHAR(50) NOT NULL COMMENT '商品编号',
  29 + ck VARCHAR(50) NOT NULL COMMENT '仓库ID',
  30 + cbj DECIMAL(18,2) DEFAULT 0.00 COMMENT '当前加权平均成本价',
  31 + sl INT DEFAULT 0 COMMENT '当前在库数量',
  32 + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  33 + PRIMARY KEY (F_Id),
  34 + UNIQUE KEY uk_sp_ck (spbh, ck)
  35 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品仓库成本表';
  36 +```
  37 +
  38 +### 2.2 新建表:`wt_tjd`(调价单主表)
  39 +
  40 +```sql
  41 +CREATE TABLE wt_tjd (
  42 + F_Id VARCHAR(50) NOT NULL COMMENT '单据编号(TJD+日期+序号)',
  43 + djrq DATETIME COMMENT '单据日期',
  44 + ck VARCHAR(50) COMMENT '仓库(选仓库加载全部商品)',
  45 + jsr VARCHAR(50) COMMENT '经手人',
  46 + zdr VARCHAR(50) COMMENT '制单人',
  47 + djzt VARCHAR(50) DEFAULT '草稿' COMMENT '单据状态(草稿/已审核)',
  48 + bz TEXT COMMENT '备注',
  49 + PRIMARY KEY (F_Id)
  50 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品调价单';
  51 +```
  52 +
  53 +### 2.3 新建表:`wt_tjd_mx`(调价单明细)
  54 +
  55 +```sql
  56 +CREATE TABLE wt_tjd_mx (
  57 + F_Id VARCHAR(50) NOT NULL COMMENT '主键',
  58 + djbh VARCHAR(50) NOT NULL COMMENT '单据编号(FK→wt_tjd)',
  59 + ck VARCHAR(50) COMMENT '仓库',
  60 + spbh VARCHAR(50) COMMENT '商品编号',
  61 + spmc VARCHAR(200) COMMENT '商品名称',
  62 + dw VARCHAR(50) COMMENT '单位',
  63 + sl INT DEFAULT 0 COMMENT '数量(在库数量,自动带出)',
  64 + tqdj DECIMAL(18,2) DEFAULT 0.00 COMMENT '调前单价(自动带出)',
  65 + tjbl DECIMAL(8,4) DEFAULT 0.00 COMMENT '调价比率%',
  66 + thdj DECIMAL(18,2) DEFAULT 0.00 COMMENT '调后单价',
  67 + tqje DECIMAL(18,2) DEFAULT 0.00 COMMENT '调前金额(sl×tqdj)',
  68 + thje DECIMAL(18,2) DEFAULT 0.00 COMMENT '调后金额(sl×thdj)',
  69 + bz VARCHAR(500) COMMENT '备注',
  70 + PRIMARY KEY (F_Id)
  71 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品调价单明细';
  72 +```
  73 +
  74 +### 2.4 修改表:`wt_xsckd_mx`(出库单明细加成本价字段)
  75 +
  76 +> 出库时保存成本价快照,用于后续毛利计算
  77 +
  78 +```sql
  79 +ALTER TABLE wt_xsckd_mx
  80 + ADD COLUMN cbdj DECIMAL(18,2) DEFAULT 0.00 COMMENT '成本单价(出库时快照)',
  81 + ADD COLUMN cbje DECIMAL(18,2) DEFAULT 0.00 COMMENT '成本金额(出库时快照)';
  82 +```
  83 +
  84 +### 2.5 修改表:`wt_czdmx` + `wt_czdmx_2`(修复精度)
  85 +
  86 +> 当前 cbdj/cbje 是 decimal(50,0) 没有小数位,需要修复
  87 +
  88 +```sql
  89 +ALTER TABLE wt_czdmx MODIFY COLUMN cbdj DECIMAL(18,2) DEFAULT 0.00 COMMENT '成本单价';
  90 +ALTER TABLE wt_czdmx MODIFY COLUMN cbje DECIMAL(18,2) DEFAULT 0.00 COMMENT '成本金额';
  91 +ALTER TABLE wt_czdmx_2 MODIFY COLUMN cbdj DECIMAL(18,2) DEFAULT 0.00 COMMENT '成本单价';
  92 +ALTER TABLE wt_czdmx_2 MODIFY COLUMN cbje DECIMAL(18,2) DEFAULT 0.00 COMMENT '成本金额';
  93 +```
  94 +
  95 +### 2.6 初始化 `wt_sp_cost` 数据
  96 +
  97 +> 根据当前在库序列号的批次入库价计算加权平均成本
  98 +
  99 +```sql
  100 +INSERT INTO wt_sp_cost (F_Id, spbh, ck, cbj, sl, update_time)
  101 +SELECT
  102 + CONCAT(sub.spbh, '_', sub.in_warehouse) AS F_Id,
  103 + sub.spbh,
  104 + sub.in_warehouse AS ck,
  105 + ROUND(SUM(IFNULL(mx.dj, 0) * sub.qty) / NULLIF(SUM(sub.qty), 0), 2) AS cbj,
  106 + SUM(sub.qty) AS sl,
  107 + NOW() AS update_time
  108 +FROM (
  109 + SELECT sn.spbh, sn.in_warehouse, sn.in_djbh, COUNT(*) AS qty
  110 + FROM wt_serial_number sn
  111 + WHERE sn.status = 0
  112 + GROUP BY sn.spbh, sn.in_warehouse, sn.in_djbh
  113 +) sub
  114 +LEFT JOIN wt_xsckd_mx mx ON sub.in_djbh = mx.djbh AND sub.spbh = mx.spbh
  115 +GROUP BY sub.spbh, sub.in_warehouse;
  116 +```
  117 +
  118 +---
  119 +
  120 +## 三、业务逻辑变更
  121 +
  122 +### 3.1 成本价读取(统一入口)
  123 +
  124 +所有需要取成本价的地方,统一从 `wt_sp_cost` 读取:
  125 +
  126 +```
  127 +GetProductCost(商品ID, 仓库ID)
  128 + → SELECT cbj FROM wt_sp_cost WHERE spbh = 商品ID AND ck = 仓库ID
  129 + → 如果没记录,兜底取最近采购入库单价 → 取零售价 → 返回 0
  130 +```
  131 +
  132 +**影响范围**:拆装单(WtCzdService)、出库单(WtXsckdService)、调价单(新)
  133 +
  134 +### 3.2 采购入库时更新成本(WtXsckdService.Create)
  135 +
  136 +> 当 djlx = "采购入库单" 时,保存后自动更新 wt_sp_cost
  137 +
  138 +```
  139 +对每个明细行:
  140 + 旧记录 = SELECT cbj, sl FROM wt_sp_cost WHERE spbh AND ck
  141 + 如果存在:
  142 + 新成本 = (旧sl × 旧cbj + 入库数 × 入库单价) ÷ (旧sl + 入库数)
  143 + UPDATE wt_sp_cost SET cbj = 新成本, sl = 旧sl + 入库数
  144 + 如果不存在:
  145 + INSERT wt_sp_cost (spbh, ck, cbj, sl) VALUES (商品, 仓库, 入库单价, 入库数)
  146 +```
  147 +
  148 +### 3.3 销售出库时记录成本快照(WtXsckdService.Create)
  149 +
  150 +> 当 djlx = "销售出库单" 时
  151 +
  152 +```
  153 +对每个明细行:
  154 + cbdj = SELECT cbj FROM wt_sp_cost WHERE spbh AND ck
  155 + cbje = cbdj × 数量
  156 + 保存到 wt_xsckd_mx.cbdj, wt_xsckd_mx.cbje
  157 + UPDATE wt_sp_cost SET sl = sl - 出库数 WHERE spbh AND ck
  158 +```
  159 +
  160 +### 3.4 调价单审核时更新成本(WtTjdService,新建)
  161 +
  162 +```
  163 +对每个明细行:
  164 + UPDATE wt_sp_cost SET cbj = 调后单价 WHERE spbh AND ck
  165 +```
  166 +
  167 +### 3.5 拆装单保存时联动(WtCzdService.Create)
  168 +
  169 +```
  170 +出货明细:
  171 + cbdj 从 wt_sp_cost 取出货仓库的成本价
  172 + UPDATE wt_sp_cost SET sl = sl - 出库数 WHERE 出货仓库
  173 +
  174 +入货明细:
  175 + cbdj = 出货总成本金额 ÷ 入货总数量(分摊成本)
  176 + UPSERT wt_sp_cost SET cbj = 加权后新成本, sl = sl + 入库数 WHERE 入货仓库
  177 +```
  178 +
  179 +### 3.6 调拨单联动
  180 +
  181 +```
  182 +出仓成本不变(从 wt_sp_cost 取)
  183 +入仓按出仓成本入库(加权平均计入)
  184 +```
  185 +
  186 +### 3.7 采购退货 / 销售退货
  187 +
  188 +```
  189 +退货入库:按原出库成本价回入(不改变成本单价,只增加数量和总成本)
  190 +退货出库:减少数量,成本单价不变
  191 +```
  192 +
  193 +---
  194 +
  195 +## 四、后端开发任务
  196 +
  197 +### 4.1 数据库与 Entity
  198 +
  199 +| 任务 | 说明 |
  200 +|------|------|
  201 +| 执行 SQL 建表/改表 | 2.1~2.6 的 SQL 语句 |
  202 +| WtSpCostEntity | 映射 wt_sp_cost |
  203 +| WtTjdEntity | 映射 wt_tjd |
  204 +| WtTjdMxEntity | 映射 wt_tjd_mx |
  205 +| WtXsckdMxEntity 加字段 | 加 Cbdj、Cbje 属性 |
  206 +| WtCzdmxEntity 修复精度 | cbdj/cbje 改为 decimal |
  207 +
  208 +### 4.2 调价单 Service(WtTjdService,新建)
  209 +
  210 +| 接口 | 方法 | 说明 |
  211 +|------|------|------|
  212 +| GET /api/Extend/WtTjd/{id} | GetInfo | 获取调价单详情 |
  213 +| GET /api/Extend/WtTjd | GetList | 列表查询(分页) |
  214 +| POST /api/Extend/WtTjd | Create | 新建调价单 |
  215 +| PUT /api/Extend/WtTjd/{id} | Update | 修改调价单 |
  216 +| DELETE /api/Extend/WtTjd/{id} | Delete | 删除调价单 |
  217 +| POST /api/Extend/WtTjd/Actions/Audit/{id} | Audit | 审核(更新 wt_sp_cost) |
  218 +| GET /api/Extend/WtTjd/Actions/LoadProducts | LoadProducts | 按仓库加载商品+成本价+库存 |
  219 +
  220 +### 4.3 成本价 Service(WtSpCostService,新建或合并)
  221 +
  222 +| 接口 | 说明 |
  223 +|------|------|
  224 +| GET /api/Extend/WtSpCost/GetCost?spbh=&ck= | 查询单个商品在指定仓库的成本价 |
  225 +| POST /api/Extend/WtSpCost/RecalcAll | 重算所有成本(基于当前库存数据) |
  226 +
  227 +### 4.4 改造现有 Service
  228 +
  229 +| Service | 改造内容 |
  230 +|---------|---------|
  231 +| WtXsckdService.Create | 采购入库:更新 wt_sp_cost 加权平均<br>销售出库:快照 cbdj/cbje 到明细 |
  232 +| WtCzdService.Create | 出货:快照 cbdj 从 wt_sp_cost 取<br>入货:按出货总成本分摊 |
  233 +| WtCzdService.GetProductCost | 改为从 wt_sp_cost 读取 |
  234 +
  235 +---
  236 +
  237 +## 五、前端开发任务
  238 +
  239 +### 5.1 调价单页面(新建)
  240 +
  241 +**列表页 `views/wtTjd/index.vue`**:
  242 +- 搜索条件:单据编号、仓库、日期范围、状态
  243 +- 列表字段:单据编号、单据日期、仓库、经手人、状态、操作(编辑/审核/删除)
  244 +
  245 +**表单页 `views/wtTjd/Form.vue`**:
  246 +- 主表:单据编号(自动生成 TJD+日期+序号)、单据日期、仓库(下拉选择)、经手人、备注
  247 +- 明细表(el-table 内嵌编辑):
  248 +
  249 +| 列 | 说明 | 交互 |
  250 +|----|------|------|
  251 +| 仓库 | 自动带出(跟主表仓库) | 只读 |
  252 +| 商品编码 | 选择或仓库加载 | 可选/只读 |
  253 +| 商品名称 | 自动带出 | 只读 |
  254 +| 单位 | 自动带出 | 只读 |
  255 +| 数量 | 在库数量 | 只读 |
  256 +| 调前单价 | 自动从 wt_sp_cost 带出 | 只读 |
  257 +| 调价比率% | 用户输入 | **可编辑**,变化 → 自动算调后单价 |
  258 +| 调后单价 | 用户输入或自动算 | **可编辑**,变化 → 自动算比率 |
  259 +| 调前金额 | 数量×调前单价 | 自动算,只读 |
  260 +| 调后金额 | 数量×调后单价 | 自动算,只读 |
  261 +| 备注 | | 可编辑 |
  262 +
  263 +**交互逻辑**:
  264 +1. 选择仓库 → 调用 LoadProducts → 加载该仓库所有有库存商品到明细表
  265 +2. 也可手动添加行选择商品
  266 +3. 输入调价比率 → 调后单价 = 调前单价 × (1 + 比率/100)
  267 +4. 输入调后单价 → 调价比率 = (调后 - 调前) / 调前 × 100
  268 +5. 审核按钮 → 确认后批量更新成本价
  269 +
  270 +### 5.2 改造拆装单
  271 +
  272 +- `GetProductCost` 接口改为从 `wt_sp_cost` 取成本价(后端改,前端无需变)
  273 +
  274 +### 5.3 改造出库单
  275 +
  276 +- 保存时后端自动写入 cbdj/cbje(后端改,前端可选显示)
  277 +
  278 +---
  279 +
  280 +## 六、开发顺序(建议)
  281 +
  282 +| 阶段 | 任务 | 依赖 |
  283 +|------|------|------|
  284 +| **P1** | 执行 SQL:建 wt_sp_cost 表 + 初始化数据 | 无 |
  285 +| **P1** | 执行 SQL:修复 wt_czdmx 精度 | 无 |
  286 +| **P1** | 执行 SQL:wt_xsckd_mx 加 cbdj/cbje | 无 |
  287 +| **P2** | 后端:WtSpCostEntity + GetCost 接口 | P1 |
  288 +| **P2** | 后端:改造 GetProductCost 从 wt_sp_cost 读 | P2 |
  289 +| **P3** | 执行 SQL:建 wt_tjd + wt_tjd_mx 表 | 无 |
  290 +| **P3** | 后端:WtTjdEntity/DTO/Service 全套 CRUD | P3 |
  291 +| **P3** | 后端:LoadProducts + Audit 接口 | P2+P3 |
  292 +| **P3** | 前端:调价单列表页 + 表单页 | P3 后端 |
  293 +| **P4** | 后端:改造 WtXsckdService 采购入库更新成本 | P2 |
  294 +| **P4** | 后端:改造 WtXsckdService 出库快照成本 | P2 |
  295 +| **P4** | 后端:改造 WtCzdService 拆装单成本联动 | P2 |
  296 +| **P5** | 测试:全流程验证 | 全部 |
  297 +
  298 +---
  299 +
  300 +## 七、验收标准
  301 +
  302 +1. ✅ `wt_sp_cost` 初始化后,每个商品+仓库组合有正确的加权平均成本价
  303 +2. ✅ 拆装单选商品后自动带出成本单价(从 wt_sp_cost)
  304 +3. ✅ 调价单:选仓库加载所有商品,调价比率和调后单价双向联动
  305 +4. ✅ 调价单审核后,wt_sp_cost.cbj 更新为调后单价
  306 +5. ✅ 采购入库后,wt_sp_cost 自动重算加权平均成本
  307 +6. ✅ 销售出库时,wt_xsckd_mx.cbdj/cbje 记录出库时刻的成本价
  308 +7. ✅ 成本金额精度为 2 位小数
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtCzd/GetProductCostInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.WtCzd
  4 +{
  5 + /// <summary>
  6 + /// 获取商品成本价输入参数
  7 + /// </summary>
  8 + public class GetProductCostInput
  9 + {
  10 + /// <summary>
  11 + /// 商品ID
  12 + /// </summary>
  13 + public string ProductId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 仓库/门店ID
  17 + /// </summary>
  18 + public string WarehouseId { get; set; }
  19 + }
  20 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtTjd/WtTjdCrInput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.WtTjd
  5 +{
  6 + /// <summary>
  7 + /// 调价单创建输入参数
  8 + /// </summary>
  9 + public class WtTjdCrInput
  10 + {
  11 + /// <summary>
  12 + /// 单据日期
  13 + /// </summary>
  14 + public DateTime? djrq { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 仓库
  18 + /// </summary>
  19 + public string ck { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 经手人
  23 + /// </summary>
  24 + public string jsr { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 备注
  28 + /// </summary>
  29 + public string bz { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 调价单明细列表
  33 + /// </summary>
  34 + public List<WtTjdMxCrInput> wtTjdMxList { get; set; }
  35 + }
  36 +
  37 + /// <summary>
  38 + /// 调价单明细创建输入参数
  39 + /// </summary>
  40 + public class WtTjdMxCrInput
  41 + {
  42 + /// <summary>
  43 + /// 仓库
  44 + /// </summary>
  45 + public string ck { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 商品编号
  49 + /// </summary>
  50 + public string spbh { get; set; }
  51 +
  52 + /// <summary>
  53 + /// 商品名称
  54 + /// </summary>
  55 + public string spmc { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 单位
  59 + /// </summary>
  60 + public string dw { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 数量
  64 + /// </summary>
  65 + public int sl { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 调前单价
  69 + /// </summary>
  70 + public decimal tqdj { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 调价比率%
  74 + /// </summary>
  75 + public decimal tjbl { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 调后单价
  79 + /// </summary>
  80 + public decimal thdj { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 调前金额
  84 + /// </summary>
  85 + public decimal tqje { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 调后金额
  89 + /// </summary>
  90 + public decimal thje { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 备注
  94 + /// </summary>
  95 + public string bz { get; set; }
  96 + }
  97 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtTjd/WtTjdInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.WtTjd
  5 +{
  6 + /// <summary>
  7 + /// 调价单详情输出参数
  8 + /// </summary>
  9 + public class WtTjdInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 单据编号
  13 + /// </summary>
  14 + public string id { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 单据日期
  18 + /// </summary>
  19 + public DateTime? djrq { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 仓库
  23 + /// </summary>
  24 + public string ck { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 经手人
  28 + /// </summary>
  29 + public string jsr { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 制单人
  33 + /// </summary>
  34 + public string zdr { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 单据状态
  38 + /// </summary>
  39 + public string djzt { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 备注
  43 + /// </summary>
  44 + public string bz { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 调价单明细列表
  48 + /// </summary>
  49 + public List<WtTjdMxInfoOutput> wtTjdMxList { get; set; }
  50 + }
  51 +
  52 + /// <summary>
  53 + /// 调价单明细详情输出参数
  54 + /// </summary>
  55 + public class WtTjdMxInfoOutput
  56 + {
  57 + /// <summary>
  58 + /// 明细编号
  59 + /// </summary>
  60 + public string id { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 单据编号
  64 + /// </summary>
  65 + public string djbh { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 仓库
  69 + /// </summary>
  70 + public string ck { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 商品编号
  74 + /// </summary>
  75 + public string spbh { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 商品名称
  79 + /// </summary>
  80 + public string spmc { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 单位
  84 + /// </summary>
  85 + public string dw { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 数量
  89 + /// </summary>
  90 + public int sl { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 调前单价
  94 + /// </summary>
  95 + public decimal tqdj { get; set; }
  96 +
  97 + /// <summary>
  98 + /// 调价比率%
  99 + /// </summary>
  100 + public decimal tjbl { get; set; }
  101 +
  102 + /// <summary>
  103 + /// 调后单价
  104 + /// </summary>
  105 + public decimal thdj { get; set; }
  106 +
  107 + /// <summary>
  108 + /// 调前金额
  109 + /// </summary>
  110 + public decimal tqje { get; set; }
  111 +
  112 + /// <summary>
  113 + /// 调后金额
  114 + /// </summary>
  115 + public decimal thje { get; set; }
  116 +
  117 + /// <summary>
  118 + /// 备注
  119 + /// </summary>
  120 + public string bz { get; set; }
  121 + }
  122 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtTjd/WtTjdListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.WtTjd
  4 +{
  5 + /// <summary>
  6 + /// 调价单列表输出参数
  7 + /// </summary>
  8 + public class WtTjdListOutput
  9 + {
  10 + /// <summary>
  11 + /// 单据编号
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 单据日期
  17 + /// </summary>
  18 + public DateTime? djrq { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 仓库名称
  22 + /// </summary>
  23 + public string ck { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 经手人名称
  27 + /// </summary>
  28 + public string jsr { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 制单人名称
  32 + /// </summary>
  33 + public string zdr { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 单据状态
  37 + /// </summary>
  38 + public string djzt { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 备注
  42 + /// </summary>
  43 + public string bz { get; set; }
  44 + }
  45 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtTjd/WtTjdListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.WtTjd
  4 +{
  5 + /// <summary>
  6 + /// 调价单列表查询输入
  7 + /// </summary>
  8 + public class WtTjdListQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 单据编号
  12 + /// </summary>
  13 + public string code { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 单据日期范围(逗号分隔)
  17 + /// </summary>
  18 + public string djrq { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 仓库
  22 + /// </summary>
  23 + public string ck { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 单据状态
  27 + /// </summary>
  28 + public string djzt { get; set; }
  29 + }
  30 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtTjd/WtTjdUpInput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.WtTjd
  5 +{
  6 + /// <summary>
  7 + /// 调价单更新输入参数
  8 + /// </summary>
  9 + public class WtTjdUpInput : WtTjdCrInput
  10 + {
  11 + /// <summary>
  12 + /// 单据编号
  13 + /// </summary>
  14 + public string id { get; set; }
  15 + }
  16 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtSpCostEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys
  6 +{
  7 + /// <summary>
  8 + /// 商品仓库成本表
  9 + /// </summary>
  10 + [SugarTable("wt_sp_cost")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class WtSpCostEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 商品编号
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "spbh")]
  24 + public string Spbh { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 仓库ID
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "ck")]
  30 + public string Ck { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 当前加权平均成本价
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "cbj")]
  36 + public decimal Cbj { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 当前在库数量
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "sl")]
  42 + public int Sl { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 最后更新时间
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "update_time")]
  48 + public DateTime? UpdateTime { get; set; }
  49 + }
  50 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtTjdEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys
  6 +{
  7 + /// <summary>
  8 + /// 调价单主表
  9 + /// </summary>
  10 + [SugarTable("wt_tjd")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class WtTjdEntity
  13 + {
  14 + /// <summary>
  15 + /// 单据编号
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 单据日期
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "djrq")]
  24 + public DateTime? Djrq { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 仓库
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "ck")]
  30 + public string Ck { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 经手人
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "jsr")]
  36 + public string Jsr { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 制单人
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "zdr")]
  42 + public string Zdr { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 单据状态(草稿/已审核)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "djzt")]
  48 + public string Djzt { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 备注
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "bz")]
  54 + public string Bz { get; set; }
  55 + }
  56 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtTjdMxEntity.cs 0 → 100644
  1 +using NCC.Common.Const;
  2 +using SqlSugar;
  3 +using System;
  4 +
  5 +namespace NCC.Extend.Entitys
  6 +{
  7 + /// <summary>
  8 + /// 调价单明细表
  9 + /// </summary>
  10 + [SugarTable("wt_tjd_mx")]
  11 + [Tenant(ClaimConst.TENANT_ID)]
  12 + public class WtTjdMxEntity
  13 + {
  14 + /// <summary>
  15 + /// 主键
  16 + /// </summary>
  17 + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)]
  18 + public string Id { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 单据编号
  22 + /// </summary>
  23 + [SugarColumn(ColumnName = "djbh")]
  24 + public string Djbh { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 仓库
  28 + /// </summary>
  29 + [SugarColumn(ColumnName = "ck")]
  30 + public string Ck { get; set; }
  31 +
  32 + /// <summary>
  33 + /// 商品编号
  34 + /// </summary>
  35 + [SugarColumn(ColumnName = "spbh")]
  36 + public string Spbh { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 商品名称
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "spmc")]
  42 + public string Spmc { get; set; }
  43 +
  44 + /// <summary>
  45 + /// 单位
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "dw")]
  48 + public string Dw { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 数量
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "sl")]
  54 + public int Sl { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 调前单价
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "tqdj")]
  60 + public decimal Tqdj { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 调价比率%
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "tjbl")]
  66 + public decimal Tjbl { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 调后单价
  70 + /// </summary>
  71 + [SugarColumn(ColumnName = "thdj")]
  72 + public decimal Thdj { get; set; }
  73 +
  74 + /// <summary>
  75 + /// 调前金额
  76 + /// </summary>
  77 + [SugarColumn(ColumnName = "tqje")]
  78 + public decimal Tqje { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 调后金额
  82 + /// </summary>
  83 + [SugarColumn(ColumnName = "thje")]
  84 + public decimal Thje { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 备注
  88 + /// </summary>
  89 + [SugarColumn(ColumnName = "bz")]
  90 + public string Bz { get; set; }
  91 + }
  92 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtXsckdMxEntity.cs
@@ -89,5 +89,17 @@ namespace NCC.Extend.Entitys @@ -89,5 +89,17 @@ namespace NCC.Extend.Entitys
89 [SugarColumn(ColumnName = "mdxx")] 89 [SugarColumn(ColumnName = "mdxx")]
90 public string Mdxx { get; set; } 90 public string Mdxx { get; set; }
91 91
  92 + /// <summary>
  93 + /// 成本单价(出库时快照)
  94 + /// </summary>
  95 + [SugarColumn(ColumnName = "cbdj")]
  96 + public decimal? Cbdj { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 成本金额(出库时快照)
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "cbje")]
  102 + public decimal? Cbje { get; set; }
  103 +
92 } 104 }
93 } 105 }
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/IWtTjdService.cs 0 → 100644
  1 +namespace NCC.Extend.Interfaces.WtTjd
  2 +{
  3 + /// <summary>
  4 + /// 调价单服务接口
  5 + /// </summary>
  6 + public interface IWtTjdService
  7 + {
  8 + }
  9 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtCzdService.cs
1 -using NCC.Common.Core.Manager; 1 +using NCC.Common.Core.Manager;
2 using NCC.Common.Enum; 2 using NCC.Common.Enum;
3 using NCC.Common.Extension; 3 using NCC.Common.Extension;
4 using NCC.Common.Filter; 4 using NCC.Common.Filter;
@@ -220,6 +220,9 @@ namespace NCC.Extend.WtCzd @@ -220,6 +220,9 @@ namespace NCC.Extend.WtCzd
220 // 处理序列号逻辑 220 // 处理序列号逻辑
221 await ProcessDisassemblySerialNumbers(newEntity.Id, wtCzdmxEntityList, wtCzdmx2EntityList, entity.Chck, entity.Rhck); 221 await ProcessDisassemblySerialNumbers(newEntity.Id, wtCzdmxEntityList, wtCzdmx2EntityList, entity.Chck, entity.Rhck);
222 222
  223 + // ========== P4: 拆装单成本联动 wt_sp_cost ==========
  224 + await UpdateSpCostOnCzdCreate(entity, wtCzdmxEntityList, wtCzdmx2EntityList);
  225 +
223 //关闭事务 226 //关闭事务
224 _db.CommitTran(); 227 _db.CommitTran();
225 // 返回新创建的实体信息 228 // 返回新创建的实体信息
@@ -338,7 +341,7 @@ namespace NCC.Extend.WtCzd @@ -338,7 +341,7 @@ namespace NCC.Extend.WtCzd
338 } 341 }
339 342
340 /// <summary> 343 /// <summary>
341 - /// 批量删除拆装单 344 + /// 批量删除拆装单(含成本回退)
342 /// </summary> 345 /// </summary>
343 /// <param name="ids">主键数组</param> 346 /// <param name="ids">主键数组</param>
344 /// <returns></returns> 347 /// <returns></returns>
@@ -350,21 +353,21 @@ namespace NCC.Extend.WtCzd @@ -350,21 +353,21 @@ namespace NCC.Extend.WtCzd
350 { 353 {
351 try 354 try
352 { 355 {
353 - //开启事务  
354 _db.BeginTran(); 356 _db.BeginTran();
355 - //批量删除拆装单  
356 - await _db.Deleteable<WtCzdEntity>().In(d => d.Id,ids).ExecuteCommandAsync();  
357 357
358 - //清空子表数据  
359 - await _db.Deleteable<WtCzdmxEntity>().In(u => u.Djbh,ids).ExecuteCommandAsync();  
360 - await _db.Deleteable<WtCzdmx2Entity>().In(u => u.Djbh,ids).ExecuteCommandAsync(); 358 + foreach (var entity in entitys)
  359 + {
  360 + await RollbackSpCostOnCzdDelete(entity);
  361 + }
  362 +
  363 + await _db.Deleteable<WtCzdEntity>().In(d => d.Id, ids).ExecuteCommandAsync();
  364 + await _db.Deleteable<WtCzdmxEntity>().In(u => u.Djbh, ids).ExecuteCommandAsync();
  365 + await _db.Deleteable<WtCzdmx2Entity>().In(u => u.Djbh, ids).ExecuteCommandAsync();
361 366
362 - //关闭事务  
363 _db.CommitTran(); 367 _db.CommitTran();
364 } 368 }
365 catch (Exception) 369 catch (Exception)
366 { 370 {
367 - //回滚事务  
368 _db.RollbackTran(); 371 _db.RollbackTran();
369 throw NCCException.Oh(ErrorCode.COM1002); 372 throw NCCException.Oh(ErrorCode.COM1002);
370 } 373 }
@@ -434,7 +437,9 @@ namespace NCC.Extend.WtCzd @@ -434,7 +437,9 @@ namespace NCC.Extend.WtCzd
434 } 437 }
435 438
436 /// <summary> 439 /// <summary>
437 - /// 删除拆装单 440 + /// 删除拆装单(含成本回退)
  441 + /// 出库方:恢复 wt_sp_cost.sl
  442 + /// 入库方:反向加权平均回退 cbj 和 sl
438 /// </summary> 443 /// </summary>
439 /// <returns></returns> 444 /// <returns></returns>
440 [HttpDelete("{id}")] 445 [HttpDelete("{id}")]
@@ -444,22 +449,19 @@ namespace NCC.Extend.WtCzd @@ -444,22 +449,19 @@ namespace NCC.Extend.WtCzd
444 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); 449 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
445 try 450 try
446 { 451 {
447 - //开启事务  
448 _db.BeginTran(); 452 _db.BeginTran();
  453 +
  454 + // 回退成本
  455 + await RollbackSpCostOnCzdDelete(entity);
449 456
450 - //删除拆装单记录  
451 await _db.Deleteable<WtCzdEntity>().Where(d => d.Id == id).ExecuteCommandAsync(); 457 await _db.Deleteable<WtCzdEntity>().Where(d => d.Id == id).ExecuteCommandAsync();
452 -  
453 - //清空子表数据  
454 await _db.Deleteable<WtCzdmxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync(); 458 await _db.Deleteable<WtCzdmxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
455 await _db.Deleteable<WtCzdmx2Entity>().Where(u => u.Djbh == id).ExecuteCommandAsync(); 459 await _db.Deleteable<WtCzdmx2Entity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
456 460
457 - //关闭事务  
458 _db.CommitTran(); 461 _db.CommitTran();
459 } 462 }
460 catch (Exception) 463 catch (Exception)
461 { 464 {
462 - //回滚事务  
463 _db.RollbackTran(); 465 _db.RollbackTran();
464 throw NCCException.Oh(ErrorCode.COM1002); 466 throw NCCException.Oh(ErrorCode.COM1002);
465 } 467 }
@@ -639,6 +641,259 @@ namespace NCC.Extend.WtCzd @@ -639,6 +641,259 @@ namespace NCC.Extend.WtCzd
639 return nextNumber.ToString().PadLeft(8, '0'); 641 return nextNumber.ToString().PadLeft(8, '0');
640 } 642 }
641 643
  644 + #region P4: wt_sp_cost 成本表联动
  645 +
  646 + /// <summary>
  647 + /// 拆装单创建时自动维护 wt_sp_cost
  648 + /// 出库明细:快照成本价 + 减少在库数量
  649 + /// 入库明细:按出库总成本分摊入库 + 增加数量并更新加权平均成本
  650 + /// </summary>
  651 + private async Task UpdateSpCostOnCzdCreate(WtCzdEntity entity, List<WtCzdmxEntity> outList, List<WtCzdmx2Entity> inList)
  652 + {
  653 + try
  654 + {
  655 + // 出库明细:从 wt_sp_cost 快照成本,并减少数量
  656 + decimal totalOutCost = 0;
  657 + if (outList != null)
  658 + {
  659 + foreach (var item in outList)
  660 + {
  661 + if (string.IsNullOrEmpty(item.Spbh) || (item.Sl ?? 0) <= 0) continue;
  662 + var warehouseId = !string.IsNullOrEmpty(item.Chck) ? item.Chck : entity.Chck;
  663 + if (string.IsNullOrEmpty(warehouseId)) continue;
  664 +
  665 + var costResult = await _db.Ado.SqlQueryAsync<dynamic>(
  666 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  667 + new { spbh = item.Spbh, ck = warehouseId });
  668 +
  669 + if (costResult != null && costResult.Count > 0)
  670 + {
  671 + decimal cbj = Convert.ToDecimal(costResult[0].cbj);
  672 + int oldSl = Convert.ToInt32(costResult[0].sl);
  673 + int qty = item.Sl ?? 0;
  674 +
  675 + if (item.Cbdj == 0) item.Cbdj = cbj;
  676 + if (item.Cbje == 0) item.Cbje = Math.Round(cbj * qty, 2);
  677 + totalOutCost += item.Cbje;
  678 +
  679 + await _db.Ado.ExecuteCommandAsync(
  680 + "UPDATE wt_czdmx SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id",
  681 + new { cbdj = item.Cbdj, cbje = item.Cbje, id = item.Id });
  682 +
  683 + int newSl = Math.Max(oldSl - qty, 0);
  684 + await _db.Ado.ExecuteCommandAsync(
  685 + "UPDATE wt_sp_cost SET sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  686 + new { sl = newSl, spbh = item.Spbh, ck = warehouseId });
  687 + }
  688 + }
  689 + }
  690 +
  691 + // 入库明细:按出库成本分摊,更新加权平均成本
  692 + if (inList != null && inList.Count > 0)
  693 + {
  694 + int totalInQty = inList.Where(i => (i.Sl ?? 0) > 0).Sum(i => i.Sl ?? 0);
  695 +
  696 + foreach (var item in inList)
  697 + {
  698 + if (string.IsNullOrEmpty(item.Spbh) || (item.Sl ?? 0) <= 0) continue;
  699 + var warehouseId = !string.IsNullOrEmpty(item.Chck) ? item.Chck : entity.Rhck;
  700 + if (string.IsNullOrEmpty(warehouseId)) continue;
  701 +
  702 + int qty = item.Sl ?? 0;
  703 + decimal inCostPerUnit = totalInQty > 0 && totalOutCost > 0
  704 + ? Math.Round(totalOutCost / totalInQty, 2)
  705 + : item.Cbdj;
  706 +
  707 + if (item.Cbdj == 0 && inCostPerUnit > 0)
  708 + {
  709 + item.Cbdj = inCostPerUnit;
  710 + item.Cbje = Math.Round(inCostPerUnit * qty, 2);
  711 + await _db.Ado.ExecuteCommandAsync(
  712 + "UPDATE wt_czdmx_2 SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id",
  713 + new { cbdj = item.Cbdj, cbje = item.Cbje, id = item.Id });
  714 + }
  715 +
  716 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  717 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  718 + new { spbh = item.Spbh, ck = warehouseId });
  719 +
  720 + if (existing != null && existing.Count > 0)
  721 + {
  722 + decimal oldCbj = Convert.ToDecimal(existing[0].cbj);
  723 + int oldSl = Convert.ToInt32(existing[0].sl);
  724 + int newSl = oldSl + qty;
  725 + decimal newCbj = newSl > 0
  726 + ? Math.Round((oldCbj * oldSl + inCostPerUnit * qty) / newSl, 2)
  727 + : inCostPerUnit;
  728 +
  729 + await _db.Ado.ExecuteCommandAsync(
  730 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  731 + new { cbj = newCbj, sl = newSl, spbh = item.Spbh, ck = warehouseId });
  732 + }
  733 + else if (inCostPerUnit > 0)
  734 + {
  735 + await _db.Ado.ExecuteCommandAsync(
  736 + "INSERT INTO wt_sp_cost (F_Id, spbh, ck, cbj, sl, update_time) VALUES (@id, @spbh, @ck, @cbj, @sl, NOW())",
  737 + new { id = $"{item.Spbh}_{warehouseId}", spbh = item.Spbh, ck = warehouseId, cbj = inCostPerUnit, sl = qty });
  738 + }
  739 + }
  740 + }
  741 + }
  742 + catch (Exception ex)
  743 + {
  744 + Console.WriteLine($"拆装单更新成本表失败(不影响主业务): {ex.Message}");
  745 + }
  746 + }
  747 +
  748 + /// <summary>
  749 + /// 拆装单删除时回退 wt_sp_cost
  750 + /// 出库方:恢复 sl(cbj 不变)
  751 + /// 入库方:反向加权平均回退 cbj 和 sl
  752 + /// </summary>
  753 + private async Task RollbackSpCostOnCzdDelete(WtCzdEntity entity)
  754 + {
  755 + var id = entity.Id;
  756 +
  757 + // 出库明细:恢复 sl
  758 + var outList = await _db.Queryable<WtCzdmxEntity>()
  759 + .Where(d => d.Djbh == id).ToListAsync();
  760 + foreach (var item in outList)
  761 + {
  762 + if (string.IsNullOrEmpty(item.Spbh) || (item.Sl ?? 0) <= 0) continue;
  763 + var warehouseId = !string.IsNullOrEmpty(item.Chck) ? item.Chck : entity.Chck;
  764 + if (string.IsNullOrEmpty(warehouseId)) continue;
  765 +
  766 + int qty = item.Sl ?? 0;
  767 + await _db.Ado.ExecuteCommandAsync(
  768 + "UPDATE wt_sp_cost SET sl = sl + @qty, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  769 + new { qty, spbh = item.Spbh, ck = warehouseId });
  770 + }
  771 +
  772 + // 入库明细:反向加权平均
  773 + var inList = await _db.Queryable<WtCzdmx2Entity>()
  774 + .Where(d => d.Djbh == id).ToListAsync();
  775 + foreach (var item in inList)
  776 + {
  777 + if (string.IsNullOrEmpty(item.Spbh) || (item.Sl ?? 0) <= 0) continue;
  778 + var warehouseId = !string.IsNullOrEmpty(item.Chck) ? item.Chck : entity.Rhck;
  779 + if (string.IsNullOrEmpty(warehouseId)) continue;
  780 +
  781 + int qty = item.Sl ?? 0;
  782 + decimal inCostPerUnit = item.Cbdj;
  783 +
  784 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  785 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  786 + new { spbh = item.Spbh, ck = warehouseId });
  787 +
  788 + if (existing != null && existing.Count > 0)
  789 + {
  790 + decimal currentCbj = Convert.ToDecimal(existing[0].cbj);
  791 + int currentSl = Convert.ToInt32(existing[0].sl);
  792 + int newSl = currentSl - qty;
  793 +
  794 + decimal newCbj = 0;
  795 + if (newSl > 0)
  796 + {
  797 + newCbj = Math.Round((currentCbj * currentSl - inCostPerUnit * qty) / newSl, 2);
  798 + }
  799 + else
  800 + {
  801 + newSl = 0;
  802 + }
  803 +
  804 + await _db.Ado.ExecuteCommandAsync(
  805 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  806 + new { cbj = newCbj, sl = newSl, spbh = item.Spbh, ck = warehouseId });
  807 + }
  808 + }
  809 + }
  810 +
  811 + #endregion
  812 +
  813 + /// <summary>
  814 + /// 获取商品成本单价
  815 + /// </summary>
  816 + /// <remarks>
  817 + /// 优先从最近一次采购入库单获取单价,若无则用商品档案零售价兜底
  818 + /// </remarks>
  819 + /// <param name="input">包含 ProductId(商品ID)和 WarehouseId(仓库/门店ID)</param>
  820 + /// <returns>返回 success、data(成本价)、source(价格来源)</returns>
  821 + [HttpPost("GetProductCost")]
  822 + public async Task<dynamic> GetProductCost([FromBody] GetProductCostInput input)
  823 + {
  824 + if (string.IsNullOrWhiteSpace(input?.ProductId))
  825 + return new { success = false, data = 0m, source = "", msg = "商品ID不能为空" };
  826 +
  827 + // 优先从 wt_sp_cost 成本表取(加权平均成本价)
  828 + var costSql = @"SELECT cbj FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck AND cbj > 0 LIMIT 1";
  829 + var costResult = await _db.Ado.SqlQueryAsync<decimal?>(costSql,
  830 + new { spbh = input.ProductId, ck = input.WarehouseId ?? "" });
  831 + var costPrice = costResult?.FirstOrDefault();
  832 +
  833 + if (costPrice.HasValue && costPrice.Value > 0)
  834 + return new { success = true, data = costPrice.Value, source = "成本价表" };
  835 +
  836 + // 兜底:从最近采购入库单取
  837 + var sql = @"SELECT mx.dj FROM wt_xsckd_mx mx
  838 + INNER JOIN wt_xsckd zd ON mx.djbh = zd.F_Id
  839 + WHERE mx.spbh = @spbh AND zd.djlx = '采购入库单'
  840 + ORDER BY zd.djrq DESC LIMIT 1";
  841 + var result = await _db.Ado.SqlQueryAsync<decimal?>(sql, new { spbh = input.ProductId });
  842 + var price = result?.FirstOrDefault();
  843 +
  844 + if (price.HasValue)
  845 + return new { success = true, data = price.Value, source = "采购入库单" };
  846 +
  847 + // 再兜底:从商品档案取零售价
  848 + var productSql = @"SELECT F_Lsj FROM wt_sp WHERE F_Id = @spbh LIMIT 1";
  849 + var lsjResult = await _db.Ado.SqlQueryAsync<decimal?>(productSql, new { spbh = input.ProductId });
  850 + var lsj = lsjResult?.FirstOrDefault();
  851 +
  852 + if (lsj.HasValue && lsj.Value > 0)
  853 + return new { success = true, data = lsj.Value, source = "商品零售价" };
  854 +
  855 + return new { success = true, data = 0m, source = "默认" };
  856 + }
  857 +
  858 + /// <summary>
  859 + /// 查询库存数量
  860 + /// </summary>
  861 + /// <remarks>
  862 + /// 通过门店ID查找关联仓库,再统计序列号表中在库数量
  863 + /// </remarks>
  864 + /// <param name="input">包含 warehouseId(门店ID)、productId(商品ID)、serialNumber(可选序列号)</param>
  865 + /// <returns>返回 success、data(库存数量)、msg</returns>
  866 + [HttpPost("QueryStock")]
  867 + public async Task<dynamic> QueryStock([FromBody] QueryStockInput input)
  868 + {
  869 + if (string.IsNullOrWhiteSpace(input?.ProductId))
  870 + return new { success = false, data = 0, msg = "商品ID不能为空" };
  871 +
  872 + var sql = @"SELECT COUNT(*) FROM wt_serial_number
  873 + WHERE TRIM(spbh) = TRIM(@productId)
  874 + AND TRIM(in_warehouse) = TRIM(@warehouseId)
  875 + AND status = 0";
  876 + var parameters = new List<SugarParameter>
  877 + {
  878 + new SugarParameter("@productId", input.ProductId),
  879 + new SugarParameter("@warehouseId", input.WarehouseId ?? "")
  880 + };
  881 +
  882 + if (!string.IsNullOrWhiteSpace(input.SerialNumber))
  883 + {
  884 + sql = @"SELECT COUNT(*) FROM wt_serial_number
  885 + WHERE TRIM(spbh) = TRIM(@productId)
  886 + AND TRIM(in_warehouse) = TRIM(@warehouseId)
  887 + AND status = 0
  888 + AND serial_number = @serialNumber";
  889 + parameters.Add(new SugarParameter("@serialNumber", input.SerialNumber));
  890 + }
  891 +
  892 + var stockCount = await _db.Ado.GetIntAsync(sql, parameters);
  893 +
  894 + return new { success = true, data = stockCount, msg = "查询成功" };
  895 + }
  896 +
642 /// <summary> 897 /// <summary>
643 /// 获取商品的可用序列号列表(用于出库选择) 898 /// 获取商品的可用序列号列表(用于出库选择)
644 /// </summary> 899 /// </summary>
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtTjdService.cs 0 → 100644
  1 +using NCC.Common.Core.Manager;
  2 +using NCC.Common.Extension;
  3 +using NCC.Common.Filter;
  4 +using NCC.Dependency;
  5 +using NCC.DynamicApiController;
  6 +using NCC.FriendlyException;
  7 +using NCC.Extend.Interfaces.WtTjd;
  8 +using Mapster;
  9 +using Microsoft.AspNetCore.Mvc;
  10 +using SqlSugar;
  11 +using System;
  12 +using System.Collections.Generic;
  13 +using System.Linq;
  14 +using System.Threading.Tasks;
  15 +using NCC.Extend.Entitys;
  16 +using NCC.Extend.Entitys.Dto.WtTjd;
  17 +using Yitter.IdGenerator;
  18 +using NCC.JsonSerialization;
  19 +using NCC.System.Entitys.Permission;
  20 +
  21 +namespace NCC.Extend.WtTjd
  22 +{
  23 + /// <summary>
  24 + /// 调价单服务
  25 + /// </summary>
  26 + [ApiDescriptionSettings(Tag = "Extend", Name = "WtTjd", Order = 200)]
  27 + [Route("api/Extend/[controller]")]
  28 + public class WtTjdService : IWtTjdService, IDynamicApiController, ITransient
  29 + {
  30 + private readonly ISqlSugarRepository<WtTjdEntity> _wtTjdRepository;
  31 + private readonly ISqlSugarRepository<WtTjdMxEntity> _wtTjdMxRepository;
  32 + private readonly ISqlSugarRepository<WtSpCostEntity> _wtSpCostRepository;
  33 + private readonly SqlSugarScope _db;
  34 + private readonly IUserManager _userManager;
  35 +
  36 + /// <summary>
  37 + /// 初始化一个<see cref="WtTjdService"/>类型的新实例
  38 + /// </summary>
  39 + public WtTjdService(
  40 + ISqlSugarRepository<WtTjdEntity> wtTjdRepository,
  41 + ISqlSugarRepository<WtTjdMxEntity> wtTjdMxRepository,
  42 + ISqlSugarRepository<WtSpCostEntity> wtSpCostRepository,
  43 + IUserManager userManager)
  44 + {
  45 + _wtTjdRepository = wtTjdRepository;
  46 + _wtTjdMxRepository = wtTjdMxRepository;
  47 + _wtSpCostRepository = wtSpCostRepository;
  48 + _db = _wtTjdRepository.Context;
  49 + _userManager = userManager;
  50 + }
  51 +
  52 + #region 基础 CRUD
  53 +
  54 + /// <summary>
  55 + /// 获取调价单详情
  56 + /// </summary>
  57 + /// <param name="id">单据编号</param>
  58 + /// <returns>调价单主表及明细</returns>
  59 + [HttpGet("{id}")]
  60 + public async Task<dynamic> GetInfo(string id)
  61 + {
  62 + var entity = await _db.Queryable<WtTjdEntity>().FirstAsync(p => p.Id == id);
  63 + if (entity == null)
  64 + throw NCCException.Bah("调价单不存在");
  65 +
  66 + var output = entity.Adapt<WtTjdInfoOutput>();
  67 +
  68 + var mxList = await _db.Queryable<WtTjdMxEntity>().Where(w => w.Djbh == id).ToListAsync();
  69 + output.wtTjdMxList = mxList.Adapt<List<WtTjdMxInfoOutput>>();
  70 +
  71 + return output;
  72 + }
  73 +
  74 + /// <summary>
  75 + /// 获取调价单列表(分页)
  76 + /// </summary>
  77 + /// <param name="input">查询参数</param>
  78 + /// <returns>分页列表</returns>
  79 + [HttpGet("")]
  80 + public async Task<dynamic> GetList([FromQuery] WtTjdListQueryInput input)
  81 + {
  82 + var sidx = input.sidx == null ? "id" : input.sidx;
  83 + List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null;
  84 + DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null;
  85 + DateTime? endDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.Last()) : null;
  86 +
  87 + var data = await _db.Queryable<WtTjdEntity>()
  88 + .WhereIF(!string.IsNullOrEmpty(input.code), p => p.Id.Contains(input.code))
  89 + .WhereIF(queryDjrq != null,
  90 + p => p.Djrq >= new DateTime(startDjrq.ToDate().Year, startDjrq.ToDate().Month,
  91 + startDjrq.ToDate().Day, 0, 0, 0))
  92 + .WhereIF(queryDjrq != null,
  93 + p => p.Djrq <= new DateTime(endDjrq.ToDate().Year, endDjrq.ToDate().Month,
  94 + endDjrq.ToDate().Day, 23, 59, 59))
  95 + .WhereIF(!string.IsNullOrEmpty(input.ck), p => p.Ck.Equals(input.ck))
  96 + .WhereIF(!string.IsNullOrEmpty(input.djzt), p => p.Djzt.Equals(input.djzt))
  97 + .Select(it => new WtTjdListOutput
  98 + {
  99 + id = it.Id,
  100 + djrq = it.Djrq,
  101 + ck = SqlFunc.Subqueryable<WtCkEntity>().Where(u => u.Id == it.Ck).Select(u => u.Mdmc),
  102 + jsr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Jsr).Select(u => u.RealName),
  103 + zdr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Zdr).Select(u => u.RealName),
  104 + djzt = it.Djzt,
  105 + bz = it.Bz
  106 + }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
  107 +
  108 + return PageResult<WtTjdListOutput>.SqlSugarPageResult(data);
  109 + }
  110 +
  111 + /// <summary>
  112 + /// 新建调价单
  113 + /// </summary>
  114 + /// <param name="input">创建参数</param>
  115 + /// <returns>新建的单据编号</returns>
  116 + [HttpPost("")]
  117 + public async Task<dynamic> Create([FromBody] WtTjdCrInput input)
  118 + {
  119 + var userInfo = await _userManager.GetUserInfo();
  120 + var entity = input.Adapt<WtTjdEntity>();
  121 +
  122 + var today = DateTime.Now.ToString("yyyyMMdd");
  123 + string prefix = "TJD";
  124 +
  125 + var maxId = await _db.Queryable<WtTjdEntity>()
  126 + .Where(x => x.Id.StartsWith(prefix + today))
  127 + .OrderBy(x => x.Id, OrderByType.Desc)
  128 + .Select(x => x.Id)
  129 + .FirstAsync();
  130 +
  131 + int serial = 1;
  132 + if (!string.IsNullOrEmpty(maxId) && maxId.Length >= prefix.Length + 8 + 4)
  133 + {
  134 + var serialStr = maxId.Substring(prefix.Length + 8, 4);
  135 + int.TryParse(serialStr, out serial);
  136 + serial++;
  137 + }
  138 +
  139 + try
  140 + {
  141 + _db.BeginTran();
  142 + int tryCount = 0;
  143 + while (true)
  144 + {
  145 + try
  146 + {
  147 + entity.Id = $"{prefix}{today}{serial.ToString("D4")}";
  148 + entity.Djrq = DateTime.Now;
  149 + entity.Zdr = userInfo.userId;
  150 + entity.Djzt = "草稿";
  151 +
  152 + var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
  153 +
  154 + if (input.wtTjdMxList != null && input.wtTjdMxList.Count > 0)
  155 + {
  156 + var mxEntityList = input.wtTjdMxList.Adapt<List<WtTjdMxEntity>>();
  157 + foreach (var item in mxEntityList)
  158 + {
  159 + item.Id = YitIdHelper.NextId().ToString();
  160 + item.Djbh = newEntity.Id;
  161 + item.Ck = entity.Ck;
  162 + }
  163 + await _db.Insertable(mxEntityList).ExecuteCommandAsync();
  164 + }
  165 +
  166 + _db.CommitTran();
  167 + return new { id = newEntity.Id, msg = "创建成功" };
  168 + }
  169 + catch (Exception ex)
  170 + {
  171 + if (ex.Message.Contains("Duplicate entry") && tryCount < 10)
  172 + {
  173 + serial++;
  174 + tryCount++;
  175 + continue;
  176 + }
  177 + _db.RollbackTran();
  178 + throw NCCException.Bah($"保存失败: {ex.Message}");
  179 + }
  180 + }
  181 + }
  182 + catch (Exception ex)
  183 + {
  184 + _db.RollbackTran();
  185 + throw NCCException.Bah($"保存失败: {ex.Message}");
  186 + }
  187 + }
  188 +
  189 + /// <summary>
  190 + /// 更新调价单(仅草稿状态可修改)
  191 + /// </summary>
  192 + /// <param name="id">单据编号</param>
  193 + /// <param name="input">更新参数</param>
  194 + /// <returns></returns>
  195 + [HttpPut("{id}")]
  196 + public async Task Update(string id, [FromBody] WtTjdUpInput input)
  197 + {
  198 + var existing = await _db.Queryable<WtTjdEntity>().FirstAsync(p => p.Id == id);
  199 + if (existing == null)
  200 + throw NCCException.Bah("调价单不存在");
  201 + if (existing.Djzt != "草稿")
  202 + throw NCCException.Bah("只有草稿状态的调价单才能修改");
  203 +
  204 + var entity = input.Adapt<WtTjdEntity>();
  205 + entity.Id = id;
  206 +
  207 + try
  208 + {
  209 + _db.BeginTran();
  210 +
  211 + await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
  212 +
  213 + await _db.Deleteable<WtTjdMxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
  214 +
  215 + if (input.wtTjdMxList != null && input.wtTjdMxList.Count > 0)
  216 + {
  217 + var mxEntityList = input.wtTjdMxList.Adapt<List<WtTjdMxEntity>>();
  218 + foreach (var item in mxEntityList)
  219 + {
  220 + item.Id = YitIdHelper.NextId().ToString();
  221 + item.Djbh = id;
  222 + item.Ck = entity.Ck ?? existing.Ck;
  223 + }
  224 + await _db.Insertable(mxEntityList).ExecuteCommandAsync();
  225 + }
  226 +
  227 + _db.CommitTran();
  228 + }
  229 + catch (Exception ex)
  230 + {
  231 + _db.RollbackTran();
  232 + throw NCCException.Bah($"更新失败: {ex.Message}");
  233 + }
  234 + }
  235 +
  236 + /// <summary>
  237 + /// 删除调价单(仅草稿状态可删除)
  238 + /// </summary>
  239 + /// <param name="id">单据编号</param>
  240 + /// <returns></returns>
  241 + [HttpDelete("{id}")]
  242 + public async Task Delete(string id)
  243 + {
  244 + var existing = await _db.Queryable<WtTjdEntity>().FirstAsync(p => p.Id == id);
  245 + if (existing == null)
  246 + throw NCCException.Bah("调价单不存在");
  247 + if (existing.Djzt != "草稿")
  248 + throw NCCException.Bah("只有草稿状态的调价单才能删除");
  249 +
  250 + try
  251 + {
  252 + _db.BeginTran();
  253 +
  254 + await _db.Deleteable<WtTjdEntity>().Where(d => d.Id == id).ExecuteCommandAsync();
  255 + await _db.Deleteable<WtTjdMxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
  256 +
  257 + _db.CommitTran();
  258 + }
  259 + catch (Exception)
  260 + {
  261 + _db.RollbackTran();
  262 + throw NCCException.Bah("删除失败");
  263 + }
  264 + }
  265 +
  266 + #endregion
  267 +
  268 + #region 业务操作
  269 +
  270 + /// <summary>
  271 + /// 审核调价单:更新状态为已审核,并将调后单价写入商品成本表
  272 + /// </summary>
  273 + /// <param name="id">单据编号</param>
  274 + /// <returns></returns>
  275 + [HttpPost("Actions/Audit/{id}")]
  276 + public async Task<dynamic> Audit(string id)
  277 + {
  278 + var entity = await _db.Queryable<WtTjdEntity>().FirstAsync(p => p.Id == id);
  279 + if (entity == null)
  280 + throw NCCException.Bah("调价单不存在");
  281 + if (entity.Djzt == "已审核")
  282 + throw NCCException.Bah("该调价单已审核,请勿重复操作");
  283 +
  284 + var mxList = await _db.Queryable<WtTjdMxEntity>().Where(w => w.Djbh == id).ToListAsync();
  285 + if (mxList == null || mxList.Count == 0)
  286 + throw NCCException.Bah("调价单明细为空,无法审核");
  287 +
  288 + try
  289 + {
  290 + _db.BeginTran();
  291 +
  292 + await _db.Updateable<WtTjdEntity>()
  293 + .SetColumns(it => it.Djzt == "已审核")
  294 + .Where(it => it.Id == id)
  295 + .ExecuteCommandAsync();
  296 +
  297 + foreach (var mx in mxList)
  298 + {
  299 + var costRecord = await _db.Queryable<WtSpCostEntity>()
  300 + .FirstAsync(c => c.Spbh == mx.Spbh && c.Ck == mx.Ck);
  301 +
  302 + if (costRecord != null)
  303 + {
  304 + await _db.Updateable<WtSpCostEntity>()
  305 + .SetColumns(it => new WtSpCostEntity
  306 + {
  307 + Cbj = mx.Thdj,
  308 + UpdateTime = DateTime.Now
  309 + })
  310 + .Where(it => it.Id == costRecord.Id)
  311 + .ExecuteCommandAsync();
  312 + }
  313 + else
  314 + {
  315 + var newCost = new WtSpCostEntity
  316 + {
  317 + Id = YitIdHelper.NextId().ToString(),
  318 + Spbh = mx.Spbh,
  319 + Ck = mx.Ck,
  320 + Cbj = mx.Thdj,
  321 + Sl = mx.Sl,
  322 + UpdateTime = DateTime.Now
  323 + };
  324 + await _db.Insertable(newCost).ExecuteCommandAsync();
  325 + }
  326 + }
  327 +
  328 + _db.CommitTran();
  329 + return new { msg = "审核成功" };
  330 + }
  331 + catch (Exception ex)
  332 + {
  333 + _db.RollbackTran();
  334 + throw NCCException.Bah($"审核失败: {ex.Message}");
  335 + }
  336 + }
  337 +
  338 + /// <summary>
  339 + /// 反审调价单:将 wt_sp_cost.cbj 恢复为调前单价,状态回退为草稿
  340 + /// </summary>
  341 + /// <param name="id">单据编号</param>
  342 + /// <returns></returns>
  343 + [HttpPost("Actions/ReverseAudit/{id}")]
  344 + public async Task<dynamic> ReverseAudit(string id)
  345 + {
  346 + var entity = await _db.Queryable<WtTjdEntity>().FirstAsync(p => p.Id == id);
  347 + if (entity == null)
  348 + throw NCCException.Bah("调价单不存在");
  349 + if (entity.Djzt != "已审核")
  350 + throw NCCException.Bah("只有已审核的调价单才能反审");
  351 +
  352 + var mxList = await _db.Queryable<WtTjdMxEntity>().Where(w => w.Djbh == id).ToListAsync();
  353 + if (mxList == null || mxList.Count == 0)
  354 + throw NCCException.Bah("调价单明细为空,无法反审");
  355 +
  356 + try
  357 + {
  358 + _db.BeginTran();
  359 +
  360 + foreach (var mx in mxList)
  361 + {
  362 + var costRecord = await _db.Queryable<WtSpCostEntity>()
  363 + .FirstAsync(c => c.Spbh == mx.Spbh && c.Ck == mx.Ck);
  364 +
  365 + if (costRecord != null)
  366 + {
  367 + await _db.Updateable<WtSpCostEntity>()
  368 + .SetColumns(it => new WtSpCostEntity
  369 + {
  370 + Cbj = mx.Tqdj,
  371 + UpdateTime = DateTime.Now
  372 + })
  373 + .Where(it => it.Id == costRecord.Id)
  374 + .ExecuteCommandAsync();
  375 + }
  376 + }
  377 +
  378 + await _db.Updateable<WtTjdEntity>()
  379 + .SetColumns(it => it.Djzt == "草稿")
  380 + .Where(it => it.Id == id)
  381 + .ExecuteCommandAsync();
  382 +
  383 + _db.CommitTran();
  384 + return new { msg = "反审成功,调价单已恢复为草稿状态" };
  385 + }
  386 + catch (Exception ex)
  387 + {
  388 + _db.RollbackTran();
  389 + throw NCCException.Bah($"反审失败: {ex.Message}");
  390 + }
  391 + }
  392 +
  393 + /// <summary>
  394 + /// 按仓库加载商品列表(含成本价和库存)
  395 + /// </summary>
  396 + /// <param name="ck">仓库ID</param>
  397 + /// <returns>商品列表</returns>
  398 + [HttpGet("Actions/LoadProducts")]
  399 + public async Task<dynamic> LoadProducts([FromQuery] string ck)
  400 + {
  401 + if (string.IsNullOrEmpty(ck))
  402 + throw NCCException.Bah("仓库ID不能为空");
  403 +
  404 + var products = await _db.Ado.SqlQueryAsync<dynamic>(
  405 + @"SELECT sc.spbh, sp.F_Spbm as spbm, sp.F_Spmc as spmc, sc.sl, sc.cbj as tqdj
  406 + FROM wt_sp_cost sc
  407 + INNER JOIN wt_sp sp ON sc.spbh = sp.F_Id
  408 + WHERE sc.ck = @ck AND sc.sl > 0
  409 + ORDER BY sp.F_Spmc",
  410 + new { ck = ck });
  411 +
  412 + if (products == null || products.Count == 0)
  413 + {
  414 + products = await _db.Ado.SqlQueryAsync<dynamic>(
  415 + @"SELECT sn.spbh, '' as spbm, sn.spmc, COUNT(*) as sl, 0 as tqdj
  416 + FROM wt_serial_number sn
  417 + WHERE sn.in_warehouse = @ck AND sn.status = 0
  418 + GROUP BY sn.spbh, sn.spmc",
  419 + new { ck = ck });
  420 + }
  421 +
  422 + return products;
  423 + }
  424 +
  425 + /// <summary>
  426 + /// 查询指定商品在指定仓库的成本价
  427 + /// </summary>
  428 + /// <param name="spbh">商品编号</param>
  429 + /// <param name="ck">仓库ID</param>
  430 + /// <returns>成本价</returns>
  431 + [HttpGet("Actions/GetCost")]
  432 + public async Task<dynamic> GetCost([FromQuery] string spbh, [FromQuery] string ck)
  433 + {
  434 + if (string.IsNullOrEmpty(spbh) || string.IsNullOrEmpty(ck))
  435 + return new { cbj = 0m };
  436 +
  437 + var cost = await _db.Queryable<WtSpCostEntity>()
  438 + .FirstAsync(c => c.Spbh == spbh && c.Ck == ck);
  439 +
  440 + return new { cbj = cost?.Cbj ?? 0m, sl = cost?.Sl ?? 0 };
  441 + }
  442 +
  443 + /// <summary>
  444 + /// 分页查询成本价列表
  445 + /// </summary>
  446 + /// <param name="keyword">商品名称或编码关键字</param>
  447 + /// <param name="ck">仓库ID</param>
  448 + /// <param name="currentPage">当前页码</param>
  449 + /// <param name="pageSize">每页条数</param>
  450 + /// <returns>成本价分页列表</returns>
  451 + [HttpGet("Actions/GetCostList")]
  452 + public async Task<dynamic> GetCostList([FromQuery] string keyword, [FromQuery] string ck, [FromQuery] int currentPage = 1, [FromQuery] int pageSize = 20)
  453 + {
  454 + var whereClauses = new List<string>();
  455 + if (!string.IsNullOrEmpty(keyword))
  456 + whereClauses.Add("(sp.F_Spmc LIKE @keyword OR sp.F_Spbm LIKE @keyword)");
  457 + if (!string.IsNullOrEmpty(ck))
  458 + whereClauses.Add("sc.ck = @ck");
  459 +
  460 + var whereStr = whereClauses.Count > 0 ? "WHERE " + string.Join(" AND ", whereClauses) : "";
  461 + var offset = (currentPage - 1) * pageSize;
  462 +
  463 + var queryParams = new { keyword = $"%{keyword}%", ck = ck, offset = offset, pageSize = pageSize };
  464 +
  465 + var countSql = $@"SELECT COUNT(*)
  466 + FROM wt_sp_cost sc
  467 + LEFT JOIN wt_sp sp ON sc.spbh = sp.F_Id
  468 + LEFT JOIN wt_ck ck_t ON sc.ck = ck_t.F_Id
  469 + {whereStr}";
  470 +
  471 + var dataSql = $@"SELECT sc.F_Id as id, sc.spbh, sp.F_Spbm as spbm, sp.F_Spmc as spmc,
  472 + sc.ck, ck_t.F_mdmc as ckmc, sc.cbj, sc.sl,
  473 + ROUND(sc.cbj * sc.sl, 2) as cbje, sc.update_time as updateTime
  474 + FROM wt_sp_cost sc
  475 + LEFT JOIN wt_sp sp ON sc.spbh = sp.F_Id
  476 + LEFT JOIN wt_ck ck_t ON sc.ck = ck_t.F_Id
  477 + {whereStr}
  478 + ORDER BY sp.F_Spmc
  479 + LIMIT @offset, @pageSize";
  480 +
  481 + var total = await _db.Ado.GetIntAsync(countSql, queryParams);
  482 + var data = await _db.Ado.SqlQueryAsync<dynamic>(dataSql, queryParams);
  483 +
  484 + return new { list = data, pagination = new { total, pageSize, currentPage } };
  485 + }
  486 +
  487 + /// <summary>
  488 + /// 加载某仓库所有商品(含在库数量和成本价,用于全部商品调价)
  489 + /// </summary>
  490 + /// <param name="ck">仓库ID</param>
  491 + /// <returns>商品列表</returns>
  492 + [HttpGet("Actions/LoadAllProducts")]
  493 + public async Task<dynamic> LoadAllProducts([FromQuery] string ck)
  494 + {
  495 + if (string.IsNullOrEmpty(ck))
  496 + throw NCCException.Bah("仓库ID不能为空");
  497 +
  498 + var products = await _db.Ado.SqlQueryAsync<dynamic>(
  499 + @"SELECT sn.spbh, sp.F_Spbm as spbm, sn.spmc,
  500 + COUNT(CASE WHEN sn.status = 0 THEN 1 END) as sl,
  501 + IFNULL(sc.cbj, 0) as tqdj
  502 + FROM wt_serial_number sn
  503 + LEFT JOIN wt_sp sp ON sn.spbh = sp.F_Id
  504 + LEFT JOIN wt_sp_cost sc ON sn.spbh = sc.spbh AND sc.ck = @ck
  505 + WHERE sn.in_warehouse = @ck
  506 + GROUP BY sn.spbh, sn.spmc, sp.F_Spbm, sc.cbj
  507 + ORDER BY sn.spmc",
  508 + new { ck = ck });
  509 +
  510 + return products;
  511 + }
  512 +
  513 + #endregion
  514 + }
  515 +}
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtXsckdService.cs
@@ -351,12 +351,23 @@ namespace NCC.Extend.WtXsckd @@ -351,12 +351,23 @@ namespace NCC.Extend.WtXsckd
351 //开启事务 351 //开启事务
352 _db.BeginTran(); 352 _db.BeginTran();
353 int tryCount = 0; 353 int tryCount = 0;
  354 + // 如果前端传了预生成的单号且格式正确,优先使用
  355 + bool usePreGeneratedId = !string.IsNullOrEmpty(input.id)
  356 + && input.id.StartsWith(prefix)
  357 + && input.id.Contains(today);
354 while (true) 358 while (true)
355 { 359 {
356 try 360 try
357 { 361 {
358 - // 生成单号逻辑  
359 - entity.Id = $"{prefix}{today}{serial.ToString("D4")}"; 362 + if (usePreGeneratedId)
  363 + {
  364 + entity.Id = input.id;
  365 + usePreGeneratedId = false; // 冲突重试时走自增逻辑
  366 + }
  367 + else
  368 + {
  369 + entity.Id = $"{prefix}{today}{serial.ToString("D4")}";
  370 + }
360 entity.Djrq = DateTime.Now; 371 entity.Djrq = DateTime.Now;
361 // ✅ 采购入库单:默认状态为"待审核" 372 // ✅ 采购入库单:默认状态为"待审核"
362 if (input.djlx == "采购入库单" && string.IsNullOrEmpty(entity.Djzt)) 373 if (input.djlx == "采购入库单" && string.IsNullOrEmpty(entity.Djzt))
@@ -575,6 +586,9 @@ namespace NCC.Extend.WtXsckd @@ -575,6 +586,9 @@ namespace NCC.Extend.WtXsckd
575 await GenerateCommissionRecords(newEntity.Id, wtXsckdMxEntityList, input.jsr); 586 await GenerateCommissionRecords(newEntity.Id, wtXsckdMxEntityList, input.jsr);
576 } 587 }
577 588
  589 + // ========== P4: 自动维护 wt_sp_cost 成本表 ==========
  590 + await UpdateSpCostOnCreate(input.djlx, entity, wtXsckdMxEntityList);
  591 +
578 //关闭事务 592 //关闭事务
579 _db.CommitTran(); 593 _db.CommitTran();
580 594
@@ -714,6 +728,60 @@ namespace NCC.Extend.WtXsckd @@ -714,6 +728,60 @@ namespace NCC.Extend.WtXsckd
714 } 728 }
715 729
716 /// <summary> 730 /// <summary>
  731 + /// 预生成单据编号(新建时前端展示用)
  732 + /// </summary>
  733 + /// <param name="djlx">单据类型</param>
  734 + [HttpGet("Actions/GenerateBillNo")]
  735 + public async Task<dynamic> GenerateBillNo([FromQuery] string djlx)
  736 + {
  737 + var today = DateTime.Now.ToString("yyyyMMdd");
  738 + string prefix = "CHD";
  739 + if (!string.IsNullOrEmpty(djlx))
  740 + {
  741 + if (djlx.Contains("采购入库单")) prefix = "RK";
  742 + else if (djlx.Contains("预售出库单")) prefix = "YC";
  743 + else if (djlx.Contains("预售退货单")) prefix = "YT";
  744 + else if (djlx.Contains("采购退货单")) prefix = "CT";
  745 + else if (djlx.Contains("销售出库单")) prefix = "CHD";
  746 + else if (djlx.Contains("委托代销发货单")) prefix = "WF";
  747 + else if (djlx.Contains("委托代销退货单")) prefix = "WT";
  748 + else if (djlx.Contains("委托代销结算单")) prefix = "WJ";
  749 + else if (djlx.Contains("现金费用单")) prefix = "XF";
  750 + else if (djlx.Contains("其他收入单")) prefix = "QT";
  751 + else if (djlx.Contains("盘点单")) prefix = "PDD";
  752 + else if (djlx.Contains("报损单")) prefix = "BSD";
  753 + else if (djlx.Contains("同价调拨单")) prefix = "TJD";
  754 + else if (djlx.Contains("变价调拨单")) prefix = "BJD";
  755 + else if (djlx.Contains("获赠单")) prefix = "HZD";
  756 + else if (djlx.Contains("报溢单")) prefix = "BYD";
  757 + }
  758 +
  759 + var allTodayOrders = await _db.Queryable<WtXsckdEntity>()
  760 + .Where(x => x.Id.StartsWith(prefix) && x.Id.Contains(today))
  761 + .Select(x => x.Id)
  762 + .ToListAsync();
  763 +
  764 + int serial = 1;
  765 + if (allTodayOrders.Count > 0)
  766 + {
  767 + var serialNumbers = new List<int>();
  768 + foreach (var orderId in allTodayOrders)
  769 + {
  770 + if (orderId.Length >= 4)
  771 + {
  772 + var serialStr = orderId.Substring(orderId.Length - 4);
  773 + if (int.TryParse(serialStr, out int parsedSerial))
  774 + serialNumbers.Add(parsedSerial);
  775 + }
  776 + }
  777 + if (serialNumbers.Count > 0)
  778 + serial = serialNumbers.Max() + 1;
  779 + }
  780 +
  781 + return new { billNo = $"{prefix}{today}{serial.ToString("D4")}" };
  782 + }
  783 +
  784 + /// <summary>
717 /// 导出销售出库单 785 /// 导出销售出库单
718 /// </summary> 786 /// </summary>
719 /// <param name="input">请求参数</param> 787 /// <param name="input">请求参数</param>
@@ -770,7 +838,7 @@ namespace NCC.Extend.WtXsckd @@ -770,7 +838,7 @@ namespace NCC.Extend.WtXsckd
770 } 838 }
771 839
772 /// <summary> 840 /// <summary>
773 - /// 批量删除销售出库单 841 + /// 批量删除销售出库单(含成本回退)
774 /// </summary> 842 /// </summary>
775 /// <param name="ids">主键数组</param> 843 /// <param name="ids">主键数组</param>
776 /// <returns></returns> 844 /// <returns></returns>
@@ -782,19 +850,21 @@ namespace NCC.Extend.WtXsckd @@ -782,19 +850,21 @@ namespace NCC.Extend.WtXsckd
782 { 850 {
783 try 851 try
784 { 852 {
785 - //开启事务  
786 _db.BeginTran(); 853 _db.BeginTran();
787 - //批量删除销售出库单  
788 - await _db.Deleteable<WtXsckdEntity>().In(d => d.Id,ids).ExecuteCommandAsync();  
789 854
790 - //清空子表数据  
791 - await _db.Deleteable<WtXsckdMxEntity>().In(u => u.Djbh,ids).ExecuteCommandAsync();  
792 - //关闭事务 855 + // 逐单回退成本
  856 + foreach (var entity in entitys)
  857 + {
  858 + await RollbackSpCostOnDelete(entity);
  859 + }
  860 +
  861 + await _db.Deleteable<WtXsckdEntity>().In(d => d.Id, ids).ExecuteCommandAsync();
  862 + await _db.Deleteable<WtXsckdMxEntity>().In(u => u.Djbh, ids).ExecuteCommandAsync();
  863 +
793 _db.CommitTran(); 864 _db.CommitTran();
794 } 865 }
795 catch (Exception) 866 catch (Exception)
796 { 867 {
797 - //回滚事务  
798 _db.RollbackTran(); 868 _db.RollbackTran();
799 throw NCCException.Oh(ErrorCode.COM1002); 869 throw NCCException.Oh(ErrorCode.COM1002);
800 } 870 }
@@ -866,7 +936,10 @@ namespace NCC.Extend.WtXsckd @@ -866,7 +936,10 @@ namespace NCC.Extend.WtXsckd
866 } 936 }
867 937
868 /// <summary> 938 /// <summary>
869 - /// 删除销售出库单 939 + /// 删除销售出库单(含成本回退)
  940 + /// 采购入库单(已审核):检查序列号→删除→回退成本
  941 + /// 销售/预售出库单:恢复 wt_sp_cost.sl
  942 + /// 销售退货单:减少 wt_sp_cost.sl
870 /// </summary> 943 /// </summary>
871 /// <returns></returns> 944 /// <returns></returns>
872 [HttpDelete("{id}")] 945 [HttpDelete("{id}")]
@@ -876,28 +949,62 @@ namespace NCC.Extend.WtXsckd @@ -876,28 +949,62 @@ namespace NCC.Extend.WtXsckd
876 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005); 949 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
877 try 950 try
878 { 951 {
879 - //开启事务  
880 _db.BeginTran(); 952 _db.BeginTran();
881 -  
882 - //删除销售出库单记录  
883 - await _db.Deleteable<WtXsckdEntity>().Where(d => d.Id == id).ExecuteCommandAsync();  
884 953
885 - //清空子表数据 954 + // 删除前回退成本
  955 + await RollbackSpCostOnDelete(entity);
  956 +
  957 + await _db.Deleteable<WtXsckdEntity>().Where(d => d.Id == id).ExecuteCommandAsync();
886 await _db.Deleteable<WtXsckdMxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync(); 958 await _db.Deleteable<WtXsckdMxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
887 959
888 - //关闭事务  
889 _db.CommitTran(); 960 _db.CommitTran();
890 } 961 }
891 catch (Exception) 962 catch (Exception)
892 { 963 {
893 - //回滚事务  
894 _db.RollbackTran(); 964 _db.RollbackTran();
895 throw NCCException.Oh(ErrorCode.COM1002); 965 throw NCCException.Oh(ErrorCode.COM1002);
896 } 966 }
897 } 967 }
898 968
899 -  
900 - 969 + /// <summary>
  970 + /// 根据商品+仓库查询已审核的采购入库单列表(用于采购退货单选择入库编号)
  971 + /// </summary>
  972 + [HttpGet("GetPurchaseInboundOrders")]
  973 + public async Task<dynamic> GetPurchaseInboundOrders(string spbh, string ck)
  974 + {
  975 + if (string.IsNullOrWhiteSpace(spbh) || string.IsNullOrWhiteSpace(ck))
  976 + {
  977 + return new { code = 200, data = new List<object>() };
  978 + }
  979 +
  980 + // 兼容两种传参:ck 可能是仓库ID,也可能是门店ID(F_ssmd)
  981 + var warehouseIds = await _db.Ado.SqlQueryAsync<string>(
  982 + "SELECT F_Id FROM wt_ck WHERE F_Id = @ck OR F_ssmd = @ck",
  983 + new { ck });
  984 + if (warehouseIds == null || warehouseIds.Count == 0)
  985 + {
  986 + warehouseIds = new List<string> { ck };
  987 + }
  988 +
  989 + var warehouseParams = warehouseIds
  990 + .Select((id, idx) => new SugarParameter($"@ck{idx}", id))
  991 + .ToList();
  992 + var inSql = string.Join(",", warehouseParams.Select(p => p.ParameterName));
  993 +
  994 + var sql = $@"SELECT d.F_Id as orderid, d.djrq, mx.dj, mx.sl, mx.spmc
  995 + FROM wt_xsckd d
  996 + INNER JOIN wt_xsckd_mx mx ON mx.djbh = d.F_Id
  997 + WHERE d.djlx = '采购入库单' AND d.djzt = '已审核'
  998 + AND mx.spbh = @spbh
  999 + AND (mx.rkck IN ({inSql}) OR ((mx.rkck IS NULL OR mx.rkck = '') AND d.rkck IN ({inSql})))
  1000 + ORDER BY d.F_Id DESC";
  1001 +
  1002 + var sqlParams = new List<SugarParameter> { new SugarParameter("@spbh", spbh) };
  1003 + sqlParams.AddRange(warehouseParams);
  1004 + var list = await _db.Ado.SqlQueryAsync<dynamic>(sql, sqlParams.ToArray());
  1005 + return new { code = 200, data = list };
  1006 + }
  1007 +
901 [HttpGet("GetProductSummary")] 1008 [HttpGet("GetProductSummary")]
902 public async Task<dynamic> GetProductSummary(string productName = null, string settleUnit = null, string contactUnit = null, string agent = null, string warehouse = null, string billType = null, string startDate = null, string endDate = null) 1009 public async Task<dynamic> GetProductSummary(string productName = null, string settleUnit = null, string contactUnit = null, string agent = null, string warehouse = null, string billType = null, string startDate = null, string endDate = null)
903 { 1010 {
@@ -1749,10 +1856,11 @@ ORDER BY t.`商品编号`;&quot;; @@ -1749,10 +1856,11 @@ ORDER BY t.`商品编号`;&quot;;
1749 entity.Shr1 = userId; 1856 entity.Shr1 = userId;
1750 await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1 }).ExecuteCommandAsync(); 1857 await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1 }).ExecuteCommandAsync();
1751 1858
1752 - // 采购入库单最终审核通过后生成序列号 1859 + // 采购入库单最终审核通过后生成序列号 + 更新成本
1753 if (entity.Djlx == "采购入库单") 1860 if (entity.Djlx == "采购入库单")
1754 { 1861 {
1755 await GenerateSerialNumbersForApproval(entity, id); 1862 await GenerateSerialNumbersForApproval(entity, id);
  1863 + await UpdateSpCostOnPurchaseApproval(entity, id);
1756 } 1864 }
1757 1865
1758 _db.CommitTran(); 1866 _db.CommitTran();
@@ -1767,10 +1875,11 @@ ORDER BY t.`商品编号`;&quot;; @@ -1767,10 +1875,11 @@ ORDER BY t.`商品编号`;&quot;;
1767 entity.Shr = userId; 1875 entity.Shr = userId;
1768 await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr2 }).ExecuteCommandAsync(); 1876 await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr2 }).ExecuteCommandAsync();
1769 1877
1770 - // 采购入库单最终审核通过后生成序列号 1878 + // 采购入库单最终审核通过后生成序列号 + 更新成本
1771 if (entity.Djlx == "采购入库单") 1879 if (entity.Djlx == "采购入库单")
1772 { 1880 {
1773 await GenerateSerialNumbersForApproval(entity, id); 1881 await GenerateSerialNumbersForApproval(entity, id);
  1882 + await UpdateSpCostOnPurchaseApproval(entity, id);
1774 } 1883 }
1775 1884
1776 _db.CommitTran(); 1885 _db.CommitTran();
@@ -1859,6 +1968,7 @@ ORDER BY t.`商品编号`;&quot;; @@ -1859,6 +1968,7 @@ ORDER BY t.`商品编号`;&quot;;
1859 /// <summary> 1968 /// <summary>
1860 /// 反审单据:将"已审核"或"一级已审"状态回退为"待审核",清空审核人字段 1969 /// 反审单据:将"已审核"或"一级已审"状态回退为"待审核",清空审核人字段
1861 /// 一级/二级审核人均可执行反审操作 1970 /// 一级/二级审核人均可执行反审操作
  1971 + /// 采购入库单反审时需检查序列号使用情况并回退成本
1862 /// </summary> 1972 /// </summary>
1863 [HttpPost("ReverseApproval/{id}")] 1973 [HttpPost("ReverseApproval/{id}")]
1864 public async Task<dynamic> ReverseApproval(string id) 1974 public async Task<dynamic> ReverseApproval(string id)
@@ -1887,15 +1997,49 @@ ORDER BY t.`商品编号`;&quot;; @@ -1887,15 +1997,49 @@ ORDER BY t.`商品编号`;&quot;;
1887 if (!isAuthorized) 1997 if (!isAuthorized)
1888 return new { success = false, message = "您没有反审权限,只有一级或二级审核人可以反审" }; 1998 return new { success = false, message = "您没有反审权限,只有一级或二级审核人可以反审" };
1889 1999
1890 - entity.Djzt = "待审核";  
1891 - entity.Shr = null;  
1892 - entity.Shr1 = null;  
1893 - entity.Shr2 = null;  
1894 - await _db.Updateable(entity)  
1895 - .UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1, x.Shr2 })  
1896 - .ExecuteCommandAsync(); 2000 + _db.BeginTran();
  2001 + try
  2002 + {
  2003 + // 采购入库单反审:检查序列号+回退成本
  2004 + if (entity.Djlx == "采购入库单")
  2005 + {
  2006 + var serialNumbers = await _db.Queryable<WtSerialNumberEntity>()
  2007 + .Where(s => s.InDjbh == id)
  2008 + .ToListAsync();
  2009 +
  2010 + if (serialNumbers.Any(s => s.Status != 0))
  2011 + {
  2012 + _db.RollbackTran();
  2013 + return new { success = false, message = "该入库单的商品已有出库记录,无法反审" };
  2014 + }
  2015 +
  2016 + // 删除未使用的序列号
  2017 + if (serialNumbers.Any())
  2018 + {
  2019 + var snIds = serialNumbers.Select(s => s.Id).ToList();
  2020 + await _db.Deleteable<WtSerialNumberEntity>().In(s => s.Id, snIds).ExecuteCommandAsync();
  2021 + }
  2022 +
  2023 + // 回退 wt_sp_cost
  2024 + await RollbackSpCostForPurchase(entity, id);
  2025 + }
  2026 +
  2027 + entity.Djzt = "待审核";
  2028 + entity.Shr = null;
  2029 + entity.Shr1 = null;
  2030 + entity.Shr2 = null;
  2031 + await _db.Updateable(entity)
  2032 + .UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1, x.Shr2 })
  2033 + .ExecuteCommandAsync();
1897 2034
1898 - return new { success = true, message = "反审成功,单据已恢复为待审核状态" }; 2035 + _db.CommitTran();
  2036 + return new { success = true, message = "反审成功,单据已恢复为待审核状态" };
  2037 + }
  2038 + catch (Exception ex)
  2039 + {
  2040 + _db.RollbackTran();
  2041 + return new { success = false, message = $"反审失败: {ex.Message}" };
  2042 + }
1899 } 2043 }
1900 catch (Exception ex) 2044 catch (Exception ex)
1901 { 2045 {
@@ -2587,5 +2731,369 @@ ORDER BY t.`商品编号`;&quot;; @@ -2587,5 +2731,369 @@ ORDER BY t.`商品编号`;&quot;;
2587 // 不抛出异常,避免影响主业务流程 2731 // 不抛出异常,避免影响主业务流程
2588 } 2732 }
2589 } 2733 }
  2734 +
  2735 + #region P4: wt_sp_cost 成本表自动维护
  2736 +
  2737 + /// <summary>
  2738 + /// 采购入库单审核通过后,按加权平均法更新成本表
  2739 + /// 从数据库重新读取明细(保证使用编辑后的最终数据)
  2740 + /// </summary>
  2741 + private async Task UpdateSpCostOnPurchaseApproval(WtXsckdEntity entity, string id)
  2742 + {
  2743 + try
  2744 + {
  2745 + var warehouseId = entity.Rkck;
  2746 + if (string.IsNullOrEmpty(warehouseId)) return;
  2747 +
  2748 + var detailList = await _db.Queryable<WtXsckdMxEntity>()
  2749 + .Where(d => d.Djbh == id).ToListAsync();
  2750 +
  2751 + foreach (var detail in detailList)
  2752 + {
  2753 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  2754 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  2755 +
  2756 + var detailWarehouse = !string.IsNullOrEmpty(detail.Rkck) ? detail.Rkck : warehouseId;
  2757 + decimal purchasePrice = detail.Dj;
  2758 +
  2759 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  2760 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  2761 + new { spbh = detail.Spbh, ck = detailWarehouse });
  2762 +
  2763 + if (existing != null && existing.Count > 0)
  2764 + {
  2765 + decimal oldCbj = Convert.ToDecimal(existing[0].cbj);
  2766 + int oldSl = Convert.ToInt32(existing[0].sl);
  2767 + int newSl = oldSl + qty;
  2768 + decimal newCbj = newSl > 0
  2769 + ? Math.Round((oldCbj * oldSl + purchasePrice * qty) / newSl, 2)
  2770 + : purchasePrice;
  2771 +
  2772 + await _db.Ado.ExecuteCommandAsync(
  2773 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  2774 + new { cbj = newCbj, sl = newSl, spbh = detail.Spbh, ck = detailWarehouse });
  2775 + }
  2776 + else
  2777 + {
  2778 + await _db.Ado.ExecuteCommandAsync(
  2779 + "INSERT INTO wt_sp_cost (F_Id, spbh, ck, cbj, sl, update_time) VALUES (@id, @spbh, @ck, @cbj, @sl, NOW())",
  2780 + new { id = $"{detail.Spbh}_{detailWarehouse}", spbh = detail.Spbh, ck = detailWarehouse, cbj = purchasePrice, sl = qty });
  2781 + }
  2782 + }
  2783 + }
  2784 + catch (Exception ex)
  2785 + {
  2786 + Console.WriteLine($"采购入库审核更新成本失败(不影响主业务): {ex.Message}");
  2787 + }
  2788 + }
  2789 +
  2790 + /// <summary>
  2791 + /// 创建单据时自动维护 wt_sp_cost(在事务内调用)
  2792 + /// 采购入库 → 审核通过后才更新(见 UpdateSpCostOnPurchaseApproval)
  2793 + /// 销售/预售出库 → 快照成本到明细 + 减少数量
  2794 + /// 销售退货 → 恢复数量(成本不变)
  2795 + /// </summary>
  2796 + private async Task UpdateSpCostOnCreate(string djlx, WtXsckdEntity entity, List<WtXsckdMxEntity> mxList)
  2797 + {
  2798 + if (mxList == null || mxList.Count == 0) return;
  2799 +
  2800 + try
  2801 + {
  2802 + if (djlx == "销售出库单" || djlx == "预售出库单")
  2803 + {
  2804 + var outStoreId = entity.Cjck;
  2805 + if (string.IsNullOrEmpty(outStoreId)) return;
  2806 +
  2807 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  2808 + .Where(c => c.Ssmd == outStoreId)
  2809 + .Select(c => c.Id)
  2810 + .ToListAsync();
  2811 + if (warehouseIds == null || warehouseIds.Count == 0) return;
  2812 +
  2813 + foreach (var detail in mxList)
  2814 + {
  2815 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  2816 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  2817 +
  2818 + var costResult = await _db.Ado.SqlQueryAsync<decimal?>(
  2819 + $"SELECT cbj FROM wt_sp_cost WHERE spbh = @spbh AND ck IN ({string.Join(",", warehouseIds.Select((_, i) => $"@ck{i}"))}) AND cbj > 0 LIMIT 1",
  2820 + warehouseIds.Select((id, i) => new SugarParameter($"@ck{i}", id))
  2821 + .Append(new SugarParameter("@spbh", detail.Spbh)).ToArray());
  2822 +
  2823 + decimal costPrice = costResult?.FirstOrDefault() ?? 0;
  2824 +
  2825 + if (costPrice > 0)
  2826 + {
  2827 + await _db.Ado.ExecuteCommandAsync(
  2828 + "UPDATE wt_xsckd_mx SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id",
  2829 + new { cbdj = costPrice, cbje = Math.Round(costPrice * qty, 2), id = detail.Id });
  2830 + }
  2831 +
  2832 + foreach (var whId in warehouseIds)
  2833 + {
  2834 + var actualCount = await _db.Ado.GetIntAsync(
  2835 + "SELECT COUNT(*) FROM wt_serial_number WHERE spbh = @spbh AND in_warehouse = @ck AND status = 0",
  2836 + new SugarParameter[] { new SugarParameter("@spbh", detail.Spbh), new SugarParameter("@ck", whId) });
  2837 + await _db.Ado.ExecuteCommandAsync(
  2838 + "UPDATE wt_sp_cost SET sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  2839 + new { sl = actualCount, spbh = detail.Spbh, ck = whId });
  2840 + }
  2841 + }
  2842 + }
  2843 + else if (djlx == "销售退货单")
  2844 + {
  2845 + var returnWarehouseId = entity.Rkck;
  2846 + if (string.IsNullOrEmpty(returnWarehouseId)) return;
  2847 +
  2848 + foreach (var detail in mxList)
  2849 + {
  2850 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  2851 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  2852 +
  2853 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  2854 + "SELECT sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  2855 + new { spbh = detail.Spbh, ck = returnWarehouseId });
  2856 +
  2857 + if (existing != null && existing.Count > 0)
  2858 + {
  2859 + await _db.Ado.ExecuteCommandAsync(
  2860 + "UPDATE wt_sp_cost SET sl = sl + @qty, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  2861 + new { qty, spbh = detail.Spbh, ck = returnWarehouseId });
  2862 + }
  2863 + }
  2864 + }
  2865 + else if (djlx == "采购退货单")
  2866 + {
  2867 + // 采购退货:用退货明细中的单价(即原始采购价)做反向加权平均
  2868 + var outStoreId = entity.Cjck;
  2869 + if (string.IsNullOrEmpty(outStoreId)) return;
  2870 +
  2871 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  2872 + .Where(c => c.Ssmd == outStoreId || c.Id == outStoreId)
  2873 + .Select(c => c.Id)
  2874 + .ToListAsync();
  2875 + if (warehouseIds == null || warehouseIds.Count == 0)
  2876 + {
  2877 + warehouseIds = new List<string> { outStoreId };
  2878 + }
  2879 +
  2880 + foreach (var detail in mxList)
  2881 + {
  2882 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  2883 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  2884 +
  2885 + decimal returnPrice = detail.Dj;
  2886 +
  2887 + foreach (var whId in warehouseIds)
  2888 + {
  2889 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  2890 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  2891 + new { spbh = detail.Spbh, ck = whId });
  2892 +
  2893 + if (existing != null && existing.Count > 0)
  2894 + {
  2895 + decimal currentCbj = Convert.ToDecimal(existing[0].cbj);
  2896 + int currentSl = Convert.ToInt32(existing[0].sl);
  2897 + int newSl = currentSl - qty;
  2898 +
  2899 + decimal newCbj = 0;
  2900 + if (newSl > 0)
  2901 + {
  2902 + newCbj = Math.Round((currentCbj * currentSl - returnPrice * qty) / newSl, 2);
  2903 + }
  2904 + else
  2905 + {
  2906 + newSl = 0;
  2907 + }
  2908 +
  2909 + await _db.Ado.ExecuteCommandAsync(
  2910 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  2911 + new { cbj = newCbj, sl = newSl, spbh = detail.Spbh, ck = whId });
  2912 +
  2913 + await _db.Ado.ExecuteCommandAsync(
  2914 + "UPDATE wt_xsckd_mx SET cbdj = @cbdj, cbje = @cbje WHERE F_Id = @id",
  2915 + new { cbdj = returnPrice, cbje = Math.Round(returnPrice * qty, 2), id = detail.Id });
  2916 +
  2917 + break;
  2918 + }
  2919 + }
  2920 + }
  2921 + }
  2922 + }
  2923 + catch (Exception ex)
  2924 + {
  2925 + Console.WriteLine($"更新成本表失败(不影响主业务): {ex.Message}");
  2926 + }
  2927 + }
  2928 +
  2929 + /// <summary>
  2930 + /// 采购入库单反审/删除时回退成本:反向加权平均
  2931 + /// </summary>
  2932 + private async Task RollbackSpCostForPurchase(WtXsckdEntity entity, string id)
  2933 + {
  2934 + var warehouseId = entity.Rkck;
  2935 + if (string.IsNullOrEmpty(warehouseId)) return;
  2936 +
  2937 + var detailList = await _db.Queryable<WtXsckdMxEntity>()
  2938 + .Where(d => d.Djbh == id).ToListAsync();
  2939 +
  2940 + foreach (var detail in detailList)
  2941 + {
  2942 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  2943 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  2944 +
  2945 + var detailWarehouse = !string.IsNullOrEmpty(detail.Rkck) ? detail.Rkck : warehouseId;
  2946 + decimal purchasePrice = detail.Dj;
  2947 +
  2948 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  2949 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  2950 + new { spbh = detail.Spbh, ck = detailWarehouse });
  2951 +
  2952 + if (existing != null && existing.Count > 0)
  2953 + {
  2954 + decimal currentCbj = Convert.ToDecimal(existing[0].cbj);
  2955 + int currentSl = Convert.ToInt32(existing[0].sl);
  2956 + int newSl = currentSl - qty;
  2957 +
  2958 + decimal newCbj = 0;
  2959 + if (newSl > 0)
  2960 + {
  2961 + newCbj = Math.Round((currentCbj * currentSl - purchasePrice * qty) / newSl, 2);
  2962 + }
  2963 + else
  2964 + {
  2965 + newSl = 0;
  2966 + }
  2967 +
  2968 + await _db.Ado.ExecuteCommandAsync(
  2969 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  2970 + new { cbj = newCbj, sl = newSl, spbh = detail.Spbh, ck = detailWarehouse });
  2971 + }
  2972 + }
  2973 + }
  2974 +
  2975 + /// <summary>
  2976 + /// 删除单据时根据单据类型回退 wt_sp_cost
  2977 + /// 采购入库单(已审核):检查序列号→删除→回退成本
  2978 + /// 销售/预售出库单:恢复sl
  2979 + /// 销售退货单:减少sl
  2980 + /// </summary>
  2981 + private async Task RollbackSpCostOnDelete(WtXsckdEntity entity)
  2982 + {
  2983 + var id = entity.Id;
  2984 + var djlx = entity.Djlx;
  2985 +
  2986 + if (djlx == "采购入库单" && entity.Djzt == "已审核")
  2987 + {
  2988 + // 检查序列号是否被使用
  2989 + var serialNumbers = await _db.Queryable<WtSerialNumberEntity>()
  2990 + .Where(s => s.InDjbh == id)
  2991 + .ToListAsync();
  2992 +
  2993 + if (serialNumbers.Any(s => s.Status != 0))
  2994 + throw new Exception("该入库单的商品已有出库记录,无法删除");
  2995 +
  2996 + if (serialNumbers.Any())
  2997 + {
  2998 + var snIds = serialNumbers.Select(s => s.Id).ToList();
  2999 + await _db.Deleteable<WtSerialNumberEntity>().In(s => s.Id, snIds).ExecuteCommandAsync();
  3000 + }
  3001 +
  3002 + await RollbackSpCostForPurchase(entity, id);
  3003 + }
  3004 + else if (djlx == "销售出库单" || djlx == "预售出库单")
  3005 + {
  3006 + var outStoreId = entity.Cjck;
  3007 + if (string.IsNullOrEmpty(outStoreId)) return;
  3008 +
  3009 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  3010 + .Where(c => c.Ssmd == outStoreId)
  3011 + .Select(c => c.Id)
  3012 + .ToListAsync();
  3013 + if (warehouseIds == null || warehouseIds.Count == 0) return;
  3014 +
  3015 + var mxList = await _db.Queryable<WtXsckdMxEntity>()
  3016 + .Where(d => d.Djbh == id).ToListAsync();
  3017 +
  3018 + foreach (var detail in mxList)
  3019 + {
  3020 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  3021 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  3022 +
  3023 + foreach (var whId in warehouseIds)
  3024 + {
  3025 + await _db.Ado.ExecuteCommandAsync(
  3026 + "UPDATE wt_sp_cost SET sl = sl + @qty, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  3027 + new { qty, spbh = detail.Spbh, ck = whId });
  3028 + }
  3029 + }
  3030 + }
  3031 + else if (djlx == "销售退货单")
  3032 + {
  3033 + var returnWarehouseId = entity.Rkck;
  3034 + if (string.IsNullOrEmpty(returnWarehouseId)) return;
  3035 +
  3036 + var mxList = await _db.Queryable<WtXsckdMxEntity>()
  3037 + .Where(d => d.Djbh == id).ToListAsync();
  3038 +
  3039 + foreach (var detail in mxList)
  3040 + {
  3041 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  3042 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  3043 +
  3044 + await _db.Ado.ExecuteCommandAsync(
  3045 + "UPDATE wt_sp_cost SET sl = sl - @qty, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  3046 + new { qty, spbh = detail.Spbh, ck = returnWarehouseId });
  3047 + }
  3048 + }
  3049 + else if (djlx == "采购退货单")
  3050 + {
  3051 + // 采购退货单删除:恢复成本(反向操作,相当于再入库)
  3052 + var outStoreId = entity.Cjck;
  3053 + if (string.IsNullOrEmpty(outStoreId)) return;
  3054 +
  3055 + var warehouseIds = await _db.Queryable<WtCkEntity>()
  3056 + .Where(c => c.Ssmd == outStoreId || c.Id == outStoreId)
  3057 + .Select(c => c.Id)
  3058 + .ToListAsync();
  3059 + if (warehouseIds == null || warehouseIds.Count == 0)
  3060 + warehouseIds = new List<string> { outStoreId };
  3061 +
  3062 + var mxList = await _db.Queryable<WtXsckdMxEntity>()
  3063 + .Where(d => d.Djbh == id).ToListAsync();
  3064 +
  3065 + foreach (var detail in mxList)
  3066 + {
  3067 + if (string.IsNullOrEmpty(detail.Spbh)) continue;
  3068 + if (!int.TryParse(detail.Sl, out int qty) || qty <= 0) continue;
  3069 +
  3070 + decimal returnPrice = detail.Dj;
  3071 +
  3072 + foreach (var whId in warehouseIds)
  3073 + {
  3074 + var existing = await _db.Ado.SqlQueryAsync<dynamic>(
  3075 + "SELECT cbj, sl FROM wt_sp_cost WHERE spbh = @spbh AND ck = @ck LIMIT 1",
  3076 + new { spbh = detail.Spbh, ck = whId });
  3077 +
  3078 + if (existing != null && existing.Count > 0)
  3079 + {
  3080 + decimal currentCbj = Convert.ToDecimal(existing[0].cbj);
  3081 + int currentSl = Convert.ToInt32(existing[0].sl);
  3082 + int newSl = currentSl + qty;
  3083 + decimal newCbj = newSl > 0
  3084 + ? Math.Round((currentCbj * currentSl + returnPrice * qty) / newSl, 2)
  3085 + : returnPrice;
  3086 +
  3087 + await _db.Ado.ExecuteCommandAsync(
  3088 + "UPDATE wt_sp_cost SET cbj = @cbj, sl = @sl, update_time = NOW() WHERE spbh = @spbh AND ck = @ck",
  3089 + new { cbj = newCbj, sl = newSl, spbh = detail.Spbh, ck = whId });
  3090 + break;
  3091 + }
  3092 + }
  3093 + }
  3094 + }
  3095 + }
  3096 +
  3097 + #endregion
2590 } 3098 }
2591 } 3099 }