Commit 81aab8ac9056a097b3050fbb600706d59d1b55dd

Authored by “wangming”
1 parent 2ef28d30

123

Showing 30 changed files with 5149 additions and 1466 deletions
Antis.Erp.Plat/antis-ncc-admin/src/utils/wtRejectApproval.js
... ... @@ -67,3 +67,39 @@ export function postApprovePurchaseInbound(id, remark) {
67 67 data: { remark: remark || '' }
68 68 })
69 69 }
  70 +
  71 +/** 应收款增加减少(wt_yskzjjs):提交审核 */
  72 +export function postYskzjjsSubmitForAudit(id) {
  73 + return request({
  74 + url: `/api/Extend/WtYskzjjs/Actions/SubmitForAudit/${id}`,
  75 + method: 'POST',
  76 + data: {}
  77 + })
  78 +}
  79 +
  80 +/** 应收款增加减少:撤回审核(回草稿) */
  81 +export function postYskzjjsWithdrawAudit(id) {
  82 + return request({
  83 + url: `/api/Extend/WtYskzjjs/Actions/WithdrawAudit/${id}`,
  84 + method: 'POST',
  85 + data: {}
  86 + })
  87 +}
  88 +
  89 +/** 应收款增加减少:审核通过 */
  90 +export function postYskzjjsApprove(id, remark) {
  91 + return request({
  92 + url: `/api/Extend/WtYskzjjs/Actions/Approve/${id}`,
  93 + method: 'POST',
  94 + data: { remark: remark || '' }
  95 + })
  96 +}
  97 +
  98 +/** 应收款增加减少:审核不通过 */
  99 +export function postYskzjjsReject(id, remark) {
  100 + return request({
  101 + url: `/api/Extend/WtYskzjjs/Actions/Reject/${id}`,
  102 + method: 'POST',
  103 + data: { remark: remark || '' }
  104 + })
  105 +}
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/basic/todoCenter/index.vue
... ... @@ -4,34 +4,72 @@
4 4 <div class="NCC-common-head">
5 5 <div>
6 6 <span class="page-title">
7   - <i class="icon-ym icon-ym-generator-todo" style="color:#409EFF;margin-right:8px" />
  7 + <i
  8 + class="icon-ym icon-ym-generator-todo"
  9 + style="color:#409EFF;margin-right:8px"
  10 + />
8 11 待办中心
9 12 </span>
10   - <span class="page-desc">同价调拨单、销售/预售出库、销售/预售退货、采购入库单、委托代销发/退/结算、商品调价单等待办(与首页待办数据源一致)</span>
  13 + <span class="page-desc"
  14 + >同价调拨单、销售/预售出库、销售/预售退货、采购入库单、委托代销发/退/结算、商品调价单等待办(与首页待办数据源一致)</span
  15 + >
11 16 </div>
12 17 <div class="NCC-common-head-right">
13 18 <el-tooltip effect="dark" content="刷新" placement="top">
14   - <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="loadData" />
  19 + <el-link
  20 + icon="icon-ym icon-ym-Refresh NCC-common-head-icon"
  21 + :underline="false"
  22 + @click="loadData"
  23 + />
15 24 </el-tooltip>
16 25 </div>
17 26 </div>
18 27 <div class="NCC-common-layout-main NCC-flex-main">
19   - <el-table v-loading="loading" :data="list" border stripe empty-text="暂无待办">
20   - <el-table-column type="index" width="50" label="#" align="center" :index="tableIndexMethod" />
21   - <el-table-column prop="fullName" label="待办事项" min-width="280" show-overflow-tooltip>
  28 + <el-table
  29 + v-loading="loading"
  30 + :data="list"
  31 + border
  32 + stripe
  33 + empty-text="暂无待办"
  34 + >
  35 + <el-table-column
  36 + type="index"
  37 + width="50"
  38 + label="#"
  39 + align="center"
  40 + :index="tableIndexMethod"
  41 + />
  42 + <el-table-column
  43 + prop="fullName"
  44 + label="待办事项"
  45 + min-width="280"
  46 + show-overflow-tooltip
  47 + >
22 48 <template slot-scope="scope">
23 49 <span class="cell-nowrap">
24   - <i class="el-icon-document" style="color:#409EFF;margin-right:6px" />
  50 + <i
  51 + class="el-icon-document"
  52 + style="color:#409EFF;margin-right:6px"
  53 + />
25 54 {{ cellText(scope.row.fullName) }}
26 55 </span>
27 56 </template>
28 57 </el-table-column>
29   - <el-table-column prop="creatorTime" label="日期" width="130" align="center">
30   - <template slot-scope="scope">{{ formatTime(scope.row.creatorTime) }}</template>
  58 + <el-table-column
  59 + prop="creatorTime"
  60 + label="日期"
  61 + width="130"
  62 + align="center"
  63 + >
  64 + <template slot-scope="scope">{{
  65 + formatTime(scope.row.creatorTime)
  66 + }}</template>
31 67 </el-table-column>
32 68 <el-table-column label="操作" width="200" align="left" fixed="right">
33 69 <template slot-scope="scope">
34   - <el-button type="text" @click="openProcess(scope.row)">处理</el-button>
  70 + <el-button type="text" @click="openProcess(scope.row)"
  71 + >处理</el-button
  72 + >
35 73 </template>
36 74 </el-table-column>
37 75 </el-table>
... ... @@ -53,9 +91,25 @@
53 91 >
54 92 <template slot="footer">
55 93 <el-button @click="closeProcessDialog">关闭</el-button>
56   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
57   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
58   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  94 + <el-button
  95 + v-if="canLevel1Approve"
  96 + type="warning"
  97 + @click="handleLevel1Approve"
  98 + >一级审核</el-button
  99 + >
  100 + <el-button
  101 + v-if="canLevel2Approve"
  102 + type="success"
  103 + @click="handleLevel2Approve"
  104 + >二级审核</el-button
  105 + >
  106 + <el-button
  107 + v-if="canAuditReject"
  108 + type="danger"
  109 + plain
  110 + @click="handleReject"
  111 + >审核不通过</el-button
  112 + >
59 113 </template>
60 114 </WtTjdbdDetailView>
61 115  
... ... @@ -67,9 +121,25 @@
67 121 >
68 122 <template slot="footer">
69 123 <el-button @click="closeProcessDialog">关闭</el-button>
70   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
71   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
72   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  124 + <el-button
  125 + v-if="canLevel1Approve"
  126 + type="warning"
  127 + @click="handleLevel1Approve"
  128 + >一级审核</el-button
  129 + >
  130 + <el-button
  131 + v-if="canLevel2Approve"
  132 + type="success"
  133 + @click="handleLevel2Approve"
  134 + >二级审核</el-button
  135 + >
  136 + <el-button
  137 + v-if="canAuditReject"
  138 + type="danger"
  139 + plain
  140 + @click="handleReject"
  141 + >审核不通过</el-button
  142 + >
73 143 </template>
74 144 </WtXsckdDetailView>
75 145  
... ... @@ -81,9 +151,25 @@
81 151 >
82 152 <template slot="footer">
83 153 <el-button @click="closeProcessDialog">关闭</el-button>
84   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
85   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
86   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  154 + <el-button
  155 + v-if="canLevel1Approve"
  156 + type="warning"
  157 + @click="handleLevel1Approve"
  158 + >一级审核</el-button
  159 + >
  160 + <el-button
  161 + v-if="canLevel2Approve"
  162 + type="success"
  163 + @click="handleLevel2Approve"
  164 + >二级审核</el-button
  165 + >
  166 + <el-button
  167 + v-if="canAuditReject"
  168 + type="danger"
  169 + plain
  170 + @click="handleReject"
  171 + >审核不通过</el-button
  172 + >
87 173 </template>
88 174 </WtXsthdDetailView>
89 175  
... ... @@ -95,9 +181,25 @@
95 181 >
96 182 <template slot="footer">
97 183 <el-button @click="closeProcessDialog">关闭</el-button>
98   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
99   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
100   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  184 + <el-button
  185 + v-if="canLevel1Approve"
  186 + type="warning"
  187 + @click="handleLevel1Approve"
  188 + >一级审核</el-button
  189 + >
  190 + <el-button
  191 + v-if="canLevel2Approve"
  192 + type="success"
  193 + @click="handleLevel2Approve"
  194 + >二级审核</el-button
  195 + >
  196 + <el-button
  197 + v-if="canAuditReject"
  198 + type="danger"
  199 + plain
  200 + @click="handleReject"
  201 + >审核不通过</el-button
  202 + >
101 203 </template>
102 204 </WtYsckdDetailView>
103 205  
... ... @@ -109,9 +211,25 @@
109 211 >
110 212 <template slot="footer">
111 213 <el-button @click="closeProcessDialog">关闭</el-button>
112   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
113   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
114   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  214 + <el-button
  215 + v-if="canLevel1Approve"
  216 + type="warning"
  217 + @click="handleLevel1Approve"
  218 + >一级审核</el-button
  219 + >
  220 + <el-button
  221 + v-if="canLevel2Approve"
  222 + type="success"
  223 + @click="handleLevel2Approve"
  224 + >二级审核</el-button
  225 + >
  226 + <el-button
  227 + v-if="canAuditReject"
  228 + type="danger"
  229 + plain
  230 + @click="handleReject"
  231 + >审核不通过</el-button
  232 + >
115 233 </template>
116 234 </WtYsthdDetailView>
117 235  
... ... @@ -123,9 +241,25 @@
123 241 >
124 242 <template slot="footer">
125 243 <el-button @click="closeProcessDialog">关闭</el-button>
126   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
127   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
128   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  244 + <el-button
  245 + v-if="canLevel1Approve"
  246 + type="warning"
  247 + @click="handleLevel1Approve"
  248 + >一级审核</el-button
  249 + >
  250 + <el-button
  251 + v-if="canLevel2Approve"
  252 + type="success"
  253 + @click="handleLevel2Approve"
  254 + >二级审核</el-button
  255 + >
  256 + <el-button
  257 + v-if="canAuditReject"
  258 + type="danger"
  259 + plain
  260 + @click="handleReject"
  261 + >审核不通过</el-button
  262 + >
129 263 </template>
130 264 </WtCgrkdDetailView>
131 265  
... ... @@ -137,9 +271,25 @@
137 271 >
138 272 <template slot="footer">
139 273 <el-button @click="closeProcessDialog">关闭</el-button>
140   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
141   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
142   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  274 + <el-button
  275 + v-if="canLevel1Approve"
  276 + type="warning"
  277 + @click="handleLevel1Approve"
  278 + >一级审核</el-button
  279 + >
  280 + <el-button
  281 + v-if="canLevel2Approve"
  282 + type="success"
  283 + @click="handleLevel2Approve"
  284 + >二级审核</el-button
  285 + >
  286 + <el-button
  287 + v-if="canAuditReject"
  288 + type="danger"
  289 + plain
  290 + @click="handleReject"
  291 + >审核不通过</el-button
  292 + >
143 293 </template>
144 294 </WtPriceAdjustDetailView>
145 295  
... ... @@ -151,9 +301,25 @@
151 301 >
152 302 <template slot="footer">
153 303 <el-button @click="closeProcessDialog">关闭</el-button>
154   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
155   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
156   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  304 + <el-button
  305 + v-if="canLevel1Approve"
  306 + type="warning"
  307 + @click="handleLevel1Approve"
  308 + >一级审核</el-button
  309 + >
  310 + <el-button
  311 + v-if="canLevel2Approve"
  312 + type="success"
  313 + @click="handleLevel2Approve"
  314 + >二级审核</el-button
  315 + >
  316 + <el-button
  317 + v-if="canAuditReject"
  318 + type="danger"
  319 + plain
  320 + @click="handleReject"
  321 + >审核不通过</el-button
  322 + >
157 323 </template>
158 324 </WtXswtdxfhdDetailView>
159 325  
... ... @@ -165,9 +331,25 @@
165 331 >
166 332 <template slot="footer">
167 333 <el-button @click="closeProcessDialog">关闭</el-button>
168   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
169   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
170   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  334 + <el-button
  335 + v-if="canLevel1Approve"
  336 + type="warning"
  337 + @click="handleLevel1Approve"
  338 + >一级审核</el-button
  339 + >
  340 + <el-button
  341 + v-if="canLevel2Approve"
  342 + type="success"
  343 + @click="handleLevel2Approve"
  344 + >二级审核</el-button
  345 + >
  346 + <el-button
  347 + v-if="canAuditReject"
  348 + type="danger"
  349 + plain
  350 + @click="handleReject"
  351 + >审核不通过</el-button
  352 + >
171 353 </template>
172 354 </WtXswtdxthdDetailView>
173 355  
... ... @@ -179,44 +361,98 @@
179 361 >
180 362 <template slot="footer">
181 363 <el-button @click="closeProcessDialog">关闭</el-button>
182   - <el-button v-if="canLevel1Approve" type="warning" @click="handleLevel1Approve">一级审核</el-button>
183   - <el-button v-if="canLevel2Approve" type="success" @click="handleLevel2Approve">二级审核</el-button>
184   - <el-button v-if="canAuditReject" type="danger" plain @click="handleReject">审核不通过</el-button>
  364 + <el-button
  365 + v-if="canLevel1Approve"
  366 + type="warning"
  367 + @click="handleLevel1Approve"
  368 + >一级审核</el-button
  369 + >
  370 + <el-button
  371 + v-if="canLevel2Approve"
  372 + type="success"
  373 + @click="handleLevel2Approve"
  374 + >二级审核</el-button
  375 + >
  376 + <el-button
  377 + v-if="canAuditReject"
  378 + type="danger"
  379 + plain
  380 + @click="handleReject"
  381 + >审核不通过</el-button
  382 + >
185 383 </template>
186 384 </WtXswtdxjsdDetailView>
  385 +
  386 + <WtYskzjjsQtDetailView
  387 + v-if="processVisible && processBillType === '其他应收单'"
  388 + ref="processDlg"
  389 + @close="onProcessDialogClose"
  390 + @loaded="onProcessLoaded"
  391 + >
  392 + <template slot="footer">
  393 + <el-button @click="closeProcessDialog">关闭</el-button>
  394 + <el-button
  395 + v-if="canLevel1Approve"
  396 + type="warning"
  397 + @click="handleLevel1Approve"
  398 + >一级审核</el-button
  399 + >
  400 + <el-button
  401 + v-if="canLevel2Approve"
  402 + type="success"
  403 + @click="handleLevel2Approve"
  404 + >二级审核</el-button
  405 + >
  406 + <el-button
  407 + v-if="canAuditReject"
  408 + type="danger"
  409 + plain
  410 + @click="handleReject"
  411 + >审核不通过</el-button
  412 + >
  413 + </template>
  414 + </WtYskzjjsQtDetailView>
187 415 </div>
188 416 </template>
189 417  
190 418 <script>
191   -import { getMyFlowTodo } from '@/api/home'
192   -import WtTjdbdDetailView from '@/views/wtTjdbd/detail-view.vue'
193   -import WtXsckdDetailView from '@/views/wtXsckd/detail-view.vue'
194   -import WtXsthdDetailView from '@/views/wtXsthd/detail-view.vue'
195   -import WtYsckdDetailView from '@/views/wtYsckd/detail-view.vue'
196   -import WtYsthdDetailView from '@/views/wtYsthd/detail-view.vue'
197   -import WtCgrkdDetailView from '@/views/wtCgrkd/detail-view.vue'
198   -import WtPriceAdjustDetailView from '@/views/wtPriceAdjust/detail-view.vue'
199   -import WtXswtdxfhdDetailView from '@/views/wtXswtdxfhd/detail-view.vue'
200   -import WtXswtdxthdDetailView from '@/views/wtXswtdxthd/detail-view.vue'
201   -import WtXswtdxjsdDetailView from '@/views/wtXswtdxjsd/detail-view.vue'
202   -import { promptApprovalRemark, postApproveGeneric, postRejectGeneric } from '@/utils/wtRejectApproval'
  419 +import { getMyFlowTodo } from "@/api/home";
  420 +import WtTjdbdDetailView from "@/views/wtTjdbd/detail-view.vue";
  421 +import WtXsckdDetailView from "@/views/wtXsckd/detail-view.vue";
  422 +import WtXsthdDetailView from "@/views/wtXsthd/detail-view.vue";
  423 +import WtYsckdDetailView from "@/views/wtYsckd/detail-view.vue";
  424 +import WtYsthdDetailView from "@/views/wtYsthd/detail-view.vue";
  425 +import WtCgrkdDetailView from "@/views/wtCgrkd/detail-view.vue";
  426 +import WtPriceAdjustDetailView from "@/views/wtPriceAdjust/detail-view.vue";
  427 +import WtXswtdxfhdDetailView from "@/views/wtXswtdxfhd/detail-view.vue";
  428 +import WtXswtdxthdDetailView from "@/views/wtXswtdxthd/detail-view.vue";
  429 +import WtXswtdxjsdDetailView from "@/views/wtXswtdxjsd/detail-view.vue";
  430 +import WtYskzjjsQtDetailView from "@/views/wtYskzjjs_qt/detail-view.vue";
  431 +import {
  432 + promptApprovalRemark,
  433 + postApproveGeneric,
  434 + postRejectGeneric,
  435 + postYskzjjsApprove,
  436 + postYskzjjsReject
  437 +} from "@/utils/wtRejectApproval";
203 438  
204 439 // 较长/易混淆的单据名称靠前,便于 fullName 解析
205 440 const BILL_TYPES = [
206   - '委托代销退货单',
207   - '委托代销发货单',
208   - '委托代销结算单',
209   - '同价调拨单',
210   - '预售出库单',
211   - '预售退货单',
212   - '销售出库单',
213   - '销售退货单',
214   - '采购入库单',
215   - '商品调价单'
216   -]
  441 + "委托代销退货单",
  442 + "委托代销发货单",
  443 + "委托代销结算单",
  444 + "同价调拨单",
  445 + "预售出库单",
  446 + "预售退货单",
  447 + "销售出库单",
  448 + "销售退货单",
  449 + "采购入库单",
  450 + "商品调价单",
  451 + "其他应收单"
  452 +];
217 453  
218 454 export default {
219   - name: 'todoCenter',
  455 + name: "todoCenter",
220 456 components: {
221 457 WtTjdbdDetailView,
222 458 WtXsckdDetailView,
... ... @@ -227,7 +463,8 @@ export default {
227 463 WtPriceAdjustDetailView,
228 464 WtXswtdxfhdDetailView,
229 465 WtXswtdxthdDetailView,
230   - WtXswtdxjsdDetailView
  466 + WtXswtdxjsdDetailView,
  467 + WtYskzjjsQtDetailView
231 468 },
232 469 data() {
233 470 return {
... ... @@ -239,291 +476,335 @@ export default {
239 476 pageSize: 20
240 477 },
241 478 processVisible: false,
242   - processBillId: '',
243   - processBillType: '',
  479 + processBillId: "",
  480 + processBillType: "",
244 481 processDetail: null,
245 482 /** 审核成功后刷新详情,若已为「已审核」则关闭弹窗(含单级审核一次通过) */
246 483 pendingCloseWhenApproved: false,
247 484 /** 审核不通过后刷新详情,若为「审核不通过」则关闭弹窗 */
248 485 pendingCloseAfterReject: false
249   - }
  486 + };
250 487 },
251 488 computed: {
252 489 canLevel1Approve() {
253   - const s = this.getAuditStatus(this.processDetail)
254   - return !s || s === '待审核'
  490 + const s = this.getAuditStatus(this.processDetail);
  491 + return !s || s === "待审核";
255 492 },
256 493 canLevel2Approve() {
257   - const s = this.getAuditStatus(this.processDetail)
258   - return s === '一级已审' || s === '待二级' || s === '一级已审/待二级'
  494 + const s = this.getAuditStatus(this.processDetail);
  495 + return s === "一级已审" || s === "待二级" || s === "一级已审/待二级";
259 496 },
260 497 canAuditReject() {
261   - return this.canLevel1Approve || this.canLevel2Approve
  498 + return this.canLevel1Approve || this.canLevel2Approve;
262 499 }
263 500 },
264 501 created() {
265   - this.loadData()
  502 + this.loadData();
266 503 },
267 504 methods: {
268 505 cellText(v) {
269   - if (v === null || v === undefined || v === '') return '无'
270   - return v
  506 + if (v === null || v === undefined || v === "") return "无";
  507 + return v;
271 508 },
272 509 getAuditStatus(row) {
273   - if (!row) return ''
274   - const z = row.djzt != null && row.djzt !== '' ? row.djzt : row.Djzt
275   - const sh = row.shzt != null && row.shzt !== '' ? row.shzt : row.Shzt
276   - return String(z || sh || '').trim()
  510 + if (!row) return "";
  511 + const z = row.djzt != null && row.djzt !== "" ? row.djzt : row.Djzt;
  512 + const sh = row.shzt != null && row.shzt !== "" ? row.shzt : row.Shzt;
  513 + return String(z || sh || "").trim();
277 514 },
278 515 resolveBillType(row) {
279   - const raw = row && row.billType != null ? String(row.billType).trim() : ''
280   - if (raw && BILL_TYPES.includes(raw)) return raw
281   - const name = row && row.fullName ? String(row.fullName) : ''
  516 + const raw =
  517 + row && row.billType != null ? String(row.billType).trim() : "";
  518 + if (raw && BILL_TYPES.includes(raw)) return raw;
  519 + const name = row && row.fullName ? String(row.fullName) : "";
282 520 for (let i = 0; i < BILL_TYPES.length; i++) {
283   - if (name.includes(BILL_TYPES[i])) return BILL_TYPES[i]
  521 + if (name.includes(BILL_TYPES[i])) return BILL_TYPES[i];
284 522 }
285   - if (name.includes('TJD')) return '商品调价单'
286   - return '同价调拨单'
  523 + if (name.includes("TJD")) return "商品调价单";
  524 + return "同价调拨单";
287 525 },
288 526 formatTime(v) {
289   - if (!v) return '无'
290   - const d = new Date(v)
291   - if (isNaN(d.getTime())) return '无'
292   - const y = d.getFullYear()
293   - const m = String(d.getMonth() + 1).padStart(2, '0')
294   - const day = String(d.getDate()).padStart(2, '0')
295   - return `${y}-${m}-${day}`
  527 + if (!v) return "无";
  528 + const d = new Date(v);
  529 + if (isNaN(d.getTime())) return "无";
  530 + const y = d.getFullYear();
  531 + const m = String(d.getMonth() + 1).padStart(2, "0");
  532 + const day = String(d.getDate()).padStart(2, "0");
  533 + return `${y}-${m}-${day}`;
296 534 },
297 535 tableIndexMethod(index) {
298   - return (this.listQuery.currentPage - 1) * this.listQuery.pageSize + index + 1
  536 + return (
  537 + (this.listQuery.currentPage - 1) * this.listQuery.pageSize + index + 1
  538 + );
299 539 },
300 540 loadData() {
301   - this.loading = true
  541 + this.loading = true;
302 542 return getMyFlowTodo({
303 543 currentPage: this.listQuery.currentPage,
304 544 pageSize: this.listQuery.pageSize
305 545 })
306 546 .then(res => {
307   - const raw = (res.data && res.data.list) || []
308   - this.list = Array.isArray(raw) ? raw : []
309   - const pg = res.data && res.data.pagination
310   - this.total = (pg && typeof pg.total === 'number' ? pg.total : this.list.length) || 0
311   - if (this.list.length === 0 && this.total > 0 && this.listQuery.currentPage > 1) {
312   - const lastPage = Math.max(1, Math.ceil(this.total / this.listQuery.pageSize))
  547 + const raw = (res.data && res.data.list) || [];
  548 + this.list = Array.isArray(raw) ? raw : [];
  549 + const pg = res.data && res.data.pagination;
  550 + this.total =
  551 + (pg && typeof pg.total === "number"
  552 + ? pg.total
  553 + : this.list.length) || 0;
  554 + if (
  555 + this.list.length === 0 &&
  556 + this.total > 0 &&
  557 + this.listQuery.currentPage > 1
  558 + ) {
  559 + const lastPage = Math.max(
  560 + 1,
  561 + Math.ceil(this.total / this.listQuery.pageSize)
  562 + );
313 563 if (this.listQuery.currentPage > lastPage) {
314   - this.listQuery.currentPage = lastPage
315   - return this.loadData()
  564 + this.listQuery.currentPage = lastPage;
  565 + return this.loadData();
316 566 }
317 567 }
318 568 })
319 569 .catch(() => {
320   - this.$message.error('加载待办失败')
321   - this.list = []
322   - this.total = 0
  570 + this.$message.error("加载待办失败");
  571 + this.list = [];
  572 + this.total = 0;
323 573 })
324 574 .finally(() => {
325   - this.loading = false
326   - })
  575 + this.loading = false;
  576 + });
327 577 },
328 578 openProcess(row) {
329   - const id = row && row.id
  579 + const id = row && row.id;
330 580 if (!id) {
331   - this.$message.warning('无单据编号')
332   - return
  581 + this.$message.warning("无单据编号");
  582 + return;
333 583 }
334   - const billType = this.resolveBillType(row)
335   - this.processBillId = String(id)
336   - this.processBillType = billType
337   - this.processDetail = null
338   - this.pendingCloseWhenApproved = false
339   - this.pendingCloseAfterReject = false
340   - this.processVisible = true
  584 + const billType = this.resolveBillType(row);
  585 + this.processBillId = String(id);
  586 + this.processBillType = billType;
  587 + this.processDetail = null;
  588 + this.pendingCloseWhenApproved = false;
  589 + this.pendingCloseAfterReject = false;
  590 + this.processVisible = true;
341 591 this.$nextTick(() => {
342   - if (this.$refs.processDlg) this.$refs.processDlg.init(this.processBillId)
343   - })
  592 + if (this.$refs.processDlg)
  593 + this.$refs.processDlg.init(this.processBillId);
  594 + });
344 595 },
345 596 onProcessDialogClose() {
346   - this.processVisible = false
347   - this.processBillId = ''
348   - this.processBillType = ''
349   - this.processDetail = null
350   - this.pendingCloseWhenApproved = false
351   - this.pendingCloseAfterReject = false
  597 + this.processVisible = false;
  598 + this.processBillId = "";
  599 + this.processBillType = "";
  600 + this.processDetail = null;
  601 + this.pendingCloseWhenApproved = false;
  602 + this.pendingCloseAfterReject = false;
352 603 },
353 604 closeProcessDialog() {
354   - if (this.$refs.processDlg) this.$refs.processDlg.close()
  605 + if (this.$refs.processDlg) this.$refs.processDlg.close();
355 606 },
356 607 onProcessLoaded(detail) {
357   - this.processDetail = detail || null
  608 + this.processDetail = detail || null;
358 609 if (this.pendingCloseWhenApproved) {
359   - this.pendingCloseWhenApproved = false
360   - if (this.getAuditStatus(detail) === '已审核') {
  610 + this.pendingCloseWhenApproved = false;
  611 + if (this.getAuditStatus(detail) === "已审核") {
361 612 this.$nextTick(() => {
362   - if (this.$refs.processDlg) this.$refs.processDlg.close()
363   - })
  613 + if (this.$refs.processDlg) this.$refs.processDlg.close();
  614 + });
364 615 }
365 616 }
366 617 if (this.pendingCloseAfterReject) {
367   - this.pendingCloseAfterReject = false
368   - if (this.getAuditStatus(detail) === '审核不通过') {
  618 + this.pendingCloseAfterReject = false;
  619 + if (this.getAuditStatus(detail) === "审核不通过") {
369 620 this.$nextTick(() => {
370   - if (this.$refs.processDlg) this.$refs.processDlg.close()
371   - })
  621 + if (this.$refs.processDlg) this.$refs.processDlg.close();
  622 + });
372 623 }
373 624 }
374 625 },
375 626 level1Confirm() {
376   - const t = this.processBillType
377   - if (t === '同价调拨单') {
378   - return { msg: '确认执行一级审核(调出仓确认发货)?', title: '一级审核确认' }
  627 + const t = this.processBillType;
  628 + if (t === "其他应收单") {
  629 + return { msg: "确认执行一级审核该其他应收单?", title: "审核确认" };
379 630 }
380   - if (t === '销售出库单') {
381   - return { msg: '确认审核该销售出库单?', title: '审核确认' }
  631 + if (t === "同价调拨单") {
  632 + return {
  633 + msg: "确认执行一级审核(调出仓确认发货)?",
  634 + title: "一级审核确认"
  635 + };
  636 + }
  637 + if (t === "销售出库单") {
  638 + return { msg: "确认审核该销售出库单?", title: "审核确认" };
382 639 }
383   - if (t === '销售退货单') {
384   - return { msg: '确认审核该销售退货单?', title: '审核确认' }
  640 + if (t === "销售退货单") {
  641 + return { msg: "确认审核该销售退货单?", title: "审核确认" };
385 642 }
386   - if (t === '预售出库单') {
387   - return { msg: '确认审核该预售出库单?', title: '审核确认' }
  643 + if (t === "预售出库单") {
  644 + return { msg: "确认审核该预售出库单?", title: "审核确认" };
388 645 }
389   - if (t === '预售退货单') {
390   - return { msg: '确认审核该预售退货单?', title: '审核确认' }
  646 + if (t === "预售退货单") {
  647 + return { msg: "确认审核该预售退货单?", title: "审核确认" };
391 648 }
392   - if (t === '采购入库单') {
  649 + if (t === "采购入库单") {
393 650 return {
394   - msg: '确认审核该采购入库单?最终审核通过后将生成序列号并更新库存。',
395   - title: '审核确认'
396   - }
  651 + msg: "确认审核该采购入库单?最终审核通过后将生成序列号并更新库存。",
  652 + title: "审核确认"
  653 + };
397 654 }
398   - if (t === '商品调价单') {
  655 + if (t === "商品调价单") {
399 656 return {
400   - msg: '确认审核该商品调价单?通过后调后售价将写入商品档案零售价。',
401   - title: '审核确认'
402   - }
  657 + msg: "确认审核该商品调价单?通过后调后售价将写入商品档案零售价。",
  658 + title: "审核确认"
  659 + };
403 660 }
404   - if (t === '委托代销发货单') {
405   - return { msg: '确认审核该委托代销发货单?', title: '审核确认' }
  661 + if (t === "委托代销发货单") {
  662 + return { msg: "确认审核该委托代销发货单?", title: "审核确认" };
406 663 }
407   - if (t === '委托代销退货单') {
408   - return { msg: '确认审核该委托代销退货单?', title: '审核确认' }
  664 + if (t === "委托代销退货单") {
  665 + return { msg: "确认审核该委托代销退货单?", title: "审核确认" };
409 666 }
410   - if (t === '委托代销结算单') {
411   - return { msg: '确认审核该委托代销结算单?', title: '审核确认' }
  667 + if (t === "委托代销结算单") {
  668 + return { msg: "确认审核该委托代销结算单?", title: "审核确认" };
412 669 }
413   - return { msg: '确认执行一级审核?', title: '审核确认' }
  670 + return { msg: "确认执行一级审核?", title: "审核确认" };
414 671 },
415 672 level2Confirm() {
416   - const t = this.processBillType
417   - if (t === '同价调拨单') {
418   - return { msg: '确认执行二级审核(确认收货并入库出库)?', title: '二级审核确认' }
  673 + const t = this.processBillType;
  674 + if (t === "同价调拨单") {
  675 + return {
  676 + msg: "确认执行二级审核(确认收货并入库出库)?",
  677 + title: "二级审核确认"
  678 + };
419 679 }
420   - if (t === '销售出库单') {
421   - return { msg: '确认审核该销售出库单?', title: '审核确认' }
  680 + if (t === "销售出库单") {
  681 + return { msg: "确认审核该销售出库单?", title: "审核确认" };
422 682 }
423   - if (t === '销售退货单') {
424   - return { msg: '确认审核该销售退货单?', title: '审核确认' }
  683 + if (t === "销售退货单") {
  684 + return { msg: "确认审核该销售退货单?", title: "审核确认" };
425 685 }
426   - if (t === '预售出库单') {
427   - return { msg: '确认审核该预售出库单?', title: '审核确认' }
  686 + if (t === "预售出库单") {
  687 + return { msg: "确认审核该预售出库单?", title: "审核确认" };
428 688 }
429   - if (t === '预售退货单') {
430   - return { msg: '确认审核该预售退货单?', title: '审核确认' }
  689 + if (t === "预售退货单") {
  690 + return { msg: "确认审核该预售退货单?", title: "审核确认" };
431 691 }
432   - if (t === '采购入库单') {
  692 + if (t === "采购入库单") {
433 693 return {
434   - msg: '确认审核该采购入库单?最终审核通过后将生成序列号并更新库存。',
435   - title: '审核确认'
436   - }
  694 + msg: "确认审核该采购入库单?最终审核通过后将生成序列号并更新库存。",
  695 + title: "审核确认"
  696 + };
437 697 }
438   - if (t === '商品调价单') {
  698 + if (t === "商品调价单") {
439 699 return {
440   - msg: '确认二级审核该商品调价单?通过后调后售价将写入商品档案零售价。',
441   - title: '二级审核确认'
442   - }
  700 + msg: "确认二级审核该商品调价单?通过后调后售价将写入商品档案零售价。",
  701 + title: "二级审核确认"
  702 + };
443 703 }
444   - if (t === '委托代销发货单') {
445   - return { msg: '确认审核该委托代销发货单?', title: '审核确认' }
  704 + if (t === "委托代销发货单") {
  705 + return { msg: "确认审核该委托代销发货单?", title: "审核确认" };
446 706 }
447   - if (t === '委托代销退货单') {
448   - return { msg: '确认审核该委托代销退货单?', title: '审核确认' }
  707 + if (t === "委托代销退货单") {
  708 + return { msg: "确认审核该委托代销退货单?", title: "审核确认" };
449 709 }
450   - if (t === '委托代销结算单') {
451   - return { msg: '确认审核该委托代销结算单?', title: '审核确认' }
  710 + if (t === "委托代销结算单") {
  711 + return { msg: "确认审核该委托代销结算单?", title: "审核确认" };
452 712 }
453   - return { msg: '确认执行二级审核?', title: '审核确认' }
  713 + return { msg: "确认执行二级审核?", title: "审核确认" };
454 714 },
455 715 handleLevel1Approve() {
456   - const id = this.processBillId
457   - if (!id) return
458   - const { msg, title } = this.level1Confirm()
459   - this.$confirm(msg, title, { type: 'warning' })
460   - .then(() => promptApprovalRemark(this, '审批备注'))
461   - .then(remark => postApproveGeneric(id, remark))
  716 + const id = this.processBillId;
  717 + if (!id) return;
  718 + const { msg, title } = this.level1Confirm();
  719 + this.$confirm(msg, title, { type: "warning" })
  720 + .then(() => promptApprovalRemark(this, "审批备注"))
  721 + .then(remark =>
  722 + this.processBillType === "其他应收单"
  723 + ? postYskzjjsApprove(id, remark)
  724 + : postApproveGeneric(id, remark)
  725 + )
462 726 .then(res => {
463 727 if (res.data && res.data.success) {
464   - this.$message({ type: 'success', message: res.data.message || '操作成功' })
465   - this.pendingCloseWhenApproved = true
466   - this.loadData()
  728 + this.$message({
  729 + type: "success",
  730 + message: res.data.message || "操作成功"
  731 + });
  732 + this.pendingCloseWhenApproved = true;
  733 + this.loadData();
467 734 this.$nextTick(() => {
468   - if (this.$refs.processDlg) this.$refs.processDlg.init(id)
469   - })
  735 + if (this.$refs.processDlg) this.$refs.processDlg.init(id);
  736 + });
470 737 } else {
471 738 this.$message({
472   - type: 'error',
473   - message: (res.data && res.data.message) || '审核失败'
474   - })
  739 + type: "error",
  740 + message: (res.data && res.data.message) || "审核失败"
  741 + });
475 742 }
476 743 })
477   - .catch(() => {})
  744 + .catch(() => {});
478 745 },
479 746 handleLevel2Approve() {
480   - const id = this.processBillId
481   - if (!id) return
482   - const { msg, title } = this.level2Confirm()
483   - this.$confirm(msg, title, { type: 'warning' })
484   - .then(() => promptApprovalRemark(this, '审批备注'))
485   - .then(remark => postApproveGeneric(id, remark))
  747 + const id = this.processBillId;
  748 + if (!id) return;
  749 + const { msg, title } = this.level2Confirm();
  750 + this.$confirm(msg, title, { type: "warning" })
  751 + .then(() => promptApprovalRemark(this, "审批备注"))
  752 + .then(remark =>
  753 + this.processBillType === "其他应收单"
  754 + ? postYskzjjsApprove(id, remark)
  755 + : postApproveGeneric(id, remark)
  756 + )
486 757 .then(res => {
487 758 if (res.data && res.data.success) {
488   - this.$message({ type: 'success', message: res.data.message || '操作成功' })
489   - this.pendingCloseWhenApproved = true
490   - this.loadData()
  759 + this.$message({
  760 + type: "success",
  761 + message: res.data.message || "操作成功"
  762 + });
  763 + this.pendingCloseWhenApproved = true;
  764 + this.loadData();
491 765 this.$nextTick(() => {
492   - if (this.$refs.processDlg) this.$refs.processDlg.init(id)
493   - })
  766 + if (this.$refs.processDlg) this.$refs.processDlg.init(id);
  767 + });
494 768 } else {
495 769 this.$message({
496   - type: 'error',
497   - message: (res.data && res.data.message) || '审核失败'
498   - })
  770 + type: "error",
  771 + message: (res.data && res.data.message) || "审核失败"
  772 + });
499 773 }
500 774 })
501   - .catch(() => {})
  775 + .catch(() => {});
502 776 },
503 777 handleReject() {
504   - const id = this.processBillId
505   - if (!id) return
506   - promptApprovalRemark(this, '审核不通过')
507   - .then(reason => postRejectGeneric(id, reason))
  778 + const id = this.processBillId;
  779 + if (!id) return;
  780 + promptApprovalRemark(this, "审核不通过")
  781 + .then(reason =>
  782 + this.processBillType === "其他应收单"
  783 + ? postYskzjjsReject(id, reason)
  784 + : postRejectGeneric(id, reason)
  785 + )
508 786 .then(res => {
509 787 if (res.data && res.data.success) {
510   - this.$message({ type: 'success', message: res.data.message || '已标记审核不通过' })
511   - this.pendingCloseAfterReject = true
512   - this.loadData()
  788 + this.$message({
  789 + type: "success",
  790 + message: res.data.message || "已标记审核不通过"
  791 + });
  792 + this.pendingCloseAfterReject = true;
  793 + this.loadData();
513 794 this.$nextTick(() => {
514   - if (this.$refs.processDlg) this.$refs.processDlg.init(id)
515   - })
  795 + if (this.$refs.processDlg) this.$refs.processDlg.init(id);
  796 + });
516 797 } else {
517 798 this.$message({
518   - type: 'error',
519   - message: (res.data && res.data.message) || '操作失败'
520   - })
  799 + type: "error",
  800 + message: (res.data && res.data.message) || "操作失败"
  801 + });
521 802 }
522 803 })
523   - .catch(() => {})
  804 + .catch(() => {});
524 805 }
525 806 }
526   -}
  807 +};
527 808 </script>
528 809  
529 810 <style scoped lang="scss">
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/permission/user/Form.vue
... ... @@ -21,6 +21,18 @@
21 21 <el-input v-model="dataForm.account" placeholder="账户名称" />
22 22 </el-form-item>
23 23 </el-col>
  24 + <el-col v-if="!dataForm.id" :sm="12" :xs="24">
  25 + <el-form-item label="密码" prop="password">
  26 + <el-input v-model="dataForm.password" type="password" autocomplete="new-password"
  27 + placeholder="6-18 位字符" show-password />
  28 + </el-form-item>
  29 + </el-col>
  30 + <el-col v-if="!dataForm.id" :sm="12" :xs="24">
  31 + <el-form-item label="确认密码" prop="confirmPassword">
  32 + <el-input v-model="dataForm.confirmPassword" type="password" autocomplete="new-password"
  33 + placeholder="再次输入密码" show-password />
  34 + </el-form-item>
  35 + </el-col>
24 36 <el-col :sm="12" :xs="24">
25 37 <el-form-item label="姓名" prop="realName">
26 38 <el-input v-model="dataForm.realName" placeholder="真实姓名" />
... ... @@ -215,6 +227,8 @@ export default {
215 227 sortCode: 0,
216 228 enabledMark: 1,
217 229 account: '',
  230 + password: '',
  231 + confirmPassword: '',
218 232 realName: '',
219 233 organizeId: '',
220 234 managerId: '',
... ... @@ -269,6 +283,50 @@ export default {
269 283 ],
270 284 organizeId: [
271 285 { required: true, message: '请选择组织', trigger: 'change' }
  286 + ],
  287 + password: [
  288 + {
  289 + validator: (rule, value, callback) => {
  290 + if (this.dataForm.id) {
  291 + callback()
  292 + return
  293 + }
  294 + if (!value) {
  295 + callback(new Error('请输入密码'))
  296 + return
  297 + }
  298 + const s = value.toString()
  299 + if (s.length < 6 || s.length > 18) {
  300 + callback(new Error('密码长度为6 - 18个字符'))
  301 + return
  302 + }
  303 + if (this.dataForm.confirmPassword !== '') {
  304 + this.$refs.dataForm.validateField('confirmPassword')
  305 + }
  306 + callback()
  307 + },
  308 + trigger: 'blur'
  309 + }
  310 + ],
  311 + confirmPassword: [
  312 + {
  313 + validator: (rule, value, callback) => {
  314 + if (this.dataForm.id) {
  315 + callback()
  316 + return
  317 + }
  318 + if (!value) {
  319 + callback(new Error('请再次输入密码'))
  320 + return
  321 + }
  322 + if (value !== this.dataForm.password) {
  323 + callback(new Error('两次输入密码不一致'))
  324 + return
  325 + }
  326 + callback()
  327 + },
  328 + trigger: 'blur'
  329 + }
272 330 ]
273 331 }
274 332 }
... ... @@ -328,6 +386,8 @@ export default {
328 386 this.formLoading = true
329 387 getUserInfo(this.dataForm.id).then(res => {
330 388 this.dataForm = res.data
  389 + this.$set(this.dataForm, 'password', '')
  390 + this.$set(this.dataForm, 'confirmPassword', '')
331 391 if (this.dataForm.roleId) this.roleId = this.dataForm.roleId.split(',')
332 392 this.formLoading = false
333 393 }).catch(() => this.formLoading = false)
... ... @@ -354,7 +414,12 @@ export default {
354 414 if (valid) {
355 415 this.btnLoading = true
356 416 const formMethod = this.dataForm.id ? updateUser : createUser
357   - formMethod(this.dataForm).then(res => {
  417 + const payload = { ...this.dataForm }
  418 + delete payload.confirmPassword
  419 + if (payload.id) {
  420 + delete payload.password
  421 + }
  422 + formMethod(payload).then(res => {
358 423 this.$message({
359 424 message: res.msg,
360 425 type: 'success',
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPurchaseSummary/index.vue
... ... @@ -67,20 +67,33 @@
67 67 reserve-keyword
68 68 popper-class="wt-purchase-sum-product-dropdown"
69 69 :remote-method="handleProductSearch"
70   - :loading="productLoading"
  70 + :loading="productRemoteLoading"
71 71 placeholder="输入商品编码或名称搜索"
72 72 style="width: 100%"
73 73 >
74 74 <el-option
75 75 v-for="item in productOptions"
76   - :key="item.F_Id || item.id"
  76 + :key="productOptionValue(item) + '-' + (item.spbm || '')"
77 77 :label="formatProductLabel(item)"
78   - :value="item.F_Id || item.id"
  78 + :value="productOptionValue(item)"
79 79 >
80   - <div class="product-opt-row cell-nowrap" :title="formatProductLabel(item)">
81   - <span v-if="productCodeRaw(item)" class="product-opt-code">{{ productCodeRaw(item) }}</span>
82   - <span v-if="productCodeRaw(item) && productNameRaw(item)" class="product-opt-sep">|</span>
83   - <span class="product-opt-name">{{ productNameRaw(item) || '无' }}</span>
  80 + <div
  81 + class="product-opt-row cell-nowrap"
  82 + :title="formatProductLabel(item)"
  83 + >
  84 + <span
  85 + v-if="productCodeRaw(item)"
  86 + class="product-opt-code"
  87 + >{{ productCodeRaw(item) }}</span
  88 + >
  89 + <span
  90 + v-if="productCodeRaw(item) && productNameRaw(item)"
  91 + class="product-opt-sep"
  92 + >|</span
  93 + >
  94 + <span class="product-opt-name">{{
  95 + productNameRaw(item) || "无"
  96 + }}</span>
84 97 </div>
85 98 </el-option>
86 99 </el-select>
... ... @@ -127,8 +140,15 @@
127 140 </el-col>
128 141 <el-col :span="6">
129 142 <el-form-item>
130   - <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
131   - <el-button icon="el-icon-refresh-right" @click="handleReset">重置</el-button>
  143 + <el-button
  144 + type="primary"
  145 + icon="el-icon-search"
  146 + @click="handleSearch"
  147 + >查询</el-button
  148 + >
  149 + <el-button icon="el-icon-refresh-right" @click="handleReset"
  150 + >重置</el-button
  151 + >
132 152 </el-form-item>
133 153 </el-col>
134 154 </el-form>
... ... @@ -138,11 +158,27 @@
138 158 <div class="NCC-common-head purchase-sum-head">
139 159 <div>
140 160 <span class="purchase-sum-head__title">
141   - <i class="el-icon-s-data purchase-sum-head__icon" aria-hidden="true" />
142   - 商品采购汇总(分类 → 品牌 → 商品明细分类 → 采购明细)
  161 + <i
  162 + class="el-icon-s-data purchase-sum-head__icon"
  163 + aria-hidden="true"
  164 + />
  165 + 商品采购汇总(分类 → 品牌 → 商品 → 采购明细)
143 166 </span>
144 167 </div>
145   - <div class="NCC-common-head-right">
  168 + <div class="NCC-common-head-right purchase-sum-head__tools">
  169 + <span class="linear-mode-label">
  170 + <i
  171 + class="el-icon-s-operation row-ico--primary"
  172 + aria-hidden="true"
  173 + />
  174 + 线性列表
  175 + </span>
  176 + <el-switch
  177 + v-model="linearInlineMode"
  178 + active-color="#409EFF"
  179 + inactive-color="#dcdfe6"
  180 + @change="onLinearInlineModeChange"
  181 + />
146 182 <el-tooltip effect="dark" content="刷新" placement="top">
147 183 <el-link
148 184 icon="icon-ym icon-ym-Refresh NCC-common-head-icon"
... ... @@ -155,145 +191,581 @@
155 191 </div>
156 192  
157 193 <el-table
  194 + ref="categoryTable"
158 195 v-loading="categoryLoading"
159 196 :data="categoryList"
160 197 border
161 198 class="purchase-tree-table"
162   - :row-key="r => String(r['分类Id'])"
  199 + :row-key="categoryRowKey"
163 200 @expand-change="onCategoryExpand"
  201 + @sort-change="onCategorySort"
164 202 >
165 203 <el-table-column type="expand" width="48">
166 204 <template slot-scope="catScope">
167   - <div v-loading="brandLoading[categoryRowKey(catScope.row)]" class="nested-wrap">
168   - <el-table
169   - :data="brandMap[categoryRowKey(catScope.row)] || []"
170   - border
171   - size="small"
172   - class="nested-table"
173   - :row-key="r => brandRowKey(catScope.row, r)"
174   - @expand-change="(r, er) => onBrandExpand(catScope.row, r, er)"
175   - >
176   - <el-table-column type="expand" width="44">
177   - <template slot-scope="brandScope">
178   - <div v-loading="productLoading[brandRowKey(catScope.row, brandScope.row)]" class="nested-wrap">
179   - <el-table
180   - :data="productMap[brandRowKey(catScope.row, brandScope.row)] || []"
181   - border
182   - size="small"
183   - class="nested-table nested-table--deep"
184   - :row-key="r => productRowKey(catScope.row, brandScope.row, r)"
185   - @expand-change="(r, er) => onProductExpand(catScope.row, brandScope.row, r, er)"
  205 + <div
  206 + v-if="linearInlineMode"
  207 + v-loading="linearInlineLoading[categoryRowKey(catScope.row)]"
  208 + class="nested-wrap nested-wrap--linear"
  209 + >
  210 + <div class="nested-table-host">
  211 + <el-table
  212 + :data="
  213 + (linearInlineMap[categoryRowKey(catScope.row)] || {})
  214 + .list || []
  215 + "
  216 + border
  217 + size="small"
  218 + class="nested-table nested-table--scroll"
  219 + :row-key="
  220 + (r, idx) =>
  221 + lineDetailRowKey(
  222 + 'lin',
  223 + catScope.row,
  224 + null,
  225 + null,
  226 + r,
  227 + idx
  228 + )
  229 + "
  230 + >
  231 + <el-table-column
  232 + type="index"
  233 + label="行号"
  234 + width="52"
  235 + align="center"
  236 + />
  237 + <el-table-column
  238 + prop="单据日期"
  239 + label="单据日期"
  240 + width="108"
  241 + show-overflow-tooltip
  242 + />
  243 + <el-table-column
  244 + prop="单据编号"
  245 + label="单据编号"
  246 + min-width="140"
  247 + show-overflow-tooltip
  248 + />
  249 + <el-table-column
  250 + prop="单据类型"
  251 + label="单据类型"
  252 + width="108"
  253 + show-overflow-tooltip
  254 + />
  255 + <el-table-column
  256 + prop="往来单位"
  257 + label="往来单位"
  258 + min-width="120"
  259 + show-overflow-tooltip
  260 + />
  261 + <el-table-column
  262 + prop="经手人"
  263 + label="经手人"
  264 + width="88"
  265 + show-overflow-tooltip
  266 + />
  267 + <el-table-column
  268 + prop="仓库名称"
  269 + label="仓库名称"
  270 + min-width="100"
  271 + show-overflow-tooltip
  272 + />
  273 + <el-table-column
  274 + prop="商品名称"
  275 + label="商品名称"
  276 + min-width="140"
  277 + show-overflow-tooltip
  278 + />
  279 + <el-table-column
  280 + prop="数量"
  281 + label="数量"
  282 + width="88"
  283 + align="right"
  284 + >
  285 + <template slot-scope="s">{{
  286 + formatQty(s.row["数量"])
  287 + }}</template>
  288 + </el-table-column>
  289 + <el-table-column
  290 + prop="入库单价"
  291 + label="入库单价"
  292 + width="100"
  293 + align="right"
  294 + >
  295 + <template slot-scope="s">{{
  296 + formatMoney(s.row["入库单价"])
  297 + }}</template>
  298 + </el-table-column>
  299 + <el-table-column
  300 + prop="采购金额"
  301 + label="采购金额"
  302 + width="108"
  303 + align="right"
  304 + >
  305 + <template slot-scope="s">{{
  306 + formatMoney(s.row["采购金额"])
  307 + }}</template>
  308 + </el-table-column>
  309 + </el-table>
  310 + </div>
  311 + </div>
  312 + <div
  313 + v-else
  314 + v-loading="brandLoading[categoryRowKey(catScope.row)]"
  315 + class="nested-wrap"
  316 + >
  317 + <div class="nested-table-host">
  318 + <el-table
  319 + :data="brandMap[categoryRowKey(catScope.row)] || []"
  320 + border
  321 + size="small"
  322 + class="nested-table nested-table--scroll"
  323 + :row-key="r => brandRowKey(catScope.row, r)"
  324 + @expand-change="
  325 + (r, er) => onBrandExpand(catScope.row, r, er)
  326 + "
  327 + @sort-change="ev => onBrandSort(catScope.row, ev)"
  328 + >
  329 + <el-table-column type="expand" width="44">
  330 + <template slot-scope="brandScope">
  331 + <div
  332 + v-loading="
  333 + aggProductLoading[
  334 + brandRowKey(catScope.row, brandScope.row)
  335 + ]
  336 + "
  337 + class="nested-wrap"
186 338 >
187   - <el-table-column type="expand" width="42">
188   - <template slot-scope="prodScope">
189   - <div v-loading="lineLoading[lineKey(catScope.row, brandScope.row, prodScope.row)]" class="nested-wrap detail-wrap">
190   - <el-table
191   - :data="(lineMap[lineKey(catScope.row, brandScope.row, prodScope.row)] || {}).list || []"
192   - border
193   - size="mini"
194   - class="nested-table"
195   - >
196   - <el-table-column type="index" label="行号" width="52" align="center" />
197   - <el-table-column prop="单据日期" label="单据日期" width="108" show-overflow-tooltip sortable :sort-method="sortTextCol('单据日期')" />
198   - <el-table-column prop="单据编号" label="单据编号" min-width="140" show-overflow-tooltip sortable :sort-method="sortTextCol('单据编号')" />
199   - <el-table-column prop="单据类型" label="单据类型" width="108" show-overflow-tooltip sortable :sort-method="sortTextCol('单据类型')" />
200   - <el-table-column prop="往来单位" label="往来单位" min-width="120" show-overflow-tooltip sortable :sort-method="sortTextCol('往来单位')" />
201   - <el-table-column prop="经手人" label="经手人" width="88" show-overflow-tooltip sortable :sort-method="sortTextCol('经手人')" />
202   - <el-table-column prop="仓库名称" label="仓库名称" min-width="100" show-overflow-tooltip sortable :sort-method="sortTextCol('仓库名称')" />
203   - <el-table-column prop="商品名称" label="商品名称" min-width="140" show-overflow-tooltip sortable :sort-method="sortTextCol('商品名称')" />
204   - <el-table-column prop="数量" label="数量" width="88" align="right" sortable :sort-method="sortNumCol('数量')">
205   - <template slot-scope="s">{{ formatQty(s.row['数量']) }}</template>
206   - </el-table-column>
207   - <el-table-column prop="入库单价" label="入库单价" width="100" align="right" sortable :sort-method="sortNumCol('入库单价')">
208   - <template slot-scope="s">{{ formatMoney(s.row['入库单价']) }}</template>
209   - </el-table-column>
210   - <el-table-column prop="采购金额" label="采购金额" width="108" align="right" sortable :sort-method="sortNumCol('采购金额')">
211   - <template slot-scope="s">{{ formatMoney(s.row['采购金额']) }}</template>
212   - </el-table-column>
213   - </el-table>
214   - <div v-if="linePager[lineKey(catScope.row, brandScope.row, prodScope.row)]" class="mini-pager">
215   - <el-pagination
216   - small
217   - layout="prev, pager, next, total"
218   - :total="linePager[lineKey(catScope.row, brandScope.row, prodScope.row)].total"
219   - :page-size="linePager[lineKey(catScope.row, brandScope.row, prodScope.row)].pageSize"
220   - :current-page="linePager[lineKey(catScope.row, brandScope.row, prodScope.row)].currentPage"
221   - @current-change="p => fetchLines(catScope.row, brandScope.row, prodScope.row, p)"
  339 + <div class="nested-table-host">
  340 + <el-table
  341 + :data="
  342 + productMap[
  343 + brandRowKey(catScope.row, brandScope.row)
  344 + ] || []
  345 + "
  346 + border
  347 + size="small"
  348 + class="nested-table nested-table--deep nested-table--scroll"
  349 + :row-key="
  350 + r =>
  351 + productRowKey(catScope.row, brandScope.row, r)
  352 + "
  353 + @expand-change="
  354 + (r, er) =>
  355 + onProductExpand(
  356 + catScope.row,
  357 + brandScope.row,
  358 + r,
  359 + er
  360 + )
  361 + "
  362 + @sort-change="
  363 + ev =>
  364 + onProductSort(
  365 + catScope.row,
  366 + brandScope.row,
  367 + ev
  368 + )
  369 + "
  370 + >
  371 + <el-table-column type="expand" width="42">
  372 + <template slot-scope="prodScope">
  373 + <div
  374 + v-loading="
  375 + lineLoading[
  376 + lineKey(
  377 + catScope.row,
  378 + brandScope.row,
  379 + prodScope.row
  380 + )
  381 + ]
  382 + "
  383 + class="nested-wrap detail-wrap"
  384 + >
  385 + <div
  386 + class="nested-table-host nested-table-host--detail"
  387 + >
  388 + <el-table
  389 + :data="
  390 + (
  391 + lineMap[
  392 + lineKey(
  393 + catScope.row,
  394 + brandScope.row,
  395 + prodScope.row
  396 + )
  397 + ] || {}
  398 + ).list || []
  399 + "
  400 + border
  401 + size="mini"
  402 + class="nested-table nested-table--scroll"
  403 + :row-key="
  404 + (r, idx) =>
  405 + lineDetailRowKey(
  406 + 'mx',
  407 + catScope.row,
  408 + brandScope.row,
  409 + prodScope.row,
  410 + r,
  411 + idx
  412 + )
  413 + "
  414 + @sort-change="
  415 + ev =>
  416 + onLineSort(
  417 + catScope.row,
  418 + brandScope.row,
  419 + prodScope.row,
  420 + ev
  421 + )
  422 + "
  423 + >
  424 + <el-table-column
  425 + type="index"
  426 + label="行号"
  427 + width="52"
  428 + align="center"
  429 + />
  430 + <el-table-column
  431 + prop="单据日期"
  432 + label="单据日期"
  433 + width="108"
  434 + show-overflow-tooltip
  435 + sortable="custom"
  436 + />
  437 + <el-table-column
  438 + prop="单据编号"
  439 + label="单据编号"
  440 + min-width="140"
  441 + show-overflow-tooltip
  442 + sortable="custom"
  443 + />
  444 + <el-table-column
  445 + prop="单据类型"
  446 + label="单据类型"
  447 + width="108"
  448 + show-overflow-tooltip
  449 + sortable="custom"
  450 + />
  451 + <el-table-column
  452 + prop="往来单位"
  453 + label="往来单位"
  454 + min-width="120"
  455 + show-overflow-tooltip
  456 + sortable="custom"
  457 + />
  458 + <el-table-column
  459 + prop="经手人"
  460 + label="经手人"
  461 + width="88"
  462 + show-overflow-tooltip
  463 + sortable="custom"
  464 + />
  465 + <el-table-column
  466 + prop="仓库名称"
  467 + label="仓库名称"
  468 + min-width="100"
  469 + show-overflow-tooltip
  470 + sortable="custom"
  471 + />
  472 + <el-table-column
  473 + prop="商品名称"
  474 + label="商品名称"
  475 + min-width="140"
  476 + show-overflow-tooltip
  477 + sortable="custom"
  478 + />
  479 + <el-table-column
  480 + prop="数量"
  481 + label="数量"
  482 + width="88"
  483 + align="right"
  484 + sortable="custom"
  485 + >
  486 + <template slot-scope="s">{{
  487 + formatQty(s.row["数量"])
  488 + }}</template>
  489 + </el-table-column>
  490 + <el-table-column
  491 + prop="入库单价"
  492 + label="入库单价"
  493 + width="100"
  494 + align="right"
  495 + sortable="custom"
  496 + >
  497 + <template slot-scope="s">{{
  498 + formatMoney(s.row["入库单价"])
  499 + }}</template>
  500 + </el-table-column>
  501 + <el-table-column
  502 + prop="采购金额"
  503 + label="采购金额"
  504 + width="108"
  505 + align="right"
  506 + sortable="custom"
  507 + >
  508 + <template slot-scope="s">{{
  509 + formatMoney(s.row["采购金额"])
  510 + }}</template>
  511 + </el-table-column>
  512 + </el-table>
  513 + </div>
  514 + <div
  515 + v-if="
  516 + linePager[
  517 + lineKey(
  518 + catScope.row,
  519 + brandScope.row,
  520 + prodScope.row
  521 + )
  522 + ]
  523 + "
  524 + class="mini-pager"
  525 + >
  526 + <el-pagination
  527 + small
  528 + layout="prev, pager, next, total"
  529 + :total="
  530 + linePager[
  531 + lineKey(
  532 + catScope.row,
  533 + brandScope.row,
  534 + prodScope.row
  535 + )
  536 + ].total
  537 + "
  538 + :page-size="
  539 + linePager[
  540 + lineKey(
  541 + catScope.row,
  542 + brandScope.row,
  543 + prodScope.row
  544 + )
  545 + ].pageSize
  546 + "
  547 + :current-page="
  548 + linePager[
  549 + lineKey(
  550 + catScope.row,
  551 + brandScope.row,
  552 + prodScope.row
  553 + )
  554 + ].currentPage
  555 + "
  556 + @current-change="
  557 + p =>
  558 + fetchLines(
  559 + catScope.row,
  560 + brandScope.row,
  561 + prodScope.row,
  562 + p
  563 + )
  564 + "
  565 + />
  566 + </div>
  567 + </div>
  568 + </template>
  569 + </el-table-column>
  570 + <el-table-column
  571 + prop="商品编号"
  572 + label="商品编号"
  573 + min-width="100"
  574 + show-overflow-tooltip
  575 + sortable="custom"
  576 + >
  577 + <template slot-scope="s">
  578 + <i
  579 + class="el-icon-postcard row-ico row-ico--primary"
222 580 />
223   - </div>
224   - </div>
225   - </template>
226   - </el-table-column>
227   - <el-table-column label="商品编号" min-width="100" show-overflow-tooltip sortable :sort-method="sortTextCol('商品编号')">
228   - <template slot-scope="s">
229   - <i class="el-icon-postcard row-ico row-ico--primary" />
230   - {{ cellText(s.row['商品编号']) }}
231   - </template>
232   - </el-table-column>
233   - <el-table-column label="明细分类" min-width="120" show-overflow-tooltip sortable :sort-method="sortTextCol('明细分类')">
234   - <template slot-scope="s">
235   - <i class="el-icon-collection-tag row-ico row-ico--info" />
236   - {{ cellText(s.row['明细分类']) }}
237   - </template>
238   - </el-table-column>
239   - <el-table-column label="商品名称" min-width="140" show-overflow-tooltip sortable :sort-method="sortTextCol('商品名称')">
240   - <template slot-scope="s">
241   - <i class="el-icon-goods row-ico row-ico--muted" />
242   - {{ cellText(s.row['商品名称']) }}
243   - </template>
244   - </el-table-column>
245   - <el-table-column label="数量" width="88" align="right" sortable :sort-method="sortNumCol('数量')">
246   - <template slot-scope="s">{{ formatQty(s.row['数量']) }}</template>
247   - </el-table-column>
248   - <el-table-column label="入库单价" width="100" align="right" sortable :sort-method="sortNumCol('入库单价')">
249   - <template slot-scope="s">{{ formatMoney(s.row['入库单价']) }}</template>
250   - </el-table-column>
251   - <el-table-column label="采购金额" width="108" align="right" sortable :sort-method="sortNumCol('采购金额')">
252   - <template slot-scope="s">{{ formatMoney(s.row['采购金额']) }}</template>
253   - </el-table-column>
254   - </el-table>
255   - </div>
256   - </template>
257   - </el-table-column>
258   - <el-table-column label="品牌" min-width="140" show-overflow-tooltip sortable :sort-method="sortTextCol('品牌名称')">
259   - <template slot-scope="s">
260   - <i class="el-icon-medal row-ico row-ico--primary" />
261   - {{ cellText(s.row['品牌名称'] || s.row['商品名称']) }}
262   - </template>
263   - </el-table-column>
264   - <el-table-column label="数量" width="88" align="right" sortable :sort-method="sortNumCol('数量')">
265   - <template slot-scope="s">{{ formatQty(s.row['数量']) }}</template>
266   - </el-table-column>
267   - <el-table-column label="入库单价" width="100" align="right" sortable :sort-method="sortNumCol('入库单价')">
268   - <template slot-scope="s">{{ formatMoney(s.row['入库单价']) }}</template>
269   - </el-table-column>
270   - <el-table-column label="采购金额" width="108" align="right" sortable :sort-method="sortNumCol('采购金额')">
271   - <template slot-scope="s">{{ formatMoney(s.row['采购金额']) }}</template>
272   - </el-table-column>
273   - </el-table>
274   - <div v-if="!(brandMap[categoryRowKey(catScope.row)] || []).length && !brandLoading[categoryRowKey(catScope.row)]" class="nested-empty">暂无品牌数据</div>
  581 + {{ cellText(s.row["商品编号"]) }}
  582 + </template>
  583 + </el-table-column>
  584 + <el-table-column
  585 + prop="明细分类"
  586 + label="明细分类"
  587 + min-width="120"
  588 + show-overflow-tooltip
  589 + sortable="custom"
  590 + >
  591 + <template slot-scope="s">
  592 + <i
  593 + class="el-icon-collection-tag row-ico row-ico--info"
  594 + />
  595 + {{ cellText(s.row["明细分类"]) }}
  596 + </template>
  597 + </el-table-column>
  598 + <el-table-column
  599 + prop="商品名称"
  600 + label="商品名称"
  601 + min-width="140"
  602 + show-overflow-tooltip
  603 + sortable="custom"
  604 + >
  605 + <template slot-scope="s">
  606 + <i
  607 + class="el-icon-goods row-ico row-ico--muted"
  608 + />
  609 + {{ cellText(s.row["商品名称"]) }}
  610 + </template>
  611 + </el-table-column>
  612 + <el-table-column
  613 + prop="数量"
  614 + label="数量"
  615 + width="88"
  616 + align="right"
  617 + sortable="custom"
  618 + >
  619 + <template slot-scope="s">{{
  620 + formatQty(s.row["数量"])
  621 + }}</template>
  622 + </el-table-column>
  623 + <el-table-column
  624 + prop="入库单价"
  625 + label="入库单价"
  626 + width="100"
  627 + align="right"
  628 + sortable="custom"
  629 + >
  630 + <template slot-scope="s">{{
  631 + formatMoney(s.row["入库单价"])
  632 + }}</template>
  633 + </el-table-column>
  634 + <el-table-column
  635 + prop="采购金额"
  636 + label="采购金额"
  637 + width="108"
  638 + align="right"
  639 + sortable="custom"
  640 + >
  641 + <template slot-scope="s">{{
  642 + formatMoney(s.row["采购金额"])
  643 + }}</template>
  644 + </el-table-column>
  645 + </el-table>
  646 + </div>
  647 + </div>
  648 + </template>
  649 + </el-table-column>
  650 + <el-table-column
  651 + prop="品牌名称"
  652 + label="品牌"
  653 + min-width="140"
  654 + show-overflow-tooltip
  655 + sortable="custom"
  656 + >
  657 + <template slot-scope="s">
  658 + <i class="el-icon-medal row-ico row-ico--primary" />
  659 + {{ cellText(s.row["品牌名称"] || s.row["商品名称"]) }}
  660 + </template>
  661 + </el-table-column>
  662 + <el-table-column
  663 + prop="数量"
  664 + label="数量"
  665 + width="88"
  666 + align="right"
  667 + sortable="custom"
  668 + >
  669 + <template slot-scope="s">{{
  670 + formatQty(s.row["数量"])
  671 + }}</template>
  672 + </el-table-column>
  673 + <el-table-column
  674 + prop="入库单价"
  675 + label="入库单价"
  676 + width="100"
  677 + align="right"
  678 + sortable="custom"
  679 + >
  680 + <template slot-scope="s">{{
  681 + formatMoney(s.row["入库单价"])
  682 + }}</template>
  683 + </el-table-column>
  684 + <el-table-column
  685 + prop="采购金额"
  686 + label="采购金额"
  687 + width="108"
  688 + align="right"
  689 + sortable="custom"
  690 + >
  691 + <template slot-scope="s">{{
  692 + formatMoney(s.row["采购金额"])
  693 + }}</template>
  694 + </el-table-column>
  695 + </el-table>
  696 + </div>
  697 + <div
  698 + v-if="
  699 + !(brandMap[categoryRowKey(catScope.row)] || []).length &&
  700 + !brandLoading[categoryRowKey(catScope.row)]
  701 + "
  702 + class="nested-empty"
  703 + >
  704 + 暂无品牌数据
  705 + </div>
275 706 </div>
276 707 </template>
277 708 </el-table-column>
278   - <el-table-column type="index" label="行号" width="56" align="center" />
279   - <el-table-column label="分类名称" min-width="160" show-overflow-tooltip sortable :sort-method="sortTextCol('分类名称')">
  709 + <el-table-column
  710 + type="index"
  711 + label="行号"
  712 + width="56"
  713 + align="center"
  714 + />
  715 + <el-table-column
  716 + prop="分类名称"
  717 + label="分类名称"
  718 + min-width="160"
  719 + show-overflow-tooltip
  720 + sortable="custom"
  721 + >
280 722 <template slot-scope="scope">
281 723 <i class="el-icon-folder-opened row-ico row-ico--primary" />
282   - {{ cellText(scope.row['分类名称']) }}
  724 + {{ cellText(scope.row["分类名称"]) }}
283 725 </template>
284 726 </el-table-column>
285   - <el-table-column label="数量" width="96" align="right" sortable :sort-method="sortNumCol('数量')">
286   - <template slot-scope="scope">{{ formatQty(scope.row['数量']) }}</template>
  727 + <el-table-column
  728 + prop="数量"
  729 + label="数量"
  730 + width="96"
  731 + align="right"
  732 + sortable="custom"
  733 + >
  734 + <template slot-scope="scope">{{
  735 + formatQty(scope.row["数量"])
  736 + }}</template>
287 737 </el-table-column>
288   - <el-table-column label="入库单价" width="104" align="right" sortable :sort-method="sortNumCol('入库单价')">
289   - <template slot-scope="scope">{{ formatMoney(scope.row['入库单价']) }}</template>
  738 + <el-table-column
  739 + prop="入库单价"
  740 + label="入库单价"
  741 + width="104"
  742 + align="right"
  743 + sortable="custom"
  744 + >
  745 + <template slot-scope="scope">{{
  746 + formatMoney(scope.row["入库单价"])
  747 + }}</template>
290 748 </el-table-column>
291   - <el-table-column label="采购金额" width="112" align="right" sortable :sort-method="sortNumCol('采购金额')">
292   - <template slot-scope="scope">{{ formatMoney(scope.row['采购金额']) }}</template>
  749 + <el-table-column
  750 + prop="采购金额"
  751 + label="采购金额"
  752 + width="112"
  753 + align="right"
  754 + sortable="custom"
  755 + >
  756 + <template slot-scope="scope">{{
  757 + formatMoney(scope.row["采购金额"])
  758 + }}</template>
293 759 </el-table-column>
294 760 <el-table-column label="操作" width="120" align="left" fixed="right">
295 761 <template slot-scope="scope">
296   - <el-button type="text" size="mini" icon="el-icon-tickets" @click.stop="openLinearDialog(scope.row)">线性列表</el-button>
  762 + <el-button
  763 + type="text"
  764 + size="mini"
  765 + icon="el-icon-tickets"
  766 + @click.stop="openLinearDialog(scope.row)"
  767 + >线性列表</el-button
  768 + >
297 769 </template>
298 770 </el-table-column>
299 771 </el-table>
... ... @@ -302,7 +774,9 @@
302 774 <i class="el-icon-s-data row-ico--primary" />
303 775 <span>合计(当前分类列表)</span>
304 776 <span class="sum-item">数量:{{ formatQty(sumCategoryQty) }}</span>
305   - <span class="sum-item">采购金额:{{ formatMoney(sumCategoryAmt) }}</span>
  777 + <span class="sum-item"
  778 + >采购金额:{{ formatMoney(sumCategoryAmt) }}</span
  779 + >
306 780 </div>
307 781 </div>
308 782 </div>
... ... @@ -318,23 +792,91 @@
318 792 >
319 793 <div v-loading="linearLoading" class="linear-dialog-body">
320 794 <p class="linear-hint">{{ linearHint }}</p>
321   - <el-table :data="linearRows" border size="small" max-height="520" class="linear-table">
  795 + <el-table
  796 + :data="linearRows"
  797 + border
  798 + size="small"
  799 + max-height="520"
  800 + class="linear-table"
  801 + >
322 802 <el-table-column type="index" label="行号" width="52" />
323   - <el-table-column prop="单据日期" label="单据日期" width="100" show-overflow-tooltip sortable :sort-method="sortTextCol('单据日期')" />
324   - <el-table-column prop="单据编号" label="单据编号" min-width="130" show-overflow-tooltip sortable :sort-method="sortTextCol('单据编号')" />
325   - <el-table-column prop="单据类型" label="单据类型" width="100" show-overflow-tooltip />
326   - <el-table-column prop="往来单位" label="往来单位" min-width="110" show-overflow-tooltip />
327   - <el-table-column prop="经手人" label="经手人" width="80" show-overflow-tooltip />
328   - <el-table-column prop="仓库名称" label="仓库名称" min-width="96" show-overflow-tooltip />
329   - <el-table-column prop="商品名称" label="商品名称" min-width="120" show-overflow-tooltip />
330   - <el-table-column prop="数量" label="数量" width="80" align="right" sortable :sort-method="sortNumCol('数量')">
331   - <template slot-scope="s">{{ formatQty(s.row['数量']) }}</template>
  803 + <el-table-column
  804 + prop="单据日期"
  805 + label="单据日期"
  806 + width="100"
  807 + show-overflow-tooltip
  808 + sortable
  809 + :sort-method="sortTextCol('单据日期')"
  810 + />
  811 + <el-table-column
  812 + prop="单据编号"
  813 + label="单据编号"
  814 + min-width="130"
  815 + show-overflow-tooltip
  816 + sortable
  817 + :sort-method="sortTextCol('单据编号')"
  818 + />
  819 + <el-table-column
  820 + prop="单据类型"
  821 + label="单据类型"
  822 + width="100"
  823 + show-overflow-tooltip
  824 + />
  825 + <el-table-column
  826 + prop="往来单位"
  827 + label="往来单位"
  828 + min-width="110"
  829 + show-overflow-tooltip
  830 + />
  831 + <el-table-column
  832 + prop="经手人"
  833 + label="经手人"
  834 + width="80"
  835 + show-overflow-tooltip
  836 + />
  837 + <el-table-column
  838 + prop="仓库名称"
  839 + label="仓库名称"
  840 + min-width="96"
  841 + show-overflow-tooltip
  842 + />
  843 + <el-table-column
  844 + prop="商品名称"
  845 + label="商品名称"
  846 + min-width="120"
  847 + show-overflow-tooltip
  848 + />
  849 + <el-table-column
  850 + prop="数量"
  851 + label="数量"
  852 + width="80"
  853 + align="right"
  854 + sortable
  855 + :sort-method="sortNumCol('数量')"
  856 + >
  857 + <template slot-scope="s">{{ formatQty(s.row["数量"]) }}</template>
332 858 </el-table-column>
333   - <el-table-column prop="入库单价" label="入库单价" width="92" align="right">
334   - <template slot-scope="s">{{ formatMoney(s.row['入库单价']) }}</template>
  859 + <el-table-column
  860 + prop="入库单价"
  861 + label="入库单价"
  862 + width="92"
  863 + align="right"
  864 + >
  865 + <template slot-scope="s">{{
  866 + formatMoney(s.row["入库单价"])
  867 + }}</template>
335 868 </el-table-column>
336   - <el-table-column prop="采购金额" label="采购金额" width="100" align="right" sortable :sort-method="sortNumCol('采购金额')">
337   - <template slot-scope="s">{{ formatMoney(s.row['采购金额']) }}</template>
  869 + <el-table-column
  870 + prop="采购金额"
  871 + label="采购金额"
  872 + width="100"
  873 + align="right"
  874 + sortable
  875 + :sort-method="sortNumCol('采购金额')"
  876 + >
  877 + <template slot-scope="s">{{
  878 + formatMoney(s.row["采购金额"])
  879 + }}</template>
338 880 </el-table-column>
339 881 </el-table>
340 882 </div>
... ... @@ -343,13 +885,23 @@
343 885 </template>
344 886  
345 887 <script>
346   -import request from '@/utils/request'
347   -import { previewDataInterface } from '@/api/systemData/dataInterface'
  888 +import request from "@/utils/request";
  889 +import { previewDataInterface } from "@/api/systemData/dataInterface";
348 890  
349   -const DEFAULT_BILL_TYPES = ['采购入库单', '采购退货单']
  891 +const BILL_TYPE_SUGGEST = [
  892 + "采购入库单",
  893 + "采购退货单",
  894 + "销售出库单",
  895 + "销售退货单",
  896 + "预售出库单",
  897 + "预售退货单",
  898 + "委托代销发货单",
  899 + "委托代销退货单",
  900 + "委托代销结算单"
  901 +];
350 902  
351 903 export default {
352   - name: 'PurchaseSummary',
  904 + name: "PurchaseSummary",
353 905 data() {
354 906 return {
355 907 categoryList: [],
... ... @@ -357,102 +909,127 @@ export default {
357 909 brandMap: {},
358 910 brandLoading: {},
359 911 productMap: {},
360   - productLoading: {},
  912 + aggProductLoading: {},
361 913 lineMap: {},
362 914 linePager: {},
363 915 lineLoading: {},
364   - billTypeOptions: [...DEFAULT_BILL_TYPES],
  916 + lineSort: {},
  917 + billTypeOptions: [...BILL_TYPE_SUGGEST],
  918 + linearInlineMode: false,
  919 + linearInlineMap: {},
  920 + linearInlineLoading: {},
365 921 filters: {
366 922 dateRange: [],
367 923 contactUnit: [],
368 924 agent: [],
369   - productSpId: '',
  925 + productSpId: "",
370 926 warehouse: [],
371   - billType: [...DEFAULT_BILL_TYPES]
  927 + billType: []
372 928 },
373 929 contactUnitOptions: [],
374 930 agentOptions: [],
375 931 warehouseOptions: [],
376 932 productOptions: [],
377   - productLoading: false,
  933 + productRemoteLoading: false,
378 934 linearVisible: false,
379 935 linearLoading: false,
380 936 linearRows: [],
381   - linearHint: ''
382   - }
  937 + linearHint: ""
  938 + };
383 939 },
384 940 computed: {
385 941 sumCategoryQty() {
386   - return this.categoryList.reduce((s, r) => s + this.parseNum(r['数量']), 0)
  942 + return this.categoryList.reduce(
  943 + (s, r) => s + this.parseNum(r["数量"]),
  944 + 0
  945 + );
387 946 },
388 947 sumCategoryAmt() {
389   - return this.categoryList.reduce((s, r) => s + this.parseNum(r['采购金额']), 0)
  948 + return this.categoryList.reduce(
  949 + (s, r) => s + this.parseNum(r["采购金额"]),
  950 + 0
  951 + );
390 952 }
391 953 },
392 954 created() {
393   - this.loadFilterOptions()
394   - this.fetchCategories()
  955 + this.loadFilterOptions();
  956 + this.fetchCategories();
395 957 },
396 958 methods: {
  959 + /** 表格 row-key(带前缀,避免嵌套表展开后 key 冲突) */
397 960 categoryRowKey(row) {
398   - return String(row['分类Id'] != null ? row['分类Id'] : '')
  961 + return `ps-cat:${String(row["分类Id"] != null ? row["分类Id"] : "")}`;
399 962 },
400 963 brandRowKey(catRow, brandRow) {
401   - return `${this.categoryRowKey(catRow)}|${String(brandRow['品牌Id'] != null ? brandRow['品牌Id'] : '')}`
  964 + return `${this.categoryRowKey(catRow)}|br:${String(
  965 + brandRow["品牌Id"] != null ? brandRow["品牌Id"] : "_"
  966 + )}`;
402 967 },
403 968 productRowKey(catRow, brandRow, prodRow) {
404   - return `${this.brandRowKey(catRow, brandRow)}|${String(prodRow['商品Id'] != null ? prodRow['商品Id'] : '')}`
  969 + return `${this.brandRowKey(catRow, brandRow)}|sp:${String(
  970 + prodRow["商品Id"] != null ? prodRow["商品Id"] : ""
  971 + )}`;
405 972 },
406 973 lineKey(catRow, brandRow, prodRow) {
407   - return this.productRowKey(catRow, brandRow, prodRow)
  974 + return this.productRowKey(catRow, brandRow, prodRow);
408 975 },
409 976 parseNum(val) {
410   - if (val == null || val === '') return 0
411   - const n = Number(String(val).replace(/,/g, '').trim())
412   - return Number.isFinite(n) ? n : 0
  977 + if (val == null || val === "") return 0;
  978 + const n = Number(
  979 + String(val)
  980 + .replace(/,/g, "")
  981 + .trim()
  982 + );
  983 + return Number.isFinite(n) ? n : 0;
413 984 },
414 985 sortNumCol(prop) {
415   - return (a, b) => this.parseNum(a[prop]) - this.parseNum(b[prop])
  986 + return (a, b) => this.parseNum(a[prop]) - this.parseNum(b[prop]);
416 987 },
417 988 sortTextCol(prop) {
418 989 return (a, b) => {
419   - const sa = String(a[prop] != null ? a[prop] : '').trim()
420   - const sb = String(b[prop] != null ? b[prop] : '').trim()
421   - return sa.localeCompare(sb, 'zh-CN')
422   - }
  990 + const sa = String(a[prop] != null ? a[prop] : "").trim();
  991 + const sb = String(b[prop] != null ? b[prop] : "").trim();
  992 + return sa.localeCompare(sb, "zh-CN");
  993 + };
423 994 },
424 995 cellText(v) {
425   - if (v == null || String(v).trim() === '') return '无'
426   - return String(v)
  996 + if (v == null || String(v).trim() === "") return "无";
  997 + return String(v);
427 998 },
428 999 formatQty(val) {
429   - if (val === null || val === undefined || val === '') return '无'
430   - const n = Number(val)
431   - if (Number.isNaN(n)) return String(val)
432   - return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 4 })
  1000 + if (val === null || val === undefined || val === "") return "无";
  1001 + const n = Number(val);
  1002 + if (Number.isNaN(n)) return String(val);
  1003 + return n.toLocaleString("zh-CN", {
  1004 + minimumFractionDigits: 0,
  1005 + maximumFractionDigits: 4
  1006 + });
433 1007 },
434 1008 formatMoney(val) {
435   - if (val === null || val === undefined || val === '') return '无'
436   - const n = Number(val)
437   - if (Number.isNaN(n)) return String(val)
438   - return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 4 })
  1009 + if (val === null || val === undefined || val === "") return "无";
  1010 + const n = Number(val);
  1011 + if (Number.isNaN(n)) return String(val);
  1012 + return n.toLocaleString("zh-CN", {
  1013 + minimumFractionDigits: 2,
  1014 + maximumFractionDigits: 4
  1015 + });
439 1016 },
440 1017 /** 商品编码(空则返回空串,便于单行展示) */
441 1018 productCodeRaw(item) {
442   - if (!item) return ''
  1019 + if (!item) return "";
443 1020 const c =
444 1021 item.F_Spbm ||
445 1022 item.spbm ||
446 1023 item.Spbm ||
447 1024 item.商品编号 ||
448 1025 item.code ||
449   - ''
450   - const t = c != null && String(c).trim() !== '' ? String(c).trim() : ''
451   - return t
  1026 + "";
  1027 + const t = c != null && String(c).trim() !== "" ? String(c).trim() : "";
  1028 + return t;
452 1029 },
453 1030 /** 商品名称 */
454 1031 productNameRaw(item) {
455   - if (!item) return ''
  1032 + if (!item) return "";
456 1033 const n =
457 1034 item.F_Spmc ||
458 1035 item.spmc ||
... ... @@ -460,271 +1037,454 @@ export default {
460 1037 item.productName ||
461 1038 item.商品名称 ||
462 1039 item.name ||
463   - ''
464   - const t = n != null && String(n).trim() !== '' ? String(n).trim() : ''
465   - return t
  1040 + "";
  1041 + const t = n != null && String(n).trim() !== "" ? String(n).trim() : "";
  1042 + return t;
466 1043 },
467 1044 /** 选中后在输入框内显示的完整文案(编码 + 名称) */
468 1045 formatProductLabel(item) {
469   - if (!item) return ''
470   - const code = this.productCodeRaw(item)
471   - const name = this.productNameRaw(item)
472   - if (code && name) return `${code} | ${name}`
473   - if (name) return name
474   - if (code) return code
475   - const id = item.F_Id || item.id
476   - return id ? String(id) : '无'
  1046 + if (!item) return "";
  1047 + const code = this.productCodeRaw(item);
  1048 + const name = this.productNameRaw(item);
  1049 + if (code && name) return `${code} | ${name}`;
  1050 + if (name) return name;
  1051 + if (code) return code;
  1052 + const id = this.productOptionValue(item);
  1053 + return id ? String(id) : "无";
  1054 + },
  1055 + productOptionValue(item) {
  1056 + if (!item) return "";
  1057 + const v = item.id || item.F_Id || item.f_Id;
  1058 + return v != null && String(v).trim() !== "" ? String(v).trim() : "";
  1059 + },
  1060 + normalizeArrayPayload(data) {
  1061 + if (Array.isArray(data)) return data;
  1062 + if (data && Array.isArray(data.list)) return data.list;
  1063 + // 兼容:{ code, msg, data: { list: [...] } }
  1064 + if (data && data.data && Array.isArray(data.data.list))
  1065 + return data.data.list;
  1066 + if (data && Array.isArray(data.data)) return data.data;
  1067 + if (data && Array.isArray(data.records)) return data.records;
  1068 + return [];
  1069 + },
  1070 + lineDetailRowKey(prefix, catRow, brandRow, prodRow, lineRow, idx) {
  1071 + const pk = prodRow
  1072 + ? this.lineKey(catRow, brandRow, prodRow)
  1073 + : this.categoryRowKey(catRow);
  1074 + const doc =
  1075 + lineRow && lineRow["单据编号"] != null
  1076 + ? String(lineRow["单据编号"])
  1077 + : "";
  1078 + const nm =
  1079 + lineRow && lineRow["商品名称"] != null
  1080 + ? String(lineRow["商品名称"]).slice(0, 48)
  1081 + : "";
  1082 + return `${prefix}|${pk}|i${idx}|${doc}|${nm}`;
477 1083 },
478 1084 buildBasePayload() {
479   - const payload = {}
  1085 + const payload = {};
480 1086 if (this.filters.dateRange && this.filters.dateRange.length === 2) {
481   - payload.startDate = this.filters.dateRange[0]
482   - payload.endDate = this.filters.dateRange[1]
  1087 + payload.startDate = this.filters.dateRange[0];
  1088 + payload.endDate = this.filters.dateRange[1];
483 1089 }
484   - if (this.filters.contactUnit.length) payload.contactUnit = this.filters.contactUnit.join(',')
485   - if (this.filters.agent.length) payload.agent = this.filters.agent.join(',')
486   - if (this.filters.warehouse.length) payload.warehouse = this.filters.warehouse.join(',')
487   - if (this.filters.productSpId) payload.productSpId = this.filters.productSpId
488   - const bt = this.filters.billType || []
489   - if (bt.length > 0 && bt.length < this.billTypeOptions.length) {
490   - payload.billType = bt.join(',')
491   - } else {
492   - payload.billType = ''
  1090 + if (this.filters.contactUnit.length)
  1091 + payload.contactUnit = this.filters.contactUnit.join(",");
  1092 + if (this.filters.agent.length) {
  1093 + const a = this.filters.agent
  1094 + .map(x => String(x).trim())
  1095 + .filter(Boolean)
  1096 + .join(",");
  1097 + if (a) payload.agent = a;
493 1098 }
494   - return payload
  1099 + if (this.filters.warehouse.length)
  1100 + payload.warehouse = this.filters.warehouse.join(",");
  1101 + if (this.filters.productSpId)
  1102 + payload.productSpId = this.filters.productSpId;
  1103 + const bt = this.filters.billType || [];
  1104 + if (bt.length)
  1105 + payload.billType = bt
  1106 + .map(x => String(x).trim())
  1107 + .filter(Boolean)
  1108 + .join(",");
  1109 + return payload;
495 1110 },
496 1111 async loadFilterOptions() {
497 1112 try {
498 1113 const companyRes = await request({
499   - url: '/api/Extend/WtWldw',
500   - method: 'GET',
  1114 + url: "/api/Extend/WtWldw",
  1115 + method: "GET",
501 1116 data: { pageSize: 1000, currentPage: 1 }
502   - })
503   - const companyList = companyRes.data.list || companyRes.data || []
  1117 + });
  1118 + const companyList = companyRes.data.list || companyRes.data || [];
504 1119 this.contactUnitOptions = companyList
505 1120 .map(x => ({
506 1121 label: x.dwmc || x.F_dwmc || x.id,
507 1122 value: x.id || x.F_Id
508 1123 }))
509   - .filter(x => x.value)
  1124 + .filter(x => x.value);
510 1125  
511 1126 const warehouseRes = await request({
512   - url: '/api/Extend/WtCk',
513   - method: 'GET',
  1127 + url: "/api/Extend/WtCk",
  1128 + method: "GET",
514 1129 data: { pageSize: 1000, currentPage: 1 }
515   - })
516   - const warehouseList = warehouseRes.data.list || warehouseRes.data || []
  1130 + });
  1131 + const warehouseList = warehouseRes.data.list || warehouseRes.data || [];
517 1132 this.warehouseOptions = warehouseList.map(x => ({
518 1133 label: x.mdmc || x.F_mdmc || x.id,
519 1134 value: x.id || x.F_Id
520   - }))
  1135 + }));
521 1136  
522   - const userRes = await previewDataInterface('675937572047815941')
523   - const userList = userRes.data || []
524   - this.agentOptions = (userList || [])
  1137 + const userRes = await previewDataInterface("675937572047815941");
  1138 + const userList = this.normalizeArrayPayload(userRes.data);
  1139 + this.agentOptions = userList
525 1140 .map(x => ({
526   - label: x.realName || x.F_RealName || x.fullName || x.name || x.F_Account || x.account || '',
527   - value: x.id || x.F_Id || x.userId
  1141 + label: String(
  1142 + x.realName ||
  1143 + x.F_RealName ||
  1144 + x.fullName ||
  1145 + x.name ||
  1146 + x.F_Account ||
  1147 + x.account ||
  1148 + x.f_RealName ||
  1149 + ""
  1150 + ).trim(),
  1151 + value: String(x.id || x.F_Id || x.userId || x.f_Id || "").trim()
528 1152 }))
529   - .filter(x => x.value && x.label)
  1153 + .filter(x => x.value && x.label);
530 1154 } catch (e) {
531   - console.error('采购汇总筛选下拉加载失败', e)
  1155 + console.error("采购汇总筛选下拉加载失败", e);
532 1156 }
533 1157 },
534 1158 async handleProductSearch(query) {
535 1159 if (!query) {
536   - this.productOptions = []
537   - return
  1160 + this.productOptions = [];
  1161 + return;
538 1162 }
539   - this.productLoading = true
  1163 + this.productRemoteLoading = true;
540 1164 try {
541 1165 const res = await request({
542   - url: '/api/Extend/WtSp/GetListByKeyword',
543   - method: 'GET',
544   - data: { pageSize: 200, currentPage: 1, keyword: query }
545   - })
546   - this.productOptions = res.data.list || res.data || []
  1166 + url: "/api/Extend/WtSp/GetListByKeyword",
  1167 + method: "GET",
  1168 + data: { pageSize: 200, currentPage: 1, keyword: String(query).trim() }
  1169 + });
  1170 + this.productOptions = this.normalizeArrayPayload(res.data);
547 1171 } catch (e) {
548   - console.error('采购汇总商品搜索失败', e)
  1172 + console.error("采购汇总商品搜索失败", e);
549 1173 } finally {
550   - this.productLoading = false
  1174 + this.productRemoteLoading = false;
551 1175 }
552 1176 },
553 1177 clearTreeCaches() {
554   - this.brandMap = {}
555   - this.brandLoading = {}
556   - this.productMap = {}
557   - this.productLoading = {}
558   - this.lineMap = {}
559   - this.linePager = {}
560   - this.lineLoading = {}
  1178 + this.brandMap = {};
  1179 + this.brandLoading = {};
  1180 + this.productMap = {};
  1181 + this.aggProductLoading = {};
  1182 + this.lineMap = {};
  1183 + this.linePager = {};
  1184 + this.lineLoading = {};
  1185 + this.lineSort = {};
  1186 + this.linearInlineMap = {};
  1187 + this.linearInlineLoading = {};
561 1188 },
562 1189 fetchCategories() {
563   - this.categoryLoading = true
564   - this.clearTreeCaches()
  1190 + this.categoryLoading = true;
  1191 + this.clearTreeCaches();
565 1192 request({
566   - url: '/api/Extend/WtXsckd/Actions/GetPurchaseSummaryByCategory',
567   - method: 'GET',
  1193 + url: "/api/Extend/WtXsckd/Actions/GetPurchaseSummaryByCategory",
  1194 + method: "GET",
568 1195 data: this.buildBasePayload()
569 1196 })
570 1197 .then(res => {
571   - const rows = Array.isArray(res.data) ? res.data : (res.data && res.data.list) || []
572   - this.categoryList = rows
  1198 + // 兼容后端返回:{ code, msg, data: [...] } / { data: { list: [...] } } / [...]
  1199 + this.categoryList = this.normalizeArrayPayload(res.data);
573 1200 })
574 1201 .catch(() => {
575   - this.categoryList = []
  1202 + this.categoryList = [];
576 1203 })
577 1204 .finally(() => {
578   - this.categoryLoading = false
579   - })
  1205 + this.categoryLoading = false;
  1206 + });
  1207 + },
  1208 + onLinearInlineModeChange() {
  1209 + this.clearTreeCaches();
  1210 + this.fetchCategories();
  1211 + this.$nextTick(() => {
  1212 + const t = this.$refs.categoryTable;
  1213 + if (t && this.categoryList && this.categoryList.length) {
  1214 + this.categoryList.forEach(r => {
  1215 + try {
  1216 + t.toggleRowExpansion(r, false);
  1217 + } catch (e) {
  1218 + /* ignore */
  1219 + }
  1220 + });
  1221 + }
  1222 + });
580 1223 },
581 1224 onCategoryExpand(row, expandedRows) {
582   - const id = String(row['分类Id'])
583   - if (expandedRows.some(r => String(r['分类Id']) === id)) {
584   - this.loadBrands(row)
  1225 + const id = String(row["分类Id"]);
  1226 + const isOpen = expandedRows.some(r => String(r["分类Id"]) === id);
  1227 + const ck = this.categoryRowKey(row);
  1228 + if (!isOpen) {
  1229 + this.$delete(this.linearInlineMap, ck);
  1230 + this.$delete(this.brandMap, ck);
  1231 + this.$delete(this.brandLoading, ck);
  1232 + return;
  1233 + }
  1234 + if (this.linearInlineMode) {
  1235 + this.loadLinearInline(row);
  1236 + } else {
  1237 + this.loadBrands(row);
585 1238 }
586 1239 },
587   - loadBrands(catRow) {
588   - const k = this.categoryRowKey(catRow)
589   - if (this.brandMap[k] !== undefined || this.brandLoading[k]) return
590   - this.$set(this.brandLoading, k, true)
591   - const cid = String(catRow['分类Id'] != null ? catRow['分类Id'] : '')
  1240 + loadLinearInline(catRow) {
  1241 + const k = this.categoryRowKey(catRow);
  1242 + this.$set(this.linearInlineLoading, k, true);
  1243 + const cid = String(catRow["分类Id"] != null ? catRow["分类Id"] : "");
592 1244 request({
593   - url: '/api/Extend/WtXsckd/Actions/GetPurchaseSummaryByBrand',
594   - method: 'GET',
  1245 + url: "/api/Extend/WtXsckd/Actions/GetPurchaseSummaryLinear",
  1246 + method: "GET",
595 1247 data: { ...this.buildBasePayload(), categoryId: cid }
596 1248 })
597 1249 .then(res => {
598   - const rows = Array.isArray(res.data) ? res.data : (res.data && res.data.list) || []
599   - this.$set(this.brandMap, k, rows)
  1250 + const rows = this.normalizeArrayPayload(res.data);
  1251 + this.$set(this.linearInlineMap, k, { list: rows, loaded: true });
600 1252 })
601 1253 .catch(() => {
602   - this.$set(this.brandMap, k, [])
  1254 + this.$set(this.linearInlineMap, k, { list: [], loaded: true });
603 1255 })
604 1256 .finally(() => {
605   - this.$set(this.brandLoading, k, false)
  1257 + this.$set(this.linearInlineLoading, k, false);
  1258 + });
  1259 + },
  1260 + loadBrands(catRow) {
  1261 + const k = this.categoryRowKey(catRow);
  1262 + if (this.brandMap[k] !== undefined || this.brandLoading[k]) return;
  1263 + this.$set(this.brandLoading, k, true);
  1264 + const cid = String(catRow["分类Id"] != null ? catRow["分类Id"] : "");
  1265 + request({
  1266 + url: "/api/Extend/WtXsckd/Actions/GetPurchaseSummaryByBrand",
  1267 + method: "GET",
  1268 + data: { ...this.buildBasePayload(), categoryId: cid }
  1269 + })
  1270 + .then(res => {
  1271 + this.$set(this.brandMap, k, this.normalizeArrayPayload(res.data));
  1272 + })
  1273 + .catch(() => {
  1274 + this.$set(this.brandMap, k, []);
606 1275 })
  1276 + .finally(() => {
  1277 + this.$set(this.brandLoading, k, false);
  1278 + });
607 1279 },
608 1280 onBrandExpand(catRow, brandRow, expandedRows) {
609   - if (expandedRows.some(r => this.brandRowKey(catRow, r) === this.brandRowKey(catRow, brandRow))) {
610   - this.loadProducts(catRow, brandRow)
  1281 + if (
  1282 + expandedRows.some(
  1283 + r =>
  1284 + this.brandRowKey(catRow, r) === this.brandRowKey(catRow, brandRow)
  1285 + )
  1286 + ) {
  1287 + this.loadProducts(catRow, brandRow);
611 1288 }
612 1289 },
613 1290 loadProducts(catRow, brandRow) {
614   - const k = this.brandRowKey(catRow, brandRow)
615   - if (this.productMap[k] !== undefined || this.productLoading[k]) return
616   - this.$set(this.productLoading, k, true)
617   - const cid = String(catRow['分类Id'] != null ? catRow['分类Id'] : '')
618   - const bid = String(brandRow['品牌Id'] != null ? brandRow['品牌Id'] : '')
  1291 + const k = this.brandRowKey(catRow, brandRow);
  1292 + if (this.productMap[k] !== undefined || this.aggProductLoading[k]) return;
  1293 + this.$set(this.aggProductLoading, k, true);
  1294 + const cid = String(catRow["分类Id"] != null ? catRow["分类Id"] : "");
  1295 + const bid = String(brandRow["品牌Id"] != null ? brandRow["品牌Id"] : "");
619 1296 request({
620   - url: '/api/Extend/WtXsckd/Actions/GetPurchaseSummaryByProductAgg',
621   - method: 'GET',
  1297 + url: "/api/Extend/WtXsckd/Actions/GetPurchaseSummaryByProductAgg",
  1298 + method: "GET",
622 1299 data: { ...this.buildBasePayload(), categoryId: cid, brandId: bid }
623 1300 })
624 1301 .then(res => {
625   - const rows = Array.isArray(res.data) ? res.data : (res.data && res.data.list) || []
626   - this.$set(this.productMap, k, rows)
  1302 + this.$set(this.productMap, k, this.normalizeArrayPayload(res.data));
627 1303 })
628 1304 .catch(() => {
629   - this.$set(this.productMap, k, [])
  1305 + this.$set(this.productMap, k, []);
630 1306 })
631 1307 .finally(() => {
632   - this.$set(this.productLoading, k, false)
633   - })
  1308 + this.$set(this.aggProductLoading, k, false);
  1309 + });
634 1310 },
635 1311 onProductExpand(catRow, brandRow, prodRow, expandedRows) {
636   - if (expandedRows.some(r => this.productRowKey(catRow, brandRow, r) === this.productRowKey(catRow, brandRow, prodRow))) {
637   - this.initLinePager(catRow, brandRow, prodRow)
638   - this.fetchLines(catRow, brandRow, prodRow, 1)
  1312 + if (
  1313 + expandedRows.some(
  1314 + r =>
  1315 + this.productRowKey(catRow, brandRow, r) ===
  1316 + this.productRowKey(catRow, brandRow, prodRow)
  1317 + )
  1318 + ) {
  1319 + this.initLinePager(catRow, brandRow, prodRow);
  1320 + this.fetchLines(catRow, brandRow, prodRow, 1);
639 1321 }
640 1322 },
641 1323 initLinePager(catRow, brandRow, prodRow) {
642   - const lk = this.lineKey(catRow, brandRow, prodRow)
  1324 + const lk = this.lineKey(catRow, brandRow, prodRow);
643 1325 if (!this.linePager[lk]) {
644   - this.$set(this.linePager, lk, { currentPage: 1, pageSize: 50, total: 0 })
  1326 + this.$set(this.linePager, lk, {
  1327 + currentPage: 1,
  1328 + pageSize: 50,
  1329 + total: 0
  1330 + });
645 1331 }
646 1332 },
647 1333 fetchLines(catRow, brandRow, prodRow, page) {
648   - const lk = this.lineKey(catRow, brandRow, prodRow)
649   - this.initLinePager(catRow, brandRow, prodRow)
650   - const pager = this.linePager[lk]
651   - pager.currentPage = page || 1
652   - this.$set(this.lineLoading, lk, true)
653   - const cid = String(catRow['分类Id'] != null ? catRow['分类Id'] : '')
654   - const bid = String(brandRow['品牌Id'] != null ? brandRow['品牌Id'] : '')
655   - const pid = String(prodRow['商品Id'] != null ? prodRow['商品Id'] : '')
  1334 + const lk = this.lineKey(catRow, brandRow, prodRow);
  1335 + this.initLinePager(catRow, brandRow, prodRow);
  1336 + const pager = this.linePager[lk];
  1337 + pager.currentPage = page || 1;
  1338 + this.$set(this.lineLoading, lk, true);
  1339 + const cid = String(catRow["分类Id"] != null ? catRow["分类Id"] : "");
  1340 + const bid = String(brandRow["品牌Id"] != null ? brandRow["品牌Id"] : "");
  1341 + const pid = String(prodRow["商品Id"] != null ? prodRow["商品Id"] : "");
  1342 + const sortCfg = this.lineSort[lk] || {};
656 1343 request({
657   - url: '/api/Extend/WtXsckd/Actions/GetPurchaseSummary',
658   - method: 'GET',
  1344 + url: "/api/Extend/WtXsckd/Actions/GetPurchaseSummary",
  1345 + method: "GET",
659 1346 data: {
660 1347 ...this.buildBasePayload(),
661 1348 categoryId: cid,
662 1349 brandId: bid,
663 1350 productSpId: pid,
664 1351 currentPage: pager.currentPage,
665   - pageSize: pager.pageSize
  1352 + pageSize: pager.pageSize,
  1353 + ...(sortCfg.sortField
  1354 + ? {
  1355 + sortField: sortCfg.sortField,
  1356 + sortOrder: sortCfg.sortOrder || "desc"
  1357 + }
  1358 + : {})
666 1359 }
667 1360 })
668 1361 .then(res => {
669   - const body = res.data
670   - let list = []
671   - let total = 0
672   - if (body && typeof body === 'object' && !Array.isArray(body) && body.list !== undefined) {
673   - list = body.list || []
674   - total = body.total != null ? Number(body.total) : 0
  1362 + // 明细接口返回:{ code, msg, data: { list: [], total } }
  1363 + // 兼容历史:{ list, total } 或直接数组
  1364 + const body =
  1365 + res.data && res.data.data != null ? res.data.data : res.data;
  1366 + let list = [];
  1367 + let total = 0;
  1368 + if (
  1369 + body &&
  1370 + typeof body === "object" &&
  1371 + !Array.isArray(body) &&
  1372 + body.list !== undefined
  1373 + ) {
  1374 + list = body.list || [];
  1375 + total = body.total != null ? Number(body.total) : 0;
675 1376 } else if (Array.isArray(body)) {
676   - list = body
677   - total = body.length
  1377 + list = body;
  1378 + total = body.length;
678 1379 }
679   - this.$set(this.lineMap, lk, { list })
680   - this.$set(this.linePager[lk], 'total', total)
  1380 + this.$set(this.lineMap, lk, { list });
  1381 + this.$set(this.linePager[lk], "total", total);
681 1382 })
682 1383 .catch(() => {
683   - this.$set(this.lineMap, lk, { list: [] })
  1384 + this.$set(this.lineMap, lk, { list: [] });
684 1385 })
685 1386 .finally(() => {
686   - this.$set(this.lineLoading, lk, false)
687   - })
  1387 + this.$set(this.lineLoading, lk, false);
  1388 + });
688 1389 },
689 1390 openLinearDialog(catRow) {
690   - const cid = String(catRow['分类Id'] != null ? catRow['分类Id'] : '')
691   - const cname = this.cellText(catRow['分类名称'])
692   - this.linearHint = `分类:${cname}(最多展示 2000 条,与当前筛选条件一致)`
693   - this.linearVisible = true
694   - this.linearLoading = true
695   - this.linearRows = []
  1391 + const cid = String(catRow["分类Id"] != null ? catRow["分类Id"] : "");
  1392 + const cname = this.cellText(catRow["分类名称"]);
  1393 + this.linearHint = `分类:${cname}(默认每页最多 2000 条,与当前筛选条件一致;可传 currentPage、pageSize 分页)`;
  1394 + this.linearVisible = true;
  1395 + this.linearLoading = true;
  1396 + this.linearRows = [];
696 1397 request({
697   - url: '/api/Extend/WtXsckd/Actions/GetPurchaseSummaryLinear',
698   - method: 'GET',
  1398 + url: "/api/Extend/WtXsckd/Actions/GetPurchaseSummaryLinear",
  1399 + method: "GET",
699 1400 data: { ...this.buildBasePayload(), categoryId: cid }
700 1401 })
701 1402 .then(res => {
702   - this.linearRows = Array.isArray(res.data) ? res.data : (res.data && res.data.list) || []
  1403 + this.linearRows = this.normalizeArrayPayload(res.data);
703 1404 })
704 1405 .catch(() => {
705   - this.linearRows = []
  1406 + this.linearRows = [];
706 1407 })
707 1408 .finally(() => {
708   - this.linearLoading = false
709   - })
  1409 + this.linearLoading = false;
  1410 + });
710 1411 },
711 1412 handleSearch() {
712   - this.fetchCategories()
  1413 + this.fetchCategories();
713 1414 },
714 1415 handleReset() {
715 1416 this.filters = {
716 1417 dateRange: [],
717 1418 contactUnit: [],
718 1419 agent: [],
719   - productSpId: '',
  1420 + productSpId: "",
720 1421 warehouse: [],
721   - billType: [...DEFAULT_BILL_TYPES]
  1422 + billType: []
  1423 + };
  1424 + this.productOptions = [];
  1425 + this.fetchCategories();
  1426 + },
  1427 + sortAggRows(rows, prop, order) {
  1428 + if (!rows || !rows.length || !prop || !order) return;
  1429 + const dir = order === "ascending" ? 1 : -1;
  1430 + const numCols = ["数量", "入库单价", "采购金额"];
  1431 + const isNum = numCols.indexOf(prop) >= 0;
  1432 + rows.sort((a, b) => {
  1433 + if (isNum)
  1434 + return (this.parseNum(a[prop]) - this.parseNum(b[prop])) * dir;
  1435 + const sa = String(a[prop] != null ? a[prop] : "").trim();
  1436 + const sb = String(b[prop] != null ? b[prop] : "").trim();
  1437 + return sa.localeCompare(sb, "zh-CN") * dir;
  1438 + });
  1439 + },
  1440 + onCategorySort({ prop, order }) {
  1441 + if (!order) {
  1442 + this.fetchCategories();
  1443 + return;
  1444 + }
  1445 + this.sortAggRows(this.categoryList, prop, order);
  1446 + },
  1447 + onBrandSort(catRow, { prop, order }) {
  1448 + const k = this.categoryRowKey(catRow);
  1449 + const rows = this.brandMap[k];
  1450 + if (!order) {
  1451 + this.loadBrandsForce(catRow);
  1452 + return;
  1453 + }
  1454 + if (rows && rows.length) this.sortAggRows(rows, prop, order);
  1455 + },
  1456 + onProductSort(catRow, brandRow, { prop, order }) {
  1457 + const k = this.brandRowKey(catRow, brandRow);
  1458 + const rows = this.productMap[k];
  1459 + if (!order) {
  1460 + this.loadProductsForce(catRow, brandRow);
  1461 + return;
722 1462 }
723   - this.productOptions = []
724   - this.fetchCategories()
  1463 + if (rows && rows.length) this.sortAggRows(rows, prop, order);
  1464 + },
  1465 + loadBrandsForce(catRow) {
  1466 + const k = this.categoryRowKey(catRow);
  1467 + this.$delete(this.brandMap, k);
  1468 + this.loadBrands(catRow);
  1469 + },
  1470 + loadProductsForce(catRow, brandRow) {
  1471 + const k = this.brandRowKey(catRow, brandRow);
  1472 + this.$delete(this.productMap, k);
  1473 + this.loadProducts(catRow, brandRow);
  1474 + },
  1475 + onLineSort(catRow, brandRow, prodRow, { prop, order }) {
  1476 + const lk = this.lineKey(catRow, brandRow, prodRow);
  1477 + if (!prop || !order) {
  1478 + this.$delete(this.lineSort, lk);
  1479 + this.fetchLines(catRow, brandRow, prodRow, 1);
  1480 + return;
  1481 + }
  1482 + const sortOrder = order === "ascending" ? "asc" : "desc";
  1483 + this.$set(this.lineSort, lk, { sortField: prop, sortOrder });
  1484 + this.fetchLines(catRow, brandRow, prodRow, 1);
725 1485 }
726 1486 }
727   -}
  1487 +};
728 1488 </script>
729 1489  
730 1490 <style lang="scss" scoped>
... ... @@ -747,6 +1507,21 @@ export default {
747 1507 font-size: 18px;
748 1508 }
749 1509  
  1510 +.purchase-sum-head__tools {
  1511 + display: inline-flex;
  1512 + align-items: center;
  1513 + flex-wrap: wrap;
  1514 + gap: 10px;
  1515 +}
  1516 +
  1517 +.linear-mode-label {
  1518 + font-size: 13px;
  1519 + color: #606266;
  1520 + display: inline-flex;
  1521 + align-items: center;
  1522 + gap: 4px;
  1523 +}
  1524 +
750 1525 .purchase-tree-table {
751 1526 ::v-deep .el-table .cell {
752 1527 white-space: nowrap;
... ... @@ -758,8 +1533,28 @@ export default {
758 1533 background: #fafbfc;
759 1534 }
760 1535  
  1536 +.nested-wrap--linear {
  1537 + padding-left: 12px;
  1538 +}
  1539 +
  1540 +.nested-table-host {
  1541 + width: 100%;
  1542 + max-width: 100%;
  1543 + overflow-x: auto;
  1544 + -webkit-overflow-scrolling: touch;
  1545 +}
  1546 +
  1547 +.nested-table-host--detail {
  1548 + min-width: 720px;
  1549 +}
  1550 +
761 1551 .nested-table {
762 1552 width: 100%;
  1553 + min-width: 100%;
  1554 +}
  1555 +
  1556 +.nested-table--scroll {
  1557 + min-width: 960px;
763 1558 }
764 1559  
765 1560 .nested-table--deep ::v-deep .el-table__body-wrapper {
... ... @@ -768,7 +1563,8 @@ export default {
768 1563 }
769 1564  
770 1565 .detail-wrap {
771   - min-width: 720px;
  1566 + min-width: 0;
  1567 + max-width: 100%;
772 1568 }
773 1569  
774 1570 .mini-pager {
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtShrysz/djmcOptions.js
... ... @@ -3,14 +3,16 @@
3 3 * 须与 wt_xsckd.djlx、wt_shrysz.djmc、Dashboard 待办 QueryWtXsckdAuditTodosAsync 的 billDjlx、商品调价单等业务名完全一致。
4 4 */
5 5 export const djmcOptions = [
6   - { fullName: '同价调拨单', id: '同价调拨单' },
7   - { fullName: '销售出库单', id: '销售出库单' },
8   - { fullName: '预售出库单', id: '预售出库单' },
9   - { fullName: '销售退货单', id: '销售退货单' },
10   - { fullName: '预售退货单', id: '预售退货单' },
11   - { fullName: '采购入库单', id: '采购入库单' },
12   - { fullName: '委托代销发货单', id: '委托代销发货单' },
13   - { fullName: '委托代销退货单', id: '委托代销退货单' },
14   - { fullName: '委托代销结算单', id: '委托代销结算单' },
15   - { fullName: '商品调价单', id: '商品调价单' }
16   -]
  6 + { fullName: "同价调拨单", id: "同价调拨单" },
  7 + { fullName: "销售出库单", id: "销售出库单" },
  8 + { fullName: "预售出库单", id: "预售出库单" },
  9 + { fullName: "销售退货单", id: "销售退货单" },
  10 + { fullName: "预售退货单", id: "预售退货单" },
  11 + { fullName: "采购入库单", id: "采购入库单" },
  12 + { fullName: "委托代销发货单", id: "委托代销发货单" },
  13 + { fullName: "委托代销退货单", id: "委托代销退货单" },
  14 + { fullName: "委托代销结算单", id: "委托代销结算单" },
  15 + { fullName: "商品调价单", id: "商品调价单" },
  16 + // 其他收入单(独立主表 wt_yskzjjs,审核配置仍走 wt_shrysz.djmc 匹配 djlx)
  17 + { fullName: "其他应收单", id: "其他应收单" }
  18 +];
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtStockSummary/index.vue
... ... @@ -66,7 +66,7 @@
66 66 <el-col :span="12">
67 67 <el-form-item label="商品">
68 68 <el-select
69   - v-model="filters.productKeyword"
  69 + v-model="filters.productSpId"
70 70 filterable
71 71 remote
72 72 clearable
... ... @@ -80,14 +80,20 @@
80 80 v-for="item in productOptions"
81 81 :key="item.F_Id || item.id"
82 82 :label="formatProductLabel(item)"
83   - :value="formatProductLabel(item)"
  83 + :value="productOptionValue(item)"
84 84 />
85 85 </el-select>
86 86 </el-form-item>
87 87 </el-col>
88 88 <el-col :span="24">
89 89 <div class="action-row">
90   - <el-button type="primary" size="mini" icon="el-icon-search" @click="handleSearch">搜索</el-button>
  90 + <el-button
  91 + type="primary"
  92 + size="mini"
  93 + icon="el-icon-search"
  94 + @click="handleSearch"
  95 + >搜索</el-button
  96 + >
91 97 <el-button size="mini" @click="handleReset">重置</el-button>
92 98 </div>
93 99 </el-col>
... ... @@ -97,12 +103,68 @@
97 103  
98 104 <div class="table-section">
99 105 <div class="table-title-bar">
100   - <i class="el-icon-data-analysis" style="font-size: 20px; margin-right: 8px; color: #409eff" />
  106 + <i
  107 + class="el-icon-data-analysis"
  108 + style="font-size: 20px; margin-right: 8px; color: #409eff"
  109 + />
101 110 <span>商品库存汇总</span>
102   - <span class="hint-text">展开分类行可查看该分类下商品明细(品牌、编码、名称及数量成本)</span>
  111 + <span v-if="!isProductMode" class="hint-text"
  112 + >展开分类行可查看该分类下商品明细(品牌、编码、名称及数量成本)</span
  113 + >
  114 + <span v-else class="hint-text"
  115 + >已选择商品:按仓库展示数量、成本均价、总成本</span
  116 + >
103 117 </div>
104 118 <div class="table-scroll">
  119 + <el-table v-if="isProductMode" :data="list" border class="stock-table">
  120 + <el-table-column
  121 + label="仓库名称"
  122 + min-width="180"
  123 + show-overflow-tooltip
  124 + >
  125 + <template slot-scope="scope">
  126 + <i class="el-icon-office-building row-icon cat" />
  127 + <span>{{ cellText(scope.row["仓库名称"]) }}</span>
  128 + </template>
  129 + </el-table-column>
  130 + <el-table-column label="商品编码" width="140" show-overflow-tooltip>
  131 + <template slot-scope="scope">
  132 + <i class="el-icon-postcard row-icon code" />
  133 + {{ cellText(scope.row["商品编码"]) }}
  134 + </template>
  135 + </el-table-column>
  136 + <el-table-column
  137 + label="商品名称"
  138 + min-width="220"
  139 + show-overflow-tooltip
  140 + >
  141 + <template slot-scope="scope">
  142 + <i class="el-icon-tickets row-icon pname" />
  143 + {{ cellText(scope.row["商品名称"]) }}
  144 + </template>
  145 + </el-table-column>
  146 + <el-table-column label="数量" width="120" align="right">
  147 + <template slot-scope="scope">
  148 + <i class="el-icon-goods row-icon qty" />
  149 + {{ formatQty(scope.row["数量"]) }}
  150 + </template>
  151 + </el-table-column>
  152 + <el-table-column label="成本均价" width="130" align="right">
  153 + <template slot-scope="scope">
  154 + <i class="el-icon-coin row-icon money" />
  155 + {{ formatMoney(scope.row["成本均价"]) }}
  156 + </template>
  157 + </el-table-column>
  158 + <el-table-column label="总成本" width="140" align="right">
  159 + <template slot-scope="scope">
  160 + <i class="el-icon-wallet row-icon total" />
  161 + {{ formatMoney(scope.row["总成本"]) }}
  162 + </template>
  163 + </el-table-column>
  164 + </el-table>
  165 +
105 166 <el-table
  167 + v-else
106 168 ref="categoryTable"
107 169 :data="list"
108 170 :row-key="rowKeyResolver"
... ... @@ -126,45 +188,60 @@
126 188 size="small"
127 189 class="nested-product-table"
128 190 >
129   - <el-table-column label="品牌名称" width="120" show-overflow-tooltip>
  191 + <el-table-column
  192 + label="品牌名称"
  193 + width="120"
  194 + show-overflow-tooltip
  195 + >
130 196 <template slot-scope="s">
131 197 <i class="el-icon-medal row-icon brand" />
132   - {{ cellText(s.row['品牌名称']) }}
  198 + {{ cellText(s.row["品牌名称"]) }}
133 199 </template>
134 200 </el-table-column>
135   - <el-table-column label="商品编码" width="120" show-overflow-tooltip>
  201 + <el-table-column
  202 + label="商品编码"
  203 + width="120"
  204 + show-overflow-tooltip
  205 + >
136 206 <template slot-scope="s">
137 207 <i class="el-icon-postcard row-icon code" />
138   - {{ cellText(s.row['商品编码']) }}
  208 + {{ cellText(s.row["商品编码"]) }}
139 209 </template>
140 210 </el-table-column>
141   - <el-table-column label="商品名称" min-width="200" show-overflow-tooltip>
  211 + <el-table-column
  212 + label="商品名称"
  213 + min-width="200"
  214 + show-overflow-tooltip
  215 + >
142 216 <template slot-scope="s">
143 217 <i class="el-icon-tickets row-icon pname" />
144   - {{ cellText(s.row['商品名称']) }}
  218 + {{ cellText(s.row["商品名称"]) }}
145 219 </template>
146 220 </el-table-column>
147 221 <el-table-column label="数量" width="100" align="right">
148 222 <template slot-scope="s">
149 223 <i class="el-icon-goods row-icon qty" />
150   - {{ formatQty(s.row['数量']) }}
  224 + {{ formatQty(s.row["数量"]) }}
151 225 </template>
152 226 </el-table-column>
153 227 <el-table-column label="成本均价" width="110" align="right">
154 228 <template slot-scope="s">
155 229 <i class="el-icon-coin row-icon money" />
156   - {{ formatMoney(s.row['成本均价']) }}
  230 + {{ formatMoney(s.row["成本均价"]) }}
157 231 </template>
158 232 </el-table-column>
159 233 <el-table-column label="总成本" width="120" align="right">
160 234 <template slot-scope="s">
161 235 <i class="el-icon-wallet row-icon total" />
162   - {{ formatMoney(s.row['总成本']) }}
  236 + {{ formatMoney(s.row["总成本"]) }}
163 237 </template>
164 238 </el-table-column>
165 239 </el-table>
166 240 <div
167   - v-if="(productDetailMap[expandKey(props.row)] || []).length === 0 && !expandLoading[expandKey(props.row)]"
  241 + v-if="
  242 + (productDetailMap[expandKey(props.row)] || []).length ===
  243 + 0 && !expandLoading[expandKey(props.row)]
  244 + "
168 245 class="expand-empty"
169 246 >
170 247 暂无商品明细
... ... @@ -173,34 +250,44 @@
173 250 </div>
174 251 </template>
175 252 </el-table-column>
176   - <el-table-column label="分类名称" min-width="200" show-overflow-tooltip>
  253 + <el-table-column
  254 + label="分类名称"
  255 + min-width="200"
  256 + show-overflow-tooltip
  257 + >
177 258 <template slot-scope="scope">
178 259 <i class="el-icon-folder-opened row-icon cat" />
179   - <span>{{ cellText(scope.row['分类名称']) }}</span>
  260 + <span>{{ cellText(scope.row["分类名称"]) }}</span>
180 261 </template>
181 262 </el-table-column>
182 263 <el-table-column label="数量" width="120" align="right">
183 264 <template slot-scope="scope">
184 265 <i class="el-icon-goods row-icon qty" />
185   - {{ formatQty(scope.row['数量']) }}
  266 + {{ formatQty(scope.row["数量"]) }}
186 267 </template>
187 268 </el-table-column>
188 269 <el-table-column label="成本均价" width="130" align="right">
189 270 <template slot-scope="scope">
190 271 <i class="el-icon-coin row-icon money" />
191   - {{ formatMoney(scope.row['成本均价']) }}
  272 + {{ formatMoney(scope.row["成本均价"]) }}
192 273 </template>
193 274 </el-table-column>
194 275 <el-table-column label="总成本" width="140" align="right">
195 276 <template slot-scope="scope">
196 277 <i class="el-icon-wallet row-icon total" />
197   - {{ formatMoney(scope.row['总成本']) }}
  278 + {{ formatMoney(scope.row["总成本"]) }}
198 279 </template>
199 280 </el-table-column>
200 281 </el-table>
201 282  
202 283 <div class="summary-footer">
203   - <span><i class="el-icon-s-data" style="color: #409eff; margin-right: 6px" />合计(分类汇总)</span>
  284 + <span>
  285 + <i
  286 + class="el-icon-s-data"
  287 + style="color: #409eff; margin-right: 6px"
  288 + />
  289 + {{ isProductMode ? "合计(按仓库)" : "合计(分类汇总)" }}
  290 + </span>
204 291 <span class="sum-item">数量:{{ totalQty }}</span>
205 292 <span class="sum-item">总成本:{{ totalCost }}</span>
206 293 </div>
... ... @@ -210,10 +297,10 @@
210 297 </template>
211 298  
212 299 <script>
213   -import request from '@/utils/request'
  300 +import request from "@/utils/request";
214 301  
215 302 export default {
216   - name: 'StockSummary',
  303 + name: "StockSummary",
217 304 data() {
218 305 return {
219 306 list: [],
... ... @@ -224,183 +311,225 @@ export default {
224 311 warehouse: [],
225 312 brand: [],
226 313 category: [],
227   - productKeyword: ''
  314 + productSpId: ""
228 315 },
229 316 warehouseOptions: [],
230 317 brandOptions: [],
231 318 categoryOptions: [],
232 319 productOptions: [],
233 320 productLoading: false
234   - }
  321 + };
235 322 },
236 323 computed: {
  324 + isProductMode() {
  325 + return !!this.filters.productSpId;
  326 + },
237 327 totalQty() {
238   - return this.list.reduce((s, r) => s + Number(r['数量'] || 0), 0)
  328 + return this.list.reduce((s, r) => s + Number(r["数量"] || 0), 0);
239 329 },
240 330 totalCost() {
241   - const n = this.list.reduce((s, r) => s + Number(r['总成本'] || 0), 0)
242   - return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  331 + const n = this.list.reduce((s, r) => s + Number(r["总成本"] || 0), 0);
  332 + return n.toLocaleString("zh-CN", {
  333 + minimumFractionDigits: 2,
  334 + maximumFractionDigits: 2
  335 + });
243 336 }
244 337 },
245 338 methods: {
  339 + normalizeArrayPayload(data) {
  340 + if (Array.isArray(data)) return data;
  341 + if (data && Array.isArray(data.list)) return data.list;
  342 + if (data && data.data && Array.isArray(data.data.list))
  343 + return data.data.list;
  344 + if (data && Array.isArray(data.data)) return data.data;
  345 + return [];
  346 + },
246 347 rowKeyResolver(row) {
247   - const id = row['分类Id']
248   - if (id !== null && id !== undefined && id !== '') {
249   - return `cat-${id}`
  348 + const id = row["分类Id"];
  349 + if (id !== null && id !== undefined && id !== "") {
  350 + return `cat-${id}`;
250 351 }
251   - return row._rowUid || 'cat-unknown'
  352 + return row._rowUid || "cat-unknown";
252 353 },
253 354 expandKey(row) {
254   - return this.rowKeyResolver(row)
  355 + return this.rowKeyResolver(row);
255 356 },
256 357 categoryIdOf(row) {
257   - const id = row['分类Id']
258   - if (id === null || id === undefined || id === '') return null
259   - return String(id)
  358 + const id = row["分类Id"];
  359 + if (id === null || id === undefined || id === "") return null;
  360 + return String(id);
260 361 },
261 362 cellText(v) {
262   - if (v === null || v === undefined || v === '') return '无'
263   - return String(v)
  363 + if (v === null || v === undefined || v === "") return "无";
  364 + return String(v);
264 365 },
265 366 formatQty(v) {
266   - if (v === null || v === undefined || v === '') return '无'
267   - const n = Number(v)
268   - if (Number.isNaN(n)) return String(v)
269   - return n.toLocaleString('zh-CN', { maximumFractionDigits: 4 })
  367 + if (v === null || v === undefined || v === "") return "无";
  368 + const n = Number(v);
  369 + if (Number.isNaN(n)) return String(v);
  370 + return n.toLocaleString("zh-CN", { maximumFractionDigits: 4 });
270 371 },
271 372 formatMoney(v) {
272   - if (v === null || v === undefined || v === '') return '无'
273   - const n = Number(v)
274   - if (Number.isNaN(n)) return String(v)
275   - return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 4 })
  373 + if (v === null || v === undefined || v === "") return "无";
  374 + const n = Number(v);
  375 + if (Number.isNaN(n)) return String(v);
  376 + return n.toLocaleString("zh-CN", {
  377 + minimumFractionDigits: 2,
  378 + maximumFractionDigits: 4
  379 + });
276 380 },
277 381 formatProductLabel(item) {
278   - if (!item) return ''
279   - const code = item.F_Spbm || item.spbm || item.商品编号 || ''
280   - const name = item.F_Spmc || item.spmc || item.商品名称 || ''
281   - return [code, name].filter(Boolean).join(' ')
  382 + if (!item) return "";
  383 + const code = item.F_Spbm || item.spbm || item.商品编号 || "";
  384 + const name = item.F_Spmc || item.spmc || item.商品名称 || "";
  385 + return [code, name].filter(Boolean).join(" ");
  386 + },
  387 + productOptionValue(item) {
  388 + if (!item) return "";
  389 + const v = item.id || item.F_Id || item.f_Id;
  390 + return v != null && String(v).trim() !== "" ? String(v).trim() : "";
282 391 },
283 392 async loadOptions() {
284 393 try {
285 394 const [ckRes, plRes, ppRes] = await Promise.all([
286   - request({ url: '/api/Extend/WtCk', method: 'GET', data: { pageSize: 1000, currentPage: 1 } }),
287   - request({ url: '/api/Extend/WtPl', method: 'GET', data: { pageSize: 1000, currentPage: 1 } }),
288   - request({ url: '/api/Extend/WtPp', method: 'GET', data: { pageSize: 1000, currentPage: 1 } })
289   - ])
290   - const cks = ckRes.data.list || ckRes.data || []
  395 + request({
  396 + url: "/api/Extend/WtCk",
  397 + method: "GET",
  398 + data: { pageSize: 1000, currentPage: 1 }
  399 + }),
  400 + request({
  401 + url: "/api/Extend/WtPl",
  402 + method: "GET",
  403 + data: { pageSize: 1000, currentPage: 1 }
  404 + }),
  405 + request({
  406 + url: "/api/Extend/WtPp",
  407 + method: "GET",
  408 + data: { pageSize: 1000, currentPage: 1 }
  409 + })
  410 + ]);
  411 + const cks = ckRes.data.list || ckRes.data || [];
291 412 this.warehouseOptions = cks.map(x => ({
292 413 label: x.mdmc || x.F_mdmc || x.id,
293 414 value: x.id || x.F_Id
294   - }))
295   - const pls = plRes.data.list || plRes.data || []
  415 + }));
  416 + const pls = plRes.data.list || plRes.data || [];
296 417 this.categoryOptions = pls.map(x => ({
297 418 label: x.plmc || x.F_Plmc || x.id,
298 419 value: x.id || x.F_Id
299   - }))
300   - const pps = ppRes.data.list || ppRes.data || []
  420 + }));
  421 + const pps = ppRes.data.list || ppRes.data || [];
301 422 this.brandOptions = pps.map(x => ({
302 423 label: x.ppmc || x.F_Ppmc || x.id,
303 424 value: x.id || x.F_Id
304   - }))
  425 + }));
305 426 } catch (e) {
306   - console.error('库存汇总筛选项加载失败', e)
  427 + console.error("库存汇总筛选项加载失败", e);
307 428 }
308 429 },
309 430 async handleProductSearch(query) {
310 431 if (!query) {
311   - this.productOptions = []
312   - return
  432 + this.productOptions = [];
  433 + return;
313 434 }
314   - this.productLoading = true
  435 + this.productLoading = true;
315 436 try {
316 437 const res = await request({
317   - url: '/api/Extend/WtSp',
318   - method: 'GET',
319   - data: { pageSize: 200, currentPage: 1, keyword: query }
320   - })
321   - this.productOptions = res.data.list || res.data || []
  438 + url: "/api/Extend/WtSp/GetListByKeyword",
  439 + method: "GET",
  440 + data: { pageSize: 200, currentPage: 1, keyword: String(query).trim() }
  441 + });
  442 + this.productOptions = this.normalizeArrayPayload(res.data);
322 443 } catch (e) {
323   - console.error(e)
  444 + console.error(e);
324 445 } finally {
325   - this.productLoading = false
  446 + this.productLoading = false;
326 447 }
327 448 },
328 449 buildPayload() {
329   - const p = {}
330   - if (this.filters.warehouse.length) p.warehouse = this.filters.warehouse.join(',')
331   - if (this.filters.brand.length) p.brand = this.filters.brand.join(',')
332   - if (this.filters.category.length) p.category = this.filters.category.join(',')
333   - if (this.filters.productKeyword) p.product = this.filters.productKeyword
334   - return p
  450 + const p = {};
  451 + if (this.filters.warehouse.length)
  452 + p.warehouse = this.filters.warehouse.join(",");
  453 + if (this.filters.brand.length) p.brand = this.filters.brand.join(",");
  454 + if (this.filters.category.length)
  455 + p.category = this.filters.category.join(",");
  456 + if (this.filters.productSpId) p.productSpId = this.filters.productSpId;
  457 + return p;
335 458 },
336 459 normalizeCategoryRows(rows) {
337   - const arr = rows || []
  460 + const arr = rows || [];
338 461 return arr.map((r, i) => {
339   - const hasId = r['分类Id'] !== null && r['分类Id'] !== undefined && r['分类Id'] !== ''
340   - return hasId ? { ...r } : { ...r, _rowUid: `no-pl-${i}` }
341   - })
  462 + const hasId =
  463 + r["分类Id"] !== null &&
  464 + r["分类Id"] !== undefined &&
  465 + r["分类Id"] !== "";
  466 + return hasId ? { ...r } : { ...r, _rowUid: `no-pl-${i}` };
  467 + });
342 468 },
343 469 fetchData() {
344   - this.productDetailMap = {}
345   - this.productDetailLoaded = {}
346   - this.expandLoading = {}
347   - request({
348   - url: '/api/Extend/WtXsckd/Actions/GetStockSummaryByCategory',
349   - method: 'GET',
350   - data: this.buildPayload()
351   - }).then(res => {
352   - this.list = this.normalizeCategoryRows(res.data || [])
353   - })
  470 + this.productDetailMap = {};
  471 + this.productDetailLoaded = {};
  472 + this.expandLoading = {};
  473 + const payload = this.buildPayload();
  474 + const url = this.isProductMode
  475 + ? "/api/Extend/WtXsckd/Actions/GetStockSummaryByWarehouse"
  476 + : "/api/Extend/WtXsckd/Actions/GetStockSummaryByCategory";
  477 + request({ url, method: "GET", data: payload }).then(res => {
  478 + const rows = this.normalizeArrayPayload(res.data);
  479 + this.list = this.isProductMode
  480 + ? rows
  481 + : this.normalizeCategoryRows(rows);
  482 + });
354 483 },
355 484 async loadProductDetailsForRow(row) {
356   - const cid = this.categoryIdOf(row)
357   - const key = this.expandKey(row)
358   - if (!cid) return
359   - if (this.productDetailLoaded[key]) return
360   - this.$set(this.expandLoading, key, true)
  485 + const cid = this.categoryIdOf(row);
  486 + const key = this.expandKey(row);
  487 + if (!cid) return;
  488 + if (this.productDetailLoaded[key]) return;
  489 + this.$set(this.expandLoading, key, true);
361 490 try {
362   - const payload = { ...this.buildPayload(), categoryId: cid }
  491 + const payload = { ...this.buildPayload(), categoryId: cid };
363 492 const res = await request({
364   - url: '/api/Extend/WtXsckd/Actions/GetStockSummaryByProduct',
365   - method: 'GET',
  493 + url: "/api/Extend/WtXsckd/Actions/GetStockSummaryByProduct",
  494 + method: "GET",
366 495 data: payload
367   - })
368   - const rows = res.data || []
369   - this.$set(this.productDetailMap, key, rows)
370   - this.$set(this.productDetailLoaded, key, true)
  496 + });
  497 + const rows = res.data || [];
  498 + this.$set(this.productDetailMap, key, rows);
  499 + this.$set(this.productDetailLoaded, key, true);
371 500 } catch (e) {
372   - console.error(e)
373   - this.$set(this.productDetailMap, key, [])
374   - this.$set(this.productDetailLoaded, key, true)
  501 + console.error(e);
  502 + this.$set(this.productDetailMap, key, []);
  503 + this.$set(this.productDetailLoaded, key, true);
375 504 } finally {
376   - this.$set(this.expandLoading, key, false)
  505 + this.$set(this.expandLoading, key, false);
377 506 }
378 507 },
379 508 onExpandChange(row, expandedRows) {
380   - const key = this.expandKey(row)
381   - const opened = expandedRows.some(r => this.expandKey(r) === key)
382   - if (!opened) return
383   - if (!this.categoryIdOf(row)) return
384   - this.loadProductDetailsForRow(row)
  509 + const key = this.expandKey(row);
  510 + const opened = expandedRows.some(r => this.expandKey(r) === key);
  511 + if (!opened) return;
  512 + if (!this.categoryIdOf(row)) return;
  513 + this.loadProductDetailsForRow(row);
385 514 },
386 515 handleSearch() {
387   - this.fetchData()
  516 + this.fetchData();
388 517 },
389 518 handleReset() {
390 519 this.filters = {
391 520 warehouse: [],
392 521 brand: [],
393 522 category: [],
394   - productKeyword: ''
395   - }
396   - this.fetchData()
  523 + productSpId: ""
  524 + };
  525 + this.fetchData();
397 526 }
398 527 },
399 528 created() {
400   - this.loadOptions()
401   - this.fetchData()
  529 + this.loadOptions();
  530 + this.fetchData();
402 531 }
403   -}
  532 +};
404 533 </script>
405 534  
406 535 <style scoped>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtYskzjjs_qt/Form.vue
1 1 <template>
2   - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%">
3   - <el-row :gutter="15" class="" >
4   - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules">
5   - <el-col :span="8">
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>
11   - <el-col :span="8">
12   - <el-form-item label="单据日期" prop="djrq">
13   - <el-date-picker v-model="dataForm.djrq" placeholder="请选择" clearable :style='{"width":"100%"}' type='date' format="yyyy-MM-dd" value-format="timestamp" >
14   - </el-date-picker>
15   - </el-form-item>
16   - </el-col>
17   - <el-col :span="8">
18   - <el-form-item label="经手人" prop="jsr">
19   - <user-select v-model="dataForm.jsr" placeholder="请选择" clearable >
20   - </user-select>
21   - </el-form-item>
22   - </el-col>
23   - <el-col :span="8">
24   - <el-form-item label="往来单位" prop="wldw">
25   - <el-select v-model="dataForm.wldw" placeholder="请选择" clearable filterable :style='{"width":"100%"}'>
26   - <el-option v-for="(item, index) in wldwOptions" :key="index" :label="item.dwmc" :value="item.id" :disabled="item.disabled"></el-option>
27   - </el-select>
28   - </el-form-item>
29   - </el-col>
30   - <el-col :span="24">
31   - <el-form-item label-width="0">
32   - <el-table :data="dataForm.wtYskzjjsMxList" size='mini'>
33   - <el-table-column type="index" width="50" label="序号" align="center" />
34   - <el-table-column prop="srxmmc" label="收入项目">
35   - <template slot-scope="scope">
36   - <el-select
37   - v-model="scope.row.srxmmc"
38   - placeholder="请选择或输入"
39   - clearable
40   - filterable
41   - allow-create
42   - default-first-option
43   - >
44   - <el-option v-for="(item, index) in srxmmcOptions" :key="index" :label="item.fullName" :value="item.id" :disabled="item.disabled"></el-option>
45   - </el-select>
46   - </template>
47   - </el-table-column>
48   - <el-table-column prop="je" label="原币金额">
49   - <template slot-scope="scope">
50   - <el-input-number
51   - v-model.number="scope.row.je"
52   - placeholder="请输入"
53   - :min="0"
54   - :precision="2"
55   - controls-position="right"
56   - style="width: 100%"
57   - @change="calculateTotalAmount"
58   - />
59   - </template>
60   - </el-table-column>
61   - <el-table-column prop="bz" label="备注">
62   - <template slot-scope="scope">
63   - <el-input v-model="scope.row.bz" placeholder="请输入" clearable ></el-input>
64   - </template>
65   - </el-table-column>
66   - <el-table-column label="操作" width="50">
67   - <template slot-scope="scope">
68   - <el-button size="mini" type="text" class="NCC-table-delBtn" @click="handleDelWtYskzjjsMxEntityList(scope.$index)">删除</el-button>
69   - </template>
70   - </el-table-column>
71   - </el-table>
72   - <div class="table-actions" @click="addHandleWtYskzjjsMxEntityList()">
73   - <el-button type="text" icon="el-icon-plus">新增</el-button>
74   - </div>
75   - </el-form-item>
76   - </el-col>
77   - <!-- 保留收款账户和收款金额,隐藏付款账户和付款金额 -->
78   - <!--
79   - <el-col :span="6">
80   - <el-form-item label="付款账户" prop="fkzh">
81   - <el-select v-model="dataForm.fkzh" placeholder="请选择" clearable :style='{"width":"100%"}' >
82   - <el-option v-for="(item, index) in fkzhOptions" :key="index" :label="item.fullName" :value="item.id" ></el-option>
83   - </el-select>
84   - </el-form-item>
85   - </el-col>
86   - <el-col :span="6">
87   - <el-form-item label="付款金额" prop="fkje">
88   - <el-input v-model="dataForm.fkje" placeholder="请输入" clearable :style='{"width":"100%"}' >
89   - </el-input>
90   - </el-form-item>
91   - </el-col>
92   - -->
93   - <el-col :span="6">
94   - <el-form-item label="收款账户" prop="skzh">
95   - <el-select v-model="dataForm.skzh" placeholder="请选择" clearable :style='{"width":"100%"}' >
96   - <el-option v-for="(item, index) in skzhOptions" :key="index" :label="item.fullName" :value="item.id" ></el-option>
97   - </el-select>
98   - </el-form-item>
99   - </el-col>
100   - <el-col :span="6">
101   - <el-form-item label="收款金额" prop="skje">
102   - <el-input v-model="dataForm.skje" placeholder="请输入" clearable :style='{"width":"100%"}' >
103   - </el-input>
104   - </el-form-item>
105   - </el-col>
106   - <el-col :span="24" v-if="false" >
107   - <el-form-item label="单据类型" prop="djlx">
108   - <el-input v-model="dataForm.djlx" placeholder="请输入" clearable :style='{"width":"100%"}' >
109   - </el-input>
110   - </el-form-item>
111   - </el-col>
112   - </el-form>
113   - </el-row>
114   - <span slot="footer" class="dialog-footer">
115   - <el-button @click="visible = false">取 消</el-button>
116   - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button>
117   - </span>
118   - </el-dialog>
  2 + <el-dialog
  3 + :title="!dataForm.id ? '新建' : isDetail ? '详情' : '编辑'"
  4 + :close-on-click-modal="false"
  5 + :visible.sync="visible"
  6 + class="NCC-dialog NCC-dialog_center"
  7 + lock-scroll
  8 + width="90%"
  9 + >
  10 + <el-row :gutter="15" class="">
  11 + <el-form
  12 + ref="elForm"
  13 + :model="dataForm"
  14 + size="small"
  15 + label-width="100px"
  16 + label-position="right"
  17 + :disabled="!!isDetail"
  18 + :rules="rules"
  19 + >
  20 + <el-col :span="8">
  21 + <el-form-item label="单据编号" prop="id">
  22 + <el-input
  23 + v-model="dataForm.id"
  24 + placeholder="请输入"
  25 + clearable
  26 + readonly
  27 + :style="{ width: '100%' }"
  28 + >
  29 + </el-input>
  30 + </el-form-item>
  31 + </el-col>
  32 + <el-col :span="8">
  33 + <el-form-item label="单据日期" prop="djrq">
  34 + <el-date-picker
  35 + v-model="dataForm.djrq"
  36 + placeholder="请选择"
  37 + clearable
  38 + :style="{ width: '100%' }"
  39 + type="date"
  40 + format="yyyy-MM-dd"
  41 + value-format="timestamp"
  42 + >
  43 + </el-date-picker>
  44 + </el-form-item>
  45 + </el-col>
  46 + <el-col :span="8">
  47 + <el-form-item label="经手人" prop="jsr">
  48 + <user-select v-model="dataForm.jsr" placeholder="请选择" clearable>
  49 + </user-select>
  50 + </el-form-item>
  51 + </el-col>
  52 + <el-col :span="8">
  53 + <el-form-item label="往来单位" prop="wldw">
  54 + <el-select
  55 + v-model="dataForm.wldw"
  56 + placeholder="输入关键字搜索往来单位"
  57 + clearable
  58 + filterable
  59 + remote
  60 + reserve-keyword
  61 + :remote-method="remoteWldw"
  62 + :loading="wldwLoading"
  63 + :style="{ width: '100%' }"
  64 + @change="onWldwChange"
  65 + >
  66 + <el-option
  67 + v-for="item in wldwOptions"
  68 + :key="'w' + item.id"
  69 + :label="item.dwmc"
  70 + :value="String(item.id)"
  71 + />
  72 + </el-select>
  73 + </el-form-item>
  74 + </el-col>
  75 + <el-col :span="8" v-if="dataForm.id">
  76 + <el-form-item label="审核状态">
  77 + <el-input
  78 + :value="dataForm.djzt || '无'"
  79 + readonly
  80 + :style="{ width: '100%' }"
  81 + />
  82 + </el-form-item>
  83 + </el-col>
  84 + <el-col :span="24">
  85 + <el-form-item label-width="0" prop="wtYskzjjsMxList">
  86 + <el-table :data="dataForm.wtYskzjjsMxList" size="mini">
  87 + <el-table-column
  88 + type="index"
  89 + width="50"
  90 + label="序号"
  91 + align="center"
  92 + />
  93 + <el-table-column prop="srxmmc" label="收入项目">
  94 + <template slot-scope="scope">
  95 + <el-select
  96 + v-model="scope.row.srxmmc"
  97 + placeholder="请选择或输入"
  98 + clearable
  99 + filterable
  100 + allow-create
  101 + default-first-option
  102 + >
  103 + <el-option
  104 + v-for="(item, index) in srxmmcOptions"
  105 + :key="index"
  106 + :label="item.fullName"
  107 + :value="item.id"
  108 + :disabled="item.disabled"
  109 + ></el-option>
  110 + </el-select>
  111 + </template>
  112 + </el-table-column>
  113 + <el-table-column prop="je" label="原币金额">
  114 + <template slot-scope="scope">
  115 + <el-input-number
  116 + v-model.number="scope.row.je"
  117 + placeholder="请输入"
  118 + :min="0"
  119 + :precision="2"
  120 + controls-position="right"
  121 + style="width: 100%"
  122 + @change="calculateTotalAmount"
  123 + />
  124 + </template>
  125 + </el-table-column>
  126 + <el-table-column prop="bz" label="备注">
  127 + <template slot-scope="scope">
  128 + <el-input
  129 + v-model="scope.row.bz"
  130 + placeholder="请输入"
  131 + clearable
  132 + ></el-input>
  133 + </template>
  134 + </el-table-column>
  135 + <el-table-column label="操作" width="50">
  136 + <template slot-scope="scope">
  137 + <el-button
  138 + size="mini"
  139 + type="text"
  140 + class="NCC-table-delBtn"
  141 + @click="handleDelWtYskzjjsMxEntityList(scope.$index)"
  142 + >删除</el-button
  143 + >
  144 + </template>
  145 + </el-table-column>
  146 + </el-table>
  147 + <div
  148 + class="table-actions"
  149 + @click="addHandleWtYskzjjsMxEntityList()"
  150 + >
  151 + <el-button type="text" icon="el-icon-plus">新增</el-button>
  152 + </div>
  153 + </el-form-item>
  154 + </el-col>
  155 + <el-col :span="6">
  156 + <el-form-item label="收款账户" prop="skzh">
  157 + <el-select
  158 + v-model="dataForm.skzh"
  159 + placeholder="请选择"
  160 + clearable
  161 + :style="{ width: '100%' }"
  162 + >
  163 + <el-option
  164 + v-for="(item, index) in skzhOptions"
  165 + :key="index"
  166 + :label="item.fullName"
  167 + :value="item.id"
  168 + ></el-option>
  169 + </el-select>
  170 + </el-form-item>
  171 + </el-col>
  172 + <el-col :span="6">
  173 + <el-form-item label="收款金额" prop="skje">
  174 + <el-input
  175 + v-model="dataForm.skje"
  176 + placeholder="根据明细合计"
  177 + clearable
  178 + readonly
  179 + :style="{ width: '100%' }"
  180 + >
  181 + </el-input>
  182 + </el-form-item>
  183 + </el-col>
  184 + <el-col :span="24" v-if="false">
  185 + <el-form-item label="单据类型" prop="djlx">
  186 + <el-input
  187 + v-model="dataForm.djlx"
  188 + placeholder="请输入"
  189 + clearable
  190 + :style="{ width: '100%' }"
  191 + >
  192 + </el-input>
  193 + </el-form-item>
  194 + </el-col>
  195 + </el-form>
  196 + </el-row>
  197 + <span slot="footer" class="dialog-footer">
  198 + <el-button @click="visible = false">取 消</el-button>
  199 + <el-button type="default" @click="saveDraft" v-if="!isDetail"
  200 + >保存草稿</el-button
  201 + >
  202 + <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail"
  203 + >提交审核</el-button
  204 + >
  205 + </span>
  206 + </el-dialog>
119 207 </template>
120 208 <script>
121   - import request from '@/utils/request'
122   - import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
123   - import { getAccountSelector } from '@/api/extend/wtAccount'
124   - export default {
125   - components: {},
126   - props: [],
127   - data() {
128   - return {
129   - loading: false,
130   - visible: false,
131   - isDetail: false,
132   - dataForm: {
133   - id:'',
134   - id:undefined,
135   - djrq:undefined,
136   - jsr:undefined,
137   - wldw: undefined, // 新增
138   - wtYskzjjsMxList:[],
139   - fkzh:undefined,
140   - fkje:undefined,
141   - skzh:undefined,
142   - skje:undefined,
143   - djlx:undefined,
144   - },
145   - rules: {
146   - },
147   - srxmmcOptions : [],
148   - fkzhOptions : [],
149   - skzhOptions : [],
150   - wldwOptions : [],
151   - }
152   - },
153   - computed: {},
154   - watch: {},
155   - created() {
156   - this.getsrxmmcOptions();
157   - this.getfkzhOptions();
158   - this.getskzhOptions();
159   - this.getWldwOptions();
160   - // 新建时设置默认值
161   - if (!this.dataForm.id) {
162   - // 单据日期
163   - this.dataForm.djrq = Date.now();
164   - // 经手人
165   - if (this.$store && this.$store.getters && this.$store.getters.userInfo) {
166   - this.dataForm.jsr = this.$store.getters.userInfo.userId || this.$store.getters.userInfo.id;
167   - }
168   - // 单据类型
169   - this.dataForm.djlx = '其他应收款';
170   - }
171   - },
172   - mounted() {
173   - },
174   - methods: {
175   - getsrxmmcOptions(){
176   - getDictionaryDataSelector('715562947862070533').then(res => {
177   - this.srxmmcOptions = res.data.list
178   - });
179   - },
180   - getfkzhOptions(){
181   - getAccountSelector().then(res => {
182   - this.fkzhOptions = res.data.list
183   - });
184   - },
185   - getskzhOptions(){
186   - getAccountSelector().then(res => {
187   - this.skzhOptions = res.data.list
188   - });
189   - },
190   - getWldwOptions(){
191   - request({
192   - url: '/api/Extend/WtWldw',
193   - method: 'GET',
194   - data: { currentPage: 1, pageSize: 5000 }
195   - }).then(res => {
196   - this.wldwOptions = res.data.list || res.data || []
197   - })
198   - },
199   - // 计算明细表原币金额合计
200   - calculateTotalAmount() {
201   - let totalAmount = 0;
202   - if (this.dataForm.wtYskzjjsMxList && this.dataForm.wtYskzjjsMxList.length > 0) {
203   - this.dataForm.wtYskzjjsMxList.forEach(row => {
204   - const je = parseFloat(row.je) || 0;
205   - totalAmount += je;
206   - });
207   - }
208   - // 更新收款金额
209   - this.dataForm.skje = totalAmount.toFixed(2);
210   - },
211   - goBack() {
212   - this.$emit('refresh')
213   - },
214   - init(id, isDetail) {
215   - this.dataForm.id = id || 0;
216   - this.visible = true;
217   - this.isDetail = isDetail || false;
218   - this.$nextTick(() => {
219   - this.$refs['elForm'].resetFields();
220   - if (this.dataForm.id) {
221   - request({
222   - url: '/api/Extend/WtYskzjjs/' + this.dataForm.id,
223   - method: 'get'
224   - }).then(res =>{
225   - this.dataForm = res.data;
226   - // 编辑时计算合计
227   - this.$nextTick(() => {
228   - this.calculateTotalAmount();
229   - });
230   - })
231   - } else {
232   - // resetFields后再赋值,确保djlx不会被清空
233   - this.dataForm.djlx = '其他应收款';
234   - }
235   - })
236   - },
237   - dataFormSubmit() {
238   - this.$refs['elForm'].validate((valid) => {
239   - if (valid) {
240   - if (!this.dataForm.id) {
241   - request({
242   - url: `/api/Extend/WtYskzjjs`,
243   - method: 'post',
244   - data: this.dataForm,
245   - }).then((res) => {
246   - this.$message({
247   - message: res.msg,
248   - type: 'success',
249   - duration: 1000,
250   - onClose: () => {
251   - this.visible = false,
252   - this.$emit('refresh', true)
253   - }
254   - })
255   - })
256   - } else {
257   - request({
258   - url: '/api/Extend/WtYskzjjs/' + this.dataForm.id,
259   - method: 'PUT',
260   - data: this.dataForm
261   - }).then((res) => {
262   - this.$message({
263   - message: res.msg,
264   - type: 'success',
265   - duration: 1000,
266   - onClose: () => {
267   - this.visible = false
268   - this.$emit('refresh', true)
269   - }
270   - })
271   - })
272   - }
273   - }
274   - })
275   - },
276   - addHandleWtYskzjjsMxEntityList() {
277   - let item = {
278   - srxmmc: undefined,
279   - je: undefined,
280   - bz: undefined,
281   - mxlx: '其他收入单',
282   - }
283   - this.dataForm.wtYskzjjsMxList.push(item)
284   - // 新增明细行后重新计算合计
285   - this.$nextTick(() => {
286   - this.calculateTotalAmount();
287   - });
288   - },
289   - handleDelWtYskzjjsMxEntityList(index) {
290   - this.dataForm.wtYskzjjsMxList.splice(index, 1);
291   - // 删除明细行后重新计算合计
292   - this.$nextTick(() => {
293   - this.calculateTotalAmount();
294   - });
295   - },
296   - }
297   - }
  209 +import request from "@/utils/request";
  210 +import { getDictionaryDataSelector } from "@/api/systemData/dictionary";
  211 +import { getAccountSelector } from "@/api/extend/wtAccount";
  212 +export default {
  213 + components: {},
  214 + props: [],
  215 + data() {
  216 + const validateSkje = (rule, value, callback) => {
  217 + const n = parseFloat(value);
  218 + if (
  219 + value === "" ||
  220 + value === undefined ||
  221 + value === null ||
  222 + isNaN(n) ||
  223 + n <= 0
  224 + ) {
  225 + callback(new Error("请填写明细金额,收款金额须大于 0"));
  226 + } else {
  227 + callback();
  228 + }
  229 + };
  230 + const validateMxList = (rule, value, callback) => {
  231 + const list = value || [];
  232 + if (!list.length) {
  233 + callback(new Error("请至少添加一条明细"));
  234 + return;
  235 + }
  236 + for (let i = 0; i < list.length; i++) {
  237 + const row = list[i];
  238 + if (
  239 + row.srxmmc === undefined ||
  240 + row.srxmmc === null ||
  241 + row.srxmmc === ""
  242 + ) {
  243 + callback(new Error(`第 ${i + 1} 行收入项目为必填`));
  244 + return;
  245 + }
  246 + const je = parseFloat(row.je);
  247 + if (
  248 + row.je === undefined ||
  249 + row.je === null ||
  250 + row.je === "" ||
  251 + isNaN(je) ||
  252 + je <= 0
  253 + ) {
  254 + callback(new Error(`第 ${i + 1} 行原币金额为必填且须大于 0`));
  255 + return;
  256 + }
  257 + }
  258 + callback();
  259 + };
  260 + return {
  261 + loading: false,
  262 + visible: false,
  263 + isDetail: false,
  264 + wldwLoading: false,
  265 + dataForm: {
  266 + id: undefined,
  267 + djrq: undefined,
  268 + jsr: undefined,
  269 + wldw: undefined,
  270 + wtYskzjjsMxList: [],
  271 + fkzh: undefined,
  272 + fkje: undefined,
  273 + skzh: undefined,
  274 + skje: undefined,
  275 + djlx: undefined,
  276 + djzt: "草稿"
  277 + },
  278 + rules: {
  279 + djrq: [
  280 + { required: true, message: "请选择单据日期", trigger: "change" }
  281 + ],
  282 + jsr: [{ required: true, message: "请选择经手人", trigger: "change" }],
  283 + wldw: [
  284 + { required: true, message: "请选择往来单位", trigger: "change" }
  285 + ],
  286 + skzh: [
  287 + { required: true, message: "请选择收款账户", trigger: "change" }
  288 + ],
  289 + skje: [{ validator: validateSkje, trigger: "blur" }],
  290 + wtYskzjjsMxList: [{ validator: validateMxList, trigger: "change" }]
  291 + },
  292 + srxmmcOptions: [],
  293 + fkzhOptions: [],
  294 + skzhOptions: [],
  295 + wldwOptions: []
  296 + };
  297 + },
  298 + computed: {},
  299 + watch: {},
  300 + created() {
  301 + this.getsrxmmcOptions();
  302 + this.getfkzhOptions();
  303 + this.getskzhOptions();
  304 + },
  305 + mounted() {},
  306 + methods: {
  307 + getsrxmmcOptions() {
  308 + getDictionaryDataSelector("715562947862070533").then(res => {
  309 + this.srxmmcOptions = res.data.list;
  310 + });
  311 + },
  312 + getfkzhOptions() {
  313 + getAccountSelector().then(res => {
  314 + this.fkzhOptions = res.data.list;
  315 + });
  316 + },
  317 + getskzhOptions() {
  318 + getAccountSelector().then(res => {
  319 + this.skzhOptions = res.data.list;
  320 + });
  321 + },
  322 + remoteWldw(query) {
  323 + this.wldwLoading = true;
  324 + request({
  325 + url: "/api/Extend/WtWldw",
  326 + method: "GET",
  327 + data: {
  328 + currentPage: 1,
  329 + pageSize: 50,
  330 + dwmc: query && String(query).trim() ? String(query).trim() : undefined
  331 + }
  332 + })
  333 + .then(res => {
  334 + const raw = res.data.list || res.data || [];
  335 + this.wldwOptions = raw
  336 + .map(x => {
  337 + const id = x && (x.id || x.F_Id || x.f_Id);
  338 + return { ...x, id: id != null ? String(id) : "" };
  339 + })
  340 + .filter(x => x.id);
  341 + })
  342 + .finally(() => {
  343 + this.wldwLoading = false;
  344 + });
  345 + },
  346 + ensureWldwOption(id) {
  347 + if (!id) return Promise.resolve();
  348 + const sid = String(id);
  349 + if (this.wldwOptions.some(x => String(x.id) === sid))
  350 + return Promise.resolve();
  351 + return request({
  352 + url: "/api/Extend/WtWldw",
  353 + method: "GET",
  354 + data: { currentPage: 1, pageSize: 20, id: sid }
  355 + }).then(res => {
  356 + const raw = res.data.list || res.data || [];
  357 + const hit = raw.find(x => String(x.id || x.F_Id || x.f_Id) === sid);
  358 + if (hit) {
  359 + const row = { ...hit, id: String(hit.id || hit.F_Id || hit.f_Id) };
  360 + this.wldwOptions = [
  361 + row,
  362 + ...this.wldwOptions.filter(x => String(x.id) !== sid)
  363 + ];
  364 + }
  365 + });
  366 + },
  367 + onWldwChange() {
  368 + this.syncKhFromWldw();
  369 + },
  370 + syncKhFromWldw() {
  371 + const w = this.dataForm.wldw;
  372 + if (!w || !this.dataForm.wtYskzjjsMxList) return;
  373 + this.dataForm.wtYskzjjsMxList.forEach(row => {
  374 + if (!row) return;
  375 + if (row.kh === undefined || row.kh === null || row.kh === "") {
  376 + this.$set(row, "kh", String(w));
  377 + }
  378 + });
  379 + },
  380 + // 计算明细表原币金额合计
  381 + calculateTotalAmount() {
  382 + let totalAmount = 0;
  383 + if (
  384 + this.dataForm.wtYskzjjsMxList &&
  385 + this.dataForm.wtYskzjjsMxList.length > 0
  386 + ) {
  387 + this.dataForm.wtYskzjjsMxList.forEach(row => {
  388 + const je = parseFloat(row.je) || 0;
  389 + totalAmount += je;
  390 + });
  391 + }
  392 + this.dataForm.skje = totalAmount > 0 ? totalAmount.toFixed(2) : "";
  393 + this.$nextTick(() => {
  394 + this.$refs.elForm && this.$refs.elForm.validateField("skje");
  395 + this.$refs.elForm && this.$refs.elForm.validateField("wtYskzjjsMxList");
  396 + });
  397 + },
  398 + goBack() {
  399 + this.$emit("refresh");
  400 + },
  401 + init(id, isDetail) {
  402 + this.dataForm.id = id || 0;
  403 + this.visible = true;
  404 + this.isDetail = isDetail || false;
  405 + this.$nextTick(() => {
  406 + if (this.$refs["elForm"]) this.$refs["elForm"].resetFields();
  407 + this.wldwOptions = [];
  408 + if (this.dataForm.id) {
  409 + request({
  410 + url: "/api/Extend/WtYskzjjs/" + this.dataForm.id,
  411 + method: "get"
  412 + }).then(res => {
  413 + this.dataForm = Object.assign({}, this.dataForm, res.data);
  414 + if (!this.dataForm.wtYskzjjsMxList)
  415 + this.dataForm.wtYskzjjsMxList = [];
  416 + if (this.dataForm.wldw != null && this.dataForm.wldw !== "") {
  417 + this.dataForm.wldw = String(this.dataForm.wldw);
  418 + }
  419 + this.$nextTick(() => {
  420 + this.ensureWldwOption(this.dataForm.wldw).then(() => {
  421 + this.remoteWldw("");
  422 + });
  423 + this.calculateTotalAmount();
  424 + });
  425 + });
  426 + } else {
  427 + this.dataForm.djlx = "其他应收单";
  428 + this.dataForm.djzt = "草稿";
  429 + this.dataForm.djrq = Date.now();
  430 + if (
  431 + this.$store &&
  432 + this.$store.getters &&
  433 + this.$store.getters.userInfo
  434 + ) {
  435 + this.dataForm.jsr =
  436 + this.$store.getters.userInfo.userId ||
  437 + this.$store.getters.userInfo.id;
  438 + }
  439 + this.dataForm.wtYskzjjsMxList = [];
  440 + this.$nextTick(() => {
  441 + this.remoteWldw("");
  442 + });
  443 + }
  444 + });
  445 + },
  446 + validateSubmitMessage() {
  447 + return new Promise(resolve => {
  448 + this.$refs["elForm"].validate(valid => {
  449 + if (!valid) {
  450 + this.$message.warning("请完善必填项后再提交(备注除外)");
  451 + resolve(false);
  452 + return;
  453 + }
  454 + const list = this.dataForm.wtYskzjjsMxList || [];
  455 + if (!list.length) {
  456 + this.$message.warning("请至少添加一条明细后再提交");
  457 + resolve(false);
  458 + return;
  459 + }
  460 + for (let i = 0; i < list.length; i++) {
  461 + const row = list[i];
  462 + if (
  463 + row.srxmmc === undefined ||
  464 + row.srxmmc === null ||
  465 + row.srxmmc === ""
  466 + ) {
  467 + this.$message.warning(`第 ${i + 1} 行收入项目为必填`);
  468 + resolve(false);
  469 + return;
  470 + }
  471 + const je = parseFloat(row.je);
  472 + if (
  473 + row.je === undefined ||
  474 + row.je === null ||
  475 + row.je === "" ||
  476 + isNaN(je) ||
  477 + je <= 0
  478 + ) {
  479 + this.$message.warning(`第 ${i + 1} 行原币金额须大于 0`);
  480 + resolve(false);
  481 + return;
  482 + }
  483 + }
  484 + resolve(true);
  485 + });
  486 + });
  487 + },
  488 + saveDraft() {
  489 + this.dataForm.djlx = "其他应收单";
  490 + this.dataForm.djzt = "草稿";
  491 + this.syncKhFromWldw();
  492 + if (!this.dataForm.djrq) {
  493 + this.$message.warning("请选择单据日期后再保存草稿");
  494 + return;
  495 + }
  496 + if (!this.dataForm.jsr) {
  497 + this.$message.warning("请选择经手人后再保存草稿");
  498 + return;
  499 + }
  500 + if (!this.dataForm.wldw) {
  501 + this.$message.warning("请选择往来单位后再保存草稿");
  502 + return;
  503 + }
  504 + this.persistBill();
  505 + },
  506 + async dataFormSubmit() {
  507 + this.dataForm.djlx = "其他应收单";
  508 + this.dataForm.djzt = "待审核";
  509 + this.syncKhFromWldw();
  510 + const ok = await this.validateSubmitMessage();
  511 + if (!ok) return;
  512 + this.persistBill();
  513 + },
  514 + persistBill() {
  515 + const payload = { ...this.dataForm };
  516 + this.syncKhFromWldw();
  517 + if (!payload.id) {
  518 + request({
  519 + url: `/api/Extend/WtYskzjjs`,
  520 + method: "post",
  521 + data: payload
  522 + }).then(res => {
  523 + this.$message({
  524 + message: res.msg,
  525 + type: "success",
  526 + duration: 1000,
  527 + onClose: () => {
  528 + this.visible = false;
  529 + this.$emit("refresh", true);
  530 + }
  531 + });
  532 + });
  533 + } else {
  534 + request({
  535 + url: "/api/Extend/WtYskzjjs/" + this.dataForm.id,
  536 + method: "PUT",
  537 + data: payload
  538 + }).then(res => {
  539 + this.$message({
  540 + message: res.msg,
  541 + type: "success",
  542 + duration: 1000,
  543 + onClose: () => {
  544 + this.visible = false;
  545 + this.$emit("refresh", true);
  546 + }
  547 + });
  548 + });
  549 + }
  550 + },
  551 + addHandleWtYskzjjsMxEntityList() {
  552 + let item = {
  553 + srxmmc: undefined,
  554 + je: undefined,
  555 + bz: undefined,
  556 + mxlx: "其他收入单",
  557 + kh: undefined
  558 + };
  559 + this.dataForm.wtYskzjjsMxList.push(item);
  560 + this.syncKhFromWldw();
  561 + this.$nextTick(() => {
  562 + this.calculateTotalAmount();
  563 + this.$refs.elForm && this.$refs.elForm.validateField("wtYskzjjsMxList");
  564 + });
  565 + },
  566 + handleDelWtYskzjjsMxEntityList(index) {
  567 + this.dataForm.wtYskzjjsMxList.splice(index, 1);
  568 + this.$nextTick(() => {
  569 + this.calculateTotalAmount();
  570 + this.$refs.elForm && this.$refs.elForm.validateField("wtYskzjjsMxList");
  571 + });
  572 + }
  573 + }
  574 +};
298 575 </script>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtYskzjjs_qt/detail-view.vue
1 1 <template>
2   - <el-dialog
3   - title="其他应收款详情"
4   - :close-on-click-modal="false"
5   - :visible.sync="visible"
6   - class="NCC-dialog NCC-dialog_center qt-detail-dialog"
7   - lock-scroll
8   - width="960px"
9   - top="6vh"
10   - @close="handleClose"
11   - >
12   - <div v-loading="loading" class="qt-detail-body">
13   - <template v-if="detail">
14   - <el-descriptions :column="2" border size="small">
15   - <el-descriptions-item label="单据编号">{{ cellText(detail.id) }}</el-descriptions-item>
16   - <el-descriptions-item label="审核状态">{{ detail.djzt || detail.shzt || '未审核' }}</el-descriptions-item>
17   - <el-descriptions-item label="日期">{{ formatDate(detail.djrq) }}</el-descriptions-item>
18   - <el-descriptions-item label="往来单位">{{ wldwText(detail.wldw) }}</el-descriptions-item>
19   - <el-descriptions-item label="金额">{{ money(detail.skje) }}</el-descriptions-item>
20   - <el-descriptions-item label="经手人">{{ cellText(detail.jsr) }}</el-descriptions-item>
21   - <el-descriptions-item label="摘要" :span="2">
22   - <ncc-bill-summary :value="detail.billZy || detail.zy || detail.bz" mode="block" :show-icon="false" />
23   - </el-descriptions-item>
24   - </el-descriptions>
  2 + <el-dialog
  3 + title="其他应收单详情"
  4 + :close-on-click-modal="false"
  5 + :visible.sync="visible"
  6 + class="NCC-dialog NCC-dialog_center qt-detail-dialog"
  7 + lock-scroll
  8 + width="960px"
  9 + top="6vh"
  10 + @close="handleClose"
  11 + >
  12 + <div v-loading="loading" class="qt-detail-body">
  13 + <template v-if="detail">
  14 + <el-descriptions :column="2" border size="small">
  15 + <el-descriptions-item label="单据编号">{{
  16 + cellText(detail.id)
  17 + }}</el-descriptions-item>
  18 + <el-descriptions-item label="审核状态">{{
  19 + cellText(detail.djzt || detail.shzt || "未审核")
  20 + }}</el-descriptions-item>
  21 + <el-descriptions-item label="日期">{{
  22 + formatDate(detail.djrq)
  23 + }}</el-descriptions-item>
  24 + <el-descriptions-item label="往来单位">{{
  25 + cellText(detail.wldwmc || wldwText(detail.wldw))
  26 + }}</el-descriptions-item>
  27 + <el-descriptions-item label="金额">{{
  28 + money(detail.skje)
  29 + }}</el-descriptions-item>
  30 + <el-descriptions-item label="经手人">{{
  31 + cellText(detail.jsr)
  32 + }}</el-descriptions-item>
  33 + <el-descriptions-item label="审批备注" :span="2">{{
  34 + cellText(detail.spbz)
  35 + }}</el-descriptions-item>
  36 + <el-descriptions-item label="摘要" :span="2">
  37 + <ncc-bill-summary
  38 + :value="detail.billZy || detail.zy || detail.bz"
  39 + mode="block"
  40 + :show-icon="false"
  41 + />
  42 + </el-descriptions-item>
  43 + </el-descriptions>
25 44  
26   - <div class="mx-title">明细</div>
27   - <el-table :data="detail.wtYskzjjsMxList || []" border size="small" class="mx-table" empty-text="无">
28   - <el-table-column type="index" width="52" label="#" align="center" />
29   - <el-table-column label="往来单位" min-width="150" show-overflow-tooltip>
30   - <template slot-scope="scope">{{ wldwText(scope.row.kh) }}</template>
31   - </el-table-column>
32   - <el-table-column label="收入项目" min-width="180" show-overflow-tooltip>
33   - <template slot-scope="scope">{{ srxmText(scope.row.srxmmc) }}</template>
34   - </el-table-column>
35   - <el-table-column prop="je" label="金额" width="120" align="right">
36   - <template slot-scope="scope">{{ money(scope.row.je) }}</template>
37   - </el-table-column>
38   - <el-table-column label="备注" min-width="180" show-overflow-tooltip>
39   - <template slot-scope="scope">{{ cellText(scope.row.bz) }}</template>
40   - </el-table-column>
41   - </el-table>
42   - </template>
43   - </div>
44   - </el-dialog>
  45 + <div class="mx-title">明细</div>
  46 + <el-table
  47 + :data="detail.wtYskzjjsMxList || []"
  48 + border
  49 + size="small"
  50 + class="mx-table"
  51 + empty-text="无"
  52 + >
  53 + <el-table-column type="index" width="52" label="#" align="center" />
  54 + <el-table-column
  55 + label="往来单位"
  56 + min-width="150"
  57 + show-overflow-tooltip
  58 + >
  59 + <template slot-scope="scope">{{
  60 + wldwText(scope.row.kh || detail.wldw)
  61 + }}</template>
  62 + </el-table-column>
  63 + <el-table-column
  64 + label="收入项目"
  65 + min-width="180"
  66 + show-overflow-tooltip
  67 + >
  68 + <template slot-scope="scope">{{
  69 + srxmText(scope.row.srxmmc)
  70 + }}</template>
  71 + </el-table-column>
  72 + <el-table-column prop="je" label="金额" width="120" align="right">
  73 + <template slot-scope="scope">{{ money(scope.row.je) }}</template>
  74 + </el-table-column>
  75 + <el-table-column label="备注" min-width="180" show-overflow-tooltip>
  76 + <template slot-scope="scope">{{ cellText(scope.row.bz) }}</template>
  77 + </el-table-column>
  78 + </el-table>
  79 + </template>
  80 + </div>
  81 +
  82 + <template slot="footer">
  83 + <slot name="footer">
  84 + <el-button @click="close">关闭</el-button>
  85 + </slot>
  86 + </template>
  87 + </el-dialog>
45 88 </template>
46 89  
47 90 <script>
48   -import request from '@/utils/request'
49   -import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
  91 +import request from "@/utils/request";
  92 +import { getDictionaryDataSelector } from "@/api/systemData/dictionary";
50 93  
51 94 export default {
52   - name: 'WtYskzjjsQtDetailView',
53   - data() {
54   - return {
55   - visible: false,
56   - loading: false,
57   - detail: null,
58   - wldwOptions: [],
59   - srxmmcOptions: []
60   - }
61   - },
62   - created() {
63   - this.loadWldw()
64   - this.loadSrxmmc()
65   - },
66   - methods: {
67   - loadWldw() {
68   - request({
69   - url: '/api/Extend/WtWldw',
70   - method: 'GET',
71   - data: { currentPage: 1, pageSize: 5000 }
72   - }).then(res => {
73   - this.wldwOptions = res.data.list || res.data || []
74   - })
75   - },
76   - loadSrxmmc() {
77   - getDictionaryDataSelector('715562947862070533').then(res => {
78   - this.srxmmcOptions = res.data.list || []
79   - })
80   - },
81   - cellText(v) {
82   - return v === undefined || v === null || v === '' ? '无' : v
83   - },
84   - money(v) {
85   - const n = Number(v)
86   - return isNaN(n) ? '0.00' : n.toFixed(2)
87   - },
88   - formatDate(v) {
89   - if (!v) return '无'
90   - const d = new Date(v)
91   - if (isNaN(d.getTime())) return '无'
92   - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
93   - },
94   - wldwText(id) {
95   - if (!id) return '无'
96   - const hit = this.wldwOptions.find(item => String(item.id) === String(id))
97   - return hit && hit.dwmc ? hit.dwmc : id
98   - },
99   - srxmText(v) {
100   - if (!v) return '无'
101   - const hit = this.srxmmcOptions.find(item => String(item.id) === String(v))
102   - return hit && hit.fullName ? hit.fullName : v
103   - },
104   - handleClose() {
105   - this.detail = null
106   - this.$emit('close')
107   - },
108   - init(id) {
109   - if (!id) return
110   - this.visible = true
111   - this.loading = true
112   - request({
113   - url: `/api/Extend/WtYskzjjs/${id}`,
114   - method: 'GET'
115   - }).then(res => {
116   - this.detail = res.data || null
117   - if (this.detail && !this.detail.wtYskzjjsMxList) {
118   - this.$set(this.detail, 'wtYskzjjsMxList', [])
119   - }
120   - }).finally(() => {
121   - this.loading = false
122   - })
123   - }
124   - }
125   -}
  95 + name: "WtYskzjjsQtDetailView",
  96 + data() {
  97 + return {
  98 + visible: false,
  99 + loading: false,
  100 + detail: null,
  101 + wldwOptions: [],
  102 + srxmmcOptions: []
  103 + };
  104 + },
  105 + created() {
  106 + this.loadWldw();
  107 + this.loadSrxmmc();
  108 + },
  109 + methods: {
  110 + loadWldw() {
  111 + request({
  112 + url: "/api/Extend/WtWldw",
  113 + method: "GET",
  114 + data: { currentPage: 1, pageSize: 5000 }
  115 + }).then(res => {
  116 + this.wldwOptions = res.data.list || res.data || [];
  117 + });
  118 + },
  119 + loadSrxmmc() {
  120 + getDictionaryDataSelector("715562947862070533").then(res => {
  121 + this.srxmmcOptions = res.data.list || [];
  122 + });
  123 + },
  124 + cellText(v) {
  125 + return v === undefined || v === null || v === "" ? "无" : v;
  126 + },
  127 + money(v) {
  128 + const n = Number(v);
  129 + return isNaN(n) ? "0.00" : n.toFixed(2);
  130 + },
  131 + formatDate(v) {
  132 + if (!v) return "无";
  133 + const d = new Date(v);
  134 + if (isNaN(d.getTime())) return "无";
  135 + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(
  136 + 2,
  137 + "0"
  138 + )}-${String(d.getDate()).padStart(2, "0")}`;
  139 + },
  140 + wldwText(id) {
  141 + if (!id) return "无";
  142 + const hit = this.wldwOptions.find(item => String(item.id) === String(id));
  143 + return hit && hit.dwmc ? hit.dwmc : id;
  144 + },
  145 + srxmText(v) {
  146 + if (!v) return "无";
  147 + const hit = this.srxmmcOptions.find(
  148 + item => String(item.id) === String(v)
  149 + );
  150 + return hit && hit.fullName ? hit.fullName : v;
  151 + },
  152 + handleClose() {
  153 + this.detail = null;
  154 + this.$emit("close");
  155 + },
  156 + close() {
  157 + this.visible = false;
  158 + },
  159 + init(id) {
  160 + if (!id) return;
  161 + this.visible = true;
  162 + this.loading = true;
  163 + request({
  164 + url: `/api/Extend/WtYskzjjs/${id}`,
  165 + method: "GET"
  166 + })
  167 + .then(res => {
  168 + this.detail = res.data || null;
  169 + if (this.detail && !this.detail.wtYskzjjsMxList) {
  170 + this.$set(this.detail, "wtYskzjjsMxList", []);
  171 + }
  172 + this.$emit("loaded", this.detail);
  173 + })
  174 + .finally(() => {
  175 + this.loading = false;
  176 + });
  177 + }
  178 + }
  179 +};
126 180 </script>
127 181  
128 182 <style scoped lang="scss">
129 183 .mx-title {
130   - margin: 14px 0 10px;
131   - font-weight: 600;
  184 + margin: 14px 0 10px;
  185 + font-weight: 600;
132 186 }
133 187 .mx-table ::v-deep .cell {
134   - white-space: nowrap;
  188 + white-space: nowrap;
135 189 }
136 190 .mx-table {
137   - margin-bottom: 12px;
  191 + margin-bottom: 12px;
138 192 }
139 193 .qt-detail-body {
140   - padding-bottom: 8px;
  194 + padding-bottom: 8px;
141 195 }
142 196 .qt-detail-dialog ::v-deep .el-dialog__body {
143   - padding-bottom: 20px;
  197 + padding-bottom: 20px;
144 198 }
145 199 </style>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtYskzjjs_qt/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.id" placeholder="单据编号" clearable />
9   - </el-form-item>
10   - </el-col>
11   - <el-col :span="6">
12   - <el-form-item label="单据日期">
13   - <el-date-picker v-model="query.djrq" type="daterange" value-format="timestamp" format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期">
14   - </el-date-picker>
15   - </el-form-item>
16   - </el-col>
17   - <template v-if="showAll">
18   - <el-col :span="6">
19   - <el-form-item label="往来单位">
20   - <el-select v-model="query.kh" placeholder="请选择往来单位" clearable filterable >
21   - <el-option v-for="(item, index) in wldwOptions" :key="index" :label="item.dwmc" :value="item.id" />
22   - </el-select>
23   - </el-form-item>
24   - </el-col>
25   - <el-col :span="6">
26   - <el-form-item label="经手人">
27   - <userSelect v-model="query.jsr" placeholder="请选择经手人" />
28   - </el-form-item>
29   - </el-col>
30   - </template>
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-button type="text" icon="el-icon-arrow-down" @click="showAll=true" v-if="!showAll">展开</el-button>
36   - <el-button type="text" icon="el-icon-arrow-up" @click="showAll=false" v-else>收起</el-button>
37   - </el-form-item>
38   - </el-col>
39   - </el-form>
40   - </el-row>
41   - <div class="NCC-common-layout-main NCC-flex-main">
42   - <div class="NCC-common-head">
43   - <div>
44   - <el-button type="primary" icon="el-icon-plus" @click="addOrUpdateHandle()">新增</el-button>
45   - <el-button type="text" icon="el-icon-download" @click="exportData()">导出</el-button>
46   - <el-button type="text" icon="el-icon-delete" @click="handleBatchRemoveDel()">批量删除</el-button>
47   - </div>
48   - <div class="NCC-common-head-right">
49   - <el-tooltip effect="dark" content="刷新" placement="top">
50   - <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="reset()" />
51   - </el-tooltip>
52   - <screenfull isContainer />
53   - </div>
54   - </div>
55   - <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange">
56   - <el-table-column prop="djrq" label="单据日期" align="left">
57   - <template slot-scope="scope">
58   - {{ scope.row.djrq ? (new Date(scope.row.djrq).toLocaleDateString('zh-CN', {year:'numeric',month:'2-digit',day:'2-digit'}).replace(/\//g,'-')) : '' }}
59   - </template>
60   - </el-table-column>
61   - <el-table-column prop="id" label="单据编号" align="left" />
62   - <el-table-column label="审核状态" align="left" width="100">
63   - <template slot-scope="scope">
64   - {{ scope.row.djzt || scope.row.shzt || '未审核' }}
65   - </template>
66   - </el-table-column>
67   - <el-table-column label="往来单位" align="left" min-width="160" show-overflow-tooltip>
68   - <template slot-scope="scope">
69   - {{ wldwText(scope.row.wldw || scope.row.kh) }}
70   - </template>
71   - </el-table-column>
72   - <el-table-column prop="skje" label="金额" align="left" />
73   - <el-table-column prop="jsr" label="经手人" align="left" />
74   - <el-table-column label="摘要" align="left" min-width="200">
75   - <template slot-scope="scope">
76   - <ncc-table-summary-cell :row="scope.row" fields="billZy,BillZy,zy,Zy,bz,Bz" />
77   - </template>
78   - </el-table-column>
79   - <el-table-column label="操作" fixed="right" width="140">
80   - <template slot-scope="scope">
81   - <el-button type="text" @click="openDetail(scope.row.id)" >查看</el-button>
82   - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button>
83   - <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button>
84   - </template>
85   - </el-table-column>
86   - </NCC-table>
87   - <pagination :total="total" :page.sync="listQuery.currentPage" :limit.sync="listQuery.pageSize" @pagination="initData" />
88   - </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.id" placeholder="单据编号" clearable />
  9 + </el-form-item>
  10 + </el-col>
  11 + <el-col :span="6">
  12 + <el-form-item label="单据日期">
  13 + <el-date-picker
  14 + v-model="query.djrq"
  15 + type="daterange"
  16 + value-format="timestamp"
  17 + format="yyyy-MM-dd"
  18 + start-placeholder="开始日期"
  19 + end-placeholder="结束日期"
  20 + >
  21 + </el-date-picker>
  22 + </el-form-item>
  23 + </el-col>
  24 + <template v-if="showAll">
  25 + <el-col :span="6">
  26 + <el-form-item label="往来单位">
  27 + <el-select
  28 + v-model="query.kh"
  29 + placeholder="请选择往来单位"
  30 + clearable
  31 + filterable
  32 + >
  33 + <el-option
  34 + v-for="(item, index) in wldwOptions"
  35 + :key="index"
  36 + :label="item.dwmc"
  37 + :value="item.id"
  38 + />
  39 + </el-select>
  40 + </el-form-item>
  41 + </el-col>
  42 + <el-col :span="6">
  43 + <el-form-item label="经手人">
  44 + <userSelect v-model="query.jsr" placeholder="请选择经手人" />
  45 + </el-form-item>
  46 + </el-col>
  47 + </template>
  48 + <el-col :span="6">
  49 + <el-form-item>
  50 + <el-button type="primary" icon="el-icon-search" @click="search()"
  51 + >查询</el-button
  52 + >
  53 + <el-button icon="el-icon-refresh-right" @click="reset()"
  54 + >重置</el-button
  55 + >
  56 + <el-button
  57 + type="text"
  58 + icon="el-icon-arrow-down"
  59 + @click="showAll = true"
  60 + v-if="!showAll"
  61 + >展开</el-button
  62 + >
  63 + <el-button
  64 + type="text"
  65 + icon="el-icon-arrow-up"
  66 + @click="showAll = false"
  67 + v-else
  68 + >收起</el-button
  69 + >
  70 + </el-form-item>
  71 + </el-col>
  72 + </el-form>
  73 + </el-row>
  74 + <div class="NCC-common-layout-main NCC-flex-main">
  75 + <div class="NCC-common-head">
  76 + <div>
  77 + <el-button
  78 + type="primary"
  79 + icon="el-icon-plus"
  80 + @click="addOrUpdateHandle()"
  81 + >新增</el-button
  82 + >
  83 + <el-button type="text" icon="el-icon-download" @click="exportData()"
  84 + >导出</el-button
  85 + >
  86 + <el-button
  87 + type="text"
  88 + icon="el-icon-delete"
  89 + @click="handleBatchRemoveDel()"
  90 + >批量删除</el-button
  91 + >
  92 + </div>
  93 + <div class="NCC-common-head-right">
  94 + <el-tooltip effect="dark" content="刷新" placement="top">
  95 + <el-link
  96 + icon="icon-ym icon-ym-Refresh NCC-common-head-icon"
  97 + :underline="false"
  98 + @click="reset()"
  99 + />
  100 + </el-tooltip>
  101 + <screenfull isContainer />
  102 + </div>
89 103 </div>
90   - <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
91   - <DetailView v-if="detailVisible" ref="NCCDetailView" @close="detailVisible = false" />
92   - <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" />
93   - </div>
  104 + <NCC-table
  105 + v-loading="listLoading"
  106 + :data="list"
  107 + has-c
  108 + @selection-change="handleSelectionChange"
  109 + >
  110 + <el-table-column prop="djrq" label="单据日期" align="left">
  111 + <template slot-scope="scope">
  112 + {{
  113 + scope.row.djrq
  114 + ? new Date(scope.row.djrq)
  115 + .toLocaleDateString("zh-CN", {
  116 + year: "numeric",
  117 + month: "2-digit",
  118 + day: "2-digit"
  119 + })
  120 + .replace(/\//g, "-")
  121 + : ""
  122 + }}
  123 + </template>
  124 + </el-table-column>
  125 + <el-table-column prop="id" label="单据编号" align="left" />
  126 + <el-table-column label="审核状态" align="left" width="130">
  127 + <template slot-scope="scope">
  128 + <el-tag
  129 + v-if="auditStatus(scope.row) === '草稿'"
  130 + type="info"
  131 + size="mini"
  132 + >草稿</el-tag
  133 + >
  134 + <el-tag
  135 + v-else-if="auditStatus(scope.row) === '待审核'"
  136 + type="warning"
  137 + size="mini"
  138 + >待审核</el-tag
  139 + >
  140 + <el-tag
  141 + v-else-if="
  142 + auditStatus(scope.row) === '一级已审' ||
  143 + auditStatus(scope.row) === '待二级' ||
  144 + auditStatus(scope.row) === '一级已审/待二级'
  145 + "
  146 + type="warning"
  147 + size="mini"
  148 + >一级已审</el-tag
  149 + >
  150 + <el-tag
  151 + v-else-if="auditStatus(scope.row) === '已审核'"
  152 + type="success"
  153 + size="mini"
  154 + >已审核</el-tag
  155 + >
  156 + <el-tag
  157 + v-else-if="auditStatus(scope.row) === '审核不通过'"
  158 + type="danger"
  159 + size="mini"
  160 + >审核不通过</el-tag
  161 + >
  162 + <el-tag v-else type="info" size="mini">{{
  163 + cellText(auditStatus(scope.row) || "未审核")
  164 + }}</el-tag>
  165 + </template>
  166 + </el-table-column>
  167 + <el-table-column
  168 + label="往来单位"
  169 + align="left"
  170 + min-width="160"
  171 + show-overflow-tooltip
  172 + >
  173 + <template slot-scope="scope">
  174 + {{ showWldw(scope.row) }}
  175 + </template>
  176 + </el-table-column>
  177 + <el-table-column prop="skje" label="金额" align="left" />
  178 + <el-table-column prop="jsr" label="经手人" align="left" />
  179 + <el-table-column label="摘要" align="left" min-width="200">
  180 + <template slot-scope="scope">
  181 + <ncc-table-summary-cell
  182 + :row="scope.row"
  183 + fields="billZy,BillZy,zy,Zy,bz,Bz"
  184 + />
  185 + </template>
  186 + </el-table-column>
  187 + <el-table-column label="操作" fixed="right" width="300">
  188 + <template slot-scope="scope">
  189 + <el-button type="text" @click="openDetail(scope.row.id)"
  190 + >查看</el-button
  191 + >
  192 + <el-button type="text" @click="openAuditInfo(scope.row)"
  193 + >审核信息</el-button
  194 + >
  195 + <el-button
  196 + v-if="canEditRow(scope.row)"
  197 + type="text"
  198 + @click="addOrUpdateHandle(scope.row.id)"
  199 + >编辑</el-button
  200 + >
  201 + <el-button
  202 + type="text"
  203 + style="color:#E6A23C"
  204 + v-if="canSubmitAudit(scope.row)"
  205 + @click="handleSubmitAudit(scope.row.id)"
  206 + >提交审核</el-button
  207 + >
  208 + <el-button
  209 + type="text"
  210 + v-if="canWithdrawAudit(scope.row)"
  211 + @click="handleWithdrawAudit(scope.row.id)"
  212 + >撤回</el-button
  213 + >
  214 + <el-button
  215 + type="text"
  216 + style="color:#E6A23C"
  217 + v-if="canLevel1Approve(scope.row)"
  218 + @click="handleLevel1Approve(scope.row.id)"
  219 + >一级审核</el-button
  220 + >
  221 + <el-button
  222 + type="text"
  223 + style="color:#67C23A"
  224 + v-if="canLevel2Approve(scope.row)"
  225 + @click="handleLevel2Approve(scope.row.id)"
  226 + >二级审核</el-button
  227 + >
  228 + <el-button
  229 + type="text"
  230 + style="color:#F56C6C"
  231 + v-if="canReject(scope.row)"
  232 + @click="handleReject(scope.row.id)"
  233 + >审核不通过</el-button
  234 + >
  235 + <el-button
  236 + type="text"
  237 + @click="handleDel(scope.row.id)"
  238 + class="NCC-table-delBtn"
  239 + v-if="canEditRow(scope.row)"
  240 + >删除</el-button
  241 + >
  242 + </template>
  243 + </el-table-column>
  244 + </NCC-table>
  245 + <pagination
  246 + :total="total"
  247 + :page.sync="listQuery.currentPage"
  248 + :limit.sync="listQuery.pageSize"
  249 + @pagination="initData"
  250 + />
  251 + </div>
  252 + </div>
  253 + <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
  254 + <DetailView
  255 + v-if="detailVisible"
  256 + ref="NCCDetailView"
  257 + @close="detailVisible = false"
  258 + />
  259 + <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" />
  260 + </div>
94 261 </template>
95 262 <script>
96   - import request from '@/utils/request'
97   - import NCCForm from './Form'
98   - import DetailView from './detail-view'
99   - import ExportBox from './ExportBox'
100   - export default {
101   - components: { NCCForm, DetailView, ExportBox },
102   - data() {
103   - const billType = '其他应收款'
104   - return {
105   - billType,
106   - showAll: false,
107   - query: {
108   - id:undefined,
109   - djrq:undefined,
110   - kh:undefined,
111   - jsr:undefined,
112   - djlx: billType,
113   - },
114   - list: [],
115   - listLoading: true,
116   - multipleSelection: [], total: 0,
117   - listQuery: {
118   - currentPage: 1,
119   - pageSize: 20,
120   - sort: "desc",
121   - sidx: "",
122   - },
123   - formVisible: false,
124   - detailVisible: false,
125   - exportBoxVisible: false,
126   - columnList: [
127   - { prop: 'djrq', label: '单据日期' },
128   - { prop: 'id', label: '单据编号' },
129   - { prop: 'djzt', label: '审核状态' },
130   - { prop: 'wldw', label: '往来单位' },
131   - { prop: 'skje', label: '金额' },
132   - { prop: 'jsr', label: '经手人' },
133   - { prop: 'zy', label: '摘要' },
134   - ],
135   - wldwOptions : [],
136   - }
137   - },
138   - computed: {},
139   - created() {
140   - this.initData()
141   - this.getWldwOptions();
142   - },
143   - methods: {
144   - getWldwOptions() {
145   - request({
146   - url: '/api/Extend/WtWldw',
147   - method: 'GET',
148   - data: { currentPage: 1, pageSize: 5000 }
149   - }).then(res => {
150   - this.wldwOptions = res.data.list || res.data || []
151   - })
152   - },
153   - wldwText(val) {
154   - if (!val) return '无'
155   - const hit = this.wldwOptions.find(item => String(item.id) === String(val))
156   - return hit && hit.dwmc ? hit.dwmc : val
157   - },
158   - openDetail(id) {
159   - if (!id) return
160   - this.detailVisible = true
161   - this.$nextTick(() => {
162   - this.$refs.NCCDetailView && this.$refs.NCCDetailView.init(id)
163   - })
164   - },
165   - initData() {
166   - this.listLoading = true;
167   - let _query = {
168   - ...this.listQuery,
169   - ...this.query
170   - };
171   - let query = {}
172   - for (let key in _query) {
173   - if (Array.isArray(_query[key])) {
174   - query[key] = _query[key].join()
175   - } else {
176   - query[key] = _query[key]
177   - }
178   - }
179   - request({
180   - url: `/api/Extend/WtYskzjjs`,
181   - method: 'GET',
182   - data: query
183   - }).then(res => {
184   - this.list = res.data.list
185   - this.total = res.data.pagination.total
186   - this.listLoading = false
187   - })
188   - },
189   - handleDel(id) {
190   - this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
191   - type: 'warning'
192   - }).then(() => {
193   - request({
194   - url: `/api/Extend/WtYskzjjs/${id}`,
195   - method: 'DELETE'
196   - }).then(res => {
197   - this.$message({
198   - type: 'success',
199   - message: res.msg,
200   - onClose: () => {
201   - this.initData()
202   - }
203   - });
204   - })
205   - }).catch(() => {
206   - });
207   - },
208   - handleSelectionChange(val) {
209   - const res = val.map(item => item.id)
210   - this.multipleSelection = res
211   - },
212   - handleBatchRemoveDel() {
213   - if (!this.multipleSelection.length) {
214   - this.$message({
215   - type: 'error',
216   - message: '请选择一条数据',
217   - duration: 1500,
218   - })
219   - return
220   - }
221   - const ids = this.multipleSelection
222   - this.$confirm('您确定要删除这些数据吗, 是否继续?', '提示', {
223   - type: 'warning'
224   - }).then(() => {
225   - request({
226   - url: `/api/Extend/WtYskzjjs/batchRemove`,
227   - method: 'POST',
228   - data: ids ,
229   - }).then(res => {
230   - this.$message({
231   - type: 'success',
232   - message: res.msg,
233   - onClose: () => {
234   - this.initData()
235   - }
236   - });
237   - })
238   - }).catch(() => { })
239   - },
240   - addOrUpdateHandle(id, isDetail) {
241   - this.formVisible = true
242   - this.$nextTick(() => {
243   - this.$refs.NCCForm.init(id, isDetail)
244   - })
245   - },
246   - exportData() {
247   - this.exportBoxVisible = true
248   - this.$nextTick(() => {
249   - this.$refs.ExportBox.init(this.columnList)
250   - })
251   - },
252   - download(data) {
253   - let query = { ...data, ...this.listQuery, ...this.query }
254   - request({
255   - url: `/api/Extend/WtYskzjjs/Actions/Export`,
256   - method: 'GET',
257   - data: query
258   - }).then(res => {
259   - if (!res.data.url) return
260   - window.location.href = this.define.comUrl + res.data.url
261   - this.$refs.ExportBox.visible = false
262   - this.exportBoxVisible = false
263   - })
264   - },
265   - search() {
266   - this.listQuery = {
267   - currentPage: 1,
268   - pageSize: 20,
269   - sort: "desc",
270   - sidx: "",
271   - }
272   - this.query.djlx = this.billType
273   - this.initData()
274   - },
275   - refresh(isrRefresh) {
276   - this.formVisible = false
277   - if (isrRefresh) this.reset()
278   - },
279   - reset() {
280   - for (let key in this.query) {
281   - this.query[key] = undefined
282   - }
283   - this.query.djlx = this.billType
284   - this.listQuery = {
285   - currentPage: 1,
286   - pageSize: 20,
287   - sort: "desc",
288   - sidx: "",
289   - }
290   - this.initData()
291   - }
292   - }
  263 +import request from "@/utils/request";
  264 +import NCCForm from "./Form";
  265 +import DetailView from "./detail-view";
  266 +import ExportBox from "./ExportBox";
  267 +import {
  268 + promptApprovalRemark,
  269 + postYskzjjsSubmitForAudit,
  270 + postYskzjjsWithdrawAudit,
  271 + postYskzjjsApprove,
  272 + postYskzjjsReject
  273 +} from "@/utils/wtRejectApproval";
  274 +export default {
  275 + components: { NCCForm, DetailView, ExportBox },
  276 + data() {
  277 + const billType = "其他应收单";
  278 + return {
  279 + billType,
  280 + showAll: false,
  281 + query: {
  282 + id: undefined,
  283 + djrq: undefined,
  284 + kh: undefined,
  285 + jsr: undefined,
  286 + djlx: billType
  287 + },
  288 + list: [],
  289 + listLoading: true,
  290 + multipleSelection: [],
  291 + total: 0,
  292 + listQuery: {
  293 + currentPage: 1,
  294 + pageSize: 20,
  295 + sort: "desc",
  296 + sidx: ""
  297 + },
  298 + formVisible: false,
  299 + detailVisible: false,
  300 + exportBoxVisible: false,
  301 + columnList: [
  302 + { prop: "djrq", label: "单据日期" },
  303 + { prop: "id", label: "单据编号" },
  304 + { prop: "djzt", label: "审核状态" },
  305 + { prop: "wldw", label: "往来单位" },
  306 + { prop: "skje", label: "金额" },
  307 + { prop: "jsr", label: "经手人" },
  308 + { prop: "zy", label: "摘要" }
  309 + ],
  310 + wldwOptions: []
  311 + };
  312 + },
  313 + computed: {},
  314 + created() {
  315 + this.initData();
  316 + this.getWldwOptions();
  317 + },
  318 + methods: {
  319 + cellText(v) {
  320 + if (v === null || v === undefined || v === "") return "无";
  321 + return v;
  322 + },
  323 + auditStatus(row) {
  324 + if (!row) return "";
  325 + return (row.djzt || row.shzt || "").trim();
  326 + },
  327 + showWldw(row) {
  328 + if (!row) return "无";
  329 + if (row.wldwmc && row.wldwmc !== "无") return row.wldwmc;
  330 + return this.wldwText(row.wldw || row.kh);
  331 + },
  332 + canEditRow(row) {
  333 + const z = this.auditStatus(row);
  334 + return !z || z === "草稿" || z === "审核不通过";
  335 + },
  336 + canSubmitAudit(row) {
  337 + const z = this.auditStatus(row);
  338 + return z === "草稿" || z === "审核不通过";
  339 + },
  340 + canWithdrawAudit(row) {
  341 + const z = this.auditStatus(row);
  342 + return (
  343 + z === "待审核" ||
  344 + z === "一级已审" ||
  345 + z === "待二级" ||
  346 + z === "一级已审/待二级"
  347 + );
  348 + },
  349 + canLevel1Approve(row) {
  350 + const z = this.auditStatus(row);
  351 + return z === "待审核" || !z;
  352 + },
  353 + canLevel2Approve(row) {
  354 + const z = this.auditStatus(row);
  355 + return z === "一级已审" || z === "待二级" || z === "一级已审/待二级";
  356 + },
  357 + canReject(row) {
  358 + return this.canLevel1Approve(row) || this.canLevel2Approve(row);
  359 + },
  360 + openAuditInfo(row) {
  361 + const z = this.auditStatus(row) || "无";
  362 + const sp = row && row.spbz ? String(row.spbz) : "无";
  363 + this.$alert(
  364 + `审核状态:${this.cellText(z)}\n\n审批备注:\n${this.cellText(sp)}`,
  365 + "审核信息"
  366 + );
  367 + },
  368 + handleSubmitAudit(id) {
  369 + if (!id) return;
  370 + this.$confirm("确认提交审核?", "提示", { type: "warning" })
  371 + .then(() => postYskzjjsSubmitForAudit(id))
  372 + .then(res => {
  373 + const d = res.data || {};
  374 + if (d.success) {
  375 + this.$message.success(d.message || "已提交审核");
  376 + this.initData();
  377 + } else {
  378 + this.$message.error(d.message || "提交失败");
  379 + }
  380 + })
  381 + .catch(() => {});
  382 + },
  383 + handleWithdrawAudit(id) {
  384 + if (!id) return;
  385 + this.$confirm("确认撤回审核(单据将变为草稿)?", "提示", {
  386 + type: "warning"
  387 + })
  388 + .then(() => postYskzjjsWithdrawAudit(id))
  389 + .then(res => {
  390 + const d = res.data || {};
  391 + if (d.success) {
  392 + this.$message.success(d.message || "已撤回");
  393 + this.initData();
  394 + } else {
  395 + this.$message.error(d.message || "撤回失败");
  396 + }
  397 + })
  398 + .catch(() => {});
  399 + },
  400 + handleLevel1Approve(id) {
  401 + this.$confirm("确认执行一级审核?", "一级审核", { type: "warning" })
  402 + .then(() => promptApprovalRemark(this, "审批备注"))
  403 + .then(remark => postYskzjjsApprove(id, remark))
  404 + .then(res => {
  405 + const d = res.data || {};
  406 + if (d.success) {
  407 + this.$message.success(d.message || "审核成功");
  408 + this.initData();
  409 + } else {
  410 + this.$message.error(d.message || "审核失败");
  411 + }
  412 + })
  413 + .catch(() => {});
  414 + },
  415 + handleLevel2Approve(id) {
  416 + this.$confirm("确认执行二级审核?", "二级审核", { type: "warning" })
  417 + .then(() => promptApprovalRemark(this, "审批备注"))
  418 + .then(remark => postYskzjjsApprove(id, remark))
  419 + .then(res => {
  420 + const d = res.data || {};
  421 + if (d.success) {
  422 + this.$message.success(d.message || "审核成功");
  423 + this.initData();
  424 + } else {
  425 + this.$message.error(d.message || "审核失败");
  426 + }
  427 + })
  428 + .catch(() => {});
  429 + },
  430 + handleReject(id) {
  431 + promptApprovalRemark(this, "审核不通过")
  432 + .then(remark => postYskzjjsReject(id, remark))
  433 + .then(res => {
  434 + const d = res.data || {};
  435 + if (d.success) {
  436 + this.$message.success(d.message || "已标记审核不通过");
  437 + this.initData();
  438 + } else {
  439 + this.$message.error(d.message || "操作失败");
  440 + }
  441 + })
  442 + .catch(() => {});
  443 + },
  444 + getWldwOptions() {
  445 + request({
  446 + url: "/api/Extend/WtWldw",
  447 + method: "GET",
  448 + data: { currentPage: 1, pageSize: 5000 }
  449 + }).then(res => {
  450 + const raw = res.data.list || res.data || [];
  451 + this.wldwOptions = raw
  452 + .map(x => ({
  453 + ...x,
  454 + id: String(x && (x.id || x.F_Id || x.f_Id || ""))
  455 + }))
  456 + .filter(x => x.id);
  457 + });
  458 + },
  459 + wldwText(val) {
  460 + if (!val) return "无";
  461 + const hit = this.wldwOptions.find(
  462 + item => String(item.id) === String(val)
  463 + );
  464 + return hit && hit.dwmc ? hit.dwmc : val;
  465 + },
  466 + openDetail(id) {
  467 + if (!id) return;
  468 + this.detailVisible = true;
  469 + this.$nextTick(() => {
  470 + this.$refs.NCCDetailView && this.$refs.NCCDetailView.init(id);
  471 + });
  472 + },
  473 + initData() {
  474 + this.listLoading = true;
  475 + let _query = {
  476 + ...this.listQuery,
  477 + ...this.query
  478 + };
  479 + let query = {};
  480 + for (let key in _query) {
  481 + if (Array.isArray(_query[key])) {
  482 + query[key] = _query[key].join();
  483 + } else {
  484 + query[key] = _query[key];
  485 + }
  486 + }
  487 + request({
  488 + url: `/api/Extend/WtYskzjjs`,
  489 + method: "GET",
  490 + data: query
  491 + }).then(res => {
  492 + this.list = res.data.list;
  493 + this.total = res.data.pagination.total;
  494 + this.listLoading = false;
  495 + });
  496 + },
  497 + handleDel(id) {
  498 + this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", {
  499 + type: "warning"
  500 + })
  501 + .then(() => {
  502 + request({
  503 + url: `/api/Extend/WtYskzjjs/${id}`,
  504 + method: "DELETE"
  505 + }).then(res => {
  506 + this.$message({
  507 + type: "success",
  508 + message: res.msg,
  509 + onClose: () => {
  510 + this.initData();
  511 + }
  512 + });
  513 + });
  514 + })
  515 + .catch(() => {});
  516 + },
  517 + handleSelectionChange(val) {
  518 + const res = val.map(item => item.id);
  519 + this.multipleSelection = res;
  520 + },
  521 + handleBatchRemoveDel() {
  522 + if (!this.multipleSelection.length) {
  523 + this.$message({
  524 + type: "error",
  525 + message: "请选择一条数据",
  526 + duration: 1500
  527 + });
  528 + return;
  529 + }
  530 + const ids = this.multipleSelection;
  531 + this.$confirm("您确定要删除这些数据吗, 是否继续?", "提示", {
  532 + type: "warning"
  533 + })
  534 + .then(() => {
  535 + request({
  536 + url: `/api/Extend/WtYskzjjs/batchRemove`,
  537 + method: "POST",
  538 + data: ids
  539 + }).then(res => {
  540 + this.$message({
  541 + type: "success",
  542 + message: res.msg,
  543 + onClose: () => {
  544 + this.initData();
  545 + }
  546 + });
  547 + });
  548 + })
  549 + .catch(() => {});
  550 + },
  551 + addOrUpdateHandle(id, isDetail) {
  552 + this.formVisible = true;
  553 + this.$nextTick(() => {
  554 + this.$refs.NCCForm.init(id, isDetail);
  555 + });
  556 + },
  557 + exportData() {
  558 + this.exportBoxVisible = true;
  559 + this.$nextTick(() => {
  560 + this.$refs.ExportBox.init(this.columnList);
  561 + });
  562 + },
  563 + download(data) {
  564 + let query = { ...data, ...this.listQuery, ...this.query };
  565 + request({
  566 + url: `/api/Extend/WtYskzjjs/Actions/Export`,
  567 + method: "GET",
  568 + data: query
  569 + }).then(res => {
  570 + if (!res.data.url) return;
  571 + window.location.href = this.define.comUrl + res.data.url;
  572 + this.$refs.ExportBox.visible = false;
  573 + this.exportBoxVisible = false;
  574 + });
  575 + },
  576 + search() {
  577 + this.listQuery = {
  578 + currentPage: 1,
  579 + pageSize: 20,
  580 + sort: "desc",
  581 + sidx: ""
  582 + };
  583 + this.query.djlx = this.billType;
  584 + this.initData();
  585 + },
  586 + refresh(isrRefresh) {
  587 + this.formVisible = false;
  588 + if (isrRefresh) this.reset();
  589 + },
  590 + reset() {
  591 + for (let key in this.query) {
  592 + this.query[key] = undefined;
  593 + }
  594 + this.query.djlx = this.billType;
  595 + this.listQuery = {
  596 + currentPage: 1,
  597 + pageSize: 20,
  598 + sort: "desc",
  599 + sidx: ""
  600 + };
  601 + this.initData();
293 602 }
294   -</script>
295 603 \ No newline at end of file
  604 + }
  605 +};
  606 +</script>
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtFysrdCrInput.cs
... ... @@ -77,6 +77,26 @@ namespace NCC.Extend.Entitys.Dto.WtFysrd
77 77 /// 付款单位
78 78 /// </summary>
79 79 public string fkdw { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 往来单位(wt_wldw.F_Id);与 fkdw 同步保存
  83 + /// </summary>
  84 + public string wldw { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 兼容请求:客户主键,落库时与 wldw/fkdw 对齐
  88 + /// </summary>
  89 + public string kh { get; set; }
  90 +
  91 + /// <summary>
  92 + /// 兼容请求:供应商主键,落库时与 wldw/fkdw 对齐
  93 + /// </summary>
  94 + public string gys { get; set; }
  95 +
  96 + /// <summary>
  97 + /// 单据状态(一般仅查询回显;写入以服务端为准)
  98 + /// </summary>
  99 + public string djzt { get; set; }
80 100  
81 101 /// <summary>
82 102 /// 费用收入单明细
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtFysrdInfoOutput.cs
... ... @@ -77,6 +77,36 @@ namespace NCC.Extend.Entitys.Dto.WtFysrd
77 77 /// 付款单位
78 78 /// </summary>
79 79 public string fkdw { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 往来单位主键(与 fkdw 一致)
  83 + /// </summary>
  84 + public string wldw { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 往来单位名称
  88 + /// </summary>
  89 + public string wldwmc { get; set; }
  90 +
  91 + /// <summary>
  92 + /// 单据状态
  93 + /// </summary>
  94 + public string djzt { get; set; }
  95 +
  96 + /// <summary>
  97 + /// 审批备注
  98 + /// </summary>
  99 + public string spbz { get; set; }
  100 +
  101 + /// <summary>
  102 + /// 一级审核人
  103 + /// </summary>
  104 + public string shr1 { get; set; }
  105 +
  106 + /// <summary>
  107 + /// 二级审核人
  108 + /// </summary>
  109 + public string shr2 { get; set; }
80 110  
81 111 /// <summary>
82 112 /// 费用收入单明细
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtFysrdListOutput.cs
... ... @@ -71,6 +71,26 @@ namespace NCC.Extend.Entitys.Dto.WtFysrd
71 71 /// 单据类型
72 72 /// </summary>
73 73 public string djlx { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 付款单位
  77 + /// </summary>
  78 + public string fkdw { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 往来单位主键
  82 + /// </summary>
  83 + public string wldw { get; set; }
  84 +
  85 + /// <summary>
  86 + /// 往来单位名称
  87 + /// </summary>
  88 + public string wldwmc { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 单据状态
  92 + /// </summary>
  93 + public string djzt { get; set; }
74 94  
75 95 }
76 96 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtFysrdListQueryInput.cs
... ... @@ -83,6 +83,16 @@ namespace NCC.Extend.Entitys.Dto.WtFysrd
83 83 /// 单据类型
84 84 /// </summary>
85 85 public string djlx { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 付款单位
  89 + /// </summary>
  90 + public string fkdw { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 往来单位(与 fkdw 任一匹配)
  94 + /// </summary>
  95 + public string wldw { get; set; }
86 96  
87 97 }
88 98 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPurchaseSummaryQueryInput.cs
... ... @@ -36,11 +36,21 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
36 36 public string Warehouse { get; set; }
37 37  
38 38 /// <summary>
39   - /// 单据类型(支持逗号分隔多值
  39 + /// 单据类型(逗号分隔,仅允许采购/销售/预售/委托代销共 9 类;空或 all 表示默认包含全部 9 类
40 40 /// </summary>
41 41 public string BillType { get; set; }
42 42  
43 43 /// <summary>
  44 + /// 排序列键(白名单):分类/品牌/商品聚合支持「分类名称」「采购金额」等;明细/线性列表支持「单据日期」「数量」等;非法则走各接口默认排序。
  45 + /// </summary>
  46 + public string SortField { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 排序方向:asc / desc(默认 desc)
  50 + /// </summary>
  51 + public string SortOrder { get; set; }
  52 +
  53 + /// <summary>
44 54 /// 下钻:商品分类主键(wt_pl.F_Id),与明细接口联用
45 55 /// </summary>
46 56 public string CategoryId { get; set; }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtStockSummaryQueryInput.cs
... ... @@ -24,5 +24,10 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
24 24 /// 商品关键字(匹配 wt_sp 名称或编码)
25 25 /// </summary>
26 26 public string Product { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 商品主键(wt_sp.F_Id);非空时按精确商品筛选(优先级高于 <see cref="Product"/>)
  30 + /// </summary>
  31 + public string ProductSpId { get; set; }
27 32 }
28 33 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtYskzjjsCrInput.cs
... ... @@ -29,6 +29,11 @@ namespace NCC.Extend.Entitys.Dto.WtYskzjjs
29 29 public string jsr { get; set; }
30 30  
31 31 /// <summary>
  32 + /// 往来单位(主表 <c>wldw</c>,与明细 <c>kh</c> 对应)
  33 + /// </summary>
  34 + public string wldw { get; set; }
  35 +
  36 + /// <summary>
32 37 /// 审核状态
33 38 /// </summary>
34 39 public string djzt { get; set; }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtYskzjjsInfoOutput.cs
... ... @@ -27,6 +27,11 @@ namespace NCC.Extend.Entitys.Dto.WtYskzjjs
27 27 /// 往来单位
28 28 /// </summary>
29 29 public string wldw { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 往来单位名称(详情展示,服务端解析)
  33 + /// </summary>
  34 + public string wldwmc { get; set; }
30 35  
31 36 /// <summary>
32 37 /// 经手人
... ... @@ -34,6 +39,26 @@ namespace NCC.Extend.Entitys.Dto.WtYskzjjs
34 39 public string jsr { get; set; }
35 40  
36 41 /// <summary>
  42 + /// 审批备注
  43 + /// </summary>
  44 + public string spbz { get; set; }
  45 +
  46 + /// <summary>
  47 + /// 审核人(终审)
  48 + /// </summary>
  49 + public string shr { get; set; }
  50 +
  51 + /// <summary>
  52 + /// 一级审核人
  53 + /// </summary>
  54 + public string shr1 { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 二级审核人
  58 + /// </summary>
  59 + public string shr2 { get; set; }
  60 +
  61 + /// <summary>
37 62 /// 审核状态
38 63 /// </summary>
39 64 public string djzt { get; set; }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtYskzjjsListOutput.cs
... ... @@ -86,6 +86,26 @@ namespace NCC.Extend.Entitys.Dto.WtYskzjjs
86 86 /// 往来单位名称(按往来单位/会员/供应商自动解析)
87 87 /// </summary>
88 88 public string wldwmc { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 审批备注
  92 + /// </summary>
  93 + public string spbz { get; set; }
  94 +
  95 + /// <summary>
  96 + /// 审核人(终审)
  97 + /// </summary>
  98 + public string shr { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 一级审核人
  102 + /// </summary>
  103 + public string shr1 { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 二级审核人
  107 + /// </summary>
  108 + public string shr2 { get; set; }
89 109  
90 110 }
91 111 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtFysrdEntity.cs
... ... @@ -94,6 +94,36 @@ namespace NCC.Extend.Entitys
94 94 /// </summary>
95 95 [SugarColumn(ColumnName = "fkdw")]
96 96 public string Fkdw { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 往来单位(与 fkdw 存同一 wt_wldw.F_Id,便于与 kh/gys/wldw 统一口径)
  100 + /// </summary>
  101 + [SugarColumn(ColumnName = "wldw", IsNullable = true)]
  102 + public string Wldw { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 单据状态(草稿/待审核/一级已审/已审核/审核不通过)
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "djzt", IsNullable = true)]
  108 + public string Djzt { get; set; }
  109 +
  110 + /// <summary>
  111 + /// 审批备注(与业务备注 bz 分离)
  112 + /// </summary>
  113 + [SugarColumn(ColumnName = "spbz", IsNullable = true)]
  114 + public string Spbz { get; set; }
  115 +
  116 + /// <summary>
  117 + /// 一级审核人
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "shr1", IsNullable = true)]
  120 + public string Shr1 { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 二级审核人
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "shr2", IsNullable = true)]
  126 + public string Shr2 { get; set; }
97 127  
98 128 }
99 129 }
100 130 \ No newline at end of file
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtYskzjjsEntity.cs
... ... @@ -42,6 +42,30 @@ namespace NCC.Extend.Entitys
42 42 public string Jsr { get; set; }
43 43  
44 44 /// <summary>
  45 + /// 审批备注(与业务备注分离;库表须含列 <c>spbz</c>)
  46 + /// </summary>
  47 + [SugarColumn(ColumnName = "spbz", IsNullable = true, ColumnDataType = "text")]
  48 + public string Spbz { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 审核人(终审)
  52 + /// </summary>
  53 + [SugarColumn(ColumnName = "shr", IsNullable = true)]
  54 + public string Shr { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 一级审核人
  58 + /// </summary>
  59 + [SugarColumn(ColumnName = "shr1", IsNullable = true)]
  60 + public string Shr1 { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 二级审核人
  64 + /// </summary>
  65 + [SugarColumn(ColumnName = "shr2", IsNullable = true)]
  66 + public string Shr2 { get; set; }
  67 +
  68 + /// <summary>
45 69 /// 审核状态
46 70 /// </summary>
47 71 [SugarColumn(ColumnName = "djzt")]
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtFysrdService.cs
... ... @@ -36,6 +36,7 @@ namespace NCC.Extend.WtFysrd
36 36 private readonly ISqlSugarRepository<WtFysrdMxEntity> _wtFysrdMxRepository;
37 37 private readonly SqlSugarScope _db;
38 38 private readonly IUserManager _userManager;
  39 + private readonly WtFysrdWorkflowHelper _fysrdWorkflow;
39 40  
40 41 /// <summary>
41 42 /// 初始化一个<see cref="WtFysrdService"/>类型的新实例
... ... @@ -43,12 +44,71 @@ namespace NCC.Extend.WtFysrd
43 44 public WtFysrdService(
44 45 ISqlSugarRepository<WtFysrdEntity> wtFysrdRepository,
45 46 ISqlSugarRepository<WtFysrdMxEntity> wtFysrdMxRepository,
46   - IUserManager userManager)
  47 + IUserManager userManager,
  48 + WtFysrdWorkflowHelper fysrdWorkflow)
47 49 {
48 50 _wtFysrdRepository = wtFysrdRepository;
49 51 _db = _wtFysrdRepository.Context;
50 52 _wtFysrdMxRepository = wtFysrdMxRepository;
51 53 _userManager = userManager;
  54 + _fysrdWorkflow = fysrdWorkflow;
  55 + }
  56 +
  57 + private static bool IsLockedForEdit(string djzt)
  58 + {
  59 + var s = djzt?.Trim() ?? "";
  60 + return s == "待审核" || s == "已审核" || s == "一级已审" || s == "待二级" || s == "一级已审/待二级";
  61 + }
  62 +
  63 + private static bool IsDeletableState(string djzt)
  64 + {
  65 + var s = djzt?.Trim() ?? "";
  66 + return string.IsNullOrEmpty(s) || s == "草稿" || s == "审核不通过";
  67 + }
  68 +
  69 + private void NormalizeHeaderFromInput(WtFysrdCrInput input, WtFysrdEntity entity)
  70 + {
  71 + if (input == null || entity == null) return;
  72 + if (!string.IsNullOrWhiteSpace(input.djlx))
  73 + entity.Djlx = WtFysrdWorkflowHelper.NormalizeOtherIncomeDjlx(input.djlx) ?? input.djlx;
  74 + if (!string.IsNullOrWhiteSpace(WtFysrdWorkflowHelper.ResolveCounterpartyId(input.wldw, input.fkdw, input.kh, input.gys)))
  75 + WtFysrdWorkflowHelper.ApplyCounterpartyToEntity(entity, input.wldw, input.fkdw, input.kh, input.gys);
  76 + }
  77 +
  78 + private async Task EnrichWldwForListAsync(IEnumerable<WtFysrdListOutput> rows)
  79 + {
  80 + if (rows == null) return;
  81 + var rowList = rows as IList<WtFysrdListOutput> ?? rows.ToList();
  82 + if (rowList.Count == 0) return;
  83 + foreach (var r in rowList)
  84 + {
  85 + if (string.IsNullOrWhiteSpace(r.wldw))
  86 + r.wldw = r.fkdw;
  87 + if (string.IsNullOrWhiteSpace(r.fkdw))
  88 + r.fkdw = r.wldw;
  89 + }
  90 + var ids = rowList.Select(x => x.wldw).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().ToList();
  91 + if (ids.Count == 0) return;
  92 + var wldwRows = await _db.Queryable<WtWldwEntity>().In(w => w.Id, ids).ToListAsync();
  93 + var map = wldwRows.ToDictionary(x => x.Id, x => x.Dwmc, StringComparer.OrdinalIgnoreCase);
  94 + foreach (var r in rowList)
  95 + {
  96 + if (!string.IsNullOrWhiteSpace(r.wldw) && map.TryGetValue(r.wldw, out var mc))
  97 + r.wldwmc = mc;
  98 + }
  99 + }
  100 +
  101 + /// <summary>
  102 + /// 提交审核:草稿/审核不通过 → 待审核(其他收入单;除备注外必填校验)
  103 + /// </summary>
  104 + /// <param name="id">单据编号</param>
  105 + [HttpPost("Actions/SubmitForAudit/{id}")]
  106 + public async Task<dynamic> SubmitForAudit(string id)
  107 + {
  108 + dynamic r = await _fysrdWorkflow.SubmitForAuditAsync(id);
  109 + if (r.success == true)
  110 + return new { msg = (string)(r.message ?? "已提交") };
  111 + throw NCCException.Bah((string)(r.message ?? "提交失败"));
52 112 }
53 113  
54 114 /// <summary>
... ... @@ -59,9 +119,22 @@ namespace NCC.Extend.WtFysrd
59 119 [HttpGet("{id}")]
60 120 public async Task<dynamic> GetInfo(string id)
61 121 {
  122 + _fysrdWorkflow.EnsureFysrdAuditColumns();
62 123 var entity = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
63 124 var output = entity.Adapt<WtFysrdInfoOutput>();
64   -
  125 + output.djlx = WtFysrdWorkflowHelper.NormalizeOtherIncomeDjlx(entity.Djlx) ?? entity.Djlx;
  126 + output.wldw = string.IsNullOrWhiteSpace(entity.Wldw) ? entity.Fkdw : entity.Wldw;
  127 + output.fkdw = string.IsNullOrWhiteSpace(entity.Fkdw) ? entity.Wldw : entity.Fkdw;
  128 + output.djzt = entity.Djzt;
  129 + output.spbz = entity.Spbz;
  130 + output.shr1 = entity.Shr1;
  131 + output.shr2 = entity.Shr2;
  132 + var wldwId = output.wldw ?? output.fkdw;
  133 + if (!string.IsNullOrWhiteSpace(wldwId))
  134 + {
  135 + var w = await _db.Queryable<WtWldwEntity>().FirstAsync(x => x.Id == wldwId);
  136 + output.wldwmc = w?.Dwmc;
  137 + }
65 138 var wtFysrdMxList = await _db.Queryable<WtFysrdMxEntity>().Where(w => w.Djbh == entity.Id).ToListAsync();
66 139 output.wtFysrdMxList = wtFysrdMxList.Adapt<List<WtFysrdMxInfoOutput>>();
67 140 return output;
... ... @@ -75,6 +148,7 @@ namespace NCC.Extend.WtFysrd
75 148 [HttpGet("")]
76 149 public async Task<dynamic> GetList([FromQuery] WtFysrdListQueryInput input)
77 150 {
  151 + _fysrdWorkflow.EnsureFysrdAuditColumns();
78 152 var sidx = input.sidx == null ? "id" : input.sidx;
79 153 List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null;
80 154 DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null;
... ... @@ -94,6 +168,8 @@ namespace NCC.Extend.WtFysrd
94 168 .WhereIF(!string.IsNullOrEmpty(input.bz), p => p.Bz.Contains(input.bz))
95 169 .WhereIF(!string.IsNullOrEmpty(input.zy), p => p.Zy.Contains(input.zy))
96 170 .WhereIF(!string.IsNullOrEmpty(input.djlx), p => p.Djlx.Contains(input.djlx))
  171 + .WhereIF(!string.IsNullOrEmpty(input.fkdw), p => p.Fkdw == input.fkdw || p.Wldw == input.fkdw)
  172 + .WhereIF(!string.IsNullOrEmpty(input.wldw), p => p.Wldw == input.wldw || p.Fkdw == input.wldw)
97 173 .Select(it=> new WtFysrdListOutput
98 174 {
99 175 id = it.Id,
... ... @@ -109,7 +185,11 @@ namespace NCC.Extend.WtFysrd
109 185 bz=it.Bz,
110 186 zy=it.Zy,
111 187 djlx=it.Djlx,
  188 + fkdw = it.Fkdw,
  189 + wldw = it.Wldw,
  190 + djzt = it.Djzt,
112 191 }).MergeTable().OrderBy(sidx+" "+input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
  192 + await EnrichWldwForListAsync(data.list);
113 193 return PageResult<WtFysrdListOutput>.SqlSugarPageResult(data);
114 194 }
115 195  
... ... @@ -121,15 +201,18 @@ namespace NCC.Extend.WtFysrd
121 201 [HttpPost("")]
122 202 public async Task Create([FromBody] WtFysrdCrInput input)
123 203 {
  204 + _fysrdWorkflow.EnsureFysrdAuditColumns();
124 205 var userInfo = await _userManager.GetUserInfo();
125 206 var entity = input.Adapt<WtFysrdEntity>();
  207 + NormalizeHeaderFromInput(input, entity);
  208 + entity.Djzt = "草稿";
126 209 // 生成每日递增单号
127 210 var today = DateTime.Now.ToString("yyyyMMdd");
128 211 var prefix = "FY";
129 212 // 根据单据类型设置前缀
130   - if (!string.IsNullOrEmpty(input.djlx)) {
131   - if (input.djlx.Contains("现金费用单")) prefix = "XF";
132   - else if (input.djlx.Contains("其他收入单")) prefix = "QT";
  213 + if (!string.IsNullOrEmpty(entity.Djlx)) {
  214 + if (entity.Djlx.Contains("现金费用单")) prefix = "XF";
  215 + else if (WtFysrdWorkflowHelper.IsOtherIncomeDjlx(entity.Djlx)) prefix = "QT";
133 216 else prefix = "FY"; // 默认费用单前缀
134 217 }
135 218 var maxId = await _db.Queryable<WtFysrdEntity>()
... ... @@ -199,6 +282,7 @@ namespace NCC.Extend.WtFysrd
199 282 [NonAction]
200 283 public async Task<dynamic> GetNoPagingList([FromQuery] WtFysrdListQueryInput input)
201 284 {
  285 + _fysrdWorkflow.EnsureFysrdAuditColumns();
202 286 var sidx = input.sidx == null ? "id" : input.sidx;
203 287 List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null;
204 288 DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null;
... ... @@ -218,6 +302,8 @@ namespace NCC.Extend.WtFysrd
218 302 .WhereIF(!string.IsNullOrEmpty(input.bz), p => p.Bz.Contains(input.bz))
219 303 .WhereIF(!string.IsNullOrEmpty(input.zy), p => p.Zy.Contains(input.zy))
220 304 .WhereIF(!string.IsNullOrEmpty(input.djlx), p => p.Djlx.Contains(input.djlx))
  305 + .WhereIF(!string.IsNullOrEmpty(input.fkdw), p => p.Fkdw == input.fkdw || p.Wldw == input.fkdw)
  306 + .WhereIF(!string.IsNullOrEmpty(input.wldw), p => p.Wldw == input.wldw || p.Fkdw == input.wldw)
221 307 .Select(it=> new WtFysrdListOutput
222 308 {
223 309 id = it.Id,
... ... @@ -233,7 +319,11 @@ namespace NCC.Extend.WtFysrd
233 319 bz=it.Bz,
234 320 zy=it.Zy,
235 321 djlx=it.Djlx,
  322 + fkdw = it.Fkdw,
  323 + wldw = it.Wldw,
  324 + djzt = it.Djzt,
236 325 }).MergeTable().OrderBy(sidx+" "+input.sort).ToListAsync();
  326 + await EnrichWldwForListAsync(data);
237 327 return data;
238 328 }
239 329  
... ... @@ -256,7 +346,7 @@ namespace NCC.Extend.WtFysrd
256 346 {
257 347 exportData = await this.GetNoPagingList(input);
258 348 }
259   - List<ParamsModel> paramList = "[{\"value\":\"单据编号\",\"field\":\"id\"},{\"value\":\"单据日期\",\"field\":\"djrq\"},{\"value\":\"分支机构\",\"field\":\"fzjg\"},{\"value\":\"部门\",\"field\":\"bm\"},{\"value\":\"经手人\",\"field\":\"jsr\"},{\"value\":\"结算账户\",\"field\":\"jszh\"},{\"value\":\"金额\",\"field\":\"je\"},{\"value\":\"制单人\",\"field\":\"zdr\"},{\"value\":\"审核人\",\"field\":\"shr\"},{\"value\":\"过账人\",\"field\":\"gzr\"},{\"value\":\"备注\",\"field\":\"bz\"},{\"value\":\"摘要\",\"field\":\"zy\"},{\"value\":\"单据类型\",\"field\":\"djlx\"},]".ToList<ParamsModel>();
  349 + List<ParamsModel> paramList = "[{\"value\":\"单据编号\",\"field\":\"id\"},{\"value\":\"单据日期\",\"field\":\"djrq\"},{\"value\":\"分支机构\",\"field\":\"fzjg\"},{\"value\":\"部门\",\"field\":\"bm\"},{\"value\":\"经手人\",\"field\":\"jsr\"},{\"value\":\"结算账户\",\"field\":\"jszh\"},{\"value\":\"金额\",\"field\":\"je\"},{\"value\":\"制单人\",\"field\":\"zdr\"},{\"value\":\"审核人\",\"field\":\"shr\"},{\"value\":\"过账人\",\"field\":\"gzr\"},{\"value\":\"备注\",\"field\":\"bz\"},{\"value\":\"摘要\",\"field\":\"zy\"},{\"value\":\"单据类型\",\"field\":\"djlx\"},{\"value\":\"往来单位\",\"field\":\"wldw\"},{\"value\":\"往来单位名称\",\"field\":\"wldwmc\"},{\"value\":\"付款单位\",\"field\":\"fkdw\"},{\"value\":\"单据状态\",\"field\":\"djzt\"},]".ToList<ParamsModel>();
260 350 ExcelConfig excelconfig = new ExcelConfig();
261 351 excelconfig.FileName = "费用单.xls";
262 352 excelconfig.HeadFont = "微软雅黑";
... ... @@ -292,6 +382,11 @@ namespace NCC.Extend.WtFysrd
292 382 public async Task BatchRemove([FromBody] List<string> ids)
293 383 {
294 384 var entitys = await _db.Queryable<WtFysrdEntity>().In(it => it.Id, ids).ToListAsync();
  385 + foreach (var e in entitys)
  386 + {
  387 + if (!IsDeletableState(e.Djzt))
  388 + throw NCCException.Bah($"单据 {e.Id} 当前状态不允许删除");
  389 + }
295 390 if (entitys.Count > 0)
296 391 {
297 392 try
... ... @@ -324,7 +419,37 @@ namespace NCC.Extend.WtFysrd
324 419 [HttpPut("{id}")]
325 420 public async Task Update(string id, [FromBody] WtFysrdUpInput input)
326 421 {
  422 + _fysrdWorkflow.EnsureFysrdAuditColumns();
  423 + var existing = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
  424 + if (IsLockedForEdit(existing.Djzt))
  425 + throw NCCException.Bah("当前单据状态不允许修改");
  426 +
327 427 var entity = input.Adapt<WtFysrdEntity>();
  428 + entity.Id = id;
  429 + entity.Djzt = existing.Djzt;
  430 + if (string.IsNullOrWhiteSpace(entity.Djlx))
  431 + entity.Djlx = existing.Djlx;
  432 + NormalizeHeaderFromInput(input, entity);
  433 + if (string.IsNullOrWhiteSpace(WtFysrdWorkflowHelper.ResolveCounterpartyId(entity.Wldw, entity.Fkdw, null, null)))
  434 + {
  435 + entity.Wldw = existing.Wldw;
  436 + entity.Fkdw = existing.Fkdw;
  437 + }
  438 +
  439 + if (WtFysrdWorkflowHelper.IsOtherIncomeDjlx(entity.Djlx)
  440 + && !string.Equals(existing.Djzt?.Trim(), "草稿", StringComparison.Ordinal))
  441 + {
  442 + var mxForVal = input.wtFysrdMxList.Adapt<List<WtFysrdMxEntity>>();
  443 + try
  444 + {
  445 + WtFysrdWorkflowHelper.ValidateOtherIncomeForSubmit(entity, mxForVal);
  446 + }
  447 + catch (InvalidOperationException ex)
  448 + {
  449 + throw NCCException.Bah(ex.Message);
  450 + }
  451 + }
  452 +
328 453 try
329 454 {
330 455 //开启事务
... ... @@ -367,6 +492,8 @@ namespace NCC.Extend.WtFysrd
367 492 {
368 493 var entity = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
369 494 _ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
  495 + if (!IsDeletableState(entity.Djzt))
  496 + throw NCCException.Bah("当前单据状态不允许删除");
370 497 try
371 498 {
372 499 //开启事务
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtFysrdWorkflowHelper.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +using System.Linq;
  4 +using System.Threading.Tasks;
  5 +using NCC.Common.Core.Manager;
  6 +using NCC.Dependency;
  7 +using NCC.Extend.Entitys;
  8 +using SqlSugar;
  9 +
  10 +namespace NCC.Extend.WtFysrd
  11 +{
  12 + /// <summary>
  13 + /// 其他收入单(wt_fysrd)审批流:与待办中心、<c>ApproveGeneric/RejectGeneric/ReverseApproval</c> 对接。
  14 + /// </summary>
  15 + public class WtFysrdWorkflowHelper : ITransient
  16 + {
  17 + /// <summary>与 <c>wt_shrysz.djmc</c>、待办 <c>billType</c> 一致</summary>
  18 + public const string BillName = "其他收入单";
  19 +
  20 + private readonly SqlSugarScope _db;
  21 + private readonly IUserManager _userManager;
  22 + private static bool _auditColumnsChecked;
  23 +
  24 + public WtFysrdWorkflowHelper(ISqlSugarRepository<WtFysrdEntity> fysrdRepository, IUserManager userManager)
  25 + {
  26 + _db = fysrdRepository.Context;
  27 + _userManager = userManager;
  28 + }
  29 +
  30 + /// <summary>
  31 + /// 确保 wt_fysrd 具备审批与往来单位统一字段(djzt、spbz、shr、shr1、shr2、wldw)
  32 + /// </summary>
  33 + public void EnsureFysrdAuditColumns()
  34 + {
  35 + if (_auditColumnsChecked) return;
  36 + lock (typeof(WtFysrdWorkflowHelper))
  37 + {
  38 + if (_auditColumnsChecked) return;
  39 + try
  40 + {
  41 + if (!_db.DbMaintenance.IsAnyTable("wt_fysrd")) { _auditColumnsChecked = true; return; }
  42 + var cols = _db.DbMaintenance.GetColumnInfosByTableName("wt_fysrd");
  43 + var names = cols.Select(c => c.DbColumnName.ToLowerInvariant()).ToHashSet();
  44 + if (!names.Contains("djzt"))
  45 + _db.Ado.ExecuteCommand(
  46 + "ALTER TABLE `wt_fysrd` ADD COLUMN `djzt` varchar(32) NULL COMMENT '单据状态'");
  47 + if (!names.Contains("spbz"))
  48 + _db.Ado.ExecuteCommand(
  49 + "ALTER TABLE `wt_fysrd` ADD COLUMN `spbz` text NULL COMMENT '审批备注'");
  50 + if (!names.Contains("shr"))
  51 + _db.Ado.ExecuteCommand(
  52 + "ALTER TABLE `wt_fysrd` ADD COLUMN `shr` varchar(64) NULL COMMENT '审核人'");
  53 + if (!names.Contains("shr1"))
  54 + _db.Ado.ExecuteCommand(
  55 + "ALTER TABLE `wt_fysrd` ADD COLUMN `shr1` varchar(500) NULL COMMENT '一级审核人'");
  56 + if (!names.Contains("shr2"))
  57 + _db.Ado.ExecuteCommand(
  58 + "ALTER TABLE `wt_fysrd` ADD COLUMN `shr2` varchar(500) NULL COMMENT '二级审核人'");
  59 + if (!names.Contains("wldw"))
  60 + _db.Ado.ExecuteCommand(
  61 + "ALTER TABLE `wt_fysrd` ADD COLUMN `wldw` varchar(64) NULL COMMENT '往来单位(wt_wldw.F_Id),与 fkdw 同步'");
  62 +
  63 + _db.Ado.ExecuteCommand(
  64 + "UPDATE `wt_fysrd` SET `djzt`='草稿' WHERE `djzt` IS NULL OR TRIM(IFNULL(`djzt`,''))=''");
  65 + _db.Ado.ExecuteCommand(
  66 + "UPDATE `wt_fysrd` SET `wldw`=`fkdw` WHERE (`wldw` IS NULL OR TRIM(IFNULL(`wldw`,''))='') AND `fkdw` IS NOT NULL AND TRIM(`fkdw`)<>''");
  67 + }
  68 + catch (Exception ex) { Console.WriteLine($"EnsureFysrdAuditColumns: {ex.Message}"); }
  69 + _auditColumnsChecked = true;
  70 + }
  71 + }
  72 +
  73 + /// <summary>是否按「其他收入单」口径处理的单据类型(含历史前端传值)</summary>
  74 + public static bool IsOtherIncomeDjlx(string djlx)
  75 + {
  76 + var n = NormalizeOtherIncomeDjlx(djlx);
  77 + return string.Equals(n, BillName, StringComparison.Ordinal);
  78 + }
  79 +
  80 + /// <summary>统一单据类型展示与审核配置匹配用字符串</summary>
  81 + public static string NormalizeOtherIncomeDjlx(string djlx)
  82 + {
  83 + if (string.IsNullOrWhiteSpace(djlx)) return djlx;
  84 + var t = djlx.Trim().Replace("其它", "其他");
  85 + if (t == "其他收入" || t == "其他收入单" || t.Contains("其他收入单"))
  86 + return BillName;
  87 + if (t.Contains("其他收入"))
  88 + return BillName;
  89 + return djlx.Trim();
  90 + }
  91 +
  92 + /// <summary>往来单位主键:优先 wldw,其次 fkdw、kh、gys(请求兼容)</summary>
  93 + public static string ResolveCounterpartyId(string wldw, string fkdw, string kh, string gys)
  94 + {
  95 + var v = !string.IsNullOrWhiteSpace(wldw) ? wldw
  96 + : !string.IsNullOrWhiteSpace(fkdw) ? fkdw
  97 + : !string.IsNullOrWhiteSpace(kh) ? kh
  98 + : gys;
  99 + return string.IsNullOrWhiteSpace(v) ? null : v.Trim();
  100 + }
  101 +
  102 + public static void ApplyCounterpartyToEntity(WtFysrdEntity entity, string wldw, string fkdw, string kh, string gys)
  103 + {
  104 + var id = ResolveCounterpartyId(wldw, fkdw, kh, gys);
  105 + if (string.IsNullOrEmpty(id)) return;
  106 + entity.Wldw = id;
  107 + entity.Fkdw = id;
  108 + }
  109 +
  110 + private static void AppendSpbz(WtFysrdEntity entity, string actionTag, string remark)
  111 + {
  112 + var r = (remark ?? "").Trim();
  113 + if (string.IsNullOrEmpty(r)) return;
  114 + var line = string.IsNullOrEmpty(actionTag) ? r : $"[{actionTag}] {r}";
  115 + var existing = entity.Spbz?.TrimEnd() ?? "";
  116 + entity.Spbz = string.IsNullOrWhiteSpace(existing) ? line : existing + "\n" + line;
  117 + }
  118 +
  119 + private static bool IsUserInAuditConfig(string configValue, string userId, string userAccount)
  120 + {
  121 + if (string.IsNullOrWhiteSpace(configValue)) return false;
  122 + if (string.IsNullOrWhiteSpace(userId) && string.IsNullOrWhiteSpace(userAccount)) return false;
  123 + var users = configValue
  124 + .Split(new[] { ',', ',', ';', ';', '|', ' ' }, StringSplitOptions.RemoveEmptyEntries)
  125 + .Select(x => x.Trim())
  126 + .Where(x => !string.IsNullOrEmpty(x))
  127 + .ToList();
  128 + return users.Any(x =>
  129 + (!string.IsNullOrWhiteSpace(userId) && string.Equals(x, userId.Trim(), StringComparison.OrdinalIgnoreCase))
  130 + || (!string.IsNullOrWhiteSpace(userAccount) && string.Equals(x, userAccount.Trim(), StringComparison.OrdinalIgnoreCase)));
  131 + }
  132 +
  133 + /// <summary>
  134 + /// 提交审核前校验:除备注外主表及明细必填(仅其他收入单)
  135 + /// </summary>
  136 + public static void ValidateOtherIncomeForSubmit(WtFysrdEntity h, List<WtFysrdMxEntity> mxList)
  137 + {
  138 + if (h == null) throw new ArgumentNullException(nameof(h));
  139 + if (!IsOtherIncomeDjlx(h.Djlx))
  140 + return;
  141 +
  142 + if (h.Djrq == null)
  143 + throw new InvalidOperationException("单据日期不能为空");
  144 + if (string.IsNullOrWhiteSpace(h.Fzjg))
  145 + throw new InvalidOperationException("分支机构不能为空");
  146 + if (string.IsNullOrWhiteSpace(h.Bm))
  147 + throw new InvalidOperationException("部门不能为空");
  148 + if (string.IsNullOrWhiteSpace(h.Jsr))
  149 + throw new InvalidOperationException("经手人不能为空");
  150 + if (string.IsNullOrWhiteSpace(h.Jszh))
  151 + throw new InvalidOperationException("结算账户不能为空");
  152 + if (h.Je <= 0)
  153 + throw new InvalidOperationException("金额必须大于 0");
  154 + if (string.IsNullOrWhiteSpace(h.Zdr))
  155 + throw new InvalidOperationException("制单人不能为空");
  156 + if (string.IsNullOrWhiteSpace(h.Zy))
  157 + throw new InvalidOperationException("摘要不能为空");
  158 + var cp = ResolveCounterpartyId(h.Wldw, h.Fkdw, null, null);
  159 + if (string.IsNullOrWhiteSpace(cp))
  160 + throw new InvalidOperationException("往来单位不能为空");
  161 +
  162 + if (mxList == null || mxList.Count == 0)
  163 + throw new InvalidOperationException("请先维护收入明细");
  164 +
  165 + foreach (var mx in mxList)
  166 + {
  167 + if (string.IsNullOrWhiteSpace(mx.Km))
  168 + throw new InvalidOperationException("明细科目不能为空");
  169 + if (mx.Je <= 0)
  170 + throw new InvalidOperationException("明细金额必须大于 0");
  171 + if (string.IsNullOrWhiteSpace(mx.Mxlx))
  172 + throw new InvalidOperationException("明细类型不能为空");
  173 + }
  174 + }
  175 +
  176 + /// <summary>
  177 + /// 草稿 / 审核不通过 → 待审核
  178 + /// </summary>
  179 + public async Task<dynamic> SubmitForAuditAsync(string id)
  180 + {
  181 + EnsureFysrdAuditColumns();
  182 + var entity = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
  183 + if (entity == null)
  184 + return new { success = false, message = "单据不存在" };
  185 + if (!IsOtherIncomeDjlx(entity.Djlx))
  186 + return new { success = false, message = "该接口仅支持其他收入单" };
  187 +
  188 + var st = entity.Djzt?.Trim() ?? "";
  189 + if (st != "草稿" && st != "审核不通过")
  190 + return new { success = false, message = "仅草稿或审核不通过状态可提交审核" };
  191 +
  192 + var mxList = await _db.Queryable<WtFysrdMxEntity>().Where(w => w.Djbh == id).ToListAsync();
  193 + try
  194 + {
  195 + ValidateOtherIncomeForSubmit(entity, mxList);
  196 + }
  197 + catch (InvalidOperationException ex)
  198 + {
  199 + return new { success = false, message = ex.Message };
  200 + }
  201 +
  202 + entity.Djzt = "待审核";
  203 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt }).ExecuteCommandAsync();
  204 + return new { success = true, message = "已提交审核,请在待办中心处理" };
  205 + }
  206 +
  207 + /// <summary>
  208 + /// 与 <see cref="NCC.Extend.WtXsckd.WtXsckdService.ApproveGeneric"/> 对接
  209 + /// </summary>
  210 + public async Task<dynamic> ApproveAsync(string id, string remark)
  211 + {
  212 + EnsureFysrdAuditColumns();
  213 + try
  214 + {
  215 + _db.BeginTran();
  216 + var entity = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
  217 + if (entity == null)
  218 + {
  219 + _db.RollbackTran();
  220 + return new { success = false, message = "单据不存在" };
  221 + }
  222 + if (!IsOtherIncomeDjlx(entity.Djlx))
  223 + {
  224 + _db.RollbackTran();
  225 + return new { success = false, message = "该单据不是其他收入单" };
  226 + }
  227 +
  228 + if (entity.Djzt == "已审核")
  229 + {
  230 + _db.RollbackTran();
  231 + return new { success = false, message = "该单据已经审核,无需重复审核" };
  232 + }
  233 +
  234 + var userInfo = await _userManager.GetUserInfo();
  235 + var userId = userInfo?.userId ?? "";
  236 + var userAccount = userInfo?.userAccount?.Trim();
  237 + if (string.IsNullOrEmpty(userAccount))
  238 + userAccount = _userManager.Account?.Trim();
  239 +
  240 + var approvalConfig = await _db.Queryable<WtShryszEntity>()
  241 + .Where(c => c.Djmc == BillName)
  242 + .FirstAsync();
  243 +
  244 + var configShr1 = approvalConfig?.Shr1;
  245 + var configShr2 = approvalConfig?.Shr2;
  246 + var hasTwoLevel = !string.IsNullOrWhiteSpace(configShr1) && !string.IsNullOrWhiteSpace(configShr2);
  247 +
  248 + if (string.IsNullOrWhiteSpace(configShr1))
  249 + {
  250 + _db.RollbackTran();
  251 + return new { success = false, message = "未配置其他收入单一级审核人员,请先在审核人员设置中维护" };
  252 + }
  253 +
  254 + var djztTrim = entity.Djzt?.Trim() ?? "";
  255 + if (entity.Djzt == "待审核" || string.IsNullOrEmpty(djztTrim))
  256 + {
  257 + if (!IsUserInAuditConfig(configShr1, userId, userAccount))
  258 + {
  259 + _db.RollbackTran();
  260 + return new { success = false, message = "当前用户不在其他收入单一级审核人员范围内" };
  261 + }
  262 +
  263 + if (hasTwoLevel)
  264 + {
  265 + entity.Djzt = "一级已审";
  266 + entity.Shr1 = userId;
  267 + AppendSpbz(entity, "一级通过", remark);
  268 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr1, x.Spbz }).ExecuteCommandAsync();
  269 + _db.CommitTran();
  270 + return new { success = true, message = "一级审核通过,等待二级审核" };
  271 + }
  272 +
  273 + entity.Djzt = "已审核";
  274 + entity.Shr = userId;
  275 + entity.Shr1 = userId;
  276 + AppendSpbz(entity, "审核通过", remark);
  277 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1, x.Spbz }).ExecuteCommandAsync();
  278 + _db.CommitTran();
  279 + return new { success = true, message = "审核通过" };
  280 + }
  281 +
  282 + if (entity.Djzt == "一级已审" || entity.Djzt == "待二级" || entity.Djzt == "一级已审/待二级")
  283 + {
  284 + if (!hasTwoLevel)
  285 + {
  286 + _db.RollbackTran();
  287 + return new { success = false, message = "当前为单级审核配置,单据状态异常" };
  288 + }
  289 + if (string.IsNullOrWhiteSpace(configShr2))
  290 + {
  291 + _db.RollbackTran();
  292 + return new { success = false, message = "未配置其他收入单二级审核人员,请先在审核人员设置中维护" };
  293 + }
  294 + if (!IsUserInAuditConfig(configShr2, userId, userAccount))
  295 + {
  296 + _db.RollbackTran();
  297 + return new { success = false, message = "当前用户不在其他收入单二级审核人员范围内" };
  298 + }
  299 +
  300 + entity.Djzt = "已审核";
  301 + entity.Shr2 = userId;
  302 + entity.Shr = userId;
  303 + AppendSpbz(entity, "二级通过", remark);
  304 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr2, x.Spbz }).ExecuteCommandAsync();
  305 + _db.CommitTran();
  306 + return new { success = true, message = "二级审核通过,审核完成" };
  307 + }
  308 +
  309 + _db.RollbackTran();
  310 + return new { success = false, message = $"当前单据状态「{entity.Djzt}」不允许审核" };
  311 + }
  312 + catch (Exception ex)
  313 + {
  314 + _db.RollbackTran();
  315 + return new { success = false, message = $"审核失败: {ex.Message}" };
  316 + }
  317 + }
  318 +
  319 + /// <summary>
  320 + /// 与 RejectGeneric 对接
  321 + /// </summary>
  322 + public async Task<dynamic> RejectAsync(string id, string remark)
  323 + {
  324 + EnsureFysrdAuditColumns();
  325 + var entity = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
  326 + if (entity == null)
  327 + return new { success = false, message = "单据不存在" };
  328 + if (!IsOtherIncomeDjlx(entity.Djlx))
  329 + return new { success = false, message = "该单据不是其他收入单" };
  330 + if (entity.Djzt == "已审核")
  331 + return new { success = false, message = "该单据已审核通过" };
  332 + if (entity.Djzt == "审核不通过")
  333 + return new { success = false, message = "该单据已是审核不通过状态" };
  334 + if (entity.Djzt == "草稿")
  335 + return new { success = false, message = "草稿状态请直接修改或删除单据" };
  336 +
  337 + var userInfo = await _userManager.GetUserInfo();
  338 + var userId = userInfo?.userId ?? "";
  339 + var userAccount = userInfo?.userAccount?.Trim();
  340 + if (string.IsNullOrEmpty(userAccount))
  341 + userAccount = _userManager.Account?.Trim();
  342 +
  343 + var approvalConfig = await _db.Queryable<WtShryszEntity>()
  344 + .Where(c => c.Djmc == BillName)
  345 + .FirstAsync();
  346 + var configShr1 = approvalConfig?.Shr1;
  347 + var configShr2 = approvalConfig?.Shr2;
  348 +
  349 + var djztTrim = entity.Djzt?.Trim() ?? "";
  350 + var level1 = djztTrim == "待审核" || string.IsNullOrEmpty(djztTrim);
  351 + var level2 = entity.Djzt == "一级已审" || entity.Djzt == "待二级" || entity.Djzt == "一级已审/待二级";
  352 +
  353 + if (!level1 && !level2)
  354 + return new { success = false, message = $"当前单据状态「{entity.Djzt}」不允许审核不通过" };
  355 +
  356 + if (level1)
  357 + {
  358 + if (string.IsNullOrWhiteSpace(configShr1))
  359 + return new { success = false, message = "未配置其他收入单一级审核人员,请先在审核人员设置中维护" };
  360 + if (!IsUserInAuditConfig(configShr1, userId, userAccount))
  361 + return new { success = false, message = "当前用户不在其他收入单一级审核人员范围内" };
  362 + }
  363 + else
  364 + {
  365 + if (string.IsNullOrWhiteSpace(configShr2))
  366 + return new { success = false, message = "未配置其他收入单二级审核人员,请先在审核人员设置中维护" };
  367 + if (!IsUserInAuditConfig(configShr2, userId, userAccount))
  368 + return new { success = false, message = "当前用户不在其他收入单二级审核人员范围内" };
  369 + }
  370 +
  371 + _db.BeginTran();
  372 + try
  373 + {
  374 + var row = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
  375 + if (row == null)
  376 + {
  377 + _db.RollbackTran();
  378 + return new { success = false, message = "单据不存在" };
  379 + }
  380 + AppendSpbz(row, "审核不通过", remark);
  381 + row.Djzt = "审核不通过";
  382 + row.Shr = userId;
  383 + row.Shr1 = null;
  384 + row.Shr2 = null;
  385 + await _db.Updateable(row).UpdateColumns(x => new { x.Djzt, x.Spbz, x.Shr, x.Shr1, x.Shr2 }).ExecuteCommandAsync();
  386 + _db.CommitTran();
  387 + return new { success = true, message = "已标记为审核不通过" };
  388 + }
  389 + catch (Exception ex)
  390 + {
  391 + _db.RollbackTran();
  392 + return new { success = false, message = $"操作失败: {ex.Message}" };
  393 + }
  394 + }
  395 +
  396 + /// <summary>
  397 + /// 与 ReverseApproval 对接:已审核或一级已审 → 待审核
  398 + /// </summary>
  399 + public async Task<dynamic> ReverseAsync(string id)
  400 + {
  401 + EnsureFysrdAuditColumns();
  402 + try
  403 + {
  404 + var entity = await _db.Queryable<WtFysrdEntity>().FirstAsync(p => p.Id == id);
  405 + if (entity == null)
  406 + return new { success = false, message = "单据不存在" };
  407 + if (!IsOtherIncomeDjlx(entity.Djlx))
  408 + return new { success = false, message = "该单据不是其他收入单" };
  409 +
  410 + if (entity.Djzt != "已审核" && entity.Djzt != "一级已审" && entity.Djzt != "一级已审/待二级" && entity.Djzt != "待二级")
  411 + return new { success = false, message = "当前单据状态不允许反审" };
  412 +
  413 + var userInfo = await _userManager.GetUserInfo();
  414 + var userId = userInfo?.userId ?? "";
  415 + var userAccount = userInfo?.userAccount?.Trim();
  416 + if (string.IsNullOrEmpty(userAccount))
  417 + userAccount = _userManager.Account?.Trim();
  418 +
  419 + var approvalConfig = await _db.Queryable<WtShryszEntity>()
  420 + .Where(c => c.Djmc == BillName)
  421 + .FirstAsync();
  422 +
  423 + var configShr1 = approvalConfig?.Shr1;
  424 + var configShr2 = approvalConfig?.Shr2;
  425 + var bothUnset = string.IsNullOrWhiteSpace(configShr1) && string.IsNullOrWhiteSpace(configShr2);
  426 + var isAuthorized = bothUnset
  427 + || IsUserInAuditConfig(configShr1 ?? "", userId, userAccount)
  428 + || IsUserInAuditConfig(configShr2 ?? "", userId, userAccount);
  429 +
  430 + if (!isAuthorized)
  431 + return new { success = false, message = "您没有反审权限,只有一级或二级审核人可以反审" };
  432 +
  433 + entity.Djzt = "待审核";
  434 + entity.Shr = null;
  435 + entity.Shr1 = null;
  436 + entity.Shr2 = null;
  437 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1, x.Shr2 }).ExecuteCommandAsync();
  438 + return new { success = true, message = "反审成功,单据已恢复为待审核状态" };
  439 + }
  440 + catch (Exception ex)
  441 + {
  442 + return new { success = false, message = $"反审失败: {ex.Message}" };
  443 + }
  444 + }
  445 + }
  446 +}
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtXsckdService.cs
... ... @@ -50,6 +50,7 @@ namespace NCC.Extend.WtXsckd
50 50 private readonly WtBillSummaryService _billSummaryService;
51 51 private readonly WtDjzyService _wtDjzyService;
52 52 private readonly WtTjd.WtTjdWorkflowHelper _tjdWorkflowHelper;
  53 + private readonly WtFysrd.WtFysrdWorkflowHelper _fysrdWorkflowHelper;
53 54  
54 55 // 序列号生成信号量,确保线程安全
55 56 private static readonly SemaphoreSlim _serialNumberSemaphore = new SemaphoreSlim(1, 1);
... ... @@ -1691,7 +1692,8 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
1691 1692 IUserManager userManager,
1692 1693 WtBillSummaryService billSummaryService,
1693 1694 WtDjzyService wtDjzyService,
1694   - WtTjd.WtTjdWorkflowHelper tjdWorkflowHelper)
  1695 + WtTjd.WtTjdWorkflowHelper tjdWorkflowHelper,
  1696 + WtFysrd.WtFysrdWorkflowHelper fysrdWorkflowHelper)
1695 1697 {
1696 1698 _wtXsckdRepository = wtXsckdRepository;
1697 1699 _db = _wtXsckdRepository.Context;
... ... @@ -1700,6 +1702,7 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
1700 1702 _billSummaryService = billSummaryService;
1701 1703 _wtDjzyService = wtDjzyService;
1702 1704 _tjdWorkflowHelper = tjdWorkflowHelper;
  1705 + _fysrdWorkflowHelper = fysrdWorkflowHelper;
1703 1706 }
1704 1707  
1705 1708 /// <summary>
... ... @@ -4453,7 +4456,12 @@ ORDER BY IFNULL(MAX(pp.F_Ppmc), &#39;&#39;), IFNULL(MAX(s.F_Spbm), &#39;&#39;)&quot;;
4453 4456 AddInCondition("c.ck", input.Warehouse, "stockWh");
4454 4457 AddInCondition("s.F_Pp", input.Brand, "stockPp");
4455 4458  
4456   - if (!string.IsNullOrWhiteSpace(input.Product))
  4459 + if (!string.IsNullOrWhiteSpace(input.ProductSpId))
  4460 + {
  4461 + whereList.Add("s.F_Id = @stockProdId");
  4462 + paramList.Add(new SugarParameter("@stockProdId", input.ProductSpId.Trim()));
  4463 + }
  4464 + else if (!string.IsNullOrWhiteSpace(input.Product))
4457 4465 {
4458 4466 whereList.Add("(s.F_Spmc LIKE @stockProdKw OR s.F_Spbm LIKE @stockProdKw)");
4459 4467 paramList.Add(new SugarParameter("@stockProdKw", $"%{input.Product.Trim()}%"));
... ... @@ -4464,6 +4472,51 @@ ORDER BY IFNULL(MAX(pp.F_Ppmc), &#39;&#39;), IFNULL(MAX(s.F_Spbm), &#39;&#39;)&quot;;
4464 4472 }
4465 4473  
4466 4474 /// <summary>
  4475 + /// 商品库存汇总(按仓库聚合):用于“选择某个商品后,展示该商品在各仓数量/成本”。
  4476 + /// </summary>
  4477 + /// <param name="input">必须提供 ProductSpId;可选 Warehouse(限定仓库范围)</param>
  4478 + [HttpGet("Actions/GetStockSummaryByWarehouse")]
  4479 + public async Task<dynamic> GetStockSummaryByWarehouse([FromQuery] WtStockSummaryQueryInput input)
  4480 + {
  4481 + input ??= new WtStockSummaryQueryInput();
  4482 + if (string.IsNullOrWhiteSpace(input.ProductSpId))
  4483 + {
  4484 + throw NCCException.Oh("商品 productSpId 不能为空");
  4485 + }
  4486 +
  4487 + // 仓库维度不需要分类/品牌筛选,但允许传 warehouse 限定仓库范围
  4488 + var (whereSql, paramList) = BuildWtStockCostSummaryWhere(
  4489 + new WtStockSummaryQueryInput
  4490 + {
  4491 + Warehouse = input.Warehouse,
  4492 + ProductSpId = input.ProductSpId
  4493 + },
  4494 + null);
  4495 +
  4496 + var sql = $@"
  4497 +SELECT
  4498 + c.ck AS `仓库Id`,
  4499 + IFNULL(NULLIF(TRIM(ck.F_mdmc), ''), CONCAT('未知仓库(', c.ck, ')')) AS `仓库名称`,
  4500 + IFNULL(NULLIF(TRIM(MAX(s.F_Spbm)), ''), '无') AS `商品编码`,
  4501 + IFNULL(NULLIF(TRIM(MAX(s.F_Spmc)), ''), '无') AS `商品名称`,
  4502 + SUM(CAST(c.sl AS DECIMAL(18,4))) AS `数量`,
  4503 + CASE WHEN SUM(CAST(c.sl AS DECIMAL(18,4))) = 0 THEN 0
  4504 + ELSE SUM(CAST(c.sl AS DECIMAL(18,4)) * c.cbj) / SUM(CAST(c.sl AS DECIMAL(18,4))) END AS `成本均价`,
  4505 + SUM(CAST(c.sl AS DECIMAL(18,4)) * c.cbj) AS `总成本`
  4506 +FROM wt_sp_cost c
  4507 +INNER JOIN wt_sp s ON s.F_Id = c.spbh
  4508 +LEFT JOIN wt_ck ck ON ck.F_Id = c.ck
  4509 +WHERE c.sl <> 0
  4510 + AND c.ck IS NOT NULL AND TRIM(c.ck) <> ''
  4511 + {whereSql}
  4512 +GROUP BY c.ck, ck.F_mdmc
  4513 +ORDER BY SUM(CAST(c.sl AS DECIMAL(18,4)) * c.cbj) DESC";
  4514 +
  4515 + var list = await _db.Ado.GetDataTableAsync(sql, paramList);
  4516 + return list;
  4517 + }
  4518 +
  4519 + /// <summary>
4467 4520 /// 获取指定商品在指定门店/仓库范围内的库存数量(与商品库存汇总口径一致:仅汇总 <c>wt_sp_cost.sl</c>,不按序列号条数统计;大量无序列号商品亦适用)。
4468 4521 /// </summary>
4469 4522 /// <param name="productId">商品ID</param>
... ... @@ -4580,11 +4633,135 @@ INNER JOIN wt_xsckd d ON d.F_Id = mx.djbh
4580 4633 LEFT JOIN wt_sp sp ON sp.F_Id = mx.spbh
4581 4634 LEFT JOIN wt_pl pl ON pl.F_Id = sp.F_Pl
4582 4635 LEFT JOIN wt_pp pp ON pp.F_Id = sp.F_Pp
4583   -LEFT JOIN wt_wldw w ON w.F_Id = d.gys
4584   -LEFT JOIN BASE_USER u ON u.F_Id = d.jsr
  4636 +LEFT JOIN wt_wldw w ON w.F_Id = IFNULL(NULLIF(TRIM(d.gys), ''), d.kh)
  4637 +LEFT JOIN BASE_USER u ON u.F_Id = IFNULL(NULLIF(TRIM(d.jsr), ''), d.zdr)
4585 4638 LEFT JOIN wt_ck ck ON ck.F_Id = IFNULL(NULLIF(TRIM(mx.rkck), ''), d.rkck)
4586 4639 WHERE 1 = 1";
4587 4640  
  4641 + /// <summary>采购汇总限定单据类型(默认全集)。</summary>
  4642 + private static readonly string[] PurSumAllowedDjlx =
  4643 + {
  4644 + "采购入库单", "采购退货单", "销售出库单", "销售退货单", "预售出库单", "预售退货单",
  4645 + "委托代销发货单", "委托代销退货单", "委托代销结算单"
  4646 + };
  4647 +
  4648 + /// <summary>主表 d.djlx 加减符号(与明细 mx 解耦)。</summary>
  4649 + private const string PurSumBizSignCase =
  4650 + "CASE WHEN d.djlx IN ('采购入库单','销售退货单','预售退货单','委托代销退货单') THEN 1 "
  4651 + + "WHEN d.djlx IN ('采购退货单','销售出库单','预售出库单','委托代销发货单','委托代销结算单') THEN -1 ELSE 0 END";
  4652 +
  4653 + // 注意:部分退货/冲销类单据明细 mx.sl/mx.je 可能已为负数;若再按单据类型乘 -1 会出现“双重取反”导致统计方向错误。
  4654 + // 统一口径:先取 ABS,再按单据类型决定正负。
  4655 + private static string PurSumSignedSlExpr => $"(ABS(CAST(mx.sl AS DECIMAL(18,4))) * ({PurSumBizSignCase}))";
  4656 +
  4657 + private static string PurSumSignedJeExpr => $"(ABS(CAST(mx.je AS DECIMAL(18,4))) * ({PurSumBizSignCase}))";
  4658 +
  4659 + // 单价保持正数展示(方向由数量/金额体现),避免出现负单价
  4660 + private static string PurSumSignedDjExpr => "(ABS(CAST(mx.dj AS DECIMAL(18,6))))";
  4661 +
  4662 + private static string NormalizePurSumDjlx(string raw)
  4663 + {
  4664 + if (string.IsNullOrWhiteSpace(raw))
  4665 + {
  4666 + return raw;
  4667 + }
  4668 +
  4669 + var t = raw.Trim();
  4670 + return t == "委托代销结算" ? "委托代销结算单" : t;
  4671 + }
  4672 +
  4673 + /// <summary>
  4674 + /// 采购汇总聚合 ORDER BY(白名单)。
  4675 + /// </summary>
  4676 + private static string BuildPurchaseSummaryAggOrderBy(string kind, string sortField, string sortOrder)
  4677 + {
  4678 + var dir = string.Equals(sortOrder?.Trim(), "asc", StringComparison.OrdinalIgnoreCase)
  4679 + ? "ASC"
  4680 + : "DESC";
  4681 +
  4682 + var sumJe = $"SUM({PurSumSignedJeExpr})";
  4683 + var sumSl = $"SUM({PurSumSignedSlExpr})";
  4684 + var avgDj = $"CASE WHEN ABS({sumSl}) < 0.00000001 THEN 0 ELSE {sumJe} / {sumSl} END";
  4685 +
  4686 + if (string.Equals(kind, "category", StringComparison.OrdinalIgnoreCase))
  4687 + {
  4688 + var col = (sortField?.Trim()) switch
  4689 + {
  4690 + "分类Id" or "categoryId" => "IFNULL(NULLIF(TRIM(sp.F_Pl), ''), '__NONE__')",
  4691 + "分类名称" or "categoryName" => "IFNULL(NULLIF(TRIM(MAX(pl.F_Plmc)), ''), '无')",
  4692 + "数量" or "qty" => sumSl,
  4693 + "入库单价" or "inboundPrice" or "unitPrice" => avgDj,
  4694 + "采购金额" or "purchaseAmount" => sumJe,
  4695 + _ => sumJe
  4696 + };
  4697 + return $"{col} {dir}";
  4698 + }
  4699 +
  4700 + if (string.Equals(kind, "brand", StringComparison.OrdinalIgnoreCase))
  4701 + {
  4702 + var col = (sortField?.Trim()) switch
  4703 + {
  4704 + "品牌Id" or "brandId" => "IFNULL(NULLIF(TRIM(sp.F_Pp), ''), '')",
  4705 + "品牌名称" or "brandName" => "IFNULL(NULLIF(TRIM(MAX(pp.F_Ppmc)), ''), '无')",
  4706 + "数量" or "qty" => sumSl,
  4707 + "入库单价" or "inboundPrice" or "unitPrice" => avgDj,
  4708 + "采购金额" or "purchaseAmount" => sumJe,
  4709 + _ => sumJe
  4710 + };
  4711 + return $"{col} {dir}";
  4712 + }
  4713 +
  4714 + if (string.Equals(kind, "productAgg", StringComparison.OrdinalIgnoreCase))
  4715 + {
  4716 + var col = (sortField?.Trim()) switch
  4717 + {
  4718 + "商品Id" or "productId" => "sp.F_Id",
  4719 + "商品编号" or "productCode" => "IFNULL(NULLIF(TRIM(MAX(sp.F_Spbm)), ''), '无')",
  4720 + "商品名称" or "productName" => "IFNULL(NULLIF(TRIM(MAX(sp.F_Spmc)), ''), '无')",
  4721 + "明细分类" or "detailCategory" =>
  4722 + "IFNULL(NULLIF(TRIM(CONCAT_WS(' / ', NULLIF(TRIM(sp.F_Splx1), ''), NULLIF(TRIM(sp.F_Splx2), ''))), ''), '无')",
  4723 + "数量" or "qty" => sumSl,
  4724 + "入库单价" or "inboundPrice" or "unitPrice" => avgDj,
  4725 + "采购金额" or "purchaseAmount" => sumJe,
  4726 + _ => sumJe
  4727 + };
  4728 + return $"{col} {dir}";
  4729 + }
  4730 +
  4731 + return $"{sumJe} DESC";
  4732 + }
  4733 +
  4734 + /// <summary>
  4735 + /// 采购明细 / 线性列表 ORDER BY(仅允许白名单列,防注入);数量金额单价按采购汇总符号口径排序。
  4736 + /// </summary>
  4737 + private static string BuildPurchaseSummaryDetailOrderBy(string sortField, string sortOrder)
  4738 + {
  4739 + var dir = string.Equals(sortOrder?.Trim(), "asc", StringComparison.OrdinalIgnoreCase)
  4740 + ? "ASC"
  4741 + : "DESC";
  4742 +
  4743 + var agentExpr = "IFNULL(NULLIF(TRIM(u.F_RealName), ''), '无')";
  4744 +
  4745 + var col = sortField?.Trim() switch
  4746 + {
  4747 + "单据日期" or "billDate" => "d.djrq",
  4748 + "单据编号" or "billId" => "d.F_Id",
  4749 + "单据类型" or "billType" => "d.djlx",
  4750 + "往来单位" or "contactName" => "IFNULL(NULLIF(TRIM(w.dwmc), ''), '无')",
  4751 + "经手人" or "agentName" => agentExpr,
  4752 + "仓库名称" or "warehouseName" => "IFNULL(NULLIF(TRIM(ck.F_mdmc), ''), '无')",
  4753 + "商品名称" or "productName" => "IFNULL(NULLIF(TRIM(mx.spmc), ''), '无')",
  4754 + "数量" or "qty" => PurSumSignedSlExpr,
  4755 + "入库单价" or "inboundPrice" or "unitPrice" => PurSumSignedDjExpr,
  4756 + "采购金额" or "purchaseAmount" => PurSumSignedJeExpr,
  4757 + _ => null
  4758 + };
  4759 +
  4760 + return string.IsNullOrEmpty(col)
  4761 + ? "d.djrq DESC, d.F_Id DESC, mx.F_Id"
  4762 + : $"{col} {dir}, d.F_Id DESC, mx.F_Id";
  4763 + }
  4764 +
4588 4765 /// <summary>
4589 4766 /// 采购汇总公共 WHERE(需配合含 sp/pl/pp 的 PurchaseSummaryJoinFromSql 使用)。
4590 4767 /// </summary>
... ... @@ -4644,22 +4821,56 @@ WHERE 1 = 1&quot;;
4644 4821 paramList.Add(new SugarParameter("@purSumEndDate", endDate.Date.AddDays(1)));
4645 4822 }
4646 4823  
4647   - AddInCondition("d.gys", input.ContactUnit, "purSumContact");
4648   -
4649   - // 经手人:主表 wt_xsckd.jsr 存 BASE_USER.F_Id,按用户主键 IN 筛选(与前端下拉 value 一致)
4650   - AddInCondition("d.jsr", input.Agent, "purSumJsr");
  4824 + whereList.Add("d.djzt = @purSumDjzt");
  4825 + paramList.Add(new SugarParameter("@purSumDjzt", "已审核"));
4651 4826  
4652   - AddInCondition("IFNULL(NULLIF(TRIM(mx.rkck), ''), d.rkck)", input.Warehouse, "purSumWh");
4653   -
4654   - var billTypeValues = SplitCsv(input.BillType);
  4827 + var allowedSet = new HashSet<string>(PurSumAllowedDjlx, StringComparer.Ordinal);
  4828 + var billRaw = input.BillType?.Trim();
  4829 + var billAll = string.IsNullOrEmpty(billRaw) || billRaw.Equals("all", StringComparison.OrdinalIgnoreCase);
  4830 + var billRequested = SplitCsv(input.BillType).Select(NormalizePurSumDjlx).Where(s => !string.IsNullOrWhiteSpace(s)).ToList();
  4831 + var billTypeValues = billAll || billRequested.Count == 0
  4832 + ? PurSumAllowedDjlx.ToList()
  4833 + : billRequested.Where(allowedSet.Contains).Distinct().ToList();
4655 4834 if (billTypeValues.Count == 0)
4656 4835 {
4657   - billTypeValues.Add("采购入库单");
4658   - billTypeValues.Add("采购退货单");
  4836 + billTypeValues = PurSumAllowedDjlx.ToList();
4659 4837 }
4660 4838  
4661 4839 AddInCondition("d.djlx", string.Join(",", billTypeValues), "purSumDjlx");
4662 4840  
  4841 + var contactVals = SplitCsv(input.ContactUnit);
  4842 + if (contactVals.Count > 0)
  4843 + {
  4844 + var gysParts = new List<string>();
  4845 + var khParts = new List<string>();
  4846 + for (var i = 0; i < contactVals.Count; i++)
  4847 + {
  4848 + var pn = $"@purSumContact{i}";
  4849 + gysParts.Add($"d.gys = {pn}");
  4850 + khParts.Add($"d.kh = {pn}");
  4851 + paramList.Add(new SugarParameter(pn, contactVals[i]));
  4852 + }
  4853 +
  4854 + whereList.Add($"(({string.Join(" OR ", gysParts)}) OR ({string.Join(" OR ", khParts)}))");
  4855 + }
  4856 +
  4857 + var agentVals = SplitCsv(input.Agent);
  4858 + if (agentVals.Count > 0)
  4859 + {
  4860 + var agentParts = new List<string>();
  4861 + for (var i = 0; i < agentVals.Count; i++)
  4862 + {
  4863 + var pn = $"@purSumJsr{i}";
  4864 + agentParts.Add(
  4865 + $"IFNULL(NULLIF(TRIM(d.jsr), ''), IFNULL(NULLIF(TRIM(d.zdr), ''), '')) = {pn}");
  4866 + paramList.Add(new SugarParameter(pn, agentVals[i]));
  4867 + }
  4868 +
  4869 + whereList.Add($"({string.Join(" OR ", agentParts)})");
  4870 + }
  4871 +
  4872 + AddInCondition("IFNULL(NULLIF(TRIM(mx.rkck), ''), d.rkck)", input.Warehouse, "purSumWh");
  4873 +
4663 4874 var productValues = SplitCsv(input.Product);
4664 4875 if (productValues.Count > 0)
4665 4876 {
... ... @@ -4668,7 +4879,8 @@ WHERE 1 = 1&quot;;
4668 4879 {
4669 4880 var pEq = $"@purProdEq{i}";
4670 4881 var pLike = $"@purProdLike{i}";
4671   - productConditions.Add($"(mx.spbh = {pEq} OR sp.F_Spbm = {pEq} OR mx.spmc LIKE {pLike} OR IFNULL(sp.F_Spmc, '') LIKE {pLike})");
  4882 + productConditions.Add(
  4883 + $"(mx.spbh = {pEq} OR sp.F_Id = {pEq} OR sp.F_Spbm = {pEq} OR mx.spmc LIKE {pLike} OR IFNULL(sp.F_Spmc, '') LIKE {pLike})");
4672 4884 paramList.Add(new SugarParameter(pEq, productValues[i]));
4673 4885 paramList.Add(new SugarParameter(pLike, $"%{productValues[i]}%"));
4674 4886 }
... ... @@ -4697,7 +4909,7 @@ WHERE 1 = 1&quot;;
4697 4909 var spId = !string.IsNullOrWhiteSpace(scopeProductSpId) ? scopeProductSpId.Trim() : (input.ProductSpId ?? string.Empty).Trim();
4698 4910 if (!string.IsNullOrEmpty(spId))
4699 4911 {
4700   - whereList.Add("mx.spbh = @purSumScopeSp");
  4912 + whereList.Add("(mx.spbh = @purSumScopeSp OR sp.F_Id = @purSumScopeSp)");
4701 4913 paramList.Add(new SugarParameter("@purSumScopeSp", spId));
4702 4914 }
4703 4915  
... ... @@ -4713,20 +4925,21 @@ WHERE 1 = 1&quot;;
4713 4925 {
4714 4926 input ??= new WtPurchaseSummaryQueryInput();
4715 4927 var (whereSql, paramList) = BuildPurchaseSummaryWhere(input, null, null, null);
  4928 + var orderBy = BuildPurchaseSummaryAggOrderBy("category", input.SortField, input.SortOrder);
4716 4929 var sql = $@"
4717 4930 SELECT
4718 4931 IFNULL(NULLIF(TRIM(sp.F_Pl), ''), '__NONE__') AS `分类Id`,
4719 4932 IFNULL(NULLIF(TRIM(MAX(pl.F_Plmc)), ''), '无') AS `分类名称`,
4720 4933 '无' AS `商品编号`,
4721 4934 IFNULL(NULLIF(TRIM(MAX(pl.F_Plmc)), ''), '无') AS `商品名称`,
4722   - SUM(CAST(mx.sl AS DECIMAL(18,4))) AS `数量`,
4723   - CASE WHEN ABS(SUM(CAST(mx.sl AS DECIMAL(18,4)))) < 0.00000001 THEN 0
4724   - ELSE SUM(CAST(mx.je AS DECIMAL(18,4))) / SUM(CAST(mx.sl AS DECIMAL(18,4))) END AS `入库单价`,
4725   - SUM(CAST(mx.je AS DECIMAL(18,4))) AS `采购金额`
  4935 + SUM({PurSumSignedSlExpr}) AS `数量`,
  4936 + CASE WHEN ABS(SUM({PurSumSignedSlExpr})) < 0.00000001 THEN 0
  4937 + ELSE SUM({PurSumSignedJeExpr}) / SUM({PurSumSignedSlExpr}) END AS `入库单价`,
  4938 + SUM({PurSumSignedJeExpr}) AS `采购金额`
4726 4939 {PurchaseSummaryJoinFromSql}
4727 4940 {whereSql}
4728 4941 GROUP BY sp.F_Pl, pl.F_Plmc
4729   -ORDER BY SUM(CAST(mx.je AS DECIMAL(18,4))) DESC";
  4942 +ORDER BY {orderBy}";
4730 4943 var list = await _db.Ado.GetDataTableAsync(sql, paramList);
4731 4944 return list;
4732 4945 }
... ... @@ -4744,20 +4957,21 @@ ORDER BY SUM(CAST(mx.je AS DECIMAL(18,4))) DESC&quot;;
4744 4957  
4745 4958 input ??= new WtPurchaseSummaryQueryInput();
4746 4959 var (whereSql, paramList) = BuildPurchaseSummaryWhere(input, categoryId, null, null);
  4960 + var orderBy = BuildPurchaseSummaryAggOrderBy("brand", input.SortField, input.SortOrder);
4747 4961 var sql = $@"
4748 4962 SELECT
4749 4963 IFNULL(NULLIF(TRIM(sp.F_Pp), ''), '') AS `品牌Id`,
4750 4964 IFNULL(NULLIF(TRIM(MAX(pp.F_Ppmc)), ''), '无') AS `品牌名称`,
4751 4965 '无' AS `商品编号`,
4752 4966 IFNULL(NULLIF(TRIM(MAX(pp.F_Ppmc)), ''), '无') AS `商品名称`,
4753   - SUM(CAST(mx.sl AS DECIMAL(18,4))) AS `数量`,
4754   - CASE WHEN ABS(SUM(CAST(mx.sl AS DECIMAL(18,4)))) < 0.00000001 THEN 0
4755   - ELSE SUM(CAST(mx.je AS DECIMAL(18,4))) / SUM(CAST(mx.sl AS DECIMAL(18,4))) END AS `入库单价`,
4756   - SUM(CAST(mx.je AS DECIMAL(18,4))) AS `采购金额`
  4967 + SUM({PurSumSignedSlExpr}) AS `数量`,
  4968 + CASE WHEN ABS(SUM({PurSumSignedSlExpr})) < 0.00000001 THEN 0
  4969 + ELSE SUM({PurSumSignedJeExpr}) / SUM({PurSumSignedSlExpr}) END AS `入库单价`,
  4970 + SUM({PurSumSignedJeExpr}) AS `采购金额`
4757 4971 {PurchaseSummaryJoinFromSql}
4758 4972 {whereSql}
4759 4973 GROUP BY sp.F_Pp, pp.F_Ppmc
4760   -ORDER BY SUM(CAST(mx.je AS DECIMAL(18,4))) DESC";
  4974 +ORDER BY {orderBy}";
4761 4975 var list = await _db.Ado.GetDataTableAsync(sql, paramList);
4762 4976 return list;
4763 4977 }
... ... @@ -4780,26 +4994,27 @@ ORDER BY SUM(CAST(mx.je AS DECIMAL(18,4))) DESC&quot;;
4780 4994  
4781 4995 input ??= new WtPurchaseSummaryQueryInput();
4782 4996 var (whereSql, paramList) = BuildPurchaseSummaryWhere(input, categoryId, brandId, null);
  4997 + var orderBy = BuildPurchaseSummaryAggOrderBy("productAgg", input.SortField, input.SortOrder);
4783 4998 var sql = $@"
4784 4999 SELECT
4785 5000 sp.F_Id AS `商品Id`,
4786 5001 IFNULL(NULLIF(TRIM(MAX(sp.F_Spbm)), ''), '无') AS `商品编号`,
4787 5002 IFNULL(NULLIF(TRIM(MAX(sp.F_Spmc)), ''), '无') AS `商品名称`,
4788 5003 IFNULL(NULLIF(TRIM(CONCAT_WS(' / ', NULLIF(TRIM(sp.F_Splx1), ''), NULLIF(TRIM(sp.F_Splx2), ''))), ''), '无') AS `明细分类`,
4789   - SUM(CAST(mx.sl AS DECIMAL(18,4))) AS `数量`,
4790   - CASE WHEN ABS(SUM(CAST(mx.sl AS DECIMAL(18,4)))) < 0.00000001 THEN 0
4791   - ELSE SUM(CAST(mx.je AS DECIMAL(18,4))) / SUM(CAST(mx.sl AS DECIMAL(18,4))) END AS `入库单价`,
4792   - SUM(CAST(mx.je AS DECIMAL(18,4))) AS `采购金额`
  5004 + SUM({PurSumSignedSlExpr}) AS `数量`,
  5005 + CASE WHEN ABS(SUM({PurSumSignedSlExpr})) < 0.00000001 THEN 0
  5006 + ELSE SUM({PurSumSignedJeExpr}) / SUM({PurSumSignedSlExpr}) END AS `入库单价`,
  5007 + SUM({PurSumSignedJeExpr}) AS `采购金额`
4793 5008 {PurchaseSummaryJoinFromSql}
4794 5009 {whereSql}
4795 5010 GROUP BY sp.F_Id, sp.F_Splx1, sp.F_Splx2
4796   -ORDER BY IFNULL(MAX(sp.F_Spbm), ''), IFNULL(MAX(sp.F_Spmc), '')";
  5011 +ORDER BY {orderBy}";
4797 5012 var list = await _db.Ado.GetDataTableAsync(sql, paramList);
4798 5013 return list;
4799 5014 }
4800 5015  
4801 5016 /// <summary>
4802   - /// 商品采购「线性列表」:指定分类下全部采购明细行(最多 2000 条),与明细列表列一致
  5017 + /// 商品采购「线性列表」:指定分类下全部相关单据明细(分页,与 GetPurchaseSummary 筛选及加减口径一致)
4803 5018 /// </summary>
4804 5019 [HttpGet("Actions/GetPurchaseSummaryLinear")]
4805 5020 public async Task<dynamic> GetPurchaseSummaryLinear([FromQuery] string categoryId, [FromQuery] WtPurchaseSummaryQueryInput input = null)
... ... @@ -4811,25 +5026,50 @@ ORDER BY IFNULL(MAX(sp.F_Spbm), &#39;&#39;), IFNULL(MAX(sp.F_Spmc), &#39;&#39;)&quot;;
4811 5026  
4812 5027 input ??= new WtPurchaseSummaryQueryInput();
4813 5028 var (whereSql, paramList) = BuildPurchaseSummaryWhere(input, categoryId, null, null);
4814   - const int maxRows = 2000;
  5029 +
  5030 + var page = input.CurrentPage.GetValueOrDefault(1);
  5031 + if (page < 1)
  5032 + {
  5033 + page = 1;
  5034 + }
  5035 +
  5036 + // 线性列表单次可拉较大窗口(默认 2000),与历史「最多 2000 条」一致;仍支持分页防一次过大。
  5037 + var pageSize = input.PageSize.GetValueOrDefault(2000);
  5038 + if (pageSize < 1)
  5039 + {
  5040 + pageSize = 2000;
  5041 + }
  5042 +
  5043 + if (pageSize > 2000)
  5044 + {
  5045 + pageSize = 2000;
  5046 + }
  5047 +
  5048 + var offset = (page - 1) * pageSize;
  5049 +
  5050 + var countSql = "SELECT COUNT(*) " + PurchaseSummaryJoinFromSql + whereSql;
  5051 + var totalObj = await _db.Ado.GetScalarAsync(countSql, paramList);
  5052 + var total = Convert.ToInt32(totalObj ?? 0);
  5053 +
  5054 + var orderByLin = BuildPurchaseSummaryDetailOrderBy(input.SortField, input.SortOrder);
4815 5055 var sql = $@"
4816 5056 SELECT
4817 5057 DATE_FORMAT(d.djrq, '%Y-%m-%d') AS `单据日期`,
4818 5058 d.F_Id AS `单据编号`,
4819 5059 d.djlx AS `单据类型`,
4820 5060 IFNULL(NULLIF(TRIM(w.dwmc), ''), '无') AS `往来单位`,
4821   - IFNULL(NULLIF(TRIM(u.F_RealName), ''), IFNULL(NULLIF(TRIM(d.jsr), ''), '无')) AS `经手人`,
  5061 + IFNULL(NULLIF(TRIM(u.F_RealName), ''), '无') AS `经手人`,
4822 5062 IFNULL(NULLIF(TRIM(ck.F_mdmc), ''), '无') AS `仓库名称`,
4823 5063 IFNULL(NULLIF(TRIM(mx.spmc), ''), '无') AS `商品名称`,
4824   - CAST(mx.sl AS DECIMAL(18,4)) AS `数量`,
4825   - mx.dj AS `入库单价`,
4826   - mx.je AS `采购金额`
  5064 + {PurSumSignedSlExpr} AS `数量`,
  5065 + {PurSumSignedDjExpr} AS `入库单价`,
  5066 + {PurSumSignedJeExpr} AS `采购金额`
4827 5067 {PurchaseSummaryJoinFromSql}
4828 5068 {whereSql}
4829   -ORDER BY d.djrq DESC, d.F_Id, mx.F_Id
4830   -LIMIT {maxRows}";
  5069 +ORDER BY {orderByLin}
  5070 +LIMIT {offset}, {pageSize}";
4831 5071 var list = await _db.Ado.GetDataTableAsync(sql, paramList);
4832   - return list;
  5072 + return new { list, total };
4833 5073 }
4834 5074  
4835 5075 /// <summary>
... ... @@ -4866,21 +5106,22 @@ LIMIT {maxRows}&quot;;
4866 5106 var totalObj = await _db.Ado.GetScalarAsync(countSql, paramList);
4867 5107 var total = Convert.ToInt32(totalObj ?? 0);
4868 5108  
  5109 + var orderByDetail = BuildPurchaseSummaryDetailOrderBy(input.SortField, input.SortOrder);
4869 5110 var listSql = $@"
4870 5111 SELECT
4871 5112 DATE_FORMAT(d.djrq, '%Y-%m-%d') AS `单据日期`,
4872 5113 d.F_Id AS `单据编号`,
4873 5114 d.djlx AS `单据类型`,
4874 5115 IFNULL(NULLIF(TRIM(w.dwmc), ''), '无') AS `往来单位`,
4875   - IFNULL(NULLIF(TRIM(u.F_RealName), ''), IFNULL(NULLIF(TRIM(d.jsr), ''), '无')) AS `经手人`,
  5116 + IFNULL(NULLIF(TRIM(u.F_RealName), ''), '无') AS `经手人`,
4876 5117 IFNULL(NULLIF(TRIM(ck.F_mdmc), ''), '无') AS `仓库名称`,
4877 5118 IFNULL(NULLIF(TRIM(mx.spmc), ''), '无') AS `商品名称`,
4878   - CAST(mx.sl AS DECIMAL(18,4)) AS `数量`,
4879   - mx.dj AS `入库单价`,
4880   - mx.je AS `采购金额`
  5119 + {PurSumSignedSlExpr} AS `数量`,
  5120 + {PurSumSignedDjExpr} AS `入库单价`,
  5121 + {PurSumSignedJeExpr} AS `采购金额`
4881 5122 {PurchaseSummaryJoinFromSql}
4882 5123 {whereSql}
4883   -ORDER BY d.djrq DESC, d.F_Id, mx.F_Id
  5124 +ORDER BY {orderByDetail}
4884 5125 LIMIT {offset}, {pageSize}";
4885 5126 var list = await _db.Ado.GetDataTableAsync(listSql, paramList);
4886 5127 return new { list, total };
... ... @@ -5744,11 +5985,13 @@ LIMIT {offset}, {pageSize}&quot;;
5744 5985 }
5745 5986  
5746 5987 /// <summary>
5747   - /// 通用审核接口:自动根据单据的 djlx 匹配审核配置;编号以 TJD 开头时优先判断是否为 wt_xsckd 同价调拨单,否则走商品调价单审批流
  5988 + /// 通用审核接口:自动根据单据的 djlx 匹配审核配置;编号以 TJD 开头时优先判断是否为 wt_xsckd 同价调拨单,否则走商品调价单审批流;wt_fysrd 其他收入单走费用收入单审批流
5748 5989 /// </summary>
5749 5990 [HttpPost("ApproveGeneric/{id}")]
5750 5991 public async Task<dynamic> ApproveGeneric(string id, [FromBody] WtApprovalRemarkInput input)
5751 5992 {
  5993 + if (!string.IsNullOrEmpty(id) && await _db.Queryable<WtFysrdEntity>().AnyAsync(x => x.Id == id))
  5994 + return await _fysrdWorkflowHelper.ApproveAsync(id, input?.remark);
5752 5995 if (!string.IsNullOrEmpty(id) && id.StartsWith("TJD", StringComparison.Ordinal))
5753 5996 {
5754 5997 var isSamePriceTransfer = await _db.Queryable<WtXsckdEntity>()
... ... @@ -5768,6 +6011,8 @@ LIMIT {offset}, {pageSize}&quot;;
5768 6011 [HttpPost("RejectGeneric/{id}")]
5769 6012 public async Task<dynamic> RejectGeneric(string id, [FromBody] WtApprovalRemarkInput input)
5770 6013 {
  6014 + if (!string.IsNullOrEmpty(id) && await _db.Queryable<WtFysrdEntity>().AnyAsync(x => x.Id == id))
  6015 + return await _fysrdWorkflowHelper.RejectAsync(id, input?.remark);
5771 6016 if (!string.IsNullOrEmpty(id) && id.StartsWith("TJD", StringComparison.Ordinal))
5772 6017 {
5773 6018 var isSamePriceTransfer = await _db.Queryable<WtXsckdEntity>()
... ... @@ -5942,6 +6187,9 @@ LIMIT {offset}, {pageSize}&quot;;
5942 6187 {
5943 6188 try
5944 6189 {
  6190 + if (!string.IsNullOrEmpty(id) && await _db.Queryable<WtFysrdEntity>().AnyAsync(x => x.Id == id))
  6191 + return await _fysrdWorkflowHelper.ReverseAsync(id);
  6192 +
5945 6193 var entity = await _db.Queryable<WtXsckdEntity>().Where(x => x.Id == id).FirstAsync();
5946 6194 if (entity == null)
5947 6195 return new { success = false, message = "单据不存在" };
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtYskzjjsService.cs
... ... @@ -14,6 +14,7 @@ using System.Collections.Generic;
14 14 using System.Linq;
15 15 using System.Threading.Tasks;
16 16 using NCC.Extend.Entitys;
  17 +using NCC.Extend.Entitys.Dto;
17 18 using NCC.Extend.Entitys.Dto.WtYskzjjs;
18 19 using Yitter.IdGenerator;
19 20 using NCC.Common.Helper;
... ... @@ -40,6 +41,7 @@ namespace NCC.Extend.WtYskzjjs
40 41 private readonly IUserManager _userManager;
41 42 private readonly WtDjzyService _wtDjzyService;
42 43 private readonly WtBillSummaryService _billSummaryService;
  44 + private static bool _yskAuditColsEnsured;
43 45  
44 46 /// <summary>
45 47 /// 初始化一个<see cref="WtYskzjjsService"/>类型的新实例
... ... @@ -60,6 +62,39 @@ namespace NCC.Extend.WtYskzjjs
60 62 }
61 63  
62 64 /// <summary>
  65 + /// 确保 wt_yskzjjs 具备审批相关列(避免线上旧库缺列导致 Unknown column)。
  66 + /// </summary>
  67 + private void EnsureYskzjjsAuditColumns()
  68 + {
  69 + if (_yskAuditColsEnsured) return;
  70 + lock (typeof(WtYskzjjsService))
  71 + {
  72 + if (_yskAuditColsEnsured) return;
  73 + try
  74 + {
  75 + if (!_db.DbMaintenance.IsAnyTable("wt_yskzjjs")) { _yskAuditColsEnsured = true; return; }
  76 + var cols = _db.DbMaintenance.GetColumnInfosByTableName("wt_yskzjjs");
  77 + var names = cols.Select(c => c.DbColumnName.ToLowerInvariant()).ToHashSet();
  78 + if (!names.Contains("djzt"))
  79 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_yskzjjs` ADD COLUMN `djzt` varchar(32) NULL COMMENT '单据状态'");
  80 + if (!names.Contains("spbz"))
  81 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_yskzjjs` ADD COLUMN `spbz` text NULL COMMENT '审批备注'");
  82 + if (!names.Contains("shr"))
  83 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_yskzjjs` ADD COLUMN `shr` varchar(64) NULL COMMENT '审核人'");
  84 + if (!names.Contains("shr1"))
  85 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_yskzjjs` ADD COLUMN `shr1` varchar(500) NULL COMMENT '一级审核人'");
  86 + if (!names.Contains("shr2"))
  87 + _db.Ado.ExecuteCommand("ALTER TABLE `wt_yskzjjs` ADD COLUMN `shr2` varchar(500) NULL COMMENT '二级审核人'");
  88 + }
  89 + catch (Exception ex)
  90 + {
  91 + Console.WriteLine($"EnsureYskzjjsAuditColumns: {ex.Message}");
  92 + }
  93 + _yskAuditColsEnsured = true;
  94 + }
  95 + }
  96 +
  97 + /// <summary>
63 98 /// 获取应收款增加减少
64 99 /// </summary>
65 100 /// <param name="id">参数</param>
... ... @@ -67,12 +102,14 @@ namespace NCC.Extend.WtYskzjjs
67 102 [HttpGet("{id}")]
68 103 public async Task<dynamic> GetInfo(string id)
69 104 {
  105 + EnsureYskzjjsAuditColumns();
70 106 var entity = await _db.Queryable<WtYskzjjsEntity>().FirstAsync(p => p.Id == id);
71 107 var output = entity.Adapt<WtYskzjjsInfoOutput>();
72 108  
73 109 var wtYskzjjsMxList = await _db.Queryable<WtYskzjjsMxEntity>().Where(w => w.Djbh == entity.Id).ToListAsync();
74 110 output.wtYskzjjsMxList = wtYskzjjsMxList.Adapt<List<WtYskzjjsMxInfoOutput>>();
75 111 output.billZy = await _billSummaryService.ComputeWtYskzjjsZyByBillIdAsync(entity.Id);
  112 + output.wldwmc = await ResolveWldwMcAsync(output.wldw);
76 113 return output;
77 114 }
78 115  
... ... @@ -84,6 +121,7 @@ namespace NCC.Extend.WtYskzjjs
84 121 [HttpGet("")]
85 122 public async Task<dynamic> GetList([FromQuery] WtYskzjjsListQueryInput input)
86 123 {
  124 + EnsureYskzjjsAuditColumns();
87 125 var sidx = input.sidx == null ? "id" : input.sidx;
88 126 List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null;
89 127 DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null;
... ... @@ -113,6 +151,10 @@ namespace NCC.Extend.WtYskzjjs
113 151 djrq = d.Djrq,
114 152 jsr = d.Jsr,
115 153 djzt = d.Djzt,
  154 + spbz = d.Spbz,
  155 + shr = d.Shr,
  156 + shr1 = d.Shr1,
  157 + shr2 = d.Shr2,
116 158 dyddh = d.Dyddh,
117 159 hysjh = d.Hysjh,
118 160 fkzh = d.Fkzh,
... ... @@ -132,6 +174,10 @@ namespace NCC.Extend.WtYskzjjs
132 174 djrq = it.djrq,
133 175 jsr = it.jsr,
134 176 djzt = it.djzt,
  177 + spbz = it.spbz,
  178 + shr = it.shr,
  179 + shr1 = it.shr1,
  180 + shr2 = it.shr2,
135 181 dyddh = it.dyddh,
136 182 hysjh = it.hysjh,
137 183 fkzh = it.fkzh,
... ... @@ -158,9 +204,12 @@ namespace NCC.Extend.WtYskzjjs
158 204 [HttpPost("")]
159 205 public async Task Create([FromBody] WtYskzjjsCrInput input)
160 206 {
  207 + EnsureYskzjjsAuditColumns();
161 208 var userInfo = await _userManager.GetUserInfo();
162 209 NormalizeDjlxForQt(input);
163 210 NormalizeDjlxForTransfer(input);
  211 + SyncMxKhFromWldw(input);
  212 + ThrowIfInvalidForSubmit(input, input?.djzt);
164 213 var entity = input.Adapt<WtYskzjjsEntity>();
165 214 entity.Id = await BuildCreateBillId(input);
166 215 try
... ... @@ -264,6 +313,10 @@ namespace NCC.Extend.WtYskzjjs
264 313 djrq = d.Djrq,
265 314 jsr = d.Jsr,
266 315 djzt = d.Djzt,
  316 + spbz = d.Spbz,
  317 + shr = d.Shr,
  318 + shr1 = d.Shr1,
  319 + shr2 = d.Shr2,
267 320 dyddh = d.Dyddh,
268 321 hysjh = d.Hysjh,
269 322 fkzh = d.Fkzh,
... ... @@ -283,6 +336,10 @@ namespace NCC.Extend.WtYskzjjs
283 336 djrq = it.djrq,
284 337 jsr = it.jsr,
285 338 djzt = it.djzt,
  339 + spbz = it.spbz,
  340 + shr = it.shr,
  341 + shr1 = it.shr1,
  342 + shr2 = it.shr2,
286 343 dyddh = it.dyddh,
287 344 hysjh = it.hysjh,
288 345 fkzh = it.fkzh,
... ... @@ -389,8 +446,11 @@ namespace NCC.Extend.WtYskzjjs
389 446 [HttpPut("{id}")]
390 447 public async Task Update(string id, [FromBody] WtYskzjjsUpInput input)
391 448 {
  449 + EnsureYskzjjsAuditColumns();
392 450 NormalizeDjlxForQt(input);
393 451 NormalizeDjlxForTransfer(input);
  452 + SyncMxKhFromWldw(input);
  453 + ThrowIfInvalidForSubmit(input, input?.djzt);
394 454 var entity = input.Adapt<WtYskzjjsEntity>();
395 455 try
396 456 {
... ... @@ -461,7 +521,7 @@ namespace NCC.Extend.WtYskzjjs
461 521 }
462 522  
463 523 /// <summary>
464   - /// 兼容 qt 页面对 djlx 的历史传值:当明细为“其他收入单”时统一落库为“其他应收”。
  524 + /// 兼容 qt 页面对 djlx 的历史传值:当明细为“其他收入单”时统一落库为“其他应收”。
465 525 /// 仅修正 qt 场景,不影响其它单据类型。
466 526 /// </summary>
467 527 /// <param name="input">新增/更新入参</param>
... ... @@ -478,9 +538,10 @@ namespace NCC.Extend.WtYskzjjs
478 538  
479 539 if (string.IsNullOrWhiteSpace(input.djlx) ||
480 540 input.djlx.Trim().Equals("应收款减少", StringComparison.Ordinal) ||
481   - input.djlx.Trim().Equals("其他收入单", StringComparison.Ordinal))
  541 + input.djlx.Trim().Equals("其他收入单", StringComparison.Ordinal) ||
  542 + input.djlx.Trim().Equals("其他应收款", StringComparison.Ordinal))
482 543 {
483   - input.djlx = "其他应收";
  544 + input.djlx = "其他应收";
484 545 }
485 546 }
486 547  
... ... @@ -598,5 +659,326 @@ namespace NCC.Extend.WtYskzjjs
598 659  
599 660 await _billSummaryService.EnrichWtYskzjjsListBillZyAsync(rowList);
600 661 }
  662 +
  663 + /// <summary>
  664 + /// 主表往来单位写入明细客户字段,便于列表按 kh 筛选与详情列展示。
  665 + /// </summary>
  666 + private static void SyncMxKhFromWldw(WtYskzjjsCrInput input)
  667 + {
  668 + if (input == null || string.IsNullOrWhiteSpace(input.wldw) || input.wtYskzjjsMxList == null)
  669 + return;
  670 + var w = input.wldw.Trim();
  671 + foreach (var mx in input.wtYskzjjsMxList.Where(m => m != null))
  672 + {
  673 + if (string.IsNullOrWhiteSpace(mx.kh))
  674 + mx.kh = w;
  675 + }
  676 + }
  677 +
  678 + private async Task<string> ResolveWldwMcAsync(string wldwId)
  679 + {
  680 + if (string.IsNullOrWhiteSpace(wldwId))
  681 + return "无";
  682 + var dwmc = (await _db.Queryable<WtWldwEntity>().Where(x => x.Id == wldwId).Select(x => x.Dwmc).ToListAsync()).FirstOrDefault();
  683 + if (!string.IsNullOrWhiteSpace(dwmc))
  684 + return dwmc;
  685 + var xm = (await _db.Queryable<WtHyEntity>().Where(x => x.Id == wldwId).Select(x => x.Xm).ToListAsync()).FirstOrDefault();
  686 + if (!string.IsNullOrWhiteSpace(xm))
  687 + return xm;
  688 + var gysmc = (await _db.Queryable<WtGysEntity>().Where(x => x.Id == wldwId).Select(x => x.Gysmc).ToListAsync()).FirstOrDefault();
  689 + if (!string.IsNullOrWhiteSpace(gysmc))
  690 + return gysmc;
  691 + return "无";
  692 + }
  693 +
  694 + private static void AppendApprovalSpbzLineYsk(WtYskzjjsEntity entity, string actionTag, string remark)
  695 + {
  696 + var r = (remark ?? "").Trim();
  697 + if (string.IsNullOrEmpty(r)) return;
  698 + var line = string.IsNullOrEmpty(actionTag) ? r : $"[{actionTag}] {r}";
  699 + var existing = entity.Spbz?.TrimEnd() ?? "";
  700 + entity.Spbz = string.IsNullOrWhiteSpace(existing) ? line : existing + "\n" + line;
  701 + }
  702 +
  703 + private static bool IsUserInAuditConfigYsk(string configValue, string userId, string userAccount = null)
  704 + {
  705 + if (string.IsNullOrWhiteSpace(configValue))
  706 + return false;
  707 + if (string.IsNullOrWhiteSpace(userId) && string.IsNullOrWhiteSpace(userAccount))
  708 + return false;
  709 +
  710 + var users = configValue
  711 + .Split(new[] { ',', ',', ';', ';', '|', ' ' }, StringSplitOptions.RemoveEmptyEntries)
  712 + .Select(x => x.Trim())
  713 + .Where(x => !string.IsNullOrEmpty(x))
  714 + .ToList();
  715 +
  716 + return users.Any(x =>
  717 + (!string.IsNullOrWhiteSpace(userId) && string.Equals(x, userId.Trim(), StringComparison.OrdinalIgnoreCase))
  718 + || (!string.IsNullOrWhiteSpace(userAccount) && string.Equals(x, userAccount.Trim(), StringComparison.OrdinalIgnoreCase)));
  719 + }
  720 +
  721 + /// <summary>
  722 + /// 提交审核:草稿 / 空 / 审核不通过 → 待审核
  723 + /// </summary>
  724 + [HttpPost("Actions/SubmitForAudit/{id}")]
  725 + public async Task<dynamic> SubmitForAudit(string id)
  726 + {
  727 + EnsureYskzjjsAuditColumns();
  728 + var entity = await _db.Queryable<WtYskzjjsEntity>().FirstAsync(p => p.Id == id);
  729 + if (entity == null)
  730 + return new { success = false, message = "单据不存在" };
  731 + var z = entity.Djzt?.Trim() ?? "";
  732 + if (z != "草稿" && z != "审核不通过" && !string.IsNullOrEmpty(z))
  733 + return new { success = false, message = $"当前状态「{entity.Djzt}」不可提交审核" };
  734 +
  735 + // 提交审核前做必填校验(备注除外)
  736 + var mxList = await _db.Queryable<WtYskzjjsMxEntity>().Where(x => x.Djbh == entity.Id).ToListAsync();
  737 + var inputLike = new WtYskzjjsCrInput
  738 + {
  739 + id = entity.Id,
  740 + djlx = entity.Djlx,
  741 + djrq = entity.Djrq,
  742 + jsr = entity.Jsr,
  743 + wldw = entity.Wldw,
  744 + djzt = "待审核",
  745 + fkzh = entity.Fkzh,
  746 + fkje = entity.Fkje,
  747 + skzh = entity.Skzh,
  748 + skje = entity.Skje,
  749 + zy = entity.Zy,
  750 + wtYskzjjsMxList = mxList?.Select(m => new WtYskzjjsMxCrInput
  751 + {
  752 + srxmmc = m.Srxmmc,
  753 + je = m.Je,
  754 + bz = m.Bz,
  755 + mxlx = m.Mxlx,
  756 + kh = m.Kh,
  757 + zhbh = m.Zhbh
  758 + }).ToList()
  759 + };
  760 + try
  761 + {
  762 + ThrowIfInvalidForSubmit(inputLike, "待审核");
  763 + }
  764 + catch (Exception ex)
  765 + {
  766 + return new { success = false, message = ex.Message };
  767 + }
  768 +
  769 + entity.Djzt = "待审核";
  770 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt }).ExecuteCommandAsync();
  771 + return new { success = true, message = "已提交审核" };
  772 + }
  773 +
  774 + /// <summary>
  775 + /// 除备注外必填:仅在提交审核/非草稿状态强制校验;草稿可放宽。
  776 + /// </summary>
  777 + private static void ThrowIfInvalidForSubmit(WtYskzjjsCrInput input, string djzt)
  778 + {
  779 + if (input == null) throw NCCException.Oh("参数不能为空");
  780 + var z = (djzt ?? input.djzt ?? "").Trim();
  781 + if (string.IsNullOrEmpty(z) || z == "草稿") return;
  782 +
  783 + if (input.djrq == null) throw NCCException.Oh("请选择单据日期");
  784 + if (string.IsNullOrWhiteSpace(input.jsr)) throw NCCException.Oh("请选择经手人");
  785 + if (string.IsNullOrWhiteSpace(input.wldw)) throw NCCException.Oh("请选择往来单位");
  786 + if (string.IsNullOrWhiteSpace(input.skzh)) throw NCCException.Oh("请选择收款账户");
  787 + if (input.skje <= 0) throw NCCException.Oh("请填写明细金额,收款金额须大于 0");
  788 + if (input.wtYskzjjsMxList == null || input.wtYskzjjsMxList.Count == 0) throw NCCException.Oh("请至少添加一条明细");
  789 +
  790 + for (var i = 0; i < input.wtYskzjjsMxList.Count; i++)
  791 + {
  792 + var row = input.wtYskzjjsMxList[i];
  793 + if (row == null) throw NCCException.Oh($"第 {i + 1} 行明细不能为空");
  794 + if (string.IsNullOrWhiteSpace(row.srxmmc)) throw NCCException.Oh($"第 {i + 1} 行收入项目为必填");
  795 + if (row.je <= 0) throw NCCException.Oh($"第 {i + 1} 行原币金额为必填且须大于 0");
  796 + }
  797 + }
  798 +
  799 + /// <summary>
  800 + /// 撤回:待审核或一级已审 → 草稿
  801 + /// </summary>
  802 + [HttpPost("Actions/WithdrawAudit/{id}")]
  803 + public async Task<dynamic> WithdrawAudit(string id)
  804 + {
  805 + EnsureYskzjjsAuditColumns();
  806 + var entity = await _db.Queryable<WtYskzjjsEntity>().FirstAsync(p => p.Id == id);
  807 + if (entity == null)
  808 + return new { success = false, message = "单据不存在" };
  809 + var z = entity.Djzt?.Trim() ?? "";
  810 + if (z != "待审核" && z != "一级已审" && z != "待二级" && z != "一级已审/待二级")
  811 + return new { success = false, message = $"当前状态「{entity.Djzt}」不可撤回" };
  812 +
  813 + entity.Djzt = "草稿";
  814 + entity.Shr1 = null;
  815 + entity.Shr2 = null;
  816 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr1, x.Shr2 }).ExecuteCommandAsync();
  817 + return new { success = true, message = "已撤回为草稿" };
  818 + }
  819 +
  820 + /// <summary>
  821 + /// 审核通过(按 <c>wt_shrysz.djmc</c> 与主表 <c>djlx</c> 匹配,支持一级/两级)
  822 + /// </summary>
  823 + [HttpPost("Actions/Approve/{id}")]
  824 + public async Task<dynamic> Approve(string id, [FromBody] WtApprovalRemarkInput input = null)
  825 + {
  826 + EnsureYskzjjsAuditColumns();
  827 + return await ApproveYskzjjsDocumentAsync(id, input?.remark);
  828 + }
  829 +
  830 + /// <summary>
  831 + /// 审核不通过(审批备注写入 <c>spbz</c>)
  832 + /// </summary>
  833 + [HttpPost("Actions/Reject/{id}")]
  834 + public async Task<dynamic> Reject(string id, [FromBody] WtApprovalRemarkInput input = null)
  835 + {
  836 + EnsureYskzjjsAuditColumns();
  837 + return await RejectYskzjjsDocumentAsync(id, input?.remark);
  838 + }
  839 +
  840 + private async Task<dynamic> ApproveYskzjjsDocumentAsync(string id, string approvalRemark)
  841 + {
  842 + try
  843 + {
  844 + _db.BeginTran();
  845 + var entity = await _db.Queryable<WtYskzjjsEntity>().Where(x => x.Id == id).FirstAsync();
  846 + if (entity == null)
  847 + {
  848 + _db.RollbackTran();
  849 + return new { success = false, message = "单据不存在" };
  850 + }
  851 +
  852 + if (entity.Djzt == "已审核")
  853 + {
  854 + _db.RollbackTran();
  855 + return new { success = false, message = "该单据已经审核,无需重复审核" };
  856 + }
  857 +
  858 + var userInfo = await _userManager.GetUserInfo();
  859 + var userId = userInfo?.userId ?? "";
  860 + var userAccount = userInfo?.userAccount?.Trim();
  861 + if (string.IsNullOrEmpty(userAccount))
  862 + userAccount = _userManager.Account?.Trim();
  863 +
  864 + var djmc = entity.Djlx?.Trim() ?? "";
  865 + var approvalConfig = await _db.Queryable<WtShryszEntity>()
  866 + .Where(c => c.Djmc == djmc)
  867 + .FirstAsync();
  868 +
  869 + var configShr1 = approvalConfig?.Shr1;
  870 + var configShr2 = approvalConfig?.Shr2;
  871 + var hasTwoLevel = !string.IsNullOrEmpty(configShr1) && !string.IsNullOrEmpty(configShr2);
  872 +
  873 + if (entity.Djzt == "待审核" || string.IsNullOrEmpty(entity.Djzt))
  874 + {
  875 + if (hasTwoLevel)
  876 + {
  877 + entity.Djzt = "一级已审";
  878 + entity.Shr1 = userId;
  879 + AppendApprovalSpbzLineYsk(entity, "一级通过", approvalRemark);
  880 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr1, x.Spbz }).ExecuteCommandAsync();
  881 + _db.CommitTran();
  882 + return new { success = true, message = "一级审核通过,等待二级审核" };
  883 + }
  884 +
  885 + entity.Djzt = "已审核";
  886 + entity.Shr = userId;
  887 + entity.Shr1 = userId;
  888 + AppendApprovalSpbzLineYsk(entity, "审核通过", approvalRemark);
  889 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1, x.Spbz }).ExecuteCommandAsync();
  890 + _db.CommitTran();
  891 + return new { success = true, message = "审核通过" };
  892 + }
  893 +
  894 + if (entity.Djzt == "一级已审" || entity.Djzt == "一级已审/待二级" || entity.Djzt == "待二级")
  895 + {
  896 + if (!hasTwoLevel)
  897 + {
  898 + _db.RollbackTran();
  899 + return new { success = false, message = "未配置两级审核,当前状态不允许二级操作" };
  900 + }
  901 + if (string.IsNullOrWhiteSpace(configShr2))
  902 + {
  903 + _db.RollbackTran();
  904 + return new { success = false, message = "未配置二级审核人员" };
  905 + }
  906 + if (!IsUserInAuditConfigYsk(configShr2, userId, userAccount))
  907 + {
  908 + _db.RollbackTran();
  909 + return new { success = false, message = "当前用户不在二级审核人员范围内" };
  910 + }
  911 +
  912 + entity.Djzt = "已审核";
  913 + entity.Shr2 = userId;
  914 + entity.Shr = userId;
  915 + AppendApprovalSpbzLineYsk(entity, "二级通过", approvalRemark);
  916 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr2, x.Spbz }).ExecuteCommandAsync();
  917 + _db.CommitTran();
  918 + return new { success = true, message = "二级审核通过,审核完成" };
  919 + }
  920 +
  921 + _db.RollbackTran();
  922 + return new { success = false, message = $"当前单据状态「{entity.Djzt}」不允许审核" };
  923 + }
  924 + catch (Exception ex)
  925 + {
  926 + _db.RollbackTran();
  927 + return new { success = false, message = $"审核失败: {ex.Message}" };
  928 + }
  929 + }
  930 +
  931 + private async Task<dynamic> RejectYskzjjsDocumentAsync(string id, string approvalRemark)
  932 + {
  933 + try
  934 + {
  935 + _db.BeginTran();
  936 + var entity = await _db.Queryable<WtYskzjjsEntity>().Where(x => x.Id == id).FirstAsync();
  937 + if (entity == null)
  938 + {
  939 + _db.RollbackTran();
  940 + return new { success = false, message = "单据不存在" };
  941 + }
  942 +
  943 + if (entity.Djzt == "已审核")
  944 + {
  945 + _db.RollbackTran();
  946 + return new { success = false, message = "该单据已审核通过" };
  947 + }
  948 +
  949 + if (entity.Djzt == "审核不通过")
  950 + {
  951 + _db.RollbackTran();
  952 + return new { success = false, message = "该单据已是审核不通过状态" };
  953 + }
  954 +
  955 + if (entity.Djzt == "草稿")
  956 + {
  957 + _db.RollbackTran();
  958 + return new { success = false, message = "草稿状态请直接修改或删除单据" };
  959 + }
  960 +
  961 + var djztTrim = entity.Djzt?.Trim() ?? "";
  962 + var level1 = djztTrim == "待审核" || string.IsNullOrEmpty(djztTrim);
  963 + var level2 = entity.Djzt == "一级已审" || entity.Djzt == "待二级" || entity.Djzt == "一级已审/待二级";
  964 +
  965 + if (!level1 && !level2)
  966 + {
  967 + _db.RollbackTran();
  968 + return new { success = false, message = $"当前单据状态「{entity.Djzt}」不允许审核不通过" };
  969 + }
  970 +
  971 + entity.Djzt = "审核不通过";
  972 + AppendApprovalSpbzLineYsk(entity, "审核不通过", approvalRemark);
  973 + await _db.Updateable(entity).UpdateColumns(x => new { x.Djzt, x.Spbz }).ExecuteCommandAsync();
  974 + _db.CommitTran();
  975 + return new { success = true, message = "已标记审核不通过" };
  976 + }
  977 + catch (Exception ex)
  978 + {
  979 + _db.RollbackTran();
  980 + return new { success = false, message = $"操作失败: {ex.Message}" };
  981 + }
  982 + }
601 983 }
602 984 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/OAuth/NCC.OAuth/Service/OAuthService.cs
... ... @@ -148,7 +148,11 @@ namespace NCC.OAuth.Service
148 148 bool isMoble = false;
149 149 if (input.account == user.MobilePhone) isMoble = true;
150 150 //获取加密后的密码
151   - var encryptPasswod = MD5Encryption.Encrypt(input.password + user.Secretkey);
  151 + var pwd = (input.password ?? "").Trim();
  152 + // 兼容:前端既可能传 MD5(明文),也可能直接传明文(如收银台)。
  153 + // 系统入库口径为:MD5(MD5(明文) + Secretkey)
  154 + var md5Once = NormalizePasswordToMd5Once(pwd);
  155 + var encryptPasswod = MD5Encryption.Encrypt(md5Once + user.Secretkey);
152 156  
153 157 var userAnyPwd = await _userService.GetInfoByLogin(input.account, encryptPasswod, isMoble);
154 158 _ = userAnyPwd ?? throw NCCException.Oh(ErrorCode.D1000);
... ... @@ -597,7 +601,9 @@ namespace NCC.OAuth.Service
597 601 var secretkey = (await _userService.GetInfoByAccount(input.account)).Secretkey;
598 602  
599 603 //获取加密后的密码
600   - var encryptPasswod = MD5Encryption.Encrypt(input.password + secretkey);
  604 + var pwd = (input.password ?? "").Trim();
  605 + var md5Once = NormalizePasswordToMd5Once(pwd);
  606 + var encryptPasswod = MD5Encryption.Encrypt(md5Once + secretkey);
601 607  
602 608 bool isMoble = false;
603 609 if (input.account == users.MobilePhone) isMoble = true;
... ... @@ -606,6 +612,25 @@ namespace NCC.OAuth.Service
606 612 _ = user ?? throw NCCException.Oh(ErrorCode.D1000);
607 613 }
608 614  
  615 + private static bool LooksLikeMd5(string s)
  616 + {
  617 + if (string.IsNullOrEmpty(s) || s.Length != 32) return false;
  618 + foreach (var ch in s)
  619 + {
  620 + var isHex = (ch >= '0' && ch <= '9')
  621 + || (ch >= 'a' && ch <= 'f')
  622 + || (ch >= 'A' && ch <= 'F');
  623 + if (!isHex) return false;
  624 + }
  625 + return true;
  626 + }
  627 +
  628 + private static string NormalizePasswordToMd5Once(string passwordOrMd5)
  629 + {
  630 + var p = (passwordOrMd5 ?? "").Trim();
  631 + return LooksLikeMd5(p) ? p : MD5Encryption.Encrypt(p);
  632 + }
  633 +
609 634 /// <summary>
610 635 /// 获取当前登录用户信息
611 636 /// </summary>
... ...
Antis.Erp.Plat/netcore/src/Modularity/System/NCC.System.Entitys/Dto/Permission/User/UserCrInput.cs
... ... @@ -133,8 +133,16 @@ namespace NCC.System.Entitys.Dto.Permission.User
133 133 /// 排序
134 134 /// </summary>
135 135 public long? sortCode { get; set; }
136   -
137   -
  136 +
  137 + /// <summary>
  138 + /// 初始密码(明文,可选)。为空或未传时仍按系统默认密码规则生成(与历史行为一致)。
  139 + /// </summary>
  140 + /// <remarks>
  141 + /// 非空时按与登录校验一致的口径入库:MD5(MD5(明文) + 用户 Secretkey)。
  142 + /// (登录请求中的 password 通常为 MD5(明文),服务端再拼接 Secretkey 后做最终 MD5)
  143 + /// </remarks>
  144 + public string password { get; set; }
  145 +
138 146 /// <summary>
139 147 /// 门店信息
140 148 /// </summary>
... ...
Antis.Erp.Plat/netcore/src/Modularity/System/NCC.System/Service/Permission/UsersService.cs
... ... @@ -262,10 +262,31 @@ namespace NCC.System.Service.Permission
262 262 }
263 263  
264 264 /// <summary>
265   - /// 新建
  265 + /// 新建用户
266 266 /// </summary>
267   - /// <param name="input">参数</param>
268   - /// <returns></returns>
  267 + /// <remarks>
  268 + /// 可选传入 <c>password</c>(明文)。非空时按与「重置密码」相同的规则写入库,便于新建用户后直接以明文登录;
  269 + /// 未传或为空时仍使用系统默认密码哈希(兼容旧前端)。
  270 + ///
  271 + /// 示例请求:
  272 + /// ```json
  273 + /// {
  274 + /// "account": "zhangsan",
  275 + /// "password": "PlainTextPwd",
  276 + /// "realName": "张三",
  277 + /// "organizeId": "orgId",
  278 + /// "headIcon": "/api/file/Image/userAvatar/001.png"
  279 + /// }
  280 + /// ```
  281 + ///
  282 + /// 参数说明:
  283 + /// - password: 可选,非空时为初始明文密码
  284 + /// </remarks>
  285 + /// <param name="input">创建用户参数</param>
  286 + /// <returns>无返回体,成功即完成创建</returns>
  287 + /// <response code="200">创建成功</response>
  288 + /// <response code="400">参数或业务校验失败</response>
  289 + /// <response code="500">服务器内部错误</response>
269 290 [HttpPost("")]
270 291 public async Task Create([FromBody] UserCrInput input)
271 292 {
... ... @@ -294,7 +315,12 @@ namespace NCC.System.Service.Permission
294 315 entity.Birthday = input.birthday.IsNullOrEmpty() ? DateTime.Now : Ext.GetDateTime(input.birthday.ToString());
295 316 entity.QuickQuery = PinyinUtil.PinyinString(input.realName);
296 317 entity.Secretkey = Guid.NewGuid().ToString();
297   - entity.Password = MD5Encryption.Encrypt(MD5Encryption.Encrypt(CommonConst.DEFAULT_PASSWORD) + entity.Secretkey);
  318 + if (string.IsNullOrWhiteSpace(input.password))
  319 + {
  320 + throw NCCException.Oh("请输入密码");
  321 + }
  322 + entity.Password = HashPasswordForStorage(input.password.Trim(), entity.Secretkey);
  323 +
298 324 entity.Mdxx = input.mdxx;
299 325 var headIcon = input.headIcon.Split('/').ToList().Last();
300 326 if (string.IsNullOrEmpty(headIcon))
... ... @@ -680,7 +706,7 @@ namespace NCC.System.Service.Permission
680 706  
681 707 _ = entity ?? throw NCCException.Oh(ErrorCode.D1002);
682 708  
683   - var password = MD5Encryption.Encrypt(input.userPassword + entity.Secretkey);
  709 + var password = HashPasswordForStorage(input.userPassword, entity.Secretkey);
684 710  
685 711 var isOk = await _userRepository.Context.Updateable<UserEntity>().SetColumns(it => new UserEntity()
686 712 {
... ... @@ -798,7 +824,9 @@ namespace NCC.System.Service.Permission
798 824 [NonAction]
799 825 public async Task<UserEntity> GetInfoByAccount(string account)
800 826 {
801   - return await _userRepository.FirstOrDefaultAsync(u => u.Account == account || u.MobilePhone == account && u.DeleteMark == null);
  827 + // 注意:必须同时过滤 DeleteMark,否则同账号存在“已删除历史记录”时会取错 Secretkey,导致登录永远校验失败
  828 + return await _userRepository.FirstOrDefaultAsync(u =>
  829 + (u.Account == account || u.MobilePhone == account) && u.DeleteMark == null);
802 830 }
803 831  
804 832 /// <summary>
... ... @@ -930,6 +958,38 @@ namespace NCC.System.Service.Permission
930 958 var ids = PositionIds.Split(",");
931 959 return await _positionRepository.Entities.In(it => it.Id, ids).Select(it => new { id = it.Id, name = it.FullName }).MergeTable().Select<PositionInfo>().ToListAsync();
932 960 }
  961 +
  962 + /// <summary>
  963 + /// 用户密码入库哈希(与重置密码、个人修改密码、OAuth 登录一致)。
  964 + /// </summary>
  965 + /// <param name="plainPassword">明文密码</param>
  966 + /// <param name="secretkey">用户秘钥</param>
  967 + /// <returns>可写入 <see cref="UserEntity.Password"/> 的哈希值</returns>
  968 + private static string HashPasswordForStorage(string passwordOrMd5, string secretkey)
  969 + {
  970 + // 系统密码入库口径(与 OAuth 登录一致):
  971 + // - 登录/改密/重置密码:前端通常传 MD5(明文),服务端再做 MD5(MD5值 + Secretkey)
  972 + // - 新建用户:本次新增支持传“明文 password”,这里自动先做一层 MD5,再按同口径入库
  973 + var p = (passwordOrMd5 ?? "").Trim();
  974 + if (string.IsNullOrEmpty(p)) return MD5Encryption.Encrypt(MD5Encryption.Encrypt(CommonConst.DEFAULT_PASSWORD) + secretkey);
  975 +
  976 + static bool LooksLikeMd5(string s)
  977 + {
  978 + if (s.Length != 32) return false;
  979 + foreach (var ch in s)
  980 + {
  981 + var isHex = (ch >= '0' && ch <= '9')
  982 + || (ch >= 'a' && ch <= 'f')
  983 + || (ch >= 'A' && ch <= 'F');
  984 + if (!isHex) return false;
  985 + }
  986 + return true;
  987 + }
  988 +
  989 + var md5Once = LooksLikeMd5(p) ? p : MD5Encryption.Encrypt(p);
  990 + return MD5Encryption.Encrypt(md5Once + secretkey);
  991 + }
  992 +
933 993 #endregion
934 994 }
935 995 }
936 996 \ No newline at end of file
... ...
Antis.Erp.Plat/netcore/src/Modularity/VisualDev/NCC.VisualDev/DashboardService.cs
... ... @@ -26,6 +26,7 @@ namespace NCC.VisualDev
26 26 private readonly IUserManager _userManager;
27 27 private readonly IDictionaryDataService _dictionaryDataService;
28 28 private readonly SqlSugarScope _db;
  29 + private static bool _wtFysrdAuditColsEnsured;
29 30  
30 31 /// <summary>
31 32 /// 初始化一个<see cref="DashboardService"/>类型的新实例
... ... @@ -344,6 +345,8 @@ namespace NCC.VisualDev
344 345 /// </summary>
345 346 private async Task<List<FlowTodoOutput>> GetExtendBillTodoListAsync(string userId, string userAccount)
346 347 {
  348 + EnsureWtFysrdAuditColumnsForTodo();
  349 + EnsureWtYskzjjsAuditColumnsForTodo();
347 350 var list = new List<FlowTodoOutput>();
348 351 list.AddRange(await QueryWtXsckdAuditTodosAsync("同价调拨单", userId, userAccount, null));
349 352 // 仅单据来源为「后台」或未标记(ly 空)的销售出库单进待办;备注「抖音订单:」同后端免审规则
... ... @@ -361,12 +364,160 @@ namespace NCC.VisualDev
361 364 list.AddRange(await QueryWtXsckdAuditTodosAsync("委托代销退货单", userId, userAccount, null));
362 365 list.AddRange(await QueryWtXsckdAuditTodosAsync("委托代销结算单", userId, userAccount, null));
363 366 list.AddRange(await QueryWtTjdAuditTodosAsync(userId, userAccount));
  367 + list.AddRange(await QueryWtFysrdQtsrAuditTodosAsync(userId, userAccount));
  368 + list.AddRange(await QueryWtYskzjjsQtAuditTodosAsync(userId, userAccount));
364 369 return list.OrderByDescending(x => x.creatorTime).ToList();
365 370 }
366 371  
  372 + /// <summary>与 <c>wt_shrysz.djmc</c>、<c>WtFysrdWorkflowHelper.BillName</c> 一致</summary>
  373 + private const string FysrdQtsrTodoBillName = "其他收入单";
  374 +
  375 + /// <summary>
  376 + /// 待办 SQL 依赖列存在;与 Extend 内 <c>WtFysrdWorkflowHelper.EnsureFysrdAuditColumns</c> 保持一致
  377 + /// </summary>
  378 + private void EnsureWtFysrdAuditColumnsForTodo()
  379 + {
  380 + if (_wtFysrdAuditColsEnsured) return;
  381 + lock (typeof(DashboardService))
  382 + {
  383 + if (_wtFysrdAuditColsEnsured) return;
  384 + try
  385 + {
  386 + if (!_db.DbMaintenance.IsAnyTable("wt_fysrd")) { _wtFysrdAuditColsEnsured = true; return; }
  387 + var cols = _db.DbMaintenance.GetColumnInfosByTableName("wt_fysrd");
  388 + var names = cols.Select(c => c.DbColumnName.ToLowerInvariant()).ToHashSet();
  389 + if (!names.Contains("djzt"))
  390 + _db.Ado.ExecuteCommand(
  391 + "ALTER TABLE `wt_fysrd` ADD COLUMN `djzt` varchar(32) NULL COMMENT '单据状态'");
  392 + if (!names.Contains("spbz"))
  393 + _db.Ado.ExecuteCommand(
  394 + "ALTER TABLE `wt_fysrd` ADD COLUMN `spbz` text NULL COMMENT '审批备注'");
  395 + if (!names.Contains("shr"))
  396 + _db.Ado.ExecuteCommand(
  397 + "ALTER TABLE `wt_fysrd` ADD COLUMN `shr` varchar(64) NULL COMMENT '审核人'");
  398 + if (!names.Contains("shr1"))
  399 + _db.Ado.ExecuteCommand(
  400 + "ALTER TABLE `wt_fysrd` ADD COLUMN `shr1` varchar(500) NULL COMMENT '一级审核人'");
  401 + if (!names.Contains("shr2"))
  402 + _db.Ado.ExecuteCommand(
  403 + "ALTER TABLE `wt_fysrd` ADD COLUMN `shr2` varchar(500) NULL COMMENT '二级审核人'");
  404 + if (!names.Contains("wldw"))
  405 + _db.Ado.ExecuteCommand(
  406 + "ALTER TABLE `wt_fysrd` ADD COLUMN `wldw` varchar(64) NULL COMMENT '往来单位'");
  407 + }
  408 + catch (Exception ex) { Console.WriteLine($"EnsureWtFysrdAuditColumnsForTodo: {ex.Message}"); }
  409 + _wtFysrdAuditColsEnsured = true;
  410 + }
  411 + }
  412 +
367 413 /// <summary>与 <c>wt_shrysz.djmc</c>、<c>WtTjdWorkflowHelper.BillName</c> 一致</summary>
368 414 private const string TjdTodoBillName = "商品调价单";
369 415  
  416 + /// <summary>与 <c>wt_shrysz.djmc</c>、其他应收单 djlx 一致</summary>
  417 + private const string YskzjjsQtTodoBillName = "其他应收单";
  418 +
  419 + private static bool _wtYskzjjsAuditColsEnsured;
  420 +
  421 + /// <summary>
  422 + /// 待办 SQL 依赖列存在;与 Extend 内 WtYskzjjsService 的列映射保持一致
  423 + /// </summary>
  424 + private void EnsureWtYskzjjsAuditColumnsForTodo()
  425 + {
  426 + if (_wtYskzjjsAuditColsEnsured) return;
  427 + lock (typeof(DashboardService))
  428 + {
  429 + if (_wtYskzjjsAuditColsEnsured) return;
  430 + try
  431 + {
  432 + if (!_db.DbMaintenance.IsAnyTable("wt_yskzjjs")) { _wtYskzjjsAuditColsEnsured = true; return; }
  433 + var cols = _db.DbMaintenance.GetColumnInfosByTableName("wt_yskzjjs");
  434 + var names = cols.Select(c => c.DbColumnName.ToLowerInvariant()).ToHashSet();
  435 + if (!names.Contains("djzt"))
  436 + _db.Ado.ExecuteCommand(
  437 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `djzt` varchar(32) NULL COMMENT '单据状态'");
  438 + if (!names.Contains("spbz"))
  439 + _db.Ado.ExecuteCommand(
  440 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `spbz` text NULL COMMENT '审批备注'");
  441 + if (!names.Contains("shr"))
  442 + _db.Ado.ExecuteCommand(
  443 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `shr` varchar(64) NULL COMMENT '审核人'");
  444 + if (!names.Contains("shr1"))
  445 + _db.Ado.ExecuteCommand(
  446 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `shr1` varchar(500) NULL COMMENT '一级审核人'");
  447 + if (!names.Contains("shr2"))
  448 + _db.Ado.ExecuteCommand(
  449 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `shr2` varchar(500) NULL COMMENT '二级审核人'");
  450 + if (!names.Contains("wldw"))
  451 + _db.Ado.ExecuteCommand(
  452 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `wldw` varchar(64) NULL COMMENT '往来单位'");
  453 + if (!names.Contains("djlx"))
  454 + _db.Ado.ExecuteCommand(
  455 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `djlx` varchar(64) NULL COMMENT '单据类型'");
  456 + if (!names.Contains("djrq"))
  457 + _db.Ado.ExecuteCommand(
  458 + "ALTER TABLE `wt_yskzjjs` ADD COLUMN `djrq` datetime NULL COMMENT '单据日期'");
  459 + }
  460 + catch (Exception ex) { Console.WriteLine($"EnsureWtYskzjjsAuditColumnsForTodo: {ex.Message}"); }
  461 + _wtYskzjjsAuditColsEnsured = true;
  462 + }
  463 + }
  464 +
  465 + /// <summary>
  466 + /// 其他收入单待办(wt_fysrd + wt_shrysz)
  467 + /// </summary>
  468 + private async Task<List<FlowTodoOutput>> QueryWtFysrdQtsrAuditTodosAsync(string userId, string userAccount)
  469 + {
  470 + var result = new List<FlowTodoOutput>();
  471 + const string sql = @"
  472 +SELECT d.F_Id AS id, d.djrq, d.djzt,
  473 + IFNULL(NULLIF(TRIM(s.shr1), ''), s.shr) AS shr1_eff,
  474 + IFNULL(NULLIF(TRIM(s.shr2), ''), s.shr) AS shr2_eff
  475 +FROM wt_fysrd d
  476 +LEFT JOIN wt_shrysz s ON TRIM(s.djmc) = @billName
  477 +WHERE (d.F_Id LIKE 'QT%' OR TRIM(IFNULL(d.djlx,'')) IN ('其他收入单','其它收入单','其他收入','其它收入'))
  478 + AND IFNULL(d.djzt, '') <> '草稿'
  479 + AND IFNULL(d.djzt, '') <> '已审核'
  480 + AND IFNULL(d.djzt, '') <> '审核不通过'
  481 + AND (
  482 + d.djzt IN ('待审核', '一级已审', '待二级', '一级已审/待二级')
  483 + OR d.djzt IS NULL
  484 + OR TRIM(IFNULL(d.djzt, '')) = ''
  485 + )
  486 +ORDER BY d.djrq DESC";
  487 +
  488 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { billName = FysrdQtsrTodoBillName });
  489 + if (rows == null || rows.Count == 0) return result;
  490 +
  491 + foreach (var row in rows)
  492 + {
  493 + var status = Convert.ToString(row.djzt)?.Trim() ?? string.Empty;
  494 + var level1 = status == "待审核" || string.IsNullOrEmpty(status);
  495 + var level2 = status == "一级已审" || status == "待二级" || status == "一级已审/待二级";
  496 + if (!level1 && !level2) continue;
  497 +
  498 + var configUsers = level1 ? Convert.ToString(row.shr1_eff) : Convert.ToString(row.shr2_eff);
  499 + if (!string.IsNullOrWhiteSpace(configUsers) && !IsInAuditUsers(configUsers, userId, userAccount))
  500 + continue;
  501 +
  502 + DateTime? createTime = null;
  503 + var rawTime = Convert.ToString(row.djrq);
  504 + if (DateTime.TryParse(rawTime, out DateTime dt))
  505 + createTime = dt;
  506 +
  507 + var id = Convert.ToString(row.id);
  508 + var levelText = level1 ? "一级审核待办" : "二级审核待办";
  509 + result.Add(new FlowTodoOutput
  510 + {
  511 + id = id,
  512 + fullName = $"{FysrdQtsrTodoBillName} {id} - {levelText}",
  513 + creatorTime = createTime,
  514 + billType = FysrdQtsrTodoBillName
  515 + });
  516 + }
  517 +
  518 + return result;
  519 + }
  520 +
370 521 /// <summary>
371 522 /// 商品调价单待办(wt_tjd + wt_shrysz)
372 523 /// </summary>
... ... @@ -424,6 +575,62 @@ ORDER BY d.djrq DESC&quot;;
424 575 }
425 576  
426 577 /// <summary>
  578 + /// 其他应收单待办(wt_yskzjjs + wt_shrysz)
  579 + /// </summary>
  580 + private async Task<List<FlowTodoOutput>> QueryWtYskzjjsQtAuditTodosAsync(string userId, string userAccount)
  581 + {
  582 + var result = new List<FlowTodoOutput>();
  583 + const string sql = @"
  584 +SELECT d.F_Id AS id, d.djrq, d.djzt,
  585 + IFNULL(NULLIF(TRIM(s.shr1), ''), s.shr) AS shr1_eff,
  586 + IFNULL(NULLIF(TRIM(s.shr2), ''), s.shr) AS shr2_eff
  587 +FROM wt_yskzjjs d
  588 +LEFT JOIN wt_shrysz s ON TRIM(s.djmc) = @billName
  589 +WHERE TRIM(IFNULL(d.djlx,'')) IN ('其他应收单','其他应收款')
  590 + AND IFNULL(d.djzt, '') <> '草稿'
  591 + AND IFNULL(d.djzt, '') <> '已审核'
  592 + AND IFNULL(d.djzt, '') <> '审核不通过'
  593 + AND (
  594 + d.djzt IN ('待审核', '一级已审', '待二级', '一级已审/待二级')
  595 + OR d.djzt IS NULL
  596 + OR TRIM(IFNULL(d.djzt, '')) = ''
  597 + )
  598 +ORDER BY d.djrq DESC";
  599 +
  600 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { billName = YskzjjsQtTodoBillName });
  601 + if (rows == null || rows.Count == 0) return result;
  602 +
  603 + foreach (var row in rows)
  604 + {
  605 + var status = Convert.ToString(row.djzt)?.Trim() ?? string.Empty;
  606 + var level1 = status == "待审核" || string.IsNullOrEmpty(status);
  607 + var level2 = status == "一级已审" || status == "待二级" || status == "一级已审/待二级";
  608 + if (!level1 && !level2) continue;
  609 +
  610 + var configUsers = level1 ? Convert.ToString(row.shr1_eff) : Convert.ToString(row.shr2_eff);
  611 + if (!string.IsNullOrWhiteSpace(configUsers) && !IsInAuditUsers(configUsers, userId, userAccount))
  612 + continue;
  613 +
  614 + DateTime? createTime = null;
  615 + var rawTime = Convert.ToString(row.djrq);
  616 + if (DateTime.TryParse(rawTime, out DateTime dt))
  617 + createTime = dt;
  618 +
  619 + var id = Convert.ToString(row.id);
  620 + var levelText = level1 ? "一级审核待办" : "二级审核待办";
  621 + result.Add(new FlowTodoOutput
  622 + {
  623 + id = id,
  624 + fullName = $"{YskzjjsQtTodoBillName} {id} - {levelText}",
  625 + creatorTime = createTime,
  626 + billType = YskzjjsQtTodoBillName
  627 + });
  628 + }
  629 +
  630 + return result;
  631 + }
  632 +
  633 + /// <summary>
427 634 /// 按单据类型查询待审核/待二级单据,并按 shr1/shr2(或 shr)过滤当前用户
428 635 /// </summary>
429 636 /// <param name="billDjlx">与 wt_xsckd.djlx、wt_shrysz.djmc 一致</param>
... ...
Antis.Erp.Plat/sy/home.html
... ... @@ -475,7 +475,7 @@
475 475 <svg class="pos-inline-svg pos-header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
476 476 <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
477 477 </svg>
478   - <span>收银台</span>
  478 + <span>{{ posCashierHeaderTitle }}</span>
479 479 </div>
480 480 <div class="home-right pos-header-tool-row">
481 481 <button type="button" class="pos-header-tool-btn settlement-touch" aria-label="刷新页面" onclick="location.reload()">
... ... @@ -800,6 +800,21 @@
800 800 },
801 801 // 添加计算属性
802 802 computed: {
  803 + /** 顶栏标题:登录所选门店名称 + 收银台 */
  804 + posCashierHeaderTitle() {
  805 + try {
  806 + var raw = localStorage.getItem('selectedStore');
  807 + if (raw) {
  808 + var s = JSON.parse(raw);
  809 + var name = String(s.mdmc || s.Mdmc || s.name || '').trim();
  810 + if (name) {
  811 + var withDian = name.lastIndexOf('店') === name.length - 1 ? name : (name + '店');
  812 + return withDian + '收银台';
  813 + }
  814 + }
  815 + } catch (e) {}
  816 + return '收银台';
  817 + },
803 818 // 入0出1:仅手动输入序列号
804 819 isSerialNumberModalR0C1() {
805 820 return this.currentSerialNumberItem && this.currentSerialNumberItem.spxlhType === '2';
... ...
Antis.Erp.Plat/sy/settlement.html
... ... @@ -3749,7 +3749,7 @@
3749 3749 <svg class="pos-inline-svg pos-header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
3750 3750 <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
3751 3751 </svg>
3752   - <span>收银台</span>
  3752 + <span>{{ posCashierHeaderTitle }}</span>
3753 3753 </div>
3754 3754 <div class="home-right pos-header-tool-row">
3755 3755 <button type="button" class="pos-header-tool-btn settlement-touch" aria-label="刷新页面" onclick="location.reload()">
... ... @@ -4410,6 +4410,21 @@
4410 4410 // }
4411 4411 },
4412 4412 computed: {
  4413 + /** 顶栏标题:登录所选门店名称 + 收银台 */
  4414 + posCashierHeaderTitle() {
  4415 + try {
  4416 + var raw = localStorage.getItem('selectedStore');
  4417 + if (raw) {
  4418 + var s = JSON.parse(raw);
  4419 + var name = String(s.mdmc || s.Mdmc || s.name || '').trim();
  4420 + if (name) {
  4421 + var withDian = name.lastIndexOf('店') === name.length - 1 ? name : (name + '店');
  4422 + return withDian + '收银台';
  4423 + }
  4424 + }
  4425 + } catch (e) {}
  4426 + return '收银台';
  4427 + },
4413 4428 settlementMemberBenefitLine() {
4414 4429 if (!this.hyinfo || this.hyinfo === 'null') return '';
4415 4430 var h = this.hyinfo;
... ...