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 3 <el-row :gutter="15" class="" >
4 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 10 <el-col :span="12">
12 11 <el-form-item label="单据日期" prop="djrq">
13 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 60 </el-select>
62 61 </template>
63 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 76 <!-- <el-table-column prop="sptm" label="商品条码" v-if="false">
71 77 <template slot-scope="scope">
72 78 <el-input v-model="scope.row.sptm" placeholder="请输入" clearable ></el-input>
... ... @@ -88,11 +94,11 @@
88 94 ></el-input>
89 95 </template>
90 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 102 <el-table-column prop="je" label="金额">
97 103 <template slot-scope="scope">
98 104 <el-input v-model="scope.row.je" placeholder="请输入" clearable @input="handleAmountFieldChange(scope.row)"></el-input>
... ... @@ -234,6 +240,7 @@
234 240 loading: false,
235 241 visible: false,
236 242 isDetail: false,
  243 + isNew: false,
237 244 dataForm: {
238 245 id:'',
239 246 id:undefined,
... ... @@ -259,7 +266,8 @@
259 266 ckckOptions : [],
260 267 spbhOptions : [],
261 268 skzhOptions : [],
262   - productCache: new Map(), // 商品信息缓存
  269 + productCache: new Map(),
  270 + inboundOrdersMap: {},
263 271 }
264 272 },
265 273 computed: {
... ... @@ -326,45 +334,53 @@
326 334 },
327 335 init(id, isDetail) {
328 336 this.dataForm.id = id || 0;
  337 + this.isNew = !id;
329 338 this.visible = true;
330 339 this.isDetail = isDetail || false;
331 340 this.$nextTick(() => {
332 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 386 dataFormSubmit() {
... ... @@ -445,7 +461,7 @@
445 461  
446 462 this.$refs['elForm'].validate((valid) => {
447 463 if (valid) {
448   - if (!this.dataForm.id) {
  464 + if (this.isNew) {
449 465 request({
450 466 url: `/api/Extend/WtXsckd`,
451 467 method: 'post',
... ... @@ -487,7 +503,7 @@
487 503  
488 504 let item = {
489 505 rkck:undefined,
490   - ckck:this.dataForm.cjck, // 自动使用主表的出库仓库
  506 + ckck:this.dataForm.cjck,
491 507 spbh:undefined,
492 508 spmc:undefined,
493 509 sptm:undefined,
... ... @@ -495,10 +511,11 @@
495 511 sl:undefined,
496 512 dj:undefined,
497 513 je:undefined,
  514 + dyddbh: undefined,
498 515 description: undefined,
499 516 selectedSerialNumbers: [],
500 517 spxlhLoaded: false,
501   - productQuery: '', // 为每一行添加独立的查询条件
  518 + productQuery: '',
502 519 }
503 520  
504 521 if (!this.dataForm.wtXsckdMxList) {
... ... @@ -515,42 +532,86 @@
515 532 },
516 533 async handleProductChange(row) {
517 534 const product = this.spbhOptions.find(item => item.F_Id === row.spbh);
518   - console.log('选中商品', row.spbh, product);
519 535 if (product) {
520 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 544 this.getProductInfo(row.spbh).then(productInfo => {
540 545 this.$set(row, 'spxlhLoaded', true);
541 546 this.$forceUpdate();
542   - if (!(productInfo && productInfo.spxlhType)) {
543   - this.$message.warning('无法获取序列号类型信息');
544   - } else {
545   - // 如果商品需要序列号,给出提示
  547 + if (productInfo && productInfo.spxlhType) {
546 548 const spxlhType = productInfo.spxlhType;
547 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 615 handleProductQuery(val, scope) {
555 616 // 为每一行设置独立的查询条件
556 617 this.$set(scope.row, 'productQuery', val);
... ... @@ -569,9 +630,14 @@
569 630  
570 631 // 处理主表出库仓库变化,同步更新所有明细行的出库仓库
571 632 handleMainWarehouseChange(value) {
572   - console.log('主表出库仓库变化:', value);
573   - // 同步更新所有明细行的出库仓库
574 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 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 3 <el-row :gutter="15" class="" >
4 4 <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules">
... ... @@ -42,16 +42,6 @@
42 42 </el-col>
43 43 <el-col :span="24">
44 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 45 <el-table :data="dataForm.wtCzdmxList" size='mini'>
56 46 <el-table-column type="index" width="50" label="序号" align="center" />
57 47 <el-table-column prop="chck" label="出货仓库">
... ... @@ -290,22 +280,16 @@
290 280 newVal.forEach((row, index) => {
291 281 // 确保行数据有效且仓库和商品编号都有值
292 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 305 newVal.forEach((row, index) => {
322 306 // 确保行数据有效且仓库和商品编号都有值
323 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 331 mounted() {
354 332 },
355 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 338 this.getWarehouseOptionsFallback();
375   - });
376   - },
  339 + }
  340 + }).catch(err => {
  341 + console.error('获取出库仓库数据失败:', err);
  342 + this.$message.error('获取出库仓库数据失败,请检查网络连接');
  343 + this.getWarehouseOptionsFallback();
  344 + });
  345 + },
377 346 getdwOptions(){
378 347 getDictionaryDataSelector('681805755032012037').then(res => {
379 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 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 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 459 handleProductQuery(val, scope) {
558 460 // 增强参数安全检查
559 461 if (!scope || !scope.row || typeof scope.row !== 'object') {
... ... @@ -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 570 let method = 'post';
722 571 let url = '/api/Extend/WtCzd';
723 572  
... ... @@ -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 627 url: url,
783 628 method: method,
784 629 data: cleanData
785   - }).then((res) => {
786   - console.log('后端响应:', res);
787   - console.log('响应数据:', res.data);
  630 + }).then((res) => {
788 631  
789 632 // 检查响应格式
790 633 if (res.data && res.data.success) {
... ... @@ -807,17 +650,8 @@
807 650 this.visible = false;
808 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 655 let errorMessage = '保存拆装单失败';
822 656 if (err.response && err.response.data) {
823 657 if (err.response.data.msg) {
... ... @@ -843,14 +677,14 @@
843 677 spmc: '',
844 678 xlh: '',
845 679 dw: '',
846   - zmkc: '',
  680 + zmkc: 0,
847 681 stockLoading: false,
848 682 productQuery: '',
849   - sl: '',
  683 + sl: 1,
850 684 cbdj: '',
851 685 cbje: '',
852 686 hasQueriedStock: false,
853   - selectedSerialNumbers: [], // 初始化序列号数组
  687 + selectedSerialNumbers: [],
854 688 };
855 689 this.dataForm.wtCzdmxList.push(item);
856 690 },
... ... @@ -866,14 +700,14 @@
866 700 spmc: '',
867 701 xlh: '',
868 702 dw: '',
869   - zmkc: '',
  703 + zmkc: 0,
870 704 stockLoading: false,
871 705 productQuery: '',
872   - sl: '',
  706 + sl: 1,
873 707 cbdj: '',
874 708 cbje: '',
875 709 hasQueriedStock: false,
876   - selectedSerialNumbers: [], // 初始化序列号数组
  710 + selectedSerialNumbers: [],
877 711 };
878 712 this.dataForm.wtCzdmx2List.push(item);
879 713 },
... ... @@ -882,261 +716,48 @@
882 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 763 debugFrontendData() {
... ... @@ -1256,74 +877,43 @@
1256 877 console.log('商品选项:', this.spbhOptions);
1257 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 918 </script>
1329 919 <style scoped>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPriceAdjust/Form.vue
1 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 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 386 </script>
108   -
109 387 <style scoped>
110   -</style>
111 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 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 80 </template>
61   -
62 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 230 </script>
115   -
116   -<style scoped>
117   -</style>
118 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 89 [SugarColumn(ColumnName = "mdxx")]
90 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 2 using NCC.Common.Enum;
3 3 using NCC.Common.Extension;
4 4 using NCC.Common.Filter;
... ... @@ -220,6 +220,9 @@ namespace NCC.Extend.WtCzd
220 220 // 处理序列号逻辑
221 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 227 _db.CommitTran();
225 228 // 返回新创建的实体信息
... ... @@ -338,7 +341,7 @@ namespace NCC.Extend.WtCzd
338 341 }
339 342  
340 343 /// <summary>
341   - /// 批量删除拆装单
  344 + /// 批量删除拆装单(含成本回退)
342 345 /// </summary>
343 346 /// <param name="ids">主键数组</param>
344 347 /// <returns></returns>
... ... @@ -350,21 +353,21 @@ namespace NCC.Extend.WtCzd
350 353 {
351 354 try
352 355 {
353   - //开启事务
354 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 367 _db.CommitTran();
364 368 }
365 369 catch (Exception)
366 370 {
367   - //回滚事务
368 371 _db.RollbackTran();
369 372 throw NCCException.Oh(ErrorCode.COM1002);
370 373 }
... ... @@ -434,7 +437,9 @@ namespace NCC.Extend.WtCzd
434 437 }
435 438  
436 439 /// <summary>
437   - /// 删除拆装单
  440 + /// 删除拆装单(含成本回退)
  441 + /// 出库方:恢复 wt_sp_cost.sl
  442 + /// 入库方:反向加权平均回退 cbj 和 sl
438 443 /// </summary>
439 444 /// <returns></returns>
440 445 [HttpDelete("{id}")]
... ... @@ -444,22 +449,19 @@ namespace NCC.Extend.WtCzd
444 449 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
445 450 try
446 451 {
447   - //开启事务
448 452 _db.BeginTran();
  453 +
  454 + // 回退成本
  455 + await RollbackSpCostOnCzdDelete(entity);
449 456  
450   - //删除拆装单记录
451 457 await _db.Deleteable<WtCzdEntity>().Where(d => d.Id == id).ExecuteCommandAsync();
452   -
453   - //清空子表数据
454 458 await _db.Deleteable<WtCzdmxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
455 459 await _db.Deleteable<WtCzdmx2Entity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
456 460  
457   - //关闭事务
458 461 _db.CommitTran();
459 462 }
460 463 catch (Exception)
461 464 {
462   - //回滚事务
463 465 _db.RollbackTran();
464 466 throw NCCException.Oh(ErrorCode.COM1002);
465 467 }
... ... @@ -639,6 +641,259 @@ namespace NCC.Extend.WtCzd
639 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 897 /// <summary>
643 898 /// 获取商品的可用序列号列表(用于出库选择)
644 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 351 //开启事务
352 352 _db.BeginTran();
353 353 int tryCount = 0;
  354 + // 如果前端传了预生成的单号且格式正确,优先使用
  355 + bool usePreGeneratedId = !string.IsNullOrEmpty(input.id)
  356 + && input.id.StartsWith(prefix)
  357 + && input.id.Contains(today);
354 358 while (true)
355 359 {
356 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 371 entity.Djrq = DateTime.Now;
361 372 // ✅ 采购入库单:默认状态为"待审核"
362 373 if (input.djlx == "采购入库单" && string.IsNullOrEmpty(entity.Djzt))
... ... @@ -575,6 +586,9 @@ namespace NCC.Extend.WtXsckd
575 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 593 _db.CommitTran();
580 594  
... ... @@ -714,6 +728,60 @@ namespace NCC.Extend.WtXsckd
714 728 }
715 729  
716 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 786 /// </summary>
719 787 /// <param name="input">请求参数</param>
... ... @@ -770,7 +838,7 @@ namespace NCC.Extend.WtXsckd
770 838 }
771 839  
772 840 /// <summary>
773   - /// 批量删除销售出库单
  841 + /// 批量删除销售出库单(含成本回退)
774 842 /// </summary>
775 843 /// <param name="ids">主键数组</param>
776 844 /// <returns></returns>
... ... @@ -782,19 +850,21 @@ namespace NCC.Extend.WtXsckd
782 850 {
783 851 try
784 852 {
785   - //开启事务
786 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 864 _db.CommitTran();
794 865 }
795 866 catch (Exception)
796 867 {
797   - //回滚事务
798 868 _db.RollbackTran();
799 869 throw NCCException.Oh(ErrorCode.COM1002);
800 870 }
... ... @@ -866,7 +936,10 @@ namespace NCC.Extend.WtXsckd
866 936 }
867 937  
868 938 /// <summary>
869   - /// 删除销售出库单
  939 + /// 删除销售出库单(含成本回退)
  940 + /// 采购入库单(已审核):检查序列号→删除→回退成本
  941 + /// 销售/预售出库单:恢复 wt_sp_cost.sl
  942 + /// 销售退货单:减少 wt_sp_cost.sl
870 943 /// </summary>
871 944 /// <returns></returns>
872 945 [HttpDelete("{id}")]
... ... @@ -876,28 +949,62 @@ namespace NCC.Extend.WtXsckd
876 949 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
877 950 try
878 951 {
879   - //开启事务
880 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 958 await _db.Deleteable<WtXsckdMxEntity>().Where(u => u.Djbh == id).ExecuteCommandAsync();
887 959  
888   - //关闭事务
889 960 _db.CommitTran();
890 961 }
891 962 catch (Exception)
892 963 {
893   - //回滚事务
894 964 _db.RollbackTran();
895 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 1008 [HttpGet("GetProductSummary")]
902 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 1856 entity.Shr1 = userId;
1750 1857 await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1 }).ExecuteCommandAsync();
1751 1858  
1752   - // 采购入库单最终审核通过后生成序列号
  1859 + // 采购入库单最终审核通过后生成序列号 + 更新成本
1753 1860 if (entity.Djlx == "采购入库单")
1754 1861 {
1755 1862 await GenerateSerialNumbersForApproval(entity, id);
  1863 + await UpdateSpCostOnPurchaseApproval(entity, id);
1756 1864 }
1757 1865  
1758 1866 _db.CommitTran();
... ... @@ -1767,10 +1875,11 @@ ORDER BY t.`商品编号`;&quot;;
1767 1875 entity.Shr = userId;
1768 1876 await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr2 }).ExecuteCommandAsync();
1769 1877  
1770   - // 采购入库单最终审核通过后生成序列号
  1878 + // 采购入库单最终审核通过后生成序列号 + 更新成本
1771 1879 if (entity.Djlx == "采购入库单")
1772 1880 {
1773 1881 await GenerateSerialNumbersForApproval(entity, id);
  1882 + await UpdateSpCostOnPurchaseApproval(entity, id);
1774 1883 }
1775 1884  
1776 1885 _db.CommitTran();
... ... @@ -1859,6 +1968,7 @@ ORDER BY t.`商品编号`;&quot;;
1859 1968 /// <summary>
1860 1969 /// 反审单据:将"已审核"或"一级已审"状态回退为"待审核",清空审核人字段
1861 1970 /// 一级/二级审核人均可执行反审操作
  1971 + /// 采购入库单反审时需检查序列号使用情况并回退成本
1862 1972 /// </summary>
1863 1973 [HttpPost("ReverseApproval/{id}")]
1864 1974 public async Task<dynamic> ReverseApproval(string id)
... ... @@ -1887,15 +1997,49 @@ ORDER BY t.`商品编号`;&quot;;
1887 1997 if (!isAuthorized)
1888 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 2044 catch (Exception ex)
1901 2045 {
... ... @@ -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 }
... ...