Commit 41e33d6bb6770c71d5ca1e7cbe0b442804e50b7f

Authored by “wangming”
1 parent 818c00db

feat(Antis.Erp.Plat): 待办中心按审核设置与 Dashboard 并行查询;出库/退货序列号弹窗;抖音及多模块调整

Made-with: Cursor
Showing 53 changed files with 2749 additions and 544 deletions
Antis.Erp.Plat/antis-ncc-admin/src/api/home.js
... ... @@ -28,7 +28,7 @@ export function getFlowTodo() {
28 28 method: 'get'
29 29 })
30 30 }
31   -// 获取我的待办事项
  31 +// 获取我的待办事项(djmc;includeStats=true 时同包 stats,与列表一次查库)
32 32 export function getMyFlowTodo(data) {
33 33 return request({
34 34 url: '/api/visualdev/Dashboard/MyFlowTodo',
... ... @@ -37,6 +37,14 @@ export function getMyFlowTodo(data) {
37 37 })
38 38 }
39 39  
  40 +/** 仅左侧条数(会全量查待办);待办中心优先用 getMyFlowTodo({ includeStats: true }) */
  41 +export function getMyFlowTodoStats() {
  42 + return request({
  43 + url: '/api/visualdev/Dashboard/MyFlowTodoStats',
  44 + method: 'get'
  45 + })
  46 +}
  47 +
40 48 // 获取数据统计
41 49 export function getCountData() {
42 50 return request({
... ...
Antis.Erp.Plat/antis-ncc-admin/src/layout/components/AppMain.vue
... ... @@ -19,7 +19,6 @@
19 19 // 此处改为默认缓存所有页面,仅用 exclude 尊重 meta.noCache;路由 name 与组件 name 不一致的 noCache 路由需把组件 name 列在下面。
20 20 const NO_CACHE_COMPONENT_NAMES = [
21 21 'DouyinLogisticsOrders',
22   - 'DouyinLogisticsCreateWaybill',
23 22 'wtMdfzManage'
24 23 ]
25 24  
... ...
Antis.Erp.Plat/antis-ncc-admin/src/router/index.js
... ... @@ -83,14 +83,14 @@ export const constantRoutes = [{
83 83 name: 'create-waybill-merged',
84 84 hidden: true,
85 85 component: () => import('@/views/douyinLogistics/CreateWaybill'),
86   - meta: { title: '合并发货单', noCache: true }
  86 + meta: { title: '合并发货单', noCache: false }
87 87 },
88 88 {
89 89 path: 'create-waybill/:id',
90 90 name: 'create-waybill',
91 91 hidden: true,
92 92 component: () => import('@/views/douyinLogistics/CreateWaybill'),
93   - meta: { title: '编辑发货单', noCache: true }
  93 + meta: { title: '编辑发货单', noCache: false }
94 94 }
95 95 ]
96 96 }
... ...
Antis.Erp.Plat/antis-ncc-admin/src/utils/douyinErpBase.js
1   -/** ERP 站点根地址(与抖音独立前端 VITE_ERP_BASE_URL 一致),用于拼接商品主图等相对路径 */
  1 +/**
  2 + * 抖音物流 / 发货单页:拼接 ERP 商品图等静态资源根地址。
  3 + * - 图片相对路径需拼 `VUE_APP_BASE_API`(与上传/文件服务一致)。
  4 + * - 历史或开发机落库成绝对地址 `http://localhost:2011/...` 时,线上会失效,需把主机替换为当前环境 API 根。
  5 + */
  6 +
  7 +/** 对外站点名(如官网);仅作无 API 根时的回退 */
2 8 export function getErpBaseUrl() {
3 9 const v = process.env.VUE_APP_ERP_PUBLIC_BASE
4 10 if (v) return String(v).replace(/\/$/, '')
... ... @@ -7,3 +13,41 @@ export function getErpBaseUrl() {
7 13 }
8 14 return 'https://www.ponggame.cn'
9 15 }
  16 +
  17 +/** 与后台同源的文件/API 根(商品图、上传目录),优先 env,其次浏览器当前 origin */
  18 +export function getErpAssetBaseUrl() {
  19 + const v = process.env.VUE_APP_BASE_API
  20 + if (v) return String(v).replace(/\/$/, '')
  21 + if (typeof window !== 'undefined' && window.location && window.location.origin) {
  22 + return String(window.location.origin).replace(/\/$/, '')
  23 + }
  24 + return getErpBaseUrl()
  25 +}
  26 +
  27 +/**
  28 + * 商品主图等:补全相对路径,并修正开发机 localhost / 错误落库的绝对地址
  29 + */
  30 +export function resolveProductPicUrl(pic) {
  31 + const s0 = (pic || '').trim()
  32 + if (!s0) return ''
  33 +
  34 + if (/^https?:\/\//i.test(s0)) {
  35 + try {
  36 + const u = new URL(s0)
  37 + // 开发机或历史错误写死的内网/本机:替换为当前部署的 API 根
  38 + if (/^(localhost|127\.0\.0\.1|0\.0\.0\.0)$/i.test(u.hostname)) {
  39 + const base = getErpAssetBaseUrl().replace(/\/$/, '')
  40 + return `${base}${u.pathname || ''}${u.search || ''}${u.hash || ''}`
  41 + }
  42 + } catch (e) {
  43 + return s0
  44 + }
  45 + return s0
  46 + }
  47 +
  48 + if (s0.startsWith('//')) return `https:${s0}`
  49 +
  50 + const base = getErpAssetBaseUrl().replace(/\/$/, '')
  51 + if (s0.startsWith('/')) return `${base}${s0}`
  52 + return s0
  53 +}
... ...
Antis.Erp.Plat/antis-ncc-admin/src/utils/wtRejectApproval.js
... ... @@ -46,6 +46,15 @@ export function postApproveGeneric(id, remark) {
46 46 })
47 47 }
48 48  
  49 +/** 赠送单/获赠单:撤回审核回草稿 */
  50 +export function postWtXsckdWithdrawAudit(id) {
  51 + return request({
  52 + url: `/api/Extend/WtXsckd/WithdrawAudit/${id}`,
  53 + method: 'POST',
  54 + data: {}
  55 + })
  56 +}
  57 +
49 58 /**
50 59 * 审核通过(销售出库)
51 60 */
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/basic/todoCenter/index.vue
1 1 <template>
2 2 <div class="NCC-common-layout todo-center-page">
3   - <div class="NCC-common-layout-center">
  3 + <div class="NCC-common-layout-center todo-center-inner">
4 4 <div class="NCC-common-head">
5 5 <div>
6 6 <span class="page-title">
... ... @@ -10,27 +10,66 @@
10 10 />
11 11 待办中心
12 12 </span>
13   - <span class="page-desc"
14   - >同价调拨单、销售/预售出库、销售/预售退货、采购入库单、委托代销发/退/结算、赠送单、商品调价单、收款单、费用单、现金费用单、待摊费用摊销等待办(与首页待办数据源一致)</span
  13 + <el-tooltip
  14 + effect="dark"
  15 + placement="bottom-start"
  16 + :open-delay="400"
  17 + popper-class="todo-center-desc-tooltip"
15 18 >
  19 + <div slot="content" class="todo-center-desc-popper">
  20 + 待办与首页同源。左侧「单据类型」来自「审核人员设置」中已配置的单据名称(wt_shrysz.djmc),顺序与审核设置页一致;若某类待办不在配置中,将归入「其他」。
  21 + </div>
  22 + <span class="page-desc page-desc--hint"
  23 + >左侧按审核设置中的单据类型筛选,右侧列表可分页处理</span
  24 + >
  25 + </el-tooltip>
16 26 </div>
17 27 <div class="NCC-common-head-right">
18   - <el-tooltip effect="dark" content="刷新" placement="top">
  28 + <el-tooltip effect="dark" content="刷新列表与条数" placement="top">
19 29 <el-link
20 30 icon="icon-ym icon-ym-Refresh NCC-common-head-icon"
21 31 :underline="false"
22   - @click="loadData"
  32 + @click="refreshAll"
23 33 />
24 34 </el-tooltip>
25 35 </div>
26 36 </div>
27   - <div class="NCC-common-layout-main NCC-flex-main">
  37 + <div class="todo-center-body">
  38 + <aside class="todo-center-aside">
  39 + <div class="todo-center-aside__head">
  40 + <span class="todo-center-aside__title">单据类型</span>
  41 + <span class="todo-center-aside__sub">审核人员设置</span>
  42 + </div>
  43 + <div class="todo-center-aside__scroll">
  44 + <ul class="todo-cat-list">
  45 + <li
  46 + v-for="row in todoNavRows"
  47 + :key="'nav-' + row.djmc"
  48 + :class="[
  49 + 'todo-cat-list__item',
  50 + { 'is-active': selectedDjmc === row.djmc }
  51 + ]"
  52 + @click="selectDjmc(row.djmc)"
  53 + >
  54 + <span class="todo-cat-list__label" :title="row.djmc === 'all' ? '全部待办' : rowLabelTitle(row)">{{
  55 + row.label
  56 + }}</span>
  57 + <span
  58 + class="todo-cat-list__count"
  59 + :class="{ 'is-zero': !row.count }"
  60 + >{{ row.count }}</span
  61 + >
  62 + </li>
  63 + </ul>
  64 + </div>
  65 + </aside>
  66 + <div class="NCC-common-layout-main NCC-flex-main todo-center-main">
28 67 <el-table
29 68 v-loading="loading"
30 69 :data="list"
31 70 border
32 71 stripe
33   - empty-text="暂无待办"
  72 + empty-text="该分类下暂无待办"
34 73 >
35 74 <el-table-column
36 75 type="index"
... ... @@ -40,9 +79,24 @@
40 79 :index="tableIndexMethod"
41 80 />
42 81 <el-table-column
  82 + label="单据类型"
  83 + width="126"
  84 + show-overflow-tooltip
  85 + >
  86 + <template slot-scope="scope">
  87 + <el-tag
  88 + type="info"
  89 + size="mini"
  90 + effect="plain"
  91 + class="todo-bill-type-tag"
  92 + >{{ displayBillType(scope.row) }}</el-tag
  93 + >
  94 + </template>
  95 + </el-table-column>
  96 + <el-table-column
43 97 prop="fullName"
44 98 label="待办事项"
45   - min-width="280"
  99 + min-width="240"
46 100 show-overflow-tooltip
47 101 >
48 102 <template slot-scope="scope">
... ... @@ -80,6 +134,7 @@
80 134 :page-sizes="[10, 20, 50, 100]"
81 135 @pagination="loadData"
82 136 />
  137 + </div>
83 138 </div>
84 139 </div>
85 140  
... ... @@ -173,6 +228,36 @@
173 228 </template>
174 229 </WtXsckdDetailView>
175 230  
  231 + <WtHzdDetailView
  232 + v-if="processVisible && processBillType === '获赠单'"
  233 + ref="processDlg"
  234 + @close="onProcessDialogClose"
  235 + @loaded="onProcessLoaded"
  236 + >
  237 + <template slot="footer">
  238 + <el-button @click="closeProcessDialog">关闭</el-button>
  239 + <el-button
  240 + v-if="canLevel1Approve"
  241 + type="warning"
  242 + @click="handleLevel1Approve"
  243 + >一级审核</el-button
  244 + >
  245 + <el-button
  246 + v-if="canLevel2Approve"
  247 + type="success"
  248 + @click="handleLevel2Approve"
  249 + >二级审核</el-button
  250 + >
  251 + <el-button
  252 + v-if="canAuditReject"
  253 + type="danger"
  254 + plain
  255 + @click="handleReject"
  256 + >审核不通过</el-button
  257 + >
  258 + </template>
  259 + </WtHzdDetailView>
  260 +
176 261 <WtXsthdDetailView
177 262 v-if="processVisible && processBillType === '销售退货单'"
178 263 ref="processDlg"
... ... @@ -580,6 +665,23 @@
580 665 </template>
581 666 </WtCwdjSkdDetailView>
582 667  
  668 + <WtCwdjFkdDetailView
  669 + v-if="processVisible && processBillType === '付款单'"
  670 + ref="processDlg"
  671 + @close="onProcessDialogClose"
  672 + @loaded="onProcessLoaded"
  673 + >
  674 + <template slot="footer">
  675 + <el-button @click="closeProcessDialog">关闭</el-button>
  676 + <el-button
  677 + v-if="canLevel1Approve"
  678 + type="warning"
  679 + @click="handleLevel1Approve"
  680 + >审核</el-button
  681 + >
  682 + </template>
  683 + </WtCwdjFkdDetailView>
  684 +
583 685 <WtCwdjFydDetailView
584 686 v-if="processVisible && processBillType === '费用单'"
585 687 ref="processDlg"
... ... @@ -626,6 +728,7 @@ import WtYsckdDetailView from &quot;@/views/wtYsckd/detail-view.vue&quot;;
626 728 import WtYsthdDetailView from "@/views/wtYsthd/detail-view.vue";
627 729 import WtCgrkdDetailView from "@/views/wtCgrkd/detail-view.vue";
628 730 import WtCwdjSkdDetailView from "@/views/wtCwdj_skd/detail-view.vue";
  731 +import WtCwdjFkdDetailView from "@/views/wtCwdj_fkd/detail-view.vue";
629 732 import WtCwdjFydDetailView from "@/views/wtCwdj_fyd/detail-view.vue";
630 733 import WtPriceAdjustDetailView from "@/views/wtPriceAdjust/detail-view.vue";
631 734 import WtXswtdxfhdDetailView from "@/views/wtXswtdxfhd/detail-view.vue";
... ... @@ -636,6 +739,7 @@ import WtYskzjjsHyDetailView from &quot;@/views/wtYskzjjs_hy/detail-view.vue&quot;;
636 739 import WtYskzjjsZkDetailView from "@/views/wtYskzjjs_zk/detail-view.vue";
637 740 import WtYskzjjsXjDetailView from "@/views/wtYskzjjs_xj/detail-view.vue";
638 741 import WtCzdDetailView from "@/views/wtCzd/detail-view.vue";
  742 +import WtHzdDetailView from "@/views/wtHzd/detail-view.vue";
639 743 import {
640 744 promptApprovalRemark,
641 745 postApproveGeneric,
... ... @@ -656,6 +760,7 @@ const BILL_TYPES = [
656 760 "预售退货单",
657 761 "销售出库单",
658 762 "赠送单",
  763 + "获赠单",
659 764 "销售退货单",
660 765 "采购入库单",
661 766 "拆装单",
... ... @@ -664,6 +769,7 @@ const BILL_TYPES = [
664 769 "费用单",
665 770 "待摊费用摊销",
666 771 "收款单",
  772 + "付款单",
667 773 "商品调价单",
668 774 "会员到期收款",
669 775 "其他应收单"
... ... @@ -679,6 +785,7 @@ export default {
679 785 WtYsthdDetailView,
680 786 WtCgrkdDetailView,
681 787 WtCwdjSkdDetailView,
  788 + WtCwdjFkdDetailView,
682 789 WtCwdjFydDetailView,
683 790 WtPriceAdjustDetailView,
684 791 WtXswtdxfhdDetailView,
... ... @@ -688,13 +795,19 @@ export default {
688 795 WtYskzjjsHyDetailView,
689 796 WtYskzjjsZkDetailView,
690 797 WtYskzjjsXjDetailView,
691   - WtCzdDetailView
  798 + WtCzdDetailView,
  799 + WtHzdDetailView
692 800 },
693 801 data() {
694 802 return {
695 803 loading: false,
696 804 list: [],
697 805 total: 0,
  806 + /** getMyFlowTodoStats:total、items[](djmc/label/count) */
  807 + statsTotal: 0,
  808 + statsItems: [],
  809 + /** 与 wt_shrysz.djmc 一致,或 all、_other_ */
  810 + selectedDjmc: "all",
698 811 listQuery: {
699 812 currentPage: 1,
700 813 pageSize: 20
... ... @@ -710,6 +823,13 @@ export default {
710 823 };
711 824 },
712 825 computed: {
  826 + todoNavRows() {
  827 + const head = [
  828 + { djmc: "all", label: "全部", count: this.statsTotal }
  829 + ];
  830 + const rest = Array.isArray(this.statsItems) ? this.statsItems : [];
  831 + return head.concat(rest);
  832 + },
713 833 canLevel1Approve() {
714 834 const s = this.getAuditStatus(this.processDetail);
715 835 return !s || s === "待审核";
... ... @@ -723,9 +843,42 @@ export default {
723 843 }
724 844 },
725 845 created() {
726   - this.loadData();
  846 + // 一次请求带左侧条数,避免与列表各跑一遍全量待办
  847 + this.loadData({ includeStats: true });
727 848 },
728 849 methods: {
  850 + selectDjmc(djmc) {
  851 + if (djmc === this.selectedDjmc) return;
  852 + this.selectedDjmc = djmc;
  853 + this.listQuery.currentPage = 1;
  854 + this.loadData();
  855 + },
  856 + rowLabelTitle(row) {
  857 + if (!row) return "";
  858 + if (row.djmc && row.djmc !== "all" && row.djmc !== "_other_") {
  859 + return `配置键:${row.djmc}`;
  860 + }
  861 + return row.label || "";
  862 + },
  863 + displayBillType(row) {
  864 + if (row && row.billType != null && String(row.billType).trim() !== "") {
  865 + return String(row.billType).trim();
  866 + }
  867 + return this.resolveBillType(row);
  868 + },
  869 + applyStatsFromPayload(stats) {
  870 + if (!stats) return;
  871 + const t = stats.total;
  872 + this.statsTotal = typeof t === "number" ? t : 0;
  873 + this.statsItems = Array.isArray(stats.items)
  874 + ? stats.items
  875 + : Array.isArray(stats.Items)
  876 + ? stats.Items
  877 + : [];
  878 + },
  879 + refreshAll() {
  880 + this.loadData({ includeStats: true });
  881 + },
729 882 cellText(v) {
730 883 if (v === null || v === undefined || v === "") return "无";
731 884 return v;
... ... @@ -763,16 +916,27 @@ export default {
763 916 (this.listQuery.currentPage - 1) * this.listQuery.pageSize + index + 1
764 917 );
765 918 },
766   - loadData() {
  919 + loadData(options) {
  920 + const opt = options || {};
  921 + const includeStats = opt.includeStats === true;
767 922 this.loading = true;
768   - return getMyFlowTodo({
  923 + const params = {
769 924 currentPage: this.listQuery.currentPage,
770   - pageSize: this.listQuery.pageSize
771   - })
  925 + pageSize: this.listQuery.pageSize,
  926 + djmc: this.selectedDjmc
  927 + };
  928 + if (includeStats) {
  929 + params.includeStats = true;
  930 + }
  931 + return getMyFlowTodo(params)
772 932 .then(res => {
773   - const raw = (res.data && res.data.list) || [];
  933 + const d = (res && res.data) || {};
  934 + if (d.stats) {
  935 + this.applyStatsFromPayload(d.stats);
  936 + }
  937 + const raw = d.list || [];
774 938 this.list = Array.isArray(raw) ? raw : [];
775   - const pg = res.data && res.data.pagination;
  939 + const pg = d.pagination;
776 940 this.total =
777 941 (pg && typeof pg.total === "number"
778 942 ? pg.total
... ... @@ -788,7 +952,7 @@ export default {
788 952 );
789 953 if (this.listQuery.currentPage > lastPage) {
790 954 this.listQuery.currentPage = lastPage;
791   - return this.loadData();
  955 + return this.loadData(opt);
792 956 }
793 957 }
794 958 })
... ... @@ -854,6 +1018,9 @@ export default {
854 1018 if (t === "收款单") {
855 1019 return { msg: "确认审核该收款单?", title: "审核确认" };
856 1020 }
  1021 + if (t === "付款单") {
  1022 + return { msg: "确认审核该付款单?", title: "审核确认" };
  1023 + }
857 1024 if (t === "费用单") {
858 1025 return { msg: "确认审核该费用单?", title: "审核确认" };
859 1026 }
... ... @@ -911,6 +1078,9 @@ export default {
911 1078 if (t === "委托代销结算单") {
912 1079 return { msg: "确认审核该委托代销结算单?", title: "审核确认" };
913 1080 }
  1081 + if (t === "获赠单") {
  1082 + return { msg: "确认审核该获赠单?通过后即按单据更新库存与成本。", title: "审核确认" };
  1083 + }
914 1084 return { msg: "确认执行一级审核?", title: "审核确认" };
915 1085 },
916 1086 level2Confirm() {
... ... @@ -969,6 +1139,9 @@ export default {
969 1139 if (t === "委托代销结算单") {
970 1140 return { msg: "确认审核该委托代销结算单?", title: "审核确认" };
971 1141 }
  1142 + if (t === "获赠单") {
  1143 + return { msg: "确认执行二级审核该获赠单?通过后即按单据更新库存与成本。", title: "二级审核确认" };
  1144 + }
972 1145 return { msg: "确认执行二级审核?", title: "审核确认" };
973 1146 },
974 1147 handleLevel1Approve() {
... ... @@ -993,6 +1166,21 @@ export default {
993 1166 return { data: { success, message } };
994 1167 });
995 1168 }
  1169 + if (this.processBillType === "付款单") {
  1170 + return request({
  1171 + url: `/api/Extend/WtCwdj/Actions/ApprovePayment/${id}`,
  1172 + method: "POST",
  1173 + data: {}
  1174 + }).then(res => {
  1175 + const d = (res && res.data) || {};
  1176 + const success =
  1177 + typeof d.success === "boolean"
  1178 + ? d.success
  1179 + : String(res && res.code) === "200";
  1180 + const message = d.message || res.msg || (success ? "审核通过" : "审核失败");
  1181 + return { data: { success, message } };
  1182 + });
  1183 + }
996 1184 if (this.processBillType === "费用单") {
997 1185 return request({
998 1186 url: `/api/Extend/WtCwdj/Actions/ApproveExpense/${id}`,
... ... @@ -1044,7 +1232,7 @@ export default {
1044 1232 message: res.data.message || "操作成功"
1045 1233 });
1046 1234 this.pendingCloseWhenApproved = true;
1047   - this.loadData();
  1235 + this.refreshAll();
1048 1236 this.$nextTick(() => {
1049 1237 if (this.$refs.processDlg) this.$refs.processDlg.init(id);
1050 1238 });
... ... @@ -1081,7 +1269,7 @@ export default {
1081 1269 message: res.data.message || "操作成功"
1082 1270 });
1083 1271 this.pendingCloseWhenApproved = true;
1084   - this.loadData();
  1272 + this.refreshAll();
1085 1273 this.$nextTick(() => {
1086 1274 if (this.$refs.processDlg) this.$refs.processDlg.init(id);
1087 1275 });
... ... @@ -1116,7 +1304,7 @@ export default {
1116 1304 message: res.data.message || "已标记审核不通过"
1117 1305 });
1118 1306 this.pendingCloseAfterReject = true;
1119   - this.loadData();
  1307 + this.refreshAll();
1120 1308 this.$nextTick(() => {
1121 1309 if (this.$refs.processDlg) this.$refs.processDlg.init(id);
1122 1310 });
... ... @@ -1135,6 +1323,12 @@ export default {
1135 1323  
1136 1324 <style scoped lang="scss">
1137 1325 .todo-center-page {
  1326 + .todo-center-inner {
  1327 + display: flex;
  1328 + flex-direction: column;
  1329 + height: 100%;
  1330 + min-height: 0;
  1331 + }
1138 1332 .page-title {
1139 1333 font-size: 16px;
1140 1334 font-weight: 600;
... ... @@ -1147,8 +1341,119 @@ export default {
1147 1341 color: #909399;
1148 1342 vertical-align: middle;
1149 1343 }
  1344 + .page-desc--hint {
  1345 + border-bottom: 1px dashed #c0c4cc;
  1346 + cursor: help;
  1347 + }
1150 1348 .cell-nowrap {
1151 1349 white-space: nowrap;
1152 1350 }
  1351 + .todo-bill-type-tag {
  1352 + max-width: 100%;
  1353 + }
  1354 + .todo-center-body {
  1355 + display: flex;
  1356 + flex: 1;
  1357 + min-height: 0;
  1358 + margin-top: 0;
  1359 + }
  1360 + .todo-center-aside {
  1361 + width: 212px;
  1362 + flex-shrink: 0;
  1363 + border: 1px solid #e4e7ed;
  1364 + border-radius: 8px;
  1365 + background: #fff;
  1366 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
  1367 + margin-right: 14px;
  1368 + display: flex;
  1369 + flex-direction: column;
  1370 + min-height: 220px;
  1371 + }
  1372 + .todo-center-aside__head {
  1373 + padding: 12px 12px 8px;
  1374 + border-bottom: 1px solid #f0f2f5;
  1375 + }
  1376 + .todo-center-aside__title {
  1377 + display: block;
  1378 + font-size: 14px;
  1379 + font-weight: 600;
  1380 + color: #303133;
  1381 + line-height: 1.3;
  1382 + }
  1383 + .todo-center-aside__sub {
  1384 + display: block;
  1385 + margin-top: 4px;
  1386 + font-size: 12px;
  1387 + color: #909399;
  1388 + }
  1389 + .todo-center-aside__scroll {
  1390 + flex: 1;
  1391 + min-height: 0;
  1392 + overflow-y: auto;
  1393 + }
  1394 + .todo-cat-list {
  1395 + list-style: none;
  1396 + margin: 0;
  1397 + padding: 6px 0 10px;
  1398 + }
  1399 + .todo-cat-list__item {
  1400 + display: flex;
  1401 + align-items: flex-start;
  1402 + justify-content: space-between;
  1403 + gap: 8px;
  1404 + margin: 2px 8px;
  1405 + padding: 8px 10px 8px 12px;
  1406 + border-radius: 6px;
  1407 + cursor: pointer;
  1408 + font-size: 13px;
  1409 + line-height: 1.4;
  1410 + color: #303133;
  1411 + border-left: 3px solid transparent;
  1412 + transition: background 0.12s, border-color 0.12s, color 0.12s;
  1413 + &:hover {
  1414 + background: #f5f9ff;
  1415 + border-left-color: #b3d8ff;
  1416 + }
  1417 + &.is-active {
  1418 + background: linear-gradient(90deg, #ecf5ff 0%, #f5f9ff 100%);
  1419 + border-left-color: #409eff;
  1420 + color: #1a4a7a;
  1421 + font-weight: 500;
  1422 + .todo-cat-list__count {
  1423 + background: #409eff;
  1424 + color: #fff;
  1425 + }
  1426 + .todo-cat-list__count.is-zero {
  1427 + background: #c0c4cc;
  1428 + color: #fff;
  1429 + }
  1430 + }
  1431 + }
  1432 + .todo-cat-list__label {
  1433 + flex: 1;
  1434 + min-width: 0;
  1435 + word-break: break-all;
  1436 + }
  1437 + .todo-cat-list__count {
  1438 + flex-shrink: 0;
  1439 + min-width: 22px;
  1440 + padding: 0 6px;
  1441 + height: 20px;
  1442 + line-height: 20px;
  1443 + text-align: center;
  1444 + font-size: 12px;
  1445 + font-weight: 500;
  1446 + color: #606266;
  1447 + background: #f0f2f5;
  1448 + border-radius: 10px;
  1449 + &.is-zero {
  1450 + color: #c0c4cc;
  1451 + background: #f4f4f5;
  1452 + }
  1453 + }
  1454 + .todo-center-main {
  1455 + flex: 1;
  1456 + min-width: 0;
  1457 + }
1153 1458 }
1154 1459 </style>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/douyinLogistics/CreateWaybill.vue
... ... @@ -164,24 +164,16 @@
164 164 :key="index"
165 165 class="order-product-item"
166 166 >
167   - <!-- 商品明细:显示抖音图,没有则显示"暂无图片" -->
168 167 <el-image
169   - v-if="item.douyin_pic || item.product_pic"
170   - :src="item.douyin_pic || item.product_pic"
  168 + v-if="orderProductThumb(item)"
  169 + :src="orderProductThumb(item)"
171 170 class="product-thumb"
172 171 fit="cover"
173   - :preview-src-list="[item.douyin_pic || item.product_pic]"
  172 + :preview-src-list="[orderProductThumb(item)]"
174 173 />
175 174 <div class="product-thumb-placeholder" v-else>暂无图片</div>
176 175 <div class="product-info">
177   - <div class="product-name">{{ item.product_name }}</div>
178   - <div class="product-name-erp">
179   - ERP:{{ item.erp_spmc || '-' }}
180   - </div>
181   - <div v-if="getSkuCode(item)" class="product-sku">
182   - <span class="sku-label">SKUID:</span>{{ getSkuCode(item) }}
183   - <span v-if="formatSpec(item.spec)" class="sku-spec">({{ formatSpec(item.spec) }})</span>
184   - </div>
  176 + <div class="product-name product-name--erp-only">{{ item.erp_spmc || item.product_name || '—' }}</div>
185 177 <div class="product-meta">
186 178 <span>×{{ item.item_num }}</span>
187 179 <span>¥{{ Number(item.goods_price || 0).toFixed(2) }}</span>
... ... @@ -298,11 +290,11 @@
298 290 <template slot-scope="scope">
299 291 <!-- 商品清单:显示 ERP 图,没有则显示"暂无图片" -->
300 292 <el-image
301   - v-if="scope.row.erp_pic"
302   - :src="scope.row.erp_pic"
  293 + v-if="resolveProductPicUrl(scope.row.erp_pic)"
  294 + :src="resolveProductPicUrl(scope.row.erp_pic)"
303 295 style="width: 50px; height: 50px; border-radius: 4px;"
304 296 fit="cover"
305   - :preview-src-list="[scope.row.erp_pic]"
  297 + :preview-src-list="[resolveProductPicUrl(scope.row.erp_pic)]"
306 298 lazy
307 299 />
308 300 <div v-else class="cell-no-image">暂无图片</div>
... ... @@ -310,12 +302,7 @@
310 302 </el-table-column>
311 303 <el-table-column label="商品名称" min-width="200" show-overflow-tooltip>
312 304 <template slot-scope="scope">
313   - <div class="product-name-cell">
314   - <div class="product-name-main">{{ scope.row.product_name || '无' }}</div>
315   - <div class="product-name-erp">
316   - ERP:{{ scope.row.erp_spmc || '-' }}
317   - </div>
318   - </div>
  305 + <span class="product-name-erp-only">{{ scope.row.erp_spmc || scope.row.product_name || '—' }}</span>
319 306 </template>
320 307 </el-table-column>
321 308 <el-table-column label="抖音ID" width="150">
... ... @@ -635,7 +622,7 @@ import router from &#39;@/router&#39;
635 622 import { Message as ElMessage, MessageBox as ElMessageBox } from 'element-ui'
636 623 import { getOrderDetail, getMergedOrderDetail, searchProducts as searchProductsAPI, manualCreateWaybill, getProductInfo as getProductInfoAPI, getWarehouses, getProductCategories, updateSellerRemark, getDefaults, getCustomers, getPaymentAccounts, getUsers, getUsersByOrganize, getShopSettings, checkStock, getStockCostBreakdown, printWaybill } from '@/api/douyinLogistics'
637 624 import { getDouyinLogisticsBaseOrigin } from '@/utils/douyinLogisticsAxios'
638   -import { getErpBaseUrl } from '@/utils/douyinErpBase'
  625 +import { getErpAssetBaseUrl, resolveProductPicUrl } from '@/utils/douyinErpBase'
639 626 import request from '@/utils/request'
640 627 import { getDepartmentSelector } from '@/api/permission/department'
641 628 import SerialNumberSelect from './components/SerialNumberSelect.vue'
... ... @@ -671,15 +658,10 @@ const currentSerialRow = ref(null)
671 658 // 商品信息缓存(用于存储序列号类型)
672 659 const productCache = new Map()
673 660  
674   -/** 商品主图:补全 ERP 相对路径,避免 el-image 请求失败 */
675   -const resolveProductPicUrl = (pic) => {
676   - const s = (pic || '').trim()
677   - if (!s) return ''
678   - if (/^https?:\/\//i.test(s)) return s
679   - if (s.startsWith('//')) return `https:${s}`
680   - const base = getErpBaseUrl().replace(/\/$/, '')
681   - if (s.startsWith('/')) return `${base}${s}`
682   - return s
  661 +/** 订单信息区缩略图:优先 ERP 图,并统一走 resolve 修正错误域名 */
  662 +const orderProductThumb = (item) => {
  663 + if (!item) return ''
  664 + return resolveProductPicUrl(item.erp_pic || item.product_pic || item.douyin_pic)
683 665 }
684 666  
685 667 /** 金额展示:分转元,无数据时显示「无」 */
... ... @@ -1260,7 +1242,7 @@ const addProduct = async (product) =&gt; {
1260 1242 const images = typeof product.spzt === 'string' ? JSON.parse(product.spzt) : product.spzt
1261 1243 if (Array.isArray(images) && images.length > 0 && images[0].url) {
1262 1244 const url = images[0].url
1263   - productPic = url.startsWith('http') ? url : `${getErpBaseUrl()}${url.startsWith('/') ? '' : '/'}${url}`
  1245 + productPic = url.startsWith('http') ? url : `${getErpAssetBaseUrl()}${url.startsWith('/') ? '' : '/'}${url}`
1264 1246 }
1265 1247 } catch (e) {
1266 1248 console.warn('解析商品主图失败:', e)
... ... @@ -2006,6 +1988,7 @@ onMounted(() =&gt; {
2006 1988 productPageIndex,
2007 1989 productPageSize,
2008 1990 productTotal,
  1991 + orderProductThumb,
2009 1992 addProduct,
2010 1993 handleSubmit,
2011 1994 handleCancel,
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/douyinLogistics/OrderList.vue
... ... @@ -110,7 +110,7 @@
110 110 <el-form-item label="商品名称">
111 111 <el-input v-model="filterForm.productName" placeholder="商品关键词" clearable class="filter-w--product" />
112 112 </el-form-item>
113   - <el-form-item label="抖音SKU名称">
  113 + <el-form-item label="抖音SKU">
114 114 <el-input v-model="filterForm.skuName" placeholder="SKU名称关键词" clearable class="filter-w--sku" />
115 115 </el-form-item>
116 116 <el-form-item label="同步时间">
... ... @@ -304,10 +304,16 @@
304 304 <div v-if="row.trackingNumber" style="color: #409eff; font-weight: 500;">
305 305 {{ row.trackingNumber }}
306 306 </div>
307   - <div v-if="row.logisticsCompany" style="color: #909399; font-size: 11px; margin-top: 2px;">
  307 + <div
  308 + v-if="row.logisticsCompany && !isDefaultShunfengLogisticsLabel(row.logisticsCompany)"
  309 + style="color: #909399; font-size: 11px; margin-top: 2px;"
  310 + >
308 311 {{ row.logisticsCompany }}
309 312 </div>
310   - <div v-if="!row.trackingNumber && !row.logisticsCompany" style="color: #909399;">
  313 + <div
  314 + v-if="!row.trackingNumber && (!row.logisticsCompany || isDefaultShunfengLogisticsLabel(row.logisticsCompany))"
  315 + style="color: #909399;"
  316 + >
311 317 -
312 318 </div>
313 319 </div>
... ... @@ -379,7 +385,7 @@
379 385 <div v-if="row.sellerWords" style="color: #67c23a; white-space: pre-line;" :title="row.sellerWords">
380 386 <span style="color: #909399; font-size: 10px;">卖家:</span>{{ row.sellerWords }}
381 387 </div>
382   - <div v-if="row.status === 1" style="margin-top: 4px;">
  388 + <div v-if="row.status === 0 || row.status === 1" style="margin-top: 4px;">
383 389 <el-button type="text" size="mini" @click="openRemarkDialog(row)">编辑并回传抖音</el-button>
384 390 </div>
385 391 <div v-if="!row.buyerWords && !row.sellerWords" style="color: #909399;">
... ... @@ -1281,8 +1287,8 @@ const handleManualShip = async () =&gt; {
1281 1287 }
1282 1288  
1283 1289 const openRemarkDialog = (order) => {
1284   - if (!order || order.status !== 1) {
1285   - ElMessage.warning('仅已发货订单支持备注回传')
  1290 + if (!order || (order.status !== 0 && order.status !== 1)) {
  1291 + ElMessage.warning('仅待发货、已发货订单支持备注回传')
1286 1292 return
1287 1293 }
1288 1294 remarkForm.value = {
... ... @@ -1358,6 +1364,13 @@ const handleCreateSalesOrder = async (order) =&gt; {
1358 1364 }
1359 1365  
1360 1366  
  1367 +/** 列表「物流信息」列:本系统/库表默认的顺丰文案不展示,仅非顺丰或其它物流公司时显示第二行。 */
  1368 +const isDefaultShunfengLogisticsLabel = (name) => {
  1369 + const t = (name == null ? '' : String(name)).trim()
  1370 + if (!t) return false
  1371 + return t === '顺丰' || t === '顺丰速运' || t.toLowerCase() === 'shunfeng' || t.toLowerCase() === 'sf'
  1372 +}
  1373 +
1361 1374 /** 运单来源:1本系统顺丰打单 2 本系统其他物流 3 抖音同步(后端 TrackingOrigin / TrackingOriginDisplay) */
1362 1375 const getTrackingOriginMeta = (order) => {
1363 1376 const raw = order.trackingOriginDisplay != null && order.trackingOriginDisplay !== ''
... ... @@ -1544,6 +1557,7 @@ onMounted(() =&gt; {
1544 1557 handlePageChange,
1545 1558 formatDateTime,
1546 1559 getStatusText,
  1560 + isDefaultShunfengLogisticsLabel,
1547 1561 getTrackingOriginMeta,
1548 1562 getStatusType,
1549 1563 formatProductSpec,
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtBjdbd/Form.vue
... ... @@ -507,6 +507,7 @@
507 507 */
508 508 async applyOutboundCostPrice(row, silent = false) {
509 509 if (!row || !row.spbh || !row.ckck) return
  510 + let appliedPositive = false
510 511 try {
511 512 const spbh = encodeURIComponent(String(row.spbh))
512 513 const ck = encodeURIComponent(String(row.ckck))
... ... @@ -519,8 +520,7 @@
519 520 const p = Number(payload.data)
520 521 if (!isNaN(p) && p > 0) {
521 522 this.$set(row, 'dj', p.toFixed(4))
522   - this.calculateRowAdjustedCost(row)
523   - this.$forceUpdate()
  523 + appliedPositive = true
524 524 }
525 525 } else if (!silent && payload && payload.msg) {
526 526 this.$message.warning(payload.msg)
... ... @@ -528,6 +528,15 @@
528 528 } catch (e) {
529 529 if (!silent) console.error(e)
530 530 }
  531 + // 未查询到有效成本单价时,成本单价列默认显示 0(与 toFixed(4) 展示一致)
  532 + if (!appliedPositive) {
  533 + this.$set(row, 'dj', '0.0000')
  534 + this.calculateRowAdjustedCost(row)
  535 + this.$forceUpdate()
  536 + } else {
  537 + this.calculateRowAdjustedCost(row)
  538 + this.$forceUpdate()
  539 + }
531 540 },
532 541 /** 组装提交体:变价系数转数值、显式 isDraft、去掉前端临时字段 */
533 542 buildPayload(isDraft) {
... ... @@ -812,7 +821,7 @@
812 821 dw: undefined,
813 822 kucun: sl,
814 823 sl,
815   - dj: cbj ? Number(cbj).toFixed(4) : undefined,
  824 + dj: Number(cbj || 0).toFixed(4),
816 825 je: undefined,
817 826 bjhcb: undefined,
818 827 bjhcbManual: false,
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgthd/Form.vue
... ... @@ -1009,7 +1009,10 @@
1009 1009 this.$refs.serialNumberSelect.open(
1010 1010 row.spbh,
1011 1011 latestWarehouse,
1012   - row.selectedSerialNumbers || []
  1012 + row.selectedSerialNumbers || [],
  1013 + this.dataForm.djlx || '采购退货单',
  1014 + undefined,
  1015 + true
1013 1016 );
1014 1017 },
1015 1018  
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCgthd/PurchaseInboundOrderSelect.vue
1 1 <template>
  2 + <div class="purchase-inbound-select-root">
2 3 <el-dialog
3 4 title="选择采购入库单"
4 5 :visible.sync="visible"
... ... @@ -99,6 +100,11 @@
99 100 </el-table-column>
100 101 <el-table-column prop="jsr" label="经手人" min-width="120" />
101 102 <el-table-column prop="djzt" label="状态" width="100" />
  103 + <el-table-column label="操作" width="100" align="center" fixed="right">
  104 + <template slot-scope="scope">
  105 + <el-button type="text" size="mini" @click.stop="openOrderDetail(scope.row)">查看明细</el-button>
  106 + </template>
  107 + </el-table-column>
102 108 </el-table>
103 109 </div>
104 110  
... ... @@ -119,14 +125,18 @@
119 125 <el-button type="primary" @click="confirmSelection">确定(已选择{{ selectedOrders.length }}张)</el-button>
120 126 </div>
121 127 </el-dialog>
  128 + <WtCgrkdDetailView ref="cgrkdDetailView" />
  129 + </div>
122 130 </template>
123 131  
124 132 <script>
125 133 import request from '@/utils/request'
126 134 import { previewDataInterface } from '@/api/systemData/dataInterface'
  135 +import WtCgrkdDetailView from '../wtCgrkd/detail-view.vue'
127 136  
128 137 export default {
129 138 name: 'PurchaseInboundOrderSelect',
  139 + components: { WtCgrkdDetailView },
130 140 data() {
131 141 return {
132 142 visible: false,
... ... @@ -216,9 +226,10 @@ export default {
216 226 query.pageSize = 1000
217 227 }
218 228 if (this.query.djrq && this.query.djrq.length === 2) query.djrq = `${this.query.djrq[0]},${this.query.djrq[1]}`
  229 + // 只传 gys:采购入库主表「供应商」在 xsckd.Gys。若再传 kh,后端会 AND Kh.Contains(同 id),
  230 + // 与入库单实际 Kh(会员/客户)不一致,导致列表被筛成空,往来单位筛选失效。
219 231 if (this.query.gys) {
220 232 query.gys = this.query.gys
221   - query.kh = this.query.gys
222 233 }
223 234 if (this.query.jsr) query.jsr = this.query.jsr
224 235 if (this.query.spbh) query.spbh = this.query.spbh
... ... @@ -267,6 +278,13 @@ export default {
267 278 handleRowClick(row) {
268 279 this.$refs.orderTable.toggleRowSelection(row)
269 280 },
  281 + openOrderDetail(row) {
  282 + if (!row || !row.id) return
  283 + this.$nextTick(() => {
  284 + const ref = this.$refs.cgrkdDetailView
  285 + if (ref) ref.init(row.id)
  286 + })
  287 + },
270 288 handleSizeChange(size) {
271 289 this.pagination.pageSize = size
272 290 this.pagination.currentPage = 1
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_fkd/Form.vue
... ... @@ -23,7 +23,7 @@
23 23 </el-col>
24 24 <el-col :span="8">
25 25 <el-form-item label="经手人" prop="jsr">
26   - <el-input :value="jsrDisplayText" readonly placeholder="由明细所选账号带出" />
  26 + <el-input :value="jsrDisplayText" readonly placeholder="当前登录用户(保存后落库)" />
27 27 </el-form-item>
28 28 </el-col>
29 29 <el-col :span="16">
... ... @@ -189,16 +189,14 @@
189 189 return '编辑'
190 190 },
191 191 jsrDisplayText() {
192   - const rows = Array.isArray(this.dataForm.wtCwdjmxList) ? this.dataForm.wtCwdjmxList : []
193   - for (let i = 0; i < rows.length; i++) {
194   - const r = rows[i]
195   - if (r && r.zhbh) {
196   - const opt = (this.zhbhOptions || []).find(o => o && o.id === r.zhbh)
197   - if (opt && opt.fullName) return opt.fullName
198   - }
  192 + if (this.dataForm && this.dataForm.id && this.dataForm.jsr != null && this.dataForm.jsr !== '') {
  193 + return String(this.dataForm.jsr)
199 194 }
200   - const j = this.dataForm.jsr
201   - return (j != null && j !== '') ? String(j) : '—'
  195 + const u = this.$store.getters.userInfo
  196 + if (u) {
  197 + return (u.userName && String(u.userName).trim()) || (u.realName && String(u.realName).trim()) || (u.userId && String(u.userId).trim()) || '—'
  198 + }
  199 + return '—'
202 200 },
203 201 },
204 202 watch: {},
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_fkd/detail-view.vue
... ... @@ -107,6 +107,12 @@
107 107 <span></span>
108 108 </div>
109 109 </div>
  110 +
  111 + <template slot="footer">
  112 + <slot name="footer">
  113 + <el-button @click="close">关闭</el-button>
  114 + </slot>
  115 + </template>
110 116 </el-dialog>
111 117 </template>
112 118  
... ... @@ -138,6 +144,9 @@ export default {
138 144 this.loadOptions()
139 145 },
140 146 methods: {
  147 + close() {
  148 + this.visible = false
  149 + },
141 150 cellText(v) {
142 151 if (v === null || v === undefined || v === '') return ''
143 152 return v
... ... @@ -194,6 +203,7 @@ export default {
194 203 if (!Array.isArray(this.detail.wtCwdjmxList)) {
195 204 this.$set(this.detail, 'wtCwdjmxList', [])
196 205 }
  206 + this.$emit('loaded', this.detail)
197 207 }).catch(() => {
198 208 this.$message.error('加载详情失败')
199 209 this.visible = false
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_fkd/index.vue
... ... @@ -24,7 +24,7 @@
24 24 <template v-if="showAll">
25 25 <el-col :span="6">
26 26 <el-form-item label="经手人">
27   - <el-input v-model="query.jsr" placeholder="账户名称等" clearable />
  27 + <el-input v-model="query.jsr" placeholder="经手人" clearable />
28 28 </el-form-item>
29 29 </el-col>
30 30 <el-col :span="6">
... ... @@ -67,7 +67,12 @@
67 67 <el-table-column label="往来单位" prop="wldw" align="left">
68 68 <template slot-scope="scope">{{ scope.row.wldw | dynamicText(wldwOptions) }}</template>
69 69 </el-table-column>
70   - <el-table-column prop="jsr" label="经手人" align="left" />
  70 + <el-table-column label="金额" prop="fsje" align="right" width="120" show-overflow-tooltip>
  71 + <template slot-scope="scope">
  72 + {{ formatFsje(scope.row.fsje) }}
  73 + </template>
  74 + </el-table-column>
  75 + <el-table-column prop="jsr" label="经手人" align="left" min-width="120" show-overflow-tooltip />
71 76 <el-table-column prop="djzt" label="审核状态" align="left" width="110">
72 77 <template slot-scope="scope">
73 78 <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag>
... ... @@ -133,6 +138,7 @@
133 138 { prop: 'id', label: '单据编号' },
134 139 { prop: 'ldrq', label: '录单日期' },
135 140 { prop: 'wldw', label: '往来单位' },
  141 + { prop: 'fsje', label: '金额' },
136 142 { prop: 'jsr', label: '经手人' },
137 143 { prop: 'zy', label: '摘要' },
138 144 ],
... ... @@ -149,6 +155,12 @@
149 155 this.getskzhOptions();
150 156 },
151 157 methods: {
  158 + formatFsje(v) {
  159 + if (v === null || v === undefined || v === '') return '—'
  160 + const n = Number(v)
  161 + if (Number.isNaN(n)) return v
  162 + return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  163 + },
152 164 isDraftRow(row) {
153 165 return row && String(row.djzt || '').trim() === '草稿'
154 166 },
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_skd/Form.vue
... ... @@ -23,7 +23,7 @@
23 23 </el-col>
24 24 <el-col :span="8">
25 25 <el-form-item label="经手人" prop="jsr">
26   - <el-input :value="jsrDisplayText" readonly placeholder="由明细所选账号带出" />
  26 + <el-input :value="jsrDisplayText" readonly placeholder="当前登录用户(保存后落库)" />
27 27 </el-form-item>
28 28 </el-col>
29 29 <el-col :span="16">
... ... @@ -188,16 +188,15 @@
188 188 return '编辑'
189 189 },
190 190 jsrDisplayText() {
191   - const rows = Array.isArray(this.dataForm.wtCwdjmxList) ? this.dataForm.wtCwdjmxList : []
192   - for (let i = 0; i < rows.length; i++) {
193   - const r = rows[i]
194   - if (r && r.zhbh) {
195   - const opt = (this.zhbhOptions || []).find(o => o && o.id === r.zhbh)
196   - if (opt && opt.fullName) return opt.fullName
197   - }
  191 + // 经手人=录单/操作用户;服务端 GetInfo 已解析为真实姓名
  192 + if (this.dataForm && this.dataForm.id && this.dataForm.jsr != null && this.dataForm.jsr !== '') {
  193 + return String(this.dataForm.jsr)
198 194 }
199   - const j = this.dataForm.jsr
200   - return (j != null && j !== '') ? String(j) : '—'
  195 + const u = this.$store.getters.userInfo
  196 + if (u) {
  197 + return (u.userName && String(u.userName).trim()) || (u.realName && String(u.realName).trim()) || (u.userId && String(u.userId).trim()) || '—'
  198 + }
  199 + return '—'
201 200 },
202 201 },
203 202 watch: {},
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtCwdj_skd/index.vue
... ... @@ -24,7 +24,7 @@
24 24 <template v-if="showAll">
25 25 <el-col :span="6">
26 26 <el-form-item label="经手人">
27   - <el-input v-model="query.jsr" placeholder="账户名称等" clearable />
  27 + <el-input v-model="query.jsr" placeholder="经手人" clearable />
28 28 </el-form-item>
29 29 </el-col>
30 30 <el-col :span="6">
... ... @@ -67,7 +67,12 @@
67 67 <el-table-column label="往来单位" prop="wldw" align="left">
68 68 <template slot-scope="scope">{{ scope.row.wldw | dynamicText(wldwOptions) }}</template>
69 69 </el-table-column>
70   - <el-table-column prop="jsr" label="经手人" align="left" />
  70 + <el-table-column label="金额" prop="fsje" align="right" width="120" show-overflow-tooltip>
  71 + <template slot-scope="scope">
  72 + {{ formatFsje(scope.row.fsje) }}
  73 + </template>
  74 + </el-table-column>
  75 + <el-table-column prop="jsr" label="经手人" align="left" min-width="120" show-overflow-tooltip />
71 76 <el-table-column prop="djzt" label="审核状态" align="left" width="110">
72 77 <template slot-scope="scope">
73 78 <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag>
... ... @@ -133,6 +138,7 @@
133 138 { prop: 'id', label: '单据编号' },
134 139 { prop: 'ldrq', label: '录单日期' },
135 140 { prop: 'wldw', label: '往来单位' },
  141 + { prop: 'fsje', label: '金额' },
136 142 { prop: 'jsr', label: '经手人' },
137 143 { prop: 'zy', label: '摘要' },
138 144 ],
... ... @@ -149,6 +155,12 @@
149 155 this.getskzhOptions();
150 156 },
151 157 methods: {
  158 + formatFsje(v) {
  159 + if (v === null || v === undefined || v === '') return '—'
  160 + const n = Number(v)
  161 + if (Number.isNaN(n)) return v
  162 + return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
  163 + },
152 164 isDraftRow(row) {
153 165 return row && String(row.djzt || '').trim() === '草稿'
154 166 },
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtDgtchsb/index.vue
... ... @@ -29,7 +29,7 @@
29 29 <template v-if="showAll">
30 30 <el-col :span="6">
31 31 <el-form-item label="单据编号">
32   - <el-input v-model="query.djbh" placeholder="CHD/XSCKD…" clearable />
  32 + <el-input v-model="query.djbh" placeholder="CKD/销售单号…" clearable />
33 33 </el-form-item>
34 34 </el-col>
35 35 <el-col :span="6">
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtFysrd_qtsr/Form.vue
1   -<template>
  1 +<template>
2 2 <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="90%">
3 3 <el-row :gutter="15" class="" >
4 4 <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules">
... ... @@ -194,7 +194,11 @@
194 194 this.$nextTick(() => {
195 195 this.$refs['elForm'].resetFields();
196 196 if (!id) {
197   - this.dataForm.djlx = '其他收入单';
  197 + this.dataForm.djlx = '其他收入单'
  198 + const u = this.$store.getters.userInfo
  199 + if (u && (u.userId || u.id)) {
  200 + this.dataForm.jsr = u.userId || u.id
  201 + }
198 202 }
199 203 if (this.dataForm.id) {
200 204 request({
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtFysrd_qtsr/index.vue
1   -<template>
  1 +<template>
2 2 <div class="NCC-common-layout">
3 3 <div class="NCC-common-layout-center">
4 4 <el-row class="NCC-common-search-box" :gutter="16">
... ... @@ -15,6 +15,13 @@
15 15 </el-form-item>
16 16 </el-col>
17 17 <el-col :span="6">
  18 + <el-form-item label="收款账户">
  19 + <el-select v-model="query.jszh" placeholder="收款账户" clearable filterable style="width:100%">
  20 + <el-option v-for="(item, index) in jszhOptions" :key="index" :label="item.fullName" :value="item.id" />
  21 + </el-select>
  22 + </el-form-item>
  23 + </el-col>
  24 + <el-col :span="6">
18 25 <el-form-item label="分支机构">
19 26 <comSelect v-model="query.fzjg" placeholder="请选择分支机构" />
20 27 </el-form-item>
... ... @@ -31,13 +38,6 @@
31 38 </el-form-item>
32 39 </el-col>
33 40 <el-col :span="6">
34   - <el-form-item label="收款账户">
35   - <el-select v-model="query.jszh" placeholder="收款账户" clearable >
36   - <el-option v-for="(item, index) in jszhOptions" :key="index" :label="item.fullName" :value="item.id" />
37   - </el-select>
38   - </el-form-item>
39   - </el-col>
40   - <el-col :span="6">
41 41 <el-form-item label="金额">
42 42 <el-input v-model="query.je" placeholder="金额" clearable />
43 43 </el-form-item>
... ... @@ -103,9 +103,7 @@
103 103 <el-table-column prop="fzjg" label="分支机构" align="left" />
104 104 <el-table-column prop="bm" label="部门" align="left" />
105 105 <el-table-column prop="jsr" label="经手人" align="left" />
106   - <el-table-column label="收款账户" prop="jszh" align="left">
107   - <template slot-scope="scope">{{ scope.row.jszh | dynamicText(jszhOptions) }}</template>
108   - </el-table-column>
  106 + <el-table-column label="收款账户" prop="jszh" align="left" min-width="120" show-overflow-tooltip />
109 107 <el-table-column prop="je" label="金额" align="left" />
110 108 <el-table-column prop="zdr" label="制单人" align="left" />
111 109 <el-table-column prop="shr" label="审核人" align="left" />
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtHzd/Form.vue
... ... @@ -285,6 +285,9 @@
285 285 <el-button @click="saveDraft">保存草稿</el-button>
286 286 <el-button type="primary" @click="submitForAudit">提交审核</el-button>
287 287 </template>
  288 + <template v-else-if="isDetail && canWithdrawAudit">
  289 + <el-button type="warning" @click="withdrawToDraft">撤回为草稿</el-button>
  290 + </template>
288 291 </span>
289 292 <!-- 商品条码选择弹窗 -->
290 293 <BarcodeSelect ref="barcodeSelect" @select="handleBarcodeSelect" />
... ... @@ -299,6 +302,7 @@
299 302 import SerialNumberSelect from './SerialNumberSelect.vue'
300 303 import { getAccountSelector } from '@/api/extend/wtAccount'
301 304 import { validateMxNoEmptyProductRows } from '@/utils/validateBillMxEmptyRows'
  305 + import { postWtXsckdWithdrawAudit } from '@/utils/wtRejectApproval'
302 306 export default {
303 307 components: { BarcodeSelect, SerialNumberSelect },
304 308 props: [],
... ... @@ -360,6 +364,11 @@
360 364 const z = this.auditStatusText
361 365 return z === '待审核' || z === '已审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级'
362 366 },
  367 + /** 详情查看时:已提交、待一/二级审核可撤回为草稿 */
  368 + canWithdrawAudit() {
  369 + const z = this.auditStatusText
  370 + return z === '待审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级'
  371 + },
363 372 formDisabled() {
364 373 return !!this.isDetail || this.isBillLockedForEdit
365 374 },
... ... @@ -369,6 +378,7 @@
369 378 const z = this.auditStatusText
370 379 if (z === '已审核') return '查看(已审核)'
371 380 if (z === '待审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级') return '查看(待审核)'
  381 + if (z === '审核不通过') return '编辑(审核不通过)'
372 382 if (z === '草稿') return '编辑'
373 383 return '编辑'
374 384 },
... ... @@ -544,8 +554,14 @@
544 554 if (md && md.length) {
545 555 row.spmc = md[0].F_Spmc;
546 556 }
547   - if (row.spbh && row.ckck) {
548   - this.getStockQuantity(row);
  557 + // 获赠单账面库存按「入库仓」查询,与报损等按出库仓不同
  558 + if (row.spbh) {
  559 + if (!row.rkck && this.dataForm.rkck) {
  560 + row.rkck = this.dataForm.rkck;
  561 + }
  562 + if (row.rkck) {
  563 + this.getStockQuantity(row);
  564 + }
549 565 }
550 566 if (row.spbh) {
551 567 const productId = String(row.spbh);
... ... @@ -564,12 +580,12 @@
564 580  
565 581  
566 582 getcjckOptions(){
567   - previewDataInterface('681758216954053893').then(res => {
  583 + return previewDataInterface('681758216954053893').then(res => {
568 584 this.cjckOptions = res.data
569 585 });
570 586 },
571 587 getrkckOptions(){
572   - previewDataInterface('681758216954053893').then(res => {
  588 + return previewDataInterface('681758216954053893').then(res => {
573 589 this.rkckOptions = res.data
574 590 });
575 591 },
... ... @@ -594,10 +610,31 @@
594 610 // });
595 611 // },
596 612 getckckOptions(){
597   - previewDataInterface('681758216954053893').then(res => {
  613 + return previewDataInterface('681758216954053893').then(res => {
598 614 this.ckckOptions = res.data
599 615 });
600 616 },
  617 + /**
  618 + * 详情接口可能把 cjck/rkck/明细仓字段由 ID 替换为展示名;
  619 + * 编辑/详情页 el-select 与 GetStockQuantity 需 ID,这里做名称 → ID 反查。
  620 + */
  621 + resolveIdByName(val, options) {
  622 + if (val == null || val === '') return val;
  623 + const v = String(val).trim();
  624 + if (!options || !options.length) return val;
  625 + if (options.some(o => o && String(o.F_Id) === v)) return val;
  626 + const hit = options.find(o => o && (o.F_mdmc === v || o.F_ckmc === v || o.F_name === v));
  627 + return hit ? hit.F_Id : val;
  628 + },
  629 + async ensureWarehouseOptionsReady() {
  630 + const tasks = [];
  631 + if (!this.cjckOptions || !this.cjckOptions.length) tasks.push(this.getcjckOptions());
  632 + if (!this.rkckOptions || !this.rkckOptions.length) tasks.push(this.getrkckOptions());
  633 + if (!this.ckckOptions || !this.ckckOptions.length) tasks.push(this.getckckOptions());
  634 + if (tasks.length > 0) {
  635 + try { await Promise.all(tasks); } catch (e) { /* 选项失败时仍尝试继续 */ }
  636 + }
  637 + },
601 638 // getspbhOptions(){
602 639 // previewDataInterface('675937572047815941').then(res => {
603 640 // this.spbhOptions = res.data
... ... @@ -642,6 +679,23 @@
642 679 goBack() {
643 680 this.$emit('refresh')
644 681 },
  682 + withdrawToDraft() {
  683 + const id = this.dataForm && this.dataForm.id
  684 + if (!id) return
  685 + this.$confirm('撤回后单据将变为草稿,可继续修改并再次提交审核。是否继续?', '撤回确认', { type: 'warning' })
  686 + .then(() => postWtXsckdWithdrawAudit(id))
  687 + .then(res => {
  688 + const d = (res && res.data) || {}
  689 + if (d.success === true) {
  690 + this.$message.success(d.message || '已撤回为草稿')
  691 + this.visible = false
  692 + this.$emit('refresh', true)
  693 + } else {
  694 + this.$message.error(d.message || '撤回失败')
  695 + }
  696 + })
  697 + .catch(() => {})
  698 + },
645 699 init(id, isDetail) {
646 700 var _this = this;
647 701 console.log('id',id,'detail',isDetail)
... ... @@ -663,7 +717,7 @@
663 717 request({
664 718 url: '/api/Extend/WtXsckd/' +id,
665 719 method: 'get'
666   - }).then(res =>{
  720 + }).then(async res =>{
667 721 _this.dataForm = res.data;
668 722 // 后端 GetInfo 将 kh 返回为展示名(如"神码"/"门店散客"),真正的 Id 放在 khId
669 723 // el-select 的 value 需用 Id,这里回读时以 khId 覆盖
... ... @@ -672,22 +726,40 @@
672 726 }
673 727 console.log('编辑时加载的数据:', _this.dataForm);
674 728 console.log('明细数据:', _this.dataForm.wtXsckdMxList);
675   -
676   - // 为每个明细项添加productQuery字段
  729 +
  730 + await _this.ensureWarehouseOptionsReady();
  731 + _this.dataForm.cjck = _this.resolveIdByName(_this.dataForm.cjck, _this.cjckOptions);
  732 + _this.dataForm.rkck = _this.resolveIdByName(_this.dataForm.rkck, _this.rkckOptions);
  733 +
677 734 if (_this.dataForm.wtXsckdMxList) {
678 735 _this.dataForm.wtXsckdMxList.forEach(item => {
679 736 if (!item.hasOwnProperty('productQuery')) {
680 737 _this.$set(item, 'productQuery', '');
681 738 }
  739 + if (!item.hasOwnProperty('loadingStock')) {
  740 + _this.$set(item, 'loadingStock', false);
  741 + }
  742 + if (!item.hasOwnProperty('kucun')) {
  743 + _this.$set(item, 'kucun', undefined);
  744 + }
  745 + _this.$set(item, 'rkck', _this.resolveIdByName(item.rkck, _this.rkckOptions));
  746 + _this.$set(item, 'ckck', _this.resolveIdByName(item.ckck, _this.ckckOptions));
682 747 });
683 748 }
684   -
685   - // 初始化时计算总金额
  749 +
686 750 _this.calculateTotalAmount();
687   - // 同步明细表出库仓库
688 751 _this.syncDetailWarehouses();
689   - // 恢复序列号信息
  752 + _this.syncDetailInboundWarehouses();
690 753 _this.restoreSerialNumbers();
  754 +
  755 + // 账面库存不落库,重新打开(含详情/只读)需按商品+入库仓拉取
  756 + if (_this.dataForm.wtXsckdMxList) {
  757 + _this.dataForm.wtXsckdMxList.forEach(item => {
  758 + if (item.spbh && item.rkck) {
  759 + _this.getStockQuantity(item, { silent: true });
  760 + }
  761 + });
  762 + }
691 763 })
692 764 } else {
693 765 _this.dataForm.djrq = Date.now()
... ... @@ -983,10 +1055,13 @@
983 1055 }
984 1056 },
985 1057  
986   - // 获取库存数量
987   - async getStockQuantity(row) {
  1058 + // 获取库存数量(silent:批量初始化时关闭提示,避免刷屏)
  1059 + async getStockQuantity(row, opts = {}) {
  1060 + const silent = opts.silent === true;
988 1061 if (!row.spbh || !row.rkck) {
989   - this.$message.warning('请先选择商品和入库仓库');
  1062 + if (!silent) {
  1063 + this.$message.warning('请先选择商品和入库仓库');
  1064 + }
990 1065 return;
991 1066 }
992 1067  
... ... @@ -1039,7 +1114,7 @@
1039 1114 });
1040 1115 this.fillAvgCostForRow(row);
1041 1116 } else {
1042   - if (response.msg !== '操作成功') {
  1117 + if (!silent && response.msg !== '操作成功') {
1043 1118 this.$message.error(response.msg || '获取库存失败');
1044 1119 }
1045 1120 // 使用$set确保响应式更新
... ... @@ -1049,7 +1124,7 @@
1049 1124 }
1050 1125 } catch (error) {
1051 1126 console.error('获取库存失败:', error);
1052   - if (!error || error.message !== '操作成功') {
  1127 + if (!silent && (!error || error.message !== '操作成功')) {
1053 1128 this.$message.error('获取库存失败,请重试');
1054 1129 }
1055 1130 row.kucun = 0;
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtHzd/detail-view.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + title="获赠单详情"
  4 + :close-on-click-modal="false"
  5 + :visible.sync="visible"
  6 + class="NCC-dialog NCC-dialog_center"
  7 + lock-scroll
  8 + append-to-body
  9 + modal-append-to-body
  10 + width="1200px"
  11 + top="6vh"
  12 + @close="handleClose"
  13 + >
  14 + <div v-loading="loading" class="hzd-detail-wrap">
  15 + <template v-if="detail">
  16 + <div class="detail-top">
  17 + <div class="detail-top__id">
  18 + <i class="el-icon-document detail-top__icon detail-top__icon--primary" />
  19 + <span class="cell-nowrap">{{ cellText(detail.id) }}</span>
  20 + </div>
  21 + <el-tag v-if="auditStatus(detail) === '草稿'" type="info" size="medium">草稿</el-tag>
  22 + <el-tag v-else-if="auditStatus(detail) === '待审核'" type="warning" size="medium">待审核</el-tag>
  23 + <el-tag v-else-if="auditStatus(detail) === '一级已审'" size="medium">一级已审</el-tag>
  24 + <el-tag v-else-if="auditStatus(detail) === '一级已审/待二级'" type="warning" size="medium">一级已审/待二级</el-tag>
  25 + <el-tag v-else-if="auditStatus(detail) === '待二级'" type="warning" size="medium">待二级</el-tag>
  26 + <el-tag v-else-if="auditStatus(detail) === '审核不通过'" type="danger" size="medium">审核不通过</el-tag>
  27 + <el-tag v-else-if="auditStatus(detail) === '已审核'" type="success" size="medium">已审核</el-tag>
  28 + <el-tag v-else type="warning" size="medium">{{ cellText(auditStatus(detail)) }}</el-tag>
  29 + </div>
  30 +
  31 + <el-descriptions :column="2" border size="small" class="detail-descriptions">
  32 + <el-descriptions-item label="单据编号">{{ cellText(detail.id) }}</el-descriptions-item>
  33 + <el-descriptions-item label="单据日期">{{ formatDate(detail.djrq) }}</el-descriptions-item>
  34 + <el-descriptions-item label="入库仓库">{{ labelFromOptions(detail.rkck, rkckOptions) }}</el-descriptions-item>
  35 + <el-descriptions-item label="经手人">{{ cellText(detail.jsr) }}</el-descriptions-item>
  36 + <el-descriptions-item label="往来单位">{{ cellText(detail.kh) }}</el-descriptions-item>
  37 + <el-descriptions-item label="单据类型">{{ cellText(detail.djlx || '获赠单') }}</el-descriptions-item>
  38 + <el-descriptions-item label="一级审核人">{{ cellText(detail.shr1) }}</el-descriptions-item>
  39 + <el-descriptions-item label="二级审核人">{{ cellText(detail.shr2) }}</el-descriptions-item>
  40 + <el-descriptions-item label="备注" :span="2">{{ cellText(detail.bz) }}</el-descriptions-item>
  41 + <el-descriptions-item label="审批备注" :span="2">{{ cellText(detail.spbz) }}</el-descriptions-item>
  42 + </el-descriptions>
  43 +
  44 + <div class="detail-section-head">
  45 + <i class="el-icon-s-order detail-section-head__icon" />
  46 + <span>明细</span>
  47 + </div>
  48 + <el-table
  49 + :data="detail.wtXsckdMxList || []"
  50 + size="small"
  51 + border
  52 + stripe
  53 + show-summary
  54 + :summary-method="getSummaries"
  55 + class="detail-mx-table"
  56 + empty-text=""
  57 + >
  58 + <el-table-column type="index" width="52" label="#" align="center" />
  59 + <el-table-column label="入库仓库" min-width="120" show-overflow-tooltip>
  60 + <template slot-scope="scope">
  61 + <span class="cell-nowrap">{{ labelFromOptions(scope.row.rkck, rkckOptions) }}</span>
  62 + </template>
  63 + </el-table-column>
  64 + <el-table-column label="商品" min-width="180" show-overflow-tooltip>
  65 + <template slot-scope="scope">
  66 + <span class="cell-nowrap">{{ cellText(scope.row.spmc) }}</span>
  67 + </template>
  68 + </el-table-column>
  69 + <el-table-column label="账面库存" width="96" align="right">
  70 + <template slot-scope="scope">
  71 + <span class="cell-nowrap">{{ resolveBookStock(scope.row) }}</span>
  72 + </template>
  73 + </el-table-column>
  74 + <el-table-column prop="sl" label="数量" width="96" align="right" />
  75 + <el-table-column prop="je" label="金额" width="100" align="right" />
  76 + </el-table>
  77 + </template>
  78 + </div>
  79 + <span v-if="$slots.footer" slot="footer" class="dialog-footer hzd-detail-footer">
  80 + <slot name="footer" />
  81 + </span>
  82 + </el-dialog>
  83 +</template>
  84 +
  85 +<script>
  86 +import request from '@/utils/request'
  87 +import { previewDataInterface } from '@/api/systemData/dataInterface'
  88 +
  89 +export default {
  90 + name: 'WtHzdDetailView',
  91 + data() {
  92 + return {
  93 + visible: false,
  94 + loading: false,
  95 + detail: null,
  96 + rkckOptions: []
  97 + }
  98 + },
  99 + created() {
  100 + this.loadOptions()
  101 + },
  102 + methods: {
  103 + isBlankValue(v) {
  104 + if (v === null || v === undefined) return true
  105 + const text = String(v).trim()
  106 + return text === '' || text === '无' || text === 'null' || text === 'undefined'
  107 + },
  108 + cellText(v) {
  109 + if (this.isBlankValue(v)) return ''
  110 + return String(v)
  111 + },
  112 + auditStatus(row) {
  113 + if (!row) return ''
  114 + const s = String(row.djzt || row.shzt || '').trim()
  115 + if (!s) return '待审核'
  116 + return s
  117 + },
  118 + resolveBookStock(row) {
  119 + if (!row || typeof row !== 'object') return ''
  120 + const candidates = [
  121 + row.kucun,
  122 + row.Kucun,
  123 + row.kc,
  124 + row.Kc
  125 + ]
  126 + for (let i = 0; i < candidates.length; i++) {
  127 + if (!this.isBlankValue(candidates[i])) return String(candidates[i])
  128 + }
  129 + return ''
  130 + },
  131 + /** 账面库存按商品 + 入库仓查询(与编辑页 getStockQuantity 一致) */
  132 + async enrichMxBookStock(detail) {
  133 + if (!detail || !Array.isArray(detail.wtXsckdMxList)) return
  134 + const rows = detail.wtXsckdMxList
  135 + await Promise.all(
  136 + rows.map(async row => {
  137 + const spbh = row.spbh != null ? row.spbh : row.Spbh
  138 + const wh = row.rkck != null ? row.rkck : row.Rkck
  139 + if (this.isBlankValue(spbh) || this.isBlankValue(wh)) return
  140 + try {
  141 + const url = `/api/Extend/WtXsckd/GetStockQuantity?productId=${encodeURIComponent(String(spbh).trim())}&warehouseId=${encodeURIComponent(String(wh).trim())}`
  142 + const response = await request({ url, method: 'get' })
  143 + const inner = response && response.data
  144 + if (inner && inner.success) {
  145 + const qty = inner.data != null ? inner.data : 0
  146 + this.$set(row, 'kucun', qty)
  147 + }
  148 + } catch (e) {
  149 + // 静默
  150 + }
  151 + })
  152 + )
  153 + },
  154 + formatDate(ts) {
  155 + if (!ts) return ''
  156 + const d = new Date(Number(ts))
  157 + if (isNaN(d.getTime())) return ''
  158 + const y = d.getFullYear()
  159 + const m = String(d.getMonth() + 1).padStart(2, '0')
  160 + const day = String(d.getDate()).padStart(2, '0')
  161 + return `${y}-${m}-${day}`
  162 + },
  163 + labelFromOptions(value, options) {
  164 + if (this.isBlankValue(value)) return ''
  165 + const found = (options || []).find(
  166 + o => o.F_Id === value || String(o.F_Id) === String(value) || o.id === value || String(o.id) === String(value)
  167 + )
  168 + const text = found ? (found.F_mdmc || found.fullName || value) : value
  169 + return this.cellText(text)
  170 + },
  171 + getSummaries(param) {
  172 + const { columns, data } = param
  173 + const sums = []
  174 + columns.forEach((column, index) => {
  175 + if (index === 0) {
  176 + sums[index] = '合计'
  177 + return
  178 + }
  179 + if (column.property === 'sl' || column.property === 'je') {
  180 + sums[index] = data.reduce((t, r) => t + (Number(r[column.property]) || 0), 0)
  181 + } else {
  182 + sums[index] = ''
  183 + }
  184 + })
  185 + return sums
  186 + },
  187 + handleClose() {
  188 + this.detail = null
  189 + this.$emit('close')
  190 + },
  191 + close() {
  192 + this.visible = false
  193 + },
  194 + loadOptions() {
  195 + previewDataInterface('681758216954053893').then(res => {
  196 + this.rkckOptions = res.data || []
  197 + })
  198 + },
  199 + init(id) {
  200 + if (!id) return
  201 + this.visible = true
  202 + this.loading = true
  203 + request({
  204 + url: '/api/Extend/WtXsckd/' + id,
  205 + method: 'GET'
  206 + })
  207 + .then(res => {
  208 + const detail = res.data || {}
  209 + detail.wtXsckdMxList = Array.isArray(detail.wtXsckdMxList) ? detail.wtXsckdMxList : []
  210 + this.detail = detail
  211 + this.$emit('loaded', detail)
  212 + this.enrichMxBookStock(detail)
  213 + })
  214 + .catch(() => {
  215 + this.$message.error('加载详情失败')
  216 + this.visible = false
  217 + })
  218 + .finally(() => {
  219 + this.loading = false
  220 + })
  221 + }
  222 + }
  223 +}
  224 +</script>
  225 +
  226 +<style scoped lang="scss">
  227 +.cell-nowrap {
  228 + white-space: nowrap;
  229 +}
  230 +.hzd-detail-wrap {
  231 + margin-bottom: 30px;
  232 +}
  233 +.detail-top {
  234 + display: flex;
  235 + align-items: center;
  236 + justify-content: space-between;
  237 + margin-bottom: 14px;
  238 + padding-bottom: 12px;
  239 + border-bottom: 1px solid #ebeef5;
  240 +}
  241 +.detail-top__id {
  242 + display: inline-flex;
  243 + align-items: center;
  244 + gap: 8px;
  245 + font-size: 15px;
  246 + font-weight: 600;
  247 + color: #303133;
  248 +}
  249 +.detail-top__icon--primary {
  250 + color: #409eff;
  251 +}
  252 +.detail-descriptions {
  253 + margin-bottom: 12px;
  254 +}
  255 +.detail-section-head {
  256 + display: flex;
  257 + align-items: center;
  258 + gap: 6px;
  259 + margin: 10px 0;
  260 + font-size: 14px;
  261 + font-weight: 600;
  262 + color: #303133;
  263 +}
  264 +.detail-section-head__icon {
  265 + color: #409eff;
  266 +}
  267 +.hzd-detail-footer {
  268 + display: inline-flex;
  269 + flex-wrap: wrap;
  270 + align-items: center;
  271 + gap: 8px;
  272 +}
  273 +</style>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtHzd/index.vue
... ... @@ -113,6 +113,7 @@
113 113 <template slot-scope="scope">
114 114 <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag>
115 115 <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag>
  116 + <el-tag v-else-if="scope.row.djzt === '审核不通过'" type="danger">审核不通过</el-tag>
116 117 <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag>
117 118 </template>
118 119 </el-table-column>
... ... @@ -121,13 +122,15 @@
121 122 <ncc-table-summary-cell :row="scope.row" fields="zy,Zy" />
122 123 </template>
123 124 </el-table-column>
124   - <el-table-column label="操作" fixed="right" width="260">
  125 + <el-table-column label="操作" fixed="right" width="320">
125 126 <template slot-scope="scope">
126 127 <el-button type="text" @click="openDetail(scope.row.id)">查看</el-button>
127   - <el-button type="text" v-if="isDraftRow(scope.row)" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button>
  128 + <el-button type="text" v-if="isEditableRow(scope.row)" @click="addOrUpdateHandle(scope.row.id)">编辑</el-button>
128 129 <el-button type="text" v-if="isPendingAudit(scope.row)" @click="handleApprove(scope.row.id)" style="color:#409EFF">审核</el-button>
  130 + <el-button type="text" v-if="isPendingAudit(scope.row)" @click="handleReject(scope.row.id)" style="color:#F56C6C">不通过</el-button>
  131 + <el-button type="text" v-if="isWithdrawable(scope.row)" @click="handleWithdraw(scope.row.id)" style="color:#E6A23C">撤回</el-button>
129 132 <el-button type="text" v-if="scope.row.djzt === '已审核'" @click="handleReverse(scope.row.id)" style="color:#E6A23C">反审</el-button>
130   - <el-button type="text" v-if="isDraftRow(scope.row)" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button>
  133 + <el-button type="text" v-if="isEditableRow(scope.row)" @click="handleDel(scope.row.id)" class="NCC-table-delBtn">删除</el-button>
131 134 </template>
132 135 </el-table-column>
133 136 </NCC-table>
... ... @@ -143,6 +146,12 @@
143 146 import NCCForm from './Form'
144 147 import ExportBox from './ExportBox'
145 148 import { previewDataInterface } from '@/api/systemData/dataInterface'
  149 + import {
  150 + promptApprovalRemark,
  151 + postApproveGeneric,
  152 + postRejectGeneric,
  153 + postWtXsckdWithdrawAudit
  154 + } from '@/utils/wtRejectApproval'
146 155 export default {
147 156 components: { NCCForm, ExportBox },
148 157 data() {
... ... @@ -197,12 +206,19 @@
197 206 this.getkhOptions();
198 207 },
199 208 methods: {
200   - isDraftRow(row) {
201   - return row && String(row.djzt || '').trim() === '草稿'
  209 + /** 草稿、审核不通过:可编辑、可删(与后端可删/可改口径一致) */
  210 + isEditableRow(row) {
  211 + const z = row && String(row.djzt || '').trim()
  212 + return z === '草稿' || z === '审核不通过'
202 213 },
203 214 isPendingAudit(row) {
204 215 const z = row && String(row.djzt || '').trim()
205   - return z === '待审核' || z === ''
  216 + return z === '待审核' || z === '' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级'
  217 + },
  218 + /** 已提交待审或待二级:可撤回为草稿 */
  219 + isWithdrawable(row) {
  220 + const z = row && String(row.djzt || '').trim()
  221 + return z === '待审核' || z === '一级已审' || z === '待二级' || z === '一级已审/待二级'
206 222 },
207 223 openDetail(id) {
208 224 this.formVisible = true
... ... @@ -211,16 +227,48 @@
211 227 })
212 228 },
213 229 handleApprove(id) {
214   - this.$confirm('确认审核该获赠单?', '提示', { type: 'warning' }).then(() => {
215   - request({
216   - url: `/api/Extend/WtXsckd/ApproveGeneric/${id}`,
217   - method: 'POST',
218   - data: {}
219   - }).then(res => {
220   - this.$message({ type: 'success', message: res.msg || '审核成功', duration: 1200 })
221   - this.initData()
  230 + this.$confirm('确认审核该获赠单?', '提示', { type: 'warning' })
  231 + .then(() => promptApprovalRemark(this, '审批备注'))
  232 + .then(remark => postApproveGeneric(id, remark))
  233 + .then(res => {
  234 + const d = (res && res.data) || {}
  235 + if (d.success === true) {
  236 + this.$message.success(d.message || '审核成功')
  237 + this.initData()
  238 + } else {
  239 + this.$message.error(d.message || '审核失败')
  240 + }
222 241 })
223   - }).catch(() => {})
  242 + .catch(() => {})
  243 + },
  244 + handleReject(id) {
  245 + promptApprovalRemark(this, '审核不通过')
  246 + .then(reason => postRejectGeneric(id, reason))
  247 + .then(res => {
  248 + const d = (res && res.data) || {}
  249 + if (d.success === true) {
  250 + this.$message.success(d.message || '已处理')
  251 + this.initData()
  252 + } else {
  253 + this.$message.error(d.message || '操作失败')
  254 + }
  255 + })
  256 + .catch(() => {})
  257 + },
  258 + handleWithdraw(id) {
  259 + if (!id) return
  260 + this.$confirm('撤回后单据将变为草稿,可继续修改并再次提交审核。是否继续?', '撤回确认', { type: 'warning' })
  261 + .then(() => postWtXsckdWithdrawAudit(id))
  262 + .then(res => {
  263 + const d = (res && res.data) || {}
  264 + if (d.success === true) {
  265 + this.$message.success(d.message || '已撤回为草稿')
  266 + this.initData()
  267 + } else {
  268 + this.$message.error(d.message || '撤回失败')
  269 + }
  270 + })
  271 + .catch(() => {})
224 272 },
225 273 handleReverse(id) {
226 274 if (!id) return
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPl/Form.vue
1   -<template>
2   - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="600px">
  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="700px">
3 3 <el-row :gutter="15" class="" >
4   - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="120px" label-position="right" :disabled="!!isDetail" :rules="rules">
5 5 <el-col :span="24" v-if="false" >
6 6 <el-form-item label="分类编号" prop="id">
7 7 <el-input v-model="dataForm.id" placeholder="请输入" clearable :style='{"width":"100%"}' >
... ... @@ -22,6 +22,16 @@
22 22 </el-radio-group>
23 23 </el-form-item>
24 24 </el-col>
  25 + <el-col :span="24" v-show="dataForm.sfmdfl === '1'">
  26 + <el-form-item label="显示门店组" prop="mdfzIds">
  27 + <div class="wt-pl-mdfz-panel">
  28 + <el-checkbox-group v-model="dataForm.mdfzIds">
  29 + <el-checkbox v-for="(item, index) in mdfzOptions" :key="index" :label="item.id">{{ item.fzmc }}</el-checkbox>
  30 + </el-checkbox-group>
  31 + </div>
  32 + <p class="wt-pl-form-tip">不选表示全部门店组均展示;仅当「是否门店分类」为是时可配置。</p>
  33 + </el-form-item>
  34 + </el-col>
25 35 <el-col :span="24">
26 36 <el-form-item label="序号" prop="xh">
27 37 <el-input-number v-model="dataForm.xh" :min="0" :max="999999" :step="1" :controls-position="'right'" placeholder="数值越小越靠前" :style='{"width":"100%"}' />
... ... @@ -38,8 +48,6 @@
38 48 </template>
39 49 <script>
40 50 import request from '@/utils/request'
41   - import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
42   - import { previewDataInterface } from '@/api/systemData/dataInterface'
43 51 export default {
44 52 components: {},
45 53 props: [],
... ... @@ -48,23 +56,35 @@
48 56 loading: false,
49 57 visible: false,
50 58 isDetail: false,
  59 + mdfzOptions: [],
51 60 dataForm: {
52 61 id:'',
53 62 plmc:undefined,
54 63 sfmdfl:'0',
55 64 xh:undefined,
  65 + mdfzIds: [],
56 66 },
57 67 rules: {
58 68 },
59 69 }
60 70 },
61 71 computed: {},
62   - watch: {},
  72 + watch: {
  73 + 'dataForm.sfmdfl'(v) {
  74 + if (v !== '1') this.dataForm.mdfzIds = []
  75 + }
  76 + },
63 77 created() {
  78 + this.getMdfzOptions()
64 79 },
65 80 mounted() {
66 81 },
67 82 methods: {
  83 + getMdfzOptions() {
  84 + request({ url: '/api/Extend/WtMdfz/Actions/Selector', method: 'get' })
  85 + .then(res => { this.mdfzOptions = res.data || [] })
  86 + .catch(() => { this.mdfzOptions = [] })
  87 + },
68 88 goBack() {
69 89 this.$emit('refresh')
70 90 },
... ... @@ -78,9 +98,21 @@
78 98 request({
79 99 url: '/api/Extend/WtPl/' + this.dataForm.id,
80 100 method: 'get'
81   - }).then(res =>{
82   - this.dataForm = res.data;
  101 + }).then(res => {
  102 + const d = res.data || {}
  103 + this.dataForm = {
  104 + ...d,
  105 + mdfzIds: Array.isArray(d.mdfzIds) ? d.mdfzIds : []
  106 + }
83 107 })
  108 + } else {
  109 + this.dataForm = {
  110 + id: '',
  111 + plmc: undefined,
  112 + sfmdfl: '0',
  113 + xh: undefined,
  114 + mdfzIds: []
  115 + }
84 116 }
85 117 })
86 118 },
... ... @@ -126,3 +158,19 @@
126 158 }
127 159 }
128 160 </script>
  161 +<style scoped>
  162 +.wt-pl-mdfz-panel {
  163 + max-height: 200px;
  164 + overflow-y: auto;
  165 + padding: 8px 12px;
  166 + border: 1px solid #ebeef5;
  167 + border-radius: 4px;
  168 + background: #fafafa;
  169 +}
  170 +.wt-pl-form-tip {
  171 + margin: 6px 0 0;
  172 + font-size: 12px;
  173 + color: #909399;
  174 + line-height: 1.5;
  175 +}
  176 +</style>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPl/index.vue
1   -<template>
  1 +<template>
2 2 <div class="NCC-common-layout">
3 3 <div class="NCC-common-layout-center">
4 4 <el-row class="NCC-common-search-box" :gutter="16">
... ... @@ -35,7 +35,7 @@
35 35 <screenfull isContainer />
36 36 </div>
37 37 </div>
38   - <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange">
  38 + <NCC-table v-loading="listLoading" :data="list" has-c :hasNO="false" @selection-change="handleSelectionChange">
39 39 <el-table-column prop="xh" label="序号" align="center" width="80">
40 40 <template slot-scope="scope">
41 41 <span>{{ scope.row.xh === null || scope.row.xh === undefined ? '-' : scope.row.xh }}</span>
... ... @@ -50,6 +50,11 @@
50 50 <el-tag v-else type="info" size="mini">-</el-tag>
51 51 </template>
52 52 </el-table-column>
  53 + <el-table-column prop="mdfzNames" label="显示门店组" align="left" min-width="160" show-overflow-tooltip>
  54 + <template slot-scope="scope">
  55 + <span>{{ scope.row.mdfzNames != null && scope.row.mdfzNames !== '' ? scope.row.mdfzNames : '—' }}</span>
  56 + </template>
  57 + </el-table-column>
53 58 <el-table-column label="操作" fixed="right" width="100">
54 59 <template slot-scope="scope">
55 60 <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" >编辑</el-button>
... ... @@ -66,10 +71,8 @@
66 71 </template>
67 72 <script>
68 73 import request from '@/utils/request'
69   - import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
70 74 import NCCForm from './Form'
71 75 import ExportBox from './ExportBox'
72   - import { previewDataInterface } from '@/api/systemData/dataInterface'
73 76 export default {
74 77 components: { NCCForm, ExportBox },
75 78 data() {
... ... @@ -94,6 +97,7 @@
94 97 { prop: 'id', label: '分类编号' },
95 98 { prop: 'plmc', label: '品类名称' },
96 99 { prop: 'sfmdfl', label: '是否门店分类' },
  100 + { prop: 'mdfzNames', label: '显示门店组' },
97 101 ],
98 102 }
99 103 },
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtPp/index.vue
1   -<template>
  1 +<template>
2 2 <div class="NCC-common-layout">
3 3 <div class="NCC-common-layout-center">
4 4 <el-row class="NCC-common-search-box" :gutter="16">
... ... @@ -28,7 +28,7 @@
28 28 <screenfull isContainer />
29 29 </div>
30 30 </div>
31   - <NCC-table v-loading="listLoading" :data="list">
  31 + <NCC-table v-loading="listLoading" :data="list" :hasNO="false">
32 32 <el-table-column prop="xh" label="序号" align="center" width="80">
33 33 <template slot-scope="scope">
34 34 <span>{{ scope.row.xh === null || scope.row.xh === undefined ? '-' : scope.row.xh }}</span>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtShrysz/djmcOptions.js
... ... @@ -14,6 +14,7 @@ export const djmcOptions = [
14 14 { fullName: "委托代销结算单", id: "委托代销结算单" },
15 15 { fullName: "商品调价单", id: "商品调价单" },
16 16 { fullName: "收款单", id: "收款单" },
  17 + { fullName: "付款单", id: "付款单" },
17 18 // 费用单:财务单据 wt_cwdj.djlx,与「现金费用单」(wt_yskzjjs)无关
18 19 { fullName: "费用单", id: "费用单" },
19 20 { fullName: "现金费用单", id: "现金费用单" },
... ... @@ -22,6 +23,7 @@ export const djmcOptions = [
22 23 { fullName: "其他应收单", id: "其他应收单" },
23 24 { fullName: "转款单", id: "转款单" },
24 25 { fullName: "赠送单", id: "赠送单" },
  26 + { fullName: "获赠单", id: "获赠单" },
25 27 // 会员到期退款:界面称「会员到期收款」;wt_shrysz.djmc / wt_yskzjjs.djlx 须为「会员到期退款」
26 28 { fullName: "会员到期收款", id: "会员到期退款" },
27 29 { fullName: "拆装单", id: "拆装单" }
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/Form.vue
... ... @@ -7,14 +7,14 @@
7 7 <el-input v-model="dataForm.id" placeholder="系统自动生成" clearable readonly :style='{"width":"100%"}' />
8 8 </el-form-item>
9 9 </el-col>
10   - <el-col :span="12" v-if="dataForm.id && yddhHasValue">
11   - <el-form-item label="运单号" prop="yddh">
12   - <el-input v-model="dataForm.yddh" disabled :style='{"width":"100%"}' />
  10 + <el-col :span="12" v-if="dataForm.id">
  11 + <el-form-item label="抖音订单号" prop="dyddh">
  12 + <el-input :value="displayDouyinOrWaybillText(dataForm.dyddh)" disabled :style='{"width":"100%"}' />
13 13 </el-form-item>
14 14 </el-col>
15   - <el-col :span="12" v-if="dataForm.id && dyddhHasValue">
16   - <el-form-item label="抖音订单号" prop="dyddh">
17   - <el-input v-model="dataForm.dyddh" disabled :style='{"width":"100%"}' />
  15 + <el-col :span="12" v-if="dataForm.id">
  16 + <el-form-item label="物流运单号" prop="yddh">
  17 + <el-input :value="displayDouyinOrWaybillText(dataForm.yddh)" disabled :style='{"width":"100%"}' />
18 18 </el-form-item>
19 19 </el-col>
20 20 <el-col :span="12">
... ... @@ -51,7 +51,21 @@
51 51 </el-col>
52 52 <el-col :span="12">
53 53 <el-form-item label="会员手机号码" prop="hysjh">
54   - <el-input v-model="dataForm.hysjh" placeholder="请输入会员手机号码" clearable :style='{"width":"100%"}'></el-input>
  54 + <el-autocomplete
  55 + v-model="dataForm.hysjh"
  56 + :fetch-suggestions="queryMemberPhoneSuggestions"
  57 + placeholder="输入姓名/手机号搜索并选择后仅带出会员手机号;往来单位请单独选择"
  58 + clearable
  59 + :trigger-on-focus="false"
  60 + :debounce="300"
  61 + :style='{"width":"100%"}'
  62 + @select="onMemberFromSearchSelect"
  63 + >
  64 + <template slot-scope="{ item }">
  65 + <span>{{ item.xm || '-' }}</span>
  66 + <span style="color:#909399;margin-left:8px;">{{ item.sjh || '' }}</span>
  67 + </template>
  68 + </el-autocomplete>
55 69 </el-form-item>
56 70 </el-col>
57 71 <el-col :span="12" v-if="false">
... ... @@ -355,14 +369,6 @@
355 369 }
356 370 },
357 371 computed: {
358   - yddhHasValue() {
359   - const v = this.dataForm.yddh
360   - return v != null && String(v).trim() !== ''
361   - },
362   - dyddhHasValue() {
363   - const v = this.dataForm.dyddh
364   - return v != null && String(v).trim() !== ''
365   - },
366 372 totalKucun() {
367 373 return (this.dataForm.wtXsckdMxList || []).reduce((sum, row) => sum + (parseFloat(row.kucun) || 0), 0)
368 374 },
... ... @@ -395,6 +401,47 @@
395 401 mounted() {
396 402 },
397 403 methods: {
  404 + displayDouyinOrWaybillText(v) {
  405 + if (v == null || v === undefined || String(v).trim() === '') return '—'
  406 + return String(v)
  407 + },
  408 + /** 会员体系:按关键字搜 wt_hy,选择后只写入「会员手机号码」,不覆盖「往来单位」(kh 仍为 WtWldw 等独立维护) */
  409 + queryMemberPhoneSuggestions(queryString, cb) {
  410 + const q = (queryString || '').trim()
  411 + // 允许单字检索(如姓氏),与会员列表页行为一致
  412 + if (q.length < 1) {
  413 + cb([])
  414 + return
  415 + }
  416 + request({
  417 + url: '/api/Extend/WtHy',
  418 + method: 'get',
  419 + // 勿传 sidx: '':后端会用它做 OrderBy,空串会导致排序/SQL 异常,列表为空
  420 + data: { keyword: q, currentPage: 1, pageSize: 20, sort: 'desc', sidx: 'id' }
  421 + })
  422 + .then((res) => {
  423 + const list =
  424 + (res.data && res.data.list) ||
  425 + (res.data && res.data.data && res.data.data.list) ||
  426 + []
  427 + cb(
  428 + list.map((m) => ({
  429 + value: (m.sjh && String(m.sjh).trim()) || m.xm || m.id,
  430 + id: m.id,
  431 + xm: m.xm,
  432 + sjh: m.sjh
  433 + }))
  434 + )
  435 + })
  436 + .catch(() => {
  437 + cb([])
  438 + })
  439 + },
  440 + onMemberFromSearchSelect(item) {
  441 + if (!item) return
  442 + // 只同步会员在册手机号,不修改 dataForm.kh,避免「往来单位」被改成会员主键
  443 + this.dataForm.hysjh = (item.sjh && String(item.sjh).trim()) || this.dataForm.hysjh
  444 + },
398 445 /** ERP 新建/保存销售出库单时固定为「后台」,避免与收银台「门店」混淆 */
399 446 ensureBackendSalesSource() {
400 447 if (this.dataForm.djlx === '销售出库单') {
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/SerialNumberSelect.vue
... ... @@ -127,7 +127,9 @@ export default {
127 127 currentDocumentType: '',
128 128 productCodeDisplay: '' // 商品编码(用于界面显示,如 206466)
129 129 ,
130   - scanSerialNumber: ''
  130 + scanSerialNumber: '',
  131 + /** 为 true 时:扫码在库表中未找到仍允许加入(线下序列号/机身码等) */
  132 + allowManualSerialWhenMissing: false
131 133 }
132 134 },
133 135 computed: {
... ... @@ -141,12 +143,20 @@ export default {
141 143 },
142 144 methods: {
143 145 // 打开弹窗
144   - open(productCode, warehouse, selectedSerialNumbers = [], documentType = null, productCodeDisplay = null) {
  146 + open(
  147 + productCode,
  148 + warehouse,
  149 + selectedSerialNumbers = [],
  150 + documentType = null,
  151 + productCodeDisplay = null,
  152 + allowManualSerialWhenMissing = false
  153 + ) {
145 154 this.visible = true
146 155 this.currentProductCode = productCode
147 156 this.currentWarehouse = warehouse
148 157 this.currentDocumentType = documentType
149 158 this.productCodeDisplay = productCodeDisplay || ''
  159 + this.allowManualSerialWhenMissing = !!allowManualSerialWhenMissing
150 160 this.searchForm.productCode = productCode
151 161 this.searchForm.warehouse = warehouse
152 162 this.searchForm.serialNumber = '' // Reset serial number when opening
... ... @@ -166,6 +176,7 @@ export default {
166 176 this.selectedSerialNumbers = []
167 177 this.productCodeDisplay = ''
168 178 this.scanSerialNumber = ''
  179 + this.allowManualSerialWhenMissing = false
169 180 },
170 181  
171 182 // 获取仓库选项
... ... @@ -286,7 +297,16 @@ export default {
286 297 this.$nextTick(() => {
287 298 const ok = trySelectBySerial(sn)
288 299 if (!ok) {
289   - this.$message.warning(`未找到序列号:${sn}`)
  300 + if (this.allowManualSerialWhenMissing) {
  301 + if (!this.selectedSerialNumbers.includes(sn)) {
  302 + this.selectedSerialNumbers.push(sn)
  303 + }
  304 + this.$message.success(
  305 + `已添加序列号(未在库中匹配,按线下/手工录入):${sn}`
  306 + )
  307 + } else {
  308 + this.$message.warning(`未找到序列号:${sn}`)
  309 + }
290 310 }
291 311 this.scanSerialNumber = ''
292 312 })
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/detail-view.vue
... ... @@ -61,6 +61,18 @@
61 61 {{ formatLy(detail.ly) }}
62 62 </span>
63 63 </el-descriptions-item>
  64 + <el-descriptions-item label="抖音订单号">
  65 + <span class="cell-nowrap">
  66 + <i class="el-icon-s-order desc-icon desc-icon--info" />
  67 + {{ cellTextOrDash(resolvedDyddh) }}
  68 + </span>
  69 + </el-descriptions-item>
  70 + <el-descriptions-item label="物流运单号">
  71 + <span class="cell-nowrap">
  72 + <i class="el-icon-truck desc-icon desc-icon--primary" />
  73 + {{ cellTextOrDash(resolvedYddh) }}
  74 + </span>
  75 + </el-descriptions-item>
64 76 <el-descriptions-item label="出库仓库">
65 77 <span class="cell-nowrap">
66 78 <i class="el-icon-s-home desc-icon desc-icon--muted" />
... ... @@ -91,18 +103,6 @@
91 103 {{ cellText(detail.kh) }}
92 104 </span>
93 105 </el-descriptions-item>
94   - <el-descriptions-item v-if="detail.yddh" label="运单号">
95   - <span class="cell-nowrap">
96   - <i class="el-icon-truck desc-icon desc-icon--primary" />
97   - {{ cellText(detail.yddh) }}
98   - </span>
99   - </el-descriptions-item>
100   - <el-descriptions-item v-if="detail.dyddh" label="抖音订单号">
101   - <span class="cell-nowrap">
102   - <i class="el-icon-s-order desc-icon desc-icon--info" />
103   - {{ cellText(detail.dyddh) }}
104   - </span>
105   - </el-descriptions-item>
106 106 </el-descriptions>
107 107  
108 108 <div class="detail-section-head">
... ... @@ -121,6 +121,24 @@
121 121 :empty-text="''"
122 122 >
123 123 <el-table-column type="index" width="52" label="#" align="center" />
  124 + <el-table-column label="类型" width="80" align="center">
  125 + <template slot-scope="scope">
  126 + <el-tag
  127 + v-if="isGiftMxLine(scope.row)"
  128 + size="mini"
  129 + type="warning"
  130 + effect="plain"
  131 + class="mx-line-type-tag"
  132 + >赠送</el-tag>
  133 + <el-tag
  134 + v-else
  135 + size="mini"
  136 + type="success"
  137 + effect="plain"
  138 + class="mx-line-type-tag"
  139 + >购买</el-tag>
  140 + </template>
  141 + </el-table-column>
124 142 <el-table-column label="出库仓库" min-width="118" show-overflow-tooltip>
125 143 <template slot-scope="scope">
126 144 <span class="cell-nowrap mx-cell">
... ... @@ -168,15 +186,25 @@
168 186 </el-table-column>
169 187 <el-table-column label="序列号" min-width="168">
170 188 <template slot-scope="scope">
171   - <div v-if="scope.row.selectedSerialNumbers && scope.row.selectedSerialNumbers.length" class="sn-tags">
172   - <el-tag
173   - v-for="(sn, idx) in scope.row.selectedSerialNumbers"
174   - :key="idx"
175   - size="mini"
176   - type="warning"
177   - class="sn-tag"
178   - >{{ sn }}</el-tag>
179   - </div>
  189 + <template v-if="serialList(scope.row).length">
  190 + <div class="sn-tags">
  191 + <el-tag
  192 + v-for="(sn, idx) in serialPreview(scope.row)"
  193 + :key="idx"
  194 + size="mini"
  195 + type="warning"
  196 + class="sn-tag"
  197 + >{{ sn }}</el-tag>
  198 + <el-button
  199 + v-if="serialList(scope.row).length > snInlineMax"
  200 + type="text"
  201 + class="sn-more-btn"
  202 + @click="openSerialDialog(scope.row)"
  203 + >
  204 + 共 {{ serialList(scope.row).length }} 个,点击查看
  205 + </el-button>
  206 + </div>
  207 + </template>
180 208 <span v-else class="text-muted"></span>
181 209 </template>
182 210 </el-table-column>
... ... @@ -233,7 +261,7 @@
233 261 </span>
234 262 </el-descriptions-item>
235 263 <el-descriptions-item label="备注" :span="2">
236   - <span class="detail-bz">{{ cellText(detail.bz) }}</span>
  264 + <span class="detail-bz">{{ displayRemarkBz }}</span>
237 265 </el-descriptions-item>
238 266 <el-descriptions-item label="审批备注" :span="2">
239 267 <span class="detail-bz">{{ cellText(detail.spbz) }}</span>
... ... @@ -329,6 +357,27 @@
329 357 <span></span>
330 358 </div>
331 359 </div>
  360 +
  361 + <el-dialog
  362 + title="序列号列表"
  363 + :visible.sync="serialDialogVisible"
  364 + width="560px"
  365 + append-to-body
  366 + class="NCC-dialog xsckd-sn-list-dialog"
  367 + @closed="serialDialogList = []"
  368 + >
  369 + <div class="sn-dialog-hint">共 {{ serialDialogList.length }} 条</div>
  370 + <div class="sn-dialog-tags">
  371 + <el-tag
  372 + v-for="(sn, idx) in serialDialogList"
  373 + :key="idx"
  374 + size="small"
  375 + type="warning"
  376 + class="sn-dialog-tag"
  377 + >{{ sn }}</el-tag>
  378 + </div>
  379 + </el-dialog>
  380 +
332 381 <span v-if="$slots.footer" slot="footer" class="dialog-footer xsckd-detail-footer-slot">
333 382 <slot name="footer" />
334 383 </span>
... ... @@ -354,7 +403,12 @@ export default {
354 403 loading: false,
355 404 detail: null,
356 405 cjckOptions: [],
357   - skzhOptions: []
  406 + skzhOptions: [],
  407 + /** 明细格内最多直接展示的序列号个数,超出则仅预览前若干 + 点击查看(与采购退货单详情一致) */
  408 + snInlineMax: 8,
  409 + snPreviewCount: 4,
  410 + serialDialogVisible: false,
  411 + serialDialogList: []
358 412 }
359 413 },
360 414 computed: {
... ... @@ -471,9 +525,87 @@ export default {
471 525 const { list, rawFallback } = parseWtMxJsonArray(this.detail.fkmx)
472 526 if (list.length > 0) return ''
473 527 return rawFallback || ''
  528 + },
  529 + /** 抖音来源:备注中不再展示「抖音订单:…」类行(单号在基本信息「抖音订单号」与 dyddh 字段展示) */
  530 + displayRemarkBz() {
  531 + return this.stripDouyinOrderPrefixFromRemark(this.detail && this.detail.bz, this.detail && this.detail.ly)
  532 + },
  533 + /** 主表 dyddh;老数据可能只在备注「抖音订单:xxx」中 */
  534 + resolvedDyddh() {
  535 + const d = this.detail
  536 + if (!d) return ''
  537 + const raw = d.dyddh != null && String(d.dyddh).trim() !== '' ? String(d.dyddh).trim() : ''
  538 + if (raw) return raw
  539 + return this.parseDouyinOrderNoFromRemark(d.bz || '') || ''
  540 + },
  541 + resolvedYddh() {
  542 + const d = this.detail
  543 + if (!d) return ''
  544 + const v = d.yddh
  545 + return v != null && String(v).trim() !== '' ? String(v).trim() : ''
474 546 }
475 547 },
476 548 methods: {
  549 + serialList(row) {
  550 + if (!row) return []
  551 + const raw = row.selectedSerialNumbers != null
  552 + ? row.selectedSerialNumbers
  553 + : (row.serialNumbers != null ? row.serialNumbers : (row.serialNumber != null ? row.serialNumber : row.xlh))
  554 + if (Array.isArray(raw)) {
  555 + return raw.map(s => (s == null ? '' : String(s)).trim()).filter(Boolean)
  556 + }
  557 + if (raw == null || raw === '') return []
  558 + const text = String(raw).trim()
  559 + if (!text) return []
  560 + if ((text.startsWith('[') && text.endsWith(']')) || (text.startsWith('{') && text.endsWith('}'))) {
  561 + try {
  562 + const arr = JSON.parse(text)
  563 + if (Array.isArray(arr)) return arr.map(s => (s == null ? '' : String(s)).trim()).filter(Boolean)
  564 + } catch (e) {}
  565 + }
  566 + return text.split(/[\n,,;;\s]+/).map(s => s.trim()).filter(Boolean)
  567 + },
  568 + serialPreview(row) {
  569 + const all = this.serialList(row)
  570 + if (all.length <= this.snInlineMax) return all
  571 + return all.slice(0, this.snPreviewCount)
  572 + },
  573 + openSerialDialog(row) {
  574 + this.serialDialogList = this.serialList(row).slice()
  575 + this.serialDialogVisible = true
  576 + },
  577 + parseDouyinOrderNoFromRemark(bz) {
  578 + if (bz == null || bz === '') return ''
  579 + const m = String(bz).match(/抖音订单[::]\s*([^\r\n]+)/)
  580 + return m && m[1] ? m[1].trim() : ''
  581 + },
  582 + stripDouyinOrderPrefixFromRemark(bz, ly) {
  583 + if (bz == null || bz === '') return ''
  584 + const src = String(bz)
  585 + if (ly !== '抖音订单') return src
  586 + const lines = src.split(/\r?\n/)
  587 + const kept = lines.filter((line) => {
  588 + const t = line.trim()
  589 + if (!t) return false
  590 + if (/^抖音订单[::]/.test(t)) return false
  591 + return true
  592 + })
  593 + return kept.join('\n').trim()
  594 + },
  595 + isGiftMxLine(row) {
  596 + if (!row) return false
  597 + if (row.mdxx != null && String(row.mdxx).trim() === '赠品') return true
  598 + if (row.isGift === true) return true
  599 + // 历史/接口未落库 mdxx:抖音单常见「单价与金额均为 0」的赠品行
  600 + const d = this.detail
  601 + if (d && d.ly === '抖音订单') {
  602 + const dj = parseFloat(row.dj)
  603 + const je = parseFloat(row.je)
  604 + const sl = parseFloat(row.sl) || 0
  605 + if (sl > 0 && !isNaN(dj) && !isNaN(je) && dj === 0 && je === 0) return true
  606 + }
  607 + return false
  608 + },
477 609 labelFromOptions(value, options) {
478 610 if (value === null || value === undefined || value === '') return ''
479 611 const opts = options || []
... ... @@ -489,6 +621,10 @@ export default {
489 621 if (v === null || v === undefined || v === '') return ''
490 622 return v
491 623 },
  624 + cellTextOrDash(v) {
  625 + if (v === null || v === undefined || String(v).trim() === '') return '—'
  626 + return v
  627 + },
492 628 formatMoneyCol(v) {
493 629 if (v === null || v === undefined || v === '') return ''
494 630 const n = Number(v)
... ... @@ -542,6 +678,8 @@ export default {
542 678 return sums
543 679 },
544 680 handleClose() {
  681 + this.serialDialogVisible = false
  682 + this.serialDialogList = []
545 683 this.detail = null
546 684 this.$emit('close')
547 685 },
... ... @@ -628,6 +766,11 @@ export default {
628 766 color: #409eff;
629 767 }
630 768  
  769 +.mx-line-type-tag {
  770 + min-width: 44px;
  771 + text-align: center;
  772 +}
  773 +
631 774 .collect-stat-card {
632 775 height: 100px;
633 776 padding: 12px;
... ... @@ -754,6 +897,29 @@ export default {
754 897 .sn-tag {
755 898 margin: 0 !important;
756 899 }
  900 +.sn-more-btn {
  901 + padding: 0 4px !important;
  902 + margin-left: 2px;
  903 + vertical-align: middle;
  904 + font-size: 12px;
  905 +}
  906 +.sn-dialog-hint {
  907 + font-size: 12px;
  908 + color: #909399;
  909 + margin: -6px 0 10px;
  910 +}
  911 +.sn-dialog-tags {
  912 + max-height: min(420px, 60vh);
  913 + overflow-y: auto;
  914 + display: flex;
  915 + flex-wrap: wrap;
  916 + gap: 6px;
  917 + align-content: flex-start;
  918 + padding: 2px 0;
  919 +}
  920 +.sn-dialog-tag {
  921 + margin: 0 !important;
  922 +}
757 923  
758 924 .detail-bz {
759 925 white-space: pre-wrap;
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/index.vue
... ... @@ -20,6 +20,16 @@
20 20 </el-select>
21 21 </el-form-item>
22 22 </el-col>
  23 + <el-col :span="6">
  24 + <el-form-item label="抖音订单号">
  25 + <el-input v-model="query.dyddh" placeholder="抖音订单号(模糊)" clearable />
  26 + </el-form-item>
  27 + </el-col>
  28 + <el-col :span="6">
  29 + <el-form-item label="物流运单号">
  30 + <el-input v-model="query.yddh" placeholder="物流运单号(模糊)" clearable />
  31 + </el-form-item>
  32 + </el-col>
23 33 <template v-if="showAll">
24 34 <el-col :span="6">
25 35 <el-form-item label="经手人">
... ... @@ -111,6 +121,12 @@
111 121 >
112 122 <el-table-column prop="id" label="单据编号" align="left" min-width="150" show-overflow-tooltip />
113 123 <el-table-column prop="djrq" label="单据日期" align="left" min-width="110" :formatter="ncc.tableDateFormat" show-overflow-tooltip />
  124 + <el-table-column prop="dyddh" label="抖音订单号" align="left" min-width="160" show-overflow-tooltip>
  125 + <template slot-scope="scope">{{ displayText(scope.row.dyddh) || '—' }}</template>
  126 + </el-table-column>
  127 + <el-table-column prop="yddh" label="物流运单号" align="left" min-width="140" show-overflow-tooltip>
  128 + <template slot-scope="scope">{{ displayText(scope.row.yddh) || '—' }}</template>
  129 + </el-table-column>
114 130 <el-table-column label="出库仓库" prop="cjck" align="left" min-width="120" show-overflow-tooltip>
115 131 <template slot-scope="scope">{{ displayText(formatCjckDisplay(scope.row)) }}</template>
116 132 </el-table-column>
... ... @@ -148,13 +164,7 @@
148 164 <template slot-scope="scope">{{ displayText(scope.row.gzr) }}</template>
149 165 </el-table-column>
150 166 <el-table-column prop="bz" label="备注" align="left" min-width="120" show-overflow-tooltip>
151   - <template slot-scope="scope">{{ displayText(scope.row.bz) }}</template>
152   - </el-table-column>
153   - <el-table-column prop="dyddh" label="抖音订单号" align="left" min-width="160" show-overflow-tooltip>
154   - <template slot-scope="scope">{{ displayText(scope.row.dyddh) }}</template>
155   - </el-table-column>
156   - <el-table-column prop="yddh" label="物流运单号" align="left" min-width="140" show-overflow-tooltip>
157   - <template slot-scope="scope">{{ displayText(scope.row.yddh) }}</template>
  167 + <template slot-scope="scope">{{ displayText(formatListRemarkBz(scope.row)) }}</template>
158 168 </el-table-column>
159 169 <el-table-column label="摘要" align="left" min-width="200" show-overflow-tooltip>
160 170 <template slot-scope="scope">
... ... @@ -294,6 +304,24 @@
294 304 this.getskzhOptions();
295 305 },
296 306 methods: {
  307 + /** 与详情一致:抖音单备注列不显示「抖音订单:…」行(单号见 dyddh 列) */
  308 + formatListRemarkBz(row) {
  309 + if (!row) return ''
  310 + const bz = row.bz
  311 + if (bz == null || bz === '') return ''
  312 + const ly = row.ly != null && row.ly !== '' ? String(row.ly).trim() : ''
  313 + if (ly !== '抖音订单') return String(bz)
  314 + return String(bz)
  315 + .split(/\r?\n/)
  316 + .filter((line) => {
  317 + const t = line.trim()
  318 + if (!t) return false
  319 + if (/^抖音订单[::]/.test(t)) return false
  320 + return true
  321 + })
  322 + .join('\n')
  323 + .trim()
  324 + },
297 325 displayText(val) {
298 326 if (val == null || String(val).trim() === '') return ''
299 327 return String(val)
... ... @@ -540,11 +568,13 @@
540 568 method: 'post'
541 569 })
542 570 }).then((res) => {
543   - if (res && res.success) {
544   - this.$message.success(res.message || '反审成功')
  571 + // 与 request 拦截器一致:业务结果在 res.data(见 wtCgrkd/index 反审处理)
  572 + const d = (res && res.data) || {}
  573 + if (d.success) {
  574 + this.$message.success(d.message || '反审成功')
545 575 this.initData()
546 576 } else {
547   - this.$message.error((res && res.message) || '反审失败')
  577 + this.$message.error(d.message || (res && res.message) || '反审失败')
548 578 }
549 579 }).catch(() => {})
550 580 },
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsthd/detail-view.vue
... ... @@ -145,15 +145,25 @@
145 145 </el-table-column>
146 146 <el-table-column label="序列号" min-width="168">
147 147 <template slot-scope="scope">
148   - <div v-if="scope.row.selectedSerialNumbers && scope.row.selectedSerialNumbers.length" class="sn-tags">
149   - <el-tag
150   - v-for="(sn, idx) in scope.row.selectedSerialNumbers"
151   - :key="idx"
152   - size="mini"
153   - type="warning"
154   - class="sn-tag"
155   - >{{ sn }}</el-tag>
156   - </div>
  148 + <template v-if="serialList(scope.row).length">
  149 + <div class="sn-tags">
  150 + <el-tag
  151 + v-for="(sn, idx) in serialPreview(scope.row)"
  152 + :key="idx"
  153 + size="mini"
  154 + type="warning"
  155 + class="sn-tag"
  156 + >{{ sn }}</el-tag>
  157 + <el-button
  158 + v-if="serialList(scope.row).length > snInlineMax"
  159 + type="text"
  160 + class="sn-more-btn"
  161 + @click="openSerialDialog(scope.row)"
  162 + >
  163 + 共 {{ serialList(scope.row).length }} 个,点击查看
  164 + </el-button>
  165 + </div>
  166 + </template>
157 167 <span v-else class="text-muted"></span>
158 168 </template>
159 169 </el-table-column>
... ... @@ -198,6 +208,27 @@
198 208 <span></span>
199 209 </div>
200 210 </div>
  211 +
  212 + <el-dialog
  213 + title="序列号列表"
  214 + :visible.sync="serialDialogVisible"
  215 + width="560px"
  216 + append-to-body
  217 + class="NCC-dialog xsthd-sn-list-dialog"
  218 + @closed="serialDialogList = []"
  219 + >
  220 + <div class="sn-dialog-hint">共 {{ serialDialogList.length }} 条</div>
  221 + <div class="sn-dialog-tags">
  222 + <el-tag
  223 + v-for="(sn, idx) in serialDialogList"
  224 + :key="idx"
  225 + size="small"
  226 + type="warning"
  227 + class="sn-dialog-tag"
  228 + >{{ sn }}</el-tag>
  229 + </div>
  230 + </el-dialog>
  231 +
201 232 <span v-if="$slots.footer" slot="footer" class="dialog-footer xsthd-detail-footer-slot">
202 233 <slot name="footer" />
203 234 </span>
... ... @@ -266,6 +297,34 @@ export default {
266 297 }
267 298 },
268 299 methods: {
  300 + serialList(row) {
  301 + if (!row) return []
  302 + const raw = row.selectedSerialNumbers != null
  303 + ? row.selectedSerialNumbers
  304 + : (row.serialNumbers != null ? row.serialNumbers : (row.serialNumber != null ? row.serialNumber : row.xlh))
  305 + if (Array.isArray(raw)) {
  306 + return raw.map(s => (s == null ? '' : String(s)).trim()).filter(Boolean)
  307 + }
  308 + if (raw == null || raw === '') return []
  309 + const text = String(raw).trim()
  310 + if (!text) return []
  311 + if ((text.startsWith('[') && text.endsWith(']')) || (text.startsWith('{') && text.endsWith('}'))) {
  312 + try {
  313 + const arr = JSON.parse(text)
  314 + if (Array.isArray(arr)) return arr.map(s => (s == null ? '' : String(s)).trim()).filter(Boolean)
  315 + } catch (e) {}
  316 + }
  317 + return text.split(/[\n,,;;\s]+/).map(s => s.trim()).filter(Boolean)
  318 + },
  319 + serialPreview(row) {
  320 + const all = this.serialList(row)
  321 + if (all.length <= this.snInlineMax) return all
  322 + return all.slice(0, this.snPreviewCount)
  323 + },
  324 + openSerialDialog(row) {
  325 + this.serialDialogList = this.serialList(row).slice()
  326 + this.serialDialogVisible = true
  327 + },
269 328 openLinkedXsckd() {
270 329 const id = this.resolvedYcddhId
271 330 if (!id) return
... ... @@ -348,6 +407,8 @@ export default {
348 407 return sums
349 408 },
350 409 handleClose() {
  410 + this.serialDialogVisible = false
  411 + this.serialDialogList = []
351 412 this.detail = null
352 413 this.userNameLabels = {}
353 414 this.userNameResolveDone = false
... ... @@ -624,6 +685,29 @@ export default {
624 685 .sn-tag {
625 686 margin: 0 !important;
626 687 }
  688 +.sn-more-btn {
  689 + padding: 0 4px !important;
  690 + margin-left: 2px;
  691 + vertical-align: middle;
  692 + font-size: 12px;
  693 +}
  694 +.sn-dialog-hint {
  695 + font-size: 12px;
  696 + color: #909399;
  697 + margin: -6px 0 10px;
  698 +}
  699 +.sn-dialog-tags {
  700 + max-height: min(420px, 60vh);
  701 + overflow-y: auto;
  702 + display: flex;
  703 + flex-wrap: wrap;
  704 + gap: 6px;
  705 + align-content: flex-start;
  706 + padding: 2px 0;
  707 +}
  708 +.sn-dialog-tag {
  709 + margin: 0 !important;
  710 +}
627 711  
628 712 .detail-bz {
629 713 white-space: pre-wrap;
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtYskzjjs_xj/Form.vue
... ... @@ -100,6 +100,24 @@
100 100 import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
101 101 import { getAccountSelector } from '@/api/extend/wtAccount'
102 102 const FYXM_DICT_ID = '816913803978474757'
  103 +/** 新建/重置用,避免编辑后整单替换再点「新增」仍残留 wldw、skzh 等字段 */
  104 +function createEmptyCashForm() {
  105 + return {
  106 + id: undefined,
  107 + djrq: undefined,
  108 + jsr: undefined,
  109 + wtYskzjjsMxList: [],
  110 + fkzh: undefined,
  111 + fkje: undefined,
  112 + skzh: undefined,
  113 + skje: undefined,
  114 + djlx: '现金费用单',
  115 + wldw: undefined,
  116 + zy: undefined,
  117 + bz: undefined,
  118 + djzt: undefined
  119 + }
  120 +}
103 121 export default {
104 122 components: {},
105 123 props: [],
... ... @@ -109,17 +127,7 @@ const FYXM_DICT_ID = &#39;816913803978474757&#39;
109 127 visible: false,
110 128 isDetail: false,
111 129 pendingDjzt: '待审核',
112   - dataForm: {
113   - id: undefined,
114   - djrq: undefined,
115   - jsr: undefined,
116   - wtYskzjjsMxList: [],
117   - fkzh: undefined,
118   - fkje: undefined,
119   - skzh: undefined,
120   - skje: undefined,
121   - djlx: undefined,
122   - },
  130 + dataForm: createEmptyCashForm(),
123 131 rules: {
124 132 },
125 133 fyxmmcOptions: [],
... ... @@ -182,30 +190,32 @@ const FYXM_DICT_ID = &#39;816913803978474757&#39;
182 190 this.$emit('refresh')
183 191 },
184 192 init(id, isDetail) {
185   - this.dataForm.id = id || 0;
186   - this.visible = true;
  193 + this.visible = true;
187 194 this.isDetail = isDetail || false;
188 195 this.pendingDjzt = '待审核'
  196 + const billId = id && String(id) !== '0' ? id : null
189 197 this.$nextTick(() => {
190   - this.$refs['elForm'].resetFields();
191   - if (this.dataForm.id) {
  198 + if (this.$refs['elForm']) {
  199 + this.$refs['elForm'].resetFields();
  200 + }
  201 + if (billId) {
192 202 request({
193   - url: '/api/Extend/WtYskzjjs/' + this.dataForm.id,
  203 + url: '/api/Extend/WtYskzjjs/' + billId,
194 204 method: 'get'
195   - }).then(res =>{
  205 + }).then(res => {
196 206 this.dataForm = res.data;
197 207 if (!this.dataForm.wtYskzjjsMxList) {
198 208 this.$set(this.dataForm, 'wtYskzjjsMxList', []);
199 209 }
200 210 })
201 211 } else {
  212 + this.dataForm = createEmptyCashForm();
202 213 this.dataForm.djrq = Date.now();
203 214 if (this.$store && this.$store.getters && this.$store.getters.userInfo) {
204 215 this.dataForm.jsr = this.$store.getters.userInfo.userId || this.$store.getters.userInfo.id;
205 216 }
206 217 this.dataForm.djlx = '现金费用单';
207 218 this.dataForm.djzt = '草稿';
208   - this.dataForm.wtYskzjjsMxList = [];
209 219 }
210 220 })
211 221 },
... ...
Antis.Erp.Plat/douyin/frontend/src/utils/config.ts
... ... @@ -42,3 +42,38 @@ export const getErpBaseUrl = (): string =&gt; {
42 42 }
43 43 return getBackendBaseUrl().replace(/\/$/, '')
44 44 }
  45 +
  46 +/** 与后台上传/文件服务同源,优先 VITE_API_BASE_URL 去 /api 后的根 */
  47 +export const getErpAssetBaseUrl = (): string => {
  48 + const env = import.meta.env.VITE_API_BASE_URL
  49 + if (env != null && String(env).trim() !== '') {
  50 + const s = String(env).trim().replace(/\/+$/, '')
  51 + const noApi = s.replace(/\/api\/?$/i, '')
  52 + if (noApi) return noApi
  53 + }
  54 + return getErpBaseUrl()
  55 +}
  56 +
  57 +/**
  58 + * 商品主图:相对路径补全;绝对地址若为 localhost/开发机落库,替换为当前部署的 ERP 根(避免线上仍请求 localhost:2011)
  59 + */
  60 +export const resolveProductPicUrl = (pic: string | undefined | null): string => {
  61 + const s0 = (pic ?? '').trim()
  62 + if (!s0) return ''
  63 + if (/^https?:\/\//i.test(s0)) {
  64 + try {
  65 + const u = new URL(s0)
  66 + if (/^(localhost|127\.0\.0\.1|0\.0\.0\.0)$/i.test(u.hostname)) {
  67 + const base = getErpAssetBaseUrl().replace(/\/$/, '')
  68 + return `${base}${u.pathname || ''}${u.search || ''}${u.hash || ''}`
  69 + }
  70 + } catch {
  71 + return s0
  72 + }
  73 + return s0
  74 + }
  75 + if (s0.startsWith('//')) return `https:${s0}`
  76 + const base = getErpAssetBaseUrl().replace(/\/$/, '')
  77 + if (s0.startsWith('/')) return `${base}${s0}`
  78 + return s0
  79 +}
... ...
Antis.Erp.Plat/douyin/frontend/src/views/CreateWaybillView.vue
... ... @@ -150,22 +150,17 @@
150 150 :key="index"
151 151 class="order-product-item"
152 152 >
153   - <!-- 商品明细:显示抖音图,没有则显示"暂无图片" -->
154 153 <el-image
155   - v-if="item.douyin_pic || item.product_pic"
156   - :src="item.douyin_pic || item.product_pic"
  154 + v-if="orderProductThumb(item)"
  155 + :src="orderProductThumb(item)"
157 156 class="product-thumb"
158 157 fit="cover"
159   - :preview-src-list="[item.douyin_pic || item.product_pic]"
  158 + :preview-src-list="[orderProductThumb(item)]"
160 159 preview-teleported
161 160 />
162 161 <div class="product-thumb-placeholder" v-else>暂无图片</div>
163 162 <div class="product-info">
164   - <div class="product-name">{{ item.product_name }}</div>
165   - <div v-if="getSkuCode(item)" class="product-sku">
166   - <span class="sku-label">SKUID:</span>{{ getSkuCode(item) }}
167   - <span v-if="formatSpec(item.spec)" class="sku-spec">({{ formatSpec(item.spec) }})</span>
168   - </div>
  163 + <div class="product-name product-name--erp-only">{{ item.erp_spmc || item.product_name || '—' }}</div>
169 164 <div class="product-meta">
170 165 <span>×{{ item.item_num }}</span>
171 166 <span>¥{{ Number(item.goods_price || 0).toFixed(2) }}</span>
... ... @@ -262,18 +257,22 @@
262 257 <template #default="{ row }">
263 258 <!-- 商品清单:显示 ERP 图,没有则显示"暂无图片" -->
264 259 <el-image
265   - v-if="row.erp_pic"
266   - :src="row.erp_pic"
  260 + v-if="resolveProductPicUrl(row.erp_pic)"
  261 + :src="resolveProductPicUrl(row.erp_pic)"
267 262 style="width: 50px; height: 50px; border-radius: 4px;"
268 263 fit="cover"
269   - :preview-src-list="[row.erp_pic]"
  264 + :preview-src-list="[resolveProductPicUrl(row.erp_pic)]"
270 265 preview-teleported
271 266 lazy
272 267 />
273 268 <div v-else class="cell-no-image">暂无图片</div>
274 269 </template>
275 270 </el-table-column>
276   - <el-table-column prop="product_name" label="商品名称" min-width="180" show-overflow-tooltip />
  271 + <el-table-column label="商品名称" min-width="180" show-overflow-tooltip>
  272 + <template #default="{ row }">
  273 + <span class="product-name-erp-only">{{ row.erp_spmc || row.product_name || '—' }}</span>
  274 + </template>
  275 + </el-table-column>
277 276 <el-table-column label="抖音ID" width="150">
278 277 <template #default="{ row }">
279 278 <div v-if="getSkuCode(row)" style="color: #409eff;">
... ... @@ -527,7 +526,7 @@ import { useRoute, useRouter } from &#39;vue-router&#39;
527 526 import { ElMessage, ElMessageBox } from 'element-plus'
528 527 import { Refresh, InfoFilled } from '@element-plus/icons-vue'
529 528 import { getOrderDetail, getMergedOrderDetail, searchProducts as searchProductsAPI, manualCreateWaybill, getProductInfo as getProductInfoAPI, getWarehouses, getProductCategories, updateSellerRemark, getDefaults, getCustomers, getPaymentAccounts, getUsers, getShopSettings, checkStock } from '@/api/order'
530   -import { getErpBaseUrl } from '@/utils/config'
  529 +import { getErpAssetBaseUrl, resolveProductPicUrl } from '@/utils/config'
531 530 import SerialNumberSelect from '@/components/SerialNumberSelect.vue'
532 531  
533 532 const route = useRoute()
... ... @@ -546,15 +545,9 @@ const currentSerialRow = ref&lt;any | null&gt;(null)
546 545 // 商品信息缓存(用于存储序列号类型)
547 546 const productCache = new Map<string, any>()
548 547  
549   -/** 商品主图:补全 ERP 相对路径,避免 el-image 请求失败 */
550   -const resolveProductPicUrl = (pic: string | undefined | null): string => {
551   - const s = (pic || '').trim()
552   - if (!s) return ''
553   - if (/^https?:\/\//i.test(s)) return s
554   - if (s.startsWith('//')) return `https:${s}`
555   - const base = getErpBaseUrl().replace(/\/$/, '')
556   - if (s.startsWith('/')) return `${base}${s}`
557   - return s
  548 +const orderProductThumb = (item: any): string => {
  549 + if (!item) return ''
  550 + return resolveProductPicUrl(item.erp_pic || item.product_pic || item.douyin_pic)
558 551 }
559 552  
560 553 /** 金额展示:分转元,无数据时显示「无」 */
... ... @@ -991,7 +984,7 @@ const addProduct = async (product: any) =&gt; {
991 984 const images = typeof product.spzt === 'string' ? JSON.parse(product.spzt) : product.spzt
992 985 if (Array.isArray(images) && images.length > 0 && images[0].url) {
993 986 const url = images[0].url
994   - productPic = url.startsWith('http') ? url : `${getErpBaseUrl()}${url.startsWith('/') ? '' : '/'}${url}`
  987 + productPic = url.startsWith('http') ? url : `${getErpAssetBaseUrl()}${url.startsWith('/') ? '' : '/'}${url}`
995 988 }
996 989 } catch (e) {
997 990 console.warn('解析商品主图失败:', e)
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.DouyinLogistics/Services/OrderService.cs
... ... @@ -831,8 +831,12 @@ public class OrderService
831 831  
832 832 if (!string.IsNullOrWhiteSpace(skuName))
833 833 {
834   - // SKU 名称在商品明细 JSON(ProductItems)中,按关键词模糊筛选。
835   - query = query.Where(o => o.ProductItems != null && o.ProductItems.Contains(skuName));
  834 + // 仅匹配 ProductItems 数组里每条商品的 SKU 标识字段,不在整段 JSON / product_name 上 Contains。
  835 + // 用 JSON_EXTRACT 固定下标展开(避免依赖 MySQL 8 的 JSON_TABLE,兼容 5.7;单行最多取前若干条子商品)。
  836 + var skuLike = "%" + EscapeMySqlLikePattern(skuName.Trim()) + "%";
  837 + query = query.Where(
  838 + "ProductItems IS NOT NULL AND JSON_VALID(ProductItems) AND (" + SkuNameFilterJsonExtractOrSql + ")",
  839 + new { skuLike });
836 840 }
837 841  
838 842 // hasWaybill 在「同人同址合并」之后处理:否则只有子单有 waybills/运单号时,SQL 会整组拆散,列表里筛不出已建运单的合并单
... ... @@ -917,6 +921,52 @@ public class OrderService
917 921 }
918 922  
919 923 /// <summary>
  924 + /// 转义 MySQL LIKE 模式中的 \、%、_,配合 LIKE ... ESCAPE '\\' 使用。
  925 + /// </summary>
  926 + private static string EscapeMySqlLikePattern(string s)
  927 + {
  928 + return s
  929 + .Replace("\\", "\\\\", StringComparison.Ordinal)
  930 + .Replace("%", "\\%", StringComparison.Ordinal)
  931 + .Replace("_", "\\_", StringComparison.Ordinal);
  932 + }
  933 +
  934 + /// <summary>抖音单条订单 ProductItems 数组中参与 SKU 关键词筛选的最大行数(0-based 下标 0..Max-1)。</summary>
  935 + private const int SkuNameFilterMaxProductItemRows = 50;
  936 +
  937 + private static readonly string SkuNameFilterJsonExtractOrSql = BuildSkuNameFilterJsonExtractOrSql();
  938 +
  939 + private static string BuildSkuNameFilterJsonExtractOrSql()
  940 + {
  941 + var sb = new StringBuilder();
  942 + for (var i = 0; i < SkuNameFilterMaxProductItemRows; i++)
  943 + {
  944 + if (i > 0) sb.Append(" OR ");
  945 + sb.Append('(');
  946 + AppendJsonSkuFieldLike(sb, i, "sku_id");
  947 + sb.Append(" OR ");
  948 + AppendJsonSkuFieldLike(sb, i, "out_sku_id");
  949 + sb.Append(" OR ");
  950 + AppendJsonSkuFieldLike(sb, i, "skuid");
  951 + sb.Append(" OR ");
  952 + AppendJsonSkuFieldLike(sb, i, "outer_sku_id");
  953 + sb.Append(')');
  954 + }
  955 +
  956 + return sb.ToString();
  957 + }
  958 +
  959 + private static void AppendJsonSkuFieldLike(StringBuilder sb, int arrayIndex, string fieldName)
  960 + {
  961 + // CAST 兼容 JSON 中为字符串或数字
  962 + sb.Append("IFNULL(CAST(JSON_EXTRACT(ProductItems, '$[");
  963 + sb.Append(arrayIndex);
  964 + sb.Append("].");
  965 + sb.Append(fieldName);
  966 + sb.Append("') AS CHAR(512)),'') LIKE @skuLike ESCAPE '\\\\'");
  967 + }
  968 +
  969 + /// <summary>
920 970 /// 构造订单列表缓存 Key:包含所有影响"候选结果集"的过滤条件和版本号。
921 971 /// 不包含 sortBy/sortOrder/pageIndex/pageSize,这些在内存里处理。
922 972 /// </summary>
... ... @@ -2065,7 +2115,7 @@ public class OrderService
2065 2115  
2066 2116 // 5. 通过SKU ID关联商品档案,获取商品编码
2067 2117 var missingProducts = new List<string>();
2068   - var resolvedItems = new List<(string spbh, string sptm, string spmc, string ckck, int sl, decimal dj, decimal je, List<string> selectedSerials)>();
  2118 + var resolvedItems = new List<(string spbh, string sptm, string spmc, string ckck, int sl, decimal dj, decimal je, List<string> selectedSerials, bool isGift)>();
2069 2119  
2070 2120 foreach (var item in productItems)
2071 2121 {
... ... @@ -2122,7 +2172,11 @@ public class OrderService
2122 2172 }
2123 2173  
2124 2174 var selectedSerials = ParseSelectedSerialNumbersFromWaybillItem(item);
2125   -
  2175 + // 抖音常出现「行单价/实付为 0」的赠品/活动赠品行,但 payload 可能不带 isGift,仅 goods_price=0
  2176 + var isGift = item["isGift"]?.ToObject<bool>() == true
  2177 + || string.Equals(item["isGift"]?.ToString(), "true", StringComparison.OrdinalIgnoreCase)
  2178 + || goodsPrice == 0L;
  2179 +
2126 2180 resolvedItems.Add((
2127 2181 detailSpbh,
2128 2182 productInfo.Spbm ?? "",
... ... @@ -2131,7 +2185,8 @@ public class OrderService
2131 2185 itemNum > 0 ? itemNum : 1,
2132 2186 goodsPrice > 0 ? goodsPrice / 100.0m : 0m,
2133 2187 (goodsPrice * itemNum) / 100.0m,
2134   - selectedSerials
  2188 + selectedSerials,
  2189 + isGift
2135 2190 ));
2136 2191 }
2137 2192  
... ... @@ -2271,7 +2326,7 @@ public class OrderService
2271 2326 dj = r.dj,
2272 2327 je = r.je,
2273 2328 djlx = finalDjlx,
2274   - mdxx = "",
  2329 + mdxx = r.isGift ? "赠品" : "",
2275 2330 selectedSerialNumbers = r.selectedSerials
2276 2331 }).ToList();
2277 2332  
... ... @@ -2432,7 +2487,8 @@ public class OrderService
2432 2487 zdr = "",
2433 2488 shr = "",
2434 2489 gzr = "",
2435   - bz = $"抖音订单:{finalDouyinOrderIds}",
  2490 + // 抖音单号与运单已写入 dyddh / yddh,主表备注不再写「抖音订单:…」避免与详情/列表展示重复
  2491 + bz = "",
2436 2492 djlx = finalDjlx,
2437 2493 kh = defaultKh,
2438 2494 gys = "",
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPlCrInput.cs
1   -using System;
  1 +using System;
2 2 using System.Collections.Generic;
3 3  
4 4 namespace NCC.Extend.Entitys.Dto.WtPl
... ... @@ -27,6 +27,10 @@ namespace NCC.Extend.Entitys.Dto.WtPl
27 27 /// 序号 - 自定义排序(升序),留空按默认排序
28 28 /// </summary>
29 29 public int? xh { get; set; }
30   -
  30 +
  31 + /// <summary>
  32 + /// 展示门店分组(<c>wt_mdfz.F_Id</c> 多选;与商品套装可售范围一致。空=全部门店组均展示)
  33 + /// </summary>
  34 + public List<string> mdfzIds { get; set; }
31 35 }
32 36 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPlInfoOutput.cs
1   -using System;
  1 +using System;
2 2 using System.Collections.Generic;
3 3  
4 4 namespace NCC.Extend.Entitys.Dto.WtPl
... ... @@ -27,6 +27,10 @@ namespace NCC.Extend.Entitys.Dto.WtPl
27 27 /// 序号 - 自定义排序(升序)
28 28 /// </summary>
29 29 public int? xh { get; set; }
30   -
  30 +
  31 + /// <summary>
  32 + /// 展示门店分组
  33 + /// </summary>
  34 + public List<string> mdfzIds { get; set; }
31 35 }
32 36 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtPlListOutput.cs
1   -using System;
  1 +using System;
2 2  
3 3 namespace NCC.Extend.Entitys.Dto.WtPl
4 4 {
... ... @@ -26,6 +26,10 @@ namespace NCC.Extend.Entitys.Dto.WtPl
26 26 /// 序号 - 自定义排序(升序)
27 27 /// </summary>
28 28 public int? xh { get; set; }
29   -
  29 +
  30 + /// <summary>
  31 + /// 展示门店分组名称(多选、顿号拼接)
  32 + /// </summary>
  33 + public string mdfzNames { get; set; }
30 34 }
31 35 }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdInfoOutput.cs
... ... @@ -157,6 +157,16 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
157 157 /// 单据来源
158 158 /// </summary>
159 159 public string ly { get; set; }
  160 +
  161 + /// <summary>
  162 + /// 物流运单号(与主表 wt_xsckd.yddh 一致)
  163 + /// </summary>
  164 + public string yddh { get; set; }
  165 +
  166 + /// <summary>
  167 + /// 抖音订单号(与主表 wt_xsckd.dyddh 一致)
  168 + /// </summary>
  169 + public string dyddh { get; set; }
160 170  
161 171 /// <summary>
162 172 /// 收款明细
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/WtXsckdListQueryInput.cs
... ... @@ -98,6 +98,11 @@ namespace NCC.Extend.Entitys.Dto.WtXsckd
98 98 /// 抖音订单号(模糊)
99 99 /// </summary>
100 100 public string dyddh { get; set; }
  101 +
  102 + /// <summary>
  103 + /// 物流运单号(模糊,主表 yddh)
  104 + /// </summary>
  105 + public string yddh { get; set; }
101 106  
102 107 /// <summary>
103 108 /// 供应商
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtPlEntity.cs
1   -using NCC.Common.Const;
  1 +using NCC.Common.Const;
2 2 using SqlSugar;
3 3 using System;
4 4  
... ... @@ -34,7 +34,11 @@ namespace NCC.Extend.Entitys
34 34 /// </summary>
35 35 [SugarColumn(ColumnName = "F_Xh", IsNullable = true)]
36 36 public int? Xh { get; set; }
37   -
38   -
  37 +
  38 + /// <summary>
  39 + /// 门店端展示所属门店分组(<c>wt_mdfz.F_Id</c> 的 JSON 数组,与可售范围口径一致;为空表示不限制即全部分组可见)
  40 + /// </summary>
  41 + [SugarColumn(ColumnName = "F_MdfzIds", IsNullable = true, ColumnDataType = "text")]
  42 + public string MdfzIds { get; set; }
39 43 }
40 44 }
41 45 \ No newline at end of file
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/WtXsckdEntity.cs
... ... @@ -132,7 +132,7 @@ namespace NCC.Extend.Entitys
132 132 public string Spbz { get; set; }
133 133  
134 134 /// <summary>
135   - /// 原销售出库单编号(退货类单据:对应原 CHD 单号;可与备注「原订单号:」同步)
  135 + /// 原销售出库单编号(退货类单据:对应原销售出库单 CKD… 单号,历史可能为 CHD…;可与备注「原订单号:」同步)
136 136 /// </summary>
137 137 [SugarColumn(ColumnName = "ycddh", IsNullable = true, Length = 64)]
138 138 public string Ycddh { get; set; }
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/BillSummary/WtBillSummaryService.cs
... ... @@ -442,7 +442,7 @@ namespace NCC.Extend.BillSummary
442 442 }
443 443  
444 444 /// <summary>
445   - /// 按 wt_yskzjjs 主键拼装摘要(自然语句 + 句末经手人;转款单对齐「转款到【账户】等共金额元:经手人」
  445 + /// 按 wt_yskzjjs 主键拼装摘要(自然语句 + 句末经手人;转款单对齐「转款到【账户】等共金额元:经手人」;qt 线「其他收入单」主表为其他应收单时按明细展示为「其他收入单」
446 446 /// </summary>
447 447 /// <param name="billId">应收款增加减少 F_Id</param>
448 448 public async Task<string> ComputeWtYskzjjsZyByBillIdAsync(string billId)
... ... @@ -583,6 +583,16 @@ namespace NCC.Extend.BillSummary
583 583 amtPart = $"金额{WtBillSummaryFormat.Money(0)}元";
584 584  
585 585 var lxShow = string.IsNullOrEmpty(lx) ? "应收款增减" : lx;
  586 + // qt 落库主表 djlx 为「其他应收单」、明细 mxlx 为「其他收入单」时,摘要对外统一称「其他收入单」
  587 + if (string.Equals(lx, "其他应收单", StringComparison.Ordinal))
  588 + {
  589 + var mxLxs = await _db.Queryable<WtYskzjjsMxEntity>()
  590 + .Where(m => m.Djbh == id)
  591 + .Select(m => m.Mxlx)
  592 + .ToListAsync();
  593 + if (mxLxs.Any(s => !string.IsNullOrWhiteSpace(s) && s.Trim() == "其他收入单"))
  594 + lxShow = "其他收入单";
  595 + }
586 596 var body = $"{WtBillSummaryFormat.Bracket(wldwDisp)}{lxShow}{amtPart}";
587 597 if (!string.IsNullOrWhiteSpace(e.Hysjh))
588 598 body += $" 手机{WtBillSummaryFormat.Bracket(e.Hysjh.Trim())}";
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtCwdjService.cs
... ... @@ -24,6 +24,7 @@ using NCC.DataEncryption;
24 24 using NCC.ClayObject;
25 25 using NCC.Extend.WtDjzy;
26 26 using NCC.Extend.BillSummary;
  27 +using NCC.System.Entitys.Permission;
27 28  
28 29 namespace NCC.Extend.WtCwdj
29 30 {
... ... @@ -73,6 +74,7 @@ namespace NCC.Extend.WtCwdj
73 74 var wtCwdjmxList = await _db.Queryable<WtCwdjmxEntity>().Where(w => w.Djbh == entity.Id).ToListAsync();
74 75 output.wtCwdjmxList = wtCwdjmxList.Adapt<List<WtCwdjmxInfoOutput>>();
75 76 output.billZy = await _billSummaryService.ComputeWtCwdjZyByBillIdAsync(entity.Id);
  77 + output.jsr = await ResolveCwdjJsrIdToRealNameForDisplayAsync(entity.Jsr) ?? output.jsr;
76 78 return output;
77 79 }
78 80  
... ... @@ -113,6 +115,7 @@ namespace NCC.Extend.WtCwdj
113 115 }).MergeTable().OrderBy(sidx+" "+input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
114 116 var cwdjPageRows = data.list?.ToList() ?? new List<WtCwdjListOutput>();
115 117 await _billSummaryService.EnrichWtCwdjListBillZyAsync(cwdjPageRows);
  118 + await EnrichWtCwdjListJsrRealNameAsync(cwdjPageRows);
116 119 return PageResult<WtCwdjListOutput>.SqlSugarPageResult(data);
117 120 }
118 121  
... ... @@ -165,8 +168,11 @@ namespace NCC.Extend.WtCwdj
165 168 entity.Djzt = "待审核";
166 169  
167 170 if (IsReceiptOrPaymentBill(resolvedDjlx))
168   - entity.Jsr = await ResolveJsrAccountNameFromMxInputAsync(input.wtCwdjmxList);
169   -
  171 + {
  172 + // 经手人=当前操作用户,不从明细账户带出(账户仅在明细/摘要中体现)
  173 + entity.Jsr = (userInfo != null && !string.IsNullOrWhiteSpace(userInfo.userId)) ? userInfo.userId.Trim() : entity.Jsr;
  174 + }
  175 +
170 176 // 生成每日递增单号,前缀根据djlx判断
171 177 var today = DateTime.Now.ToString("yyyyMMdd");
172 178 string prefix = "CW"; // 默认前缀
... ... @@ -322,6 +328,7 @@ namespace NCC.Extend.WtCwdj
322 328 shzt = it.Djzt,
323 329 }).MergeTable().OrderBy(sidx+" "+input.sort).ToListAsync();
324 330 await _billSummaryService.EnrichWtCwdjListBillZyAsync(data);
  331 + await EnrichWtCwdjListJsrRealNameAsync(data);
325 332 return data;
326 333 }
327 334  
... ... @@ -415,6 +422,7 @@ namespace NCC.Extend.WtCwdj
415 422 [HttpPut("{id}")]
416 423 public async Task Update(string id, [FromBody] WtCwdjUpInput input)
417 424 {
  425 + var userInfo = await _userManager.GetUserInfo();
418 426 var existingHeader = await _db.Queryable<WtCwdjEntity>().FirstAsync(p => p.Id == id);
419 427 _ = existingHeader ?? throw NCCException.Oh(ErrorCode.COM1005);
420 428 ThrowIfExpenseBillCannotEdit(existingHeader);
... ... @@ -448,7 +456,12 @@ namespace NCC.Extend.WtCwdj
448 456 entity.Fsje = input.wtCwdjmxList.Sum(x => x.ybje);
449 457 if (entity.Fsje <= 0) throw NCCException.Bah("发生金额必须大于0");
450 458 if (IsReceiptOrPaymentBill(djlxForMx))
451   - entity.Jsr = await ResolveJsrAccountNameFromMxInputAsync(input.wtCwdjmxList);
  459 + {
  460 + // 经手人保持首次保存的录单用户,不因改账户而变成账号名
  461 + entity.Jsr = string.IsNullOrWhiteSpace(existingHeader.Jsr)
  462 + ? ((userInfo != null && !string.IsNullOrWhiteSpace(userInfo.userId)) ? userInfo.userId.Trim() : entity.Jsr)
  463 + : existingHeader.Jsr;
  464 + }
452 465  
453 466 //更新财务单据记录
454 467 await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
... ... @@ -639,19 +652,59 @@ namespace NCC.Extend.WtCwdj
639 652 private static bool IsReceiptOrPaymentBill(string djlx) =>
640 653 IsReceiptBill(djlx) || IsPaymentBill(djlx);
641 654  
642   - /// <summary>经手人存所选明细首行账号的账户名称(<c>WtAccount.AccountName</c>)。</summary>
643   - private async Task<string> ResolveJsrAccountNameFromMxInputAsync(List<WtCwdjmxCrInput> mxList)
  655 + /// <summary>与 Create/Update 中明细 zhmc 拼接规则一致。</summary>
  656 + private static string BuildCwdjAccountDisplayName(WtAccountEntity acc)
644 657 {
645   - if (mxList == null || mxList.Count == 0) return null;
646   - var firstZhbh = mxList.Select(x => x?.zhbh).FirstOrDefault(z => !string.IsNullOrWhiteSpace(z));
647   - if (string.IsNullOrWhiteSpace(firstZhbh)) return null;
648   - var tid = firstZhbh.Trim();
649   - var accList = await _db.Queryable<WtAccountEntity>().Where(a => a.Id == tid).Take(1).ToListAsync();
650   - if (accList.Count == 0) throw NCCException.Bah("明细账号不存在,请重新选择");
651   - var acc = accList[0];
  658 + if (acc == null) return null;
652 659 var name = (acc.AccountName ?? string.Empty).Trim();
  660 + var code = (acc.AccountCode ?? string.Empty).Trim();
  661 + if (!string.IsNullOrEmpty(code) && !string.IsNullOrEmpty(name)) return $"{code} {name}";
653 662 if (!string.IsNullOrEmpty(name)) return name;
654   - return (acc.AccountCode ?? string.Empty).Trim();
  663 + return code;
  664 + }
  665 +
  666 + /// <summary>主表 <c>F_Jsr</c> 存用户 Id 时,详情/列表显示为 <c>BASE_USER.F_Realname</c>;无法解析时回退为原值(兼容历史将账户文案写入经手人的数据)。</summary>
  667 + private async Task<string> ResolveCwdjJsrIdToRealNameForDisplayAsync(string jsrRaw)
  668 + {
  669 + if (string.IsNullOrWhiteSpace(jsrRaw)) return null;
  670 + var id = jsrRaw.Trim();
  671 + var names = await _db.Queryable<UserEntity>()
  672 + .Where(u => u.Id == id)
  673 + .Select(u => u.RealName)
  674 + .Take(1)
  675 + .ToListAsync();
  676 + if (names.Count == 0 || string.IsNullOrWhiteSpace(names[0])) return null;
  677 + return names[0].Trim();
  678 + }
  679 +
  680 + /// <summary>列表/导出:经手人由用户 Id 解析为真实姓名。</summary>
  681 + private async Task EnrichWtCwdjListJsrRealNameAsync(List<WtCwdjListOutput> rows)
  682 + {
  683 + if (rows == null || rows.Count == 0) return;
  684 + var ids = rows
  685 + .Select(r => (r.jsr ?? string.Empty).Trim())
  686 + .Where(s => !string.IsNullOrEmpty(s))
  687 + .Distinct()
  688 + .ToList();
  689 + if (ids.Count == 0) return;
  690 + var mapRows = await _db.Queryable<UserEntity>()
  691 + .In(u => u.Id, ids)
  692 + .Select(u => new { u.Id, u.RealName })
  693 + .ToListAsync();
  694 + var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  695 + foreach (var u in mapRows)
  696 + {
  697 + if (string.IsNullOrEmpty(u.Id) || string.IsNullOrWhiteSpace(u.RealName)) continue;
  698 + map[u.Id.Trim()] = u.RealName.Trim();
  699 + }
  700 + foreach (var row in rows)
  701 + {
  702 + if (row == null) continue;
  703 + var key = (row.jsr ?? string.Empty).Trim();
  704 + if (string.IsNullOrEmpty(key)) continue;
  705 + if (map.TryGetValue(key, out var real) && !string.IsNullOrEmpty(real))
  706 + row.jsr = real;
  707 + }
655 708 }
656 709  
657 710 /// <summary>
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtFysrdService.cs
1   -using NCC.Common.Core.Manager;
  1 +using NCC.Common.Core.Manager;
2 2 using NCC.Common.Enum;
3 3 using NCC.Common.Extension;
4 4 using NCC.Common.Filter;
... ... @@ -22,6 +22,7 @@ using NCC.Common.Model.NPOI;
22 22 using NCC.Common.Configuration;
23 23 using NCC.DataEncryption;
24 24 using NCC.ClayObject;
  25 +using NCC.System.Entitys.Permission;
25 26  
26 27 namespace NCC.Extend.WtFysrd
27 28 {
... ... @@ -75,6 +76,60 @@ namespace NCC.Extend.WtFysrd
75 76 WtFysrdWorkflowHelper.ApplyCounterpartyToEntity(entity, input.wldw, input.fkdw, input.kh, input.gys);
76 77 }
77 78  
  79 + /// <summary>
  80 + /// 单号:前缀+yyyyMMdd+4 位流水(其他收入单为 QTSRD,现金费用单为 XF,其它为 FY)。流水按「同一单据类型 djlx + 同日前缀」独立递增(不与其它 djlx/其它表混排)。
  81 + /// </summary>
  82 + private static string ResolveFysrdIdPrefix(string djlx)
  83 + {
  84 + if (string.IsNullOrEmpty(djlx)) return "FY";
  85 + if (djlx.Contains("现金费用单")) return "XF";
  86 + if (string.Equals(djlx, WtFysrdWorkflowHelper.BillName, StringComparison.Ordinal)
  87 + || WtFysrdWorkflowHelper.IsOtherIncomeDjlx(djlx)) return "QTSRD";
  88 + return "FY";
  89 + }
  90 +
  91 + private async Task<string> NextFysrdBillIdAsync(string normalizedDjlx)
  92 + {
  93 + var today = DateTime.Now.ToString("yyyyMMdd");
  94 + var prefix = ResolveFysrdIdPrefix(normalizedDjlx);
  95 + var head = prefix + today;
  96 + // 其他收入 QTSRD:与 WtYskzjjs 中「其他应收单(含 qt 归并的其它收入单)」共用同日流水,避免两表出同号
  97 + List<string> ids;
  98 + if (string.Equals(prefix, "QTSRD", StringComparison.Ordinal))
  99 + {
  100 + var a = await _db.Queryable<WtFysrdEntity>()
  101 + .Where(x => x.Id != null && x.Id.StartsWith(head))
  102 + .Select(x => x.Id)
  103 + .ToListAsync();
  104 + var b = await _db.Queryable<WtYskzjjsEntity>()
  105 + .Where(x => x.Id != null && x.Id.StartsWith(head))
  106 + .Select(x => x.Id)
  107 + .ToListAsync();
  108 + ids = a.Concat(b).ToList();
  109 + }
  110 + else
  111 + {
  112 + // 非 QTSRD:仅按「同单据类型 + 同日前缀」在费用单表内递增(兼容旧版单号长度)
  113 + ids = await _db.Queryable<WtFysrdEntity>()
  114 + .Where(x => x.Djlx == normalizedDjlx)
  115 + .Where(x => x.Id != null && x.Id.StartsWith(head))
  116 + .Select(x => x.Id)
  117 + .ToListAsync();
  118 + }
  119 + var maxSerial = 0;
  120 + foreach (var bid in ids)
  121 + {
  122 + if (string.IsNullOrEmpty(bid) || bid.Length < head.Length + 4) continue;
  123 + if (!bid.StartsWith(head, StringComparison.Ordinal)) continue;
  124 + var tail = bid.Substring(bid.Length - 4);
  125 + if (int.TryParse(tail, out var n) && n > maxSerial) maxSerial = n;
  126 + }
  127 + var serial = maxSerial + 1;
  128 + if (serial > 9999)
  129 + throw NCCException.Bah("当日该类型单号流水已超过 9999,请联系管理员");
  130 + return $"{head}{serial:D4}";
  131 + }
  132 +
78 133 private async Task EnrichWldwForListAsync(IEnumerable<WtFysrdListOutput> rows)
79 134 {
80 135 if (rows == null) return;
... ... @@ -149,7 +204,7 @@ namespace NCC.Extend.WtFysrd
149 204 public async Task<dynamic> GetList([FromQuery] WtFysrdListQueryInput input)
150 205 {
151 206 _fysrdWorkflow.EnsureFysrdAuditColumns();
152   - var sidx = input.sidx == null ? "id" : input.sidx;
  207 + var sidx = string.IsNullOrWhiteSpace(input.sidx) ? "id" : input.sidx;
153 208 List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null;
154 209 DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null;
155 210 DateTime? endDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.Last()) : null;
... ... @@ -176,12 +231,12 @@ namespace NCC.Extend.WtFysrd
176 231 djrq=it.Djrq,
177 232 fzjg=it.Fzjg,
178 233 bm=it.Bm,
179   - jsr=it.Jsr,
180   - jszh=it.Jszh,
  234 + jsr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Jsr).Select(u => u.RealName),
  235 + jszh = SqlFunc.Subqueryable<WtAccountEntity>().Where(a => a.Id == it.Jszh).Select(a => a.AccountName),
181 236 je=it.Je,
182   - zdr=it.Zdr,
183   - shr=it.Shr,
184   - gzr=it.Gzr,
  237 + zdr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Zdr).Select(u => u.RealName),
  238 + shr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Shr).Select(u => u.RealName),
  239 + gzr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Gzr).Select(u => u.RealName),
185 240 bz=it.Bz,
186 241 zy=it.Zy,
187 242 djlx=it.Djlx,
... ... @@ -202,77 +257,39 @@ namespace NCC.Extend.WtFysrd
202 257 public async Task Create([FromBody] WtFysrdCrInput input)
203 258 {
204 259 _fysrdWorkflow.EnsureFysrdAuditColumns();
205   - var userInfo = await _userManager.GetUserInfo();
206 260 var entity = input.Adapt<WtFysrdEntity>();
207 261 NormalizeHeaderFromInput(input, entity);
208 262 entity.Djlx = WtFysrdWorkflowHelper.NormalizeOtherIncomeDjlx(entity.Djlx) ?? entity.Djlx;
209 263 entity.Djzt = "草稿";
210   - // 生成每日递增单号
211   - var today = DateTime.Now.ToString("yyyyMMdd");
212   - var prefix = "FY";
213   - // 根据单据类型设置前缀
214   - if (!string.IsNullOrEmpty(entity.Djlx)) {
215   - if (entity.Djlx.Contains("现金费用单")) prefix = "XF";
216   - else if (string.Equals(entity.Djlx, WtFysrdWorkflowHelper.BillName, StringComparison.Ordinal)
217   - || WtFysrdWorkflowHelper.IsOtherIncomeDjlx(entity.Djlx)) prefix = "QT";
218   - else prefix = "FY"; // 默认费用单前缀
219   - }
220   - var maxId = await _db.Queryable<WtFysrdEntity>()
221   - .Where(x => x.Id.StartsWith(prefix + today))
222   - .OrderBy(x => x.Id, OrderByType.Desc)
223   - .Select(x => x.Id)
224   - .FirstAsync();
225   - int serial = 1;
226   - if (!string.IsNullOrEmpty(maxId) && maxId.Length >= prefix.Length + 8 + 4)
227   - {
228   - var serialStr = maxId.Substring(prefix.Length + 8, 4);
229   - int.TryParse(serialStr, out serial);
230   - serial++;
231   - }
232   - try
  264 +
  265 + for (var attempt = 0; attempt < 10; attempt++)
233 266 {
234   - //开启事务
235   - _db.BeginTran();
236   - int tryCount = 0;
237   - while (true)
238   - {
239   - try
240   - {
241   - entity.Id = $"{prefix}{today}{serial.ToString("D4")}";
242   - //新增费用单记录
243   - var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
244   - var wtFysrdMxEntityList = input.wtFysrdMxList.Adapt<List<WtFysrdMxEntity>>();
245   - if(wtFysrdMxEntityList != null)
  267 + try
246 268 {
247   - foreach (var item in wtFysrdMxEntityList)
248   - {
249   - item.Id = YitIdHelper.NextId().ToString();
250   - item.Djbh = newEntity.Id;
251   - }
252   - await _db.Insertable(wtFysrdMxEntityList).ExecuteCommandAsync();
253   - }
254   - //关闭事务
255   - _db.CommitTran();
256   - break;
257   - }
258   - catch (Exception ex)
  269 + _db.BeginTran();
  270 + entity.Id = await NextFysrdBillIdAsync(entity.Djlx);
  271 + var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
  272 + var wtFysrdMxEntityList = input.wtFysrdMxList.Adapt<List<WtFysrdMxEntity>>();
  273 + if (wtFysrdMxEntityList != null)
259 274 {
260   - if (ex.Message.Contains("Duplicate entry") && tryCount < 10)
  275 + foreach (var item in wtFysrdMxEntityList)
261 276 {
262   - serial++;
263   - tryCount++;
264   - continue;
  277 + item.Id = YitIdHelper.NextId().ToString();
  278 + item.Djbh = newEntity.Id;
265 279 }
266   - _db.RollbackTran();
267   - throw NCCException.Bah($"保存失败: {ex.Message}");
  280 + await _db.Insertable(wtFysrdMxEntityList).ExecuteCommandAsync();
268 281 }
  282 + _db.CommitTran();
  283 + return;
  284 + }
  285 + catch (Exception ex)
  286 + {
  287 + try { _db.RollbackTran(); } catch { /* ignore */ }
  288 + var msg = ex.Message ?? "";
  289 + if (msg.Contains("Duplicate", StringComparison.OrdinalIgnoreCase) && attempt < 9)
  290 + continue;
  291 + throw NCCException.Bah($"保存失败: {msg}");
269 292 }
270   - }
271   - catch (Exception)
272   - {
273   - //回滚事务
274   - _db.RollbackTran();
275   - throw NCCException.Oh(ErrorCode.COM1000);
276 293 }
277 294 }
278 295  
... ... @@ -285,7 +302,7 @@ namespace NCC.Extend.WtFysrd
285 302 public async Task<dynamic> GetNoPagingList([FromQuery] WtFysrdListQueryInput input)
286 303 {
287 304 _fysrdWorkflow.EnsureFysrdAuditColumns();
288   - var sidx = input.sidx == null ? "id" : input.sidx;
  305 + var sidx = string.IsNullOrWhiteSpace(input.sidx) ? "id" : input.sidx;
289 306 List<string> queryDjrq = input.djrq != null ? input.djrq.Split(',').ToObeject<List<string>>() : null;
290 307 DateTime? startDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.First()) : null;
291 308 DateTime? endDjrq = queryDjrq != null ? Ext.GetDateTime(queryDjrq.Last()) : null;
... ... @@ -312,12 +329,12 @@ namespace NCC.Extend.WtFysrd
312 329 djrq=it.Djrq,
313 330 fzjg=it.Fzjg,
314 331 bm=it.Bm,
315   - jsr=it.Jsr,
316   - jszh=it.Jszh,
  332 + jsr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Jsr).Select(u => u.RealName),
  333 + jszh = SqlFunc.Subqueryable<WtAccountEntity>().Where(a => a.Id == it.Jszh).Select(a => a.AccountName),
317 334 je=it.Je,
318   - zdr=it.Zdr,
319   - shr=it.Shr,
320   - gzr=it.Gzr,
  335 + zdr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Zdr).Select(u => u.RealName),
  336 + shr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Shr).Select(u => u.RealName),
  337 + gzr = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.Gzr).Select(u => u.RealName),
321 338 bz=it.Bz,
322 339 zy=it.Zy,
323 340 djlx=it.Djlx,
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtHyService.cs
... ... @@ -153,7 +153,8 @@ namespace NCC.Extend.WtHy
153 153 [HttpGet("")]
154 154 public async Task<dynamic> GetList([FromQuery] WtHyListQueryInput input)
155 155 {
156   - var sidx = input.sidx == null ? "id" : input.sidx;
  156 + // 与前端 el-autocomplete 等场景兼容:sidx 传空串时不得参与 OrderBy,否则分页 SQL 异常
  157 + var sidx = string.IsNullOrWhiteSpace(input.sidx) ? "id" : input.sidx;
157 158 List<string> querySr = input.sr != null ? input.sr.Split(',').ToObeject<List<string>>() : null;
158 159 DateTime? startSr = querySr != null ? Ext.GetDateTime(querySr.First()) : null;
159 160 DateTime? endSr = querySr != null ? Ext.GetDateTime(querySr.Last()) : null;
... ... @@ -253,7 +254,7 @@ namespace NCC.Extend.WtHy
253 254 [NonAction]
254 255 public async Task<dynamic> GetNoPagingList([FromQuery] WtHyListQueryInput input)
255 256 {
256   - var sidx = input.sidx == null ? "id" : input.sidx;
  257 + var sidx = string.IsNullOrWhiteSpace(input.sidx) ? "id" : input.sidx;
257 258 List<string> querySr = input.sr != null ? input.sr.Split(',').ToObeject<List<string>>() : null;
258 259 DateTime? startSr = querySr != null ? Ext.GetDateTime(querySr.First()) : null;
259 260 DateTime? endSr = querySr != null ? Ext.GetDateTime(querySr.Last()) : null;
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtPlService.cs
1   -using NCC.Common.Core.Manager;
  1 +using NCC.Common.Core.Manager;
2 2 using NCC.Common.Enum;
3 3 using NCC.Common.Extension;
4 4 using NCC.Common.Filter;
... ... @@ -22,6 +22,7 @@ using NCC.Common.Model.NPOI;
22 22 using NCC.Common.Configuration;
23 23 using NCC.DataEncryption;
24 24 using NCC.ClayObject;
  25 +using Newtonsoft.Json;
25 26  
26 27 namespace NCC.Extend.WtPl
27 28 {
... ... @@ -37,6 +38,7 @@ namespace NCC.Extend.WtPl
37 38 private readonly IUserManager _userManager;
38 39  
39 40 private static bool _xhColumnChecked;
  41 + private static bool _mdfzIdsColumnChecked;
40 42  
41 43 /// <summary>
42 44 /// 初始化一个<see cref="WtPlService"/>类型的新实例
... ... @@ -77,6 +79,97 @@ namespace NCC.Extend.WtPl
77 79 }
78 80  
79 81 /// <summary>
  82 + /// 确保 wt_pl 存在 <c>F_MdfzIds</c>(门店端展示用门店分组 JSON 数组)
  83 + /// </summary>
  84 + private void EnsureMdfzIdsColumn()
  85 + {
  86 + if (_mdfzIdsColumnChecked) return;
  87 + lock (typeof(WtPlService))
  88 + {
  89 + if (_mdfzIdsColumnChecked) return;
  90 + try
  91 + {
  92 + if (!_db.DbMaintenance.IsAnyTable("wt_pl")) { _mdfzIdsColumnChecked = true; return; }
  93 + var columns = _db.DbMaintenance.GetColumnInfosByTableName("wt_pl");
  94 + var columnNames = columns.Select(c => c.DbColumnName.ToLower()).ToHashSet();
  95 + if (!columnNames.Contains("f_mdfzids"))
  96 + {
  97 + _db.Ado.ExecuteCommand(
  98 + "ALTER TABLE `wt_pl` ADD COLUMN `F_MdfzIds` text NULL COMMENT '门店端展示门店分组(wt_mdfz.F_Id JSON数组)'");
  99 + }
  100 + }
  101 + catch (Exception ex) { Console.WriteLine($"EnsureMdfzIdsColumn(wt_pl): {ex.Message}"); }
  102 + _mdfzIdsColumnChecked = true;
  103 + }
  104 + }
  105 +
  106 + private static List<string> ParseMdfzIdsFromJson(string json)
  107 + {
  108 + if (string.IsNullOrWhiteSpace(json)) return new List<string>();
  109 + try
  110 + {
  111 + var list = JsonConvert.DeserializeObject<List<string>>(json);
  112 + return list?.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct(StringComparer.Ordinal).ToList()
  113 + ?? new List<string>();
  114 + }
  115 + catch
  116 + {
  117 + return new List<string>();
  118 + }
  119 + }
  120 +
  121 + private static string MdfzIdsToJsonStorage(List<string> ids, string sfmdfl)
  122 + {
  123 + if (sfmdfl != "1" || ids == null || ids.Count == 0) return null;
  124 + return JsonConvert.SerializeObject(ids.Distinct(StringComparer.Ordinal).ToList());
  125 + }
  126 +
  127 + private async Task EnrichWtPlMdfzNamesAsync(IEnumerable<WtPlListOutput> rows)
  128 + {
  129 + var rowList = rows == null ? new List<WtPlListOutput>() : rows.ToList();
  130 + if (rowList.Count == 0) return;
  131 + var plIds = rowList.Select(x => x.id).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  132 + if (plIds.Count == 0) return;
  133 + var heads = await _db.Queryable<WtPlEntity>().In(p => p.Id, plIds)
  134 + .Select(p => new { p.Id, p.Sfmdfl, p.MdfzIds })
  135 + .ToListAsync();
  136 + var headMap = heads.ToDictionary(x => x.Id, x => x, StringComparer.Ordinal);
  137 +
  138 + var allFz = new HashSet<string>(StringComparer.Ordinal);
  139 + foreach (var h in heads)
  140 + {
  141 + foreach (var m in ParseMdfzIdsFromJson(h.MdfzIds))
  142 + allFz.Add(m);
  143 + }
  144 + var fzDict = new Dictionary<string, string>(StringComparer.Ordinal);
  145 + if (allFz.Count > 0)
  146 + {
  147 + var list = await _db.Queryable<WtMdfzEntity>().In(f => f.Id, allFz.ToList()).ToListAsync();
  148 + foreach (var f in list)
  149 + {
  150 + if (!string.IsNullOrEmpty(f.Id)) fzDict[f.Id] = string.IsNullOrEmpty(f.Fzmc) ? f.Id : f.Fzmc;
  151 + }
  152 + }
  153 +
  154 + foreach (var r in rowList)
  155 + {
  156 + if (string.IsNullOrEmpty(r.id) || !headMap.TryGetValue(r.id, out var h)) continue;
  157 + if (h.Sfmdfl != "1")
  158 + {
  159 + r.mdfzNames = "—";
  160 + continue;
  161 + }
  162 + var parts = ParseMdfzIdsFromJson(h.MdfzIds);
  163 + if (parts.Count == 0)
  164 + {
  165 + r.mdfzNames = "全部门店组";
  166 + continue;
  167 + }
  168 + r.mdfzNames = string.Join("、", parts.Select(i => fzDict.TryGetValue(i, out var nm) ? nm : i));
  169 + }
  170 + }
  171 +
  172 + /// <summary>
80 173 /// 获取商品分类
81 174 /// </summary>
82 175 /// <param name="id">参数</param>
... ... @@ -85,8 +178,11 @@ namespace NCC.Extend.WtPl
85 178 public async Task<dynamic> GetInfo(string id)
86 179 {
87 180 EnsureXhColumn();
  181 + EnsureMdfzIdsColumn();
88 182 var entity = await _db.Queryable<WtPlEntity>().FirstAsync(p => p.Id == id);
89 183 var output = entity.Adapt<WtPlInfoOutput>();
  184 + output.mdfzIds = ParseMdfzIdsFromJson(entity.MdfzIds);
  185 + if (output.mdfzIds == null) output.mdfzIds = new List<string>();
90 186 return output;
91 187 }
92 188  
... ... @@ -99,6 +195,7 @@ namespace NCC.Extend.WtPl
99 195 public async Task<dynamic> GetList([FromQuery] WtPlListQueryInput input)
100 196 {
101 197 EnsureXhColumn();
  198 + EnsureMdfzIdsColumn();
102 199 // 默认按「序号」升序,空序号的排到最后,同序号按主键 id 倒序
103 200 var defaultOrder = "ISNULL(xh) ASC, xh ASC, id DESC";
104 201 var orderBy = string.IsNullOrEmpty(input.sidx) ? defaultOrder : (input.sidx + " " + input.sort);
... ... @@ -113,7 +210,8 @@ namespace NCC.Extend.WtPl
113 210 sfmdfl=it.Sfmdfl,
114 211 xh=it.Xh,
115 212 }).MergeTable().OrderBy(orderBy).ToPagedListAsync(input.currentPage, input.pageSize);
116   - return PageResult<WtPlListOutput>.SqlSugarPageResult(data);
  213 + await EnrichWtPlMdfzNamesAsync(data.list);
  214 + return PageResult<WtPlListOutput>.SqlSugarPageResult(data);
117 215 }
118 216  
119 217 /// <summary>
... ... @@ -125,9 +223,11 @@ namespace NCC.Extend.WtPl
125 223 public async Task Create([FromBody] WtPlCrInput input)
126 224 {
127 225 EnsureXhColumn();
  226 + EnsureMdfzIdsColumn();
128 227 var userInfo = await _userManager.GetUserInfo();
129 228 var entity = input.Adapt<WtPlEntity>();
130 229 entity.Id = YitIdHelper.NextId().ToString();
  230 + entity.MdfzIds = MdfzIdsToJsonStorage(input.mdfzIds, input.sfmdfl);
131 231 var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync();
132 232 if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000);
133 233 }
... ... @@ -141,6 +241,7 @@ namespace NCC.Extend.WtPl
141 241 public async Task<dynamic> GetNoPagingList([FromQuery] WtPlListQueryInput input)
142 242 {
143 243 EnsureXhColumn();
  244 + EnsureMdfzIdsColumn();
144 245 var defaultOrder = "ISNULL(xh) ASC, xh ASC, id DESC";
145 246 var orderBy = string.IsNullOrEmpty(input.sidx) ? defaultOrder : (input.sidx + " " + input.sort);
146 247 var data = await _db.Queryable<WtPlEntity>()
... ... @@ -153,7 +254,8 @@ namespace NCC.Extend.WtPl
153 254 sfmdfl=it.Sfmdfl,
154 255 xh=it.Xh,
155 256 }).MergeTable().OrderBy(orderBy).ToListAsync();
156   - return data;
  257 + await EnrichWtPlMdfzNamesAsync(data);
  258 + return data;
157 259 }
158 260  
159 261 /// <summary>
... ... @@ -175,7 +277,7 @@ namespace NCC.Extend.WtPl
175 277 {
176 278 exportData = await this.GetNoPagingList(input);
177 279 }
178   - List<ParamsModel> paramList = "[{\"value\":\"序号\",\"field\":\"xh\"},{\"value\":\"分类编号\",\"field\":\"id\"},{\"value\":\"品类名称\",\"field\":\"plmc\"},]".ToList<ParamsModel>();
  280 + List<ParamsModel> paramList = "[{\"value\":\"序号\",\"field\":\"xh\"},{\"value\":\"分类编号\",\"field\":\"id\"},{\"value\":\"品类名称\",\"field\":\"plmc\"},{\"value\":\"是否门店分类\",\"field\":\"sfmdfl\"},{\"value\":\"展示门店组\",\"field\":\"mdfzNames\"},]".ToList<ParamsModel>();
179 281 ExcelConfig excelconfig = new ExcelConfig();
180 282 excelconfig.FileName = "商品分类.xls";
181 283 excelconfig.HeadFont = "微软雅黑";
... ... @@ -241,14 +343,18 @@ namespace NCC.Extend.WtPl
241 343 public async Task Update(string id, [FromBody] WtPlUpInput input)
242 344 {
243 345 EnsureXhColumn();
244   - var entity = input.Adapt<WtPlEntity>();
245   - var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
246   - if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001);
247   - // xh 允许清空为 null:单独显式写入(避开 IgnoreColumns:ignoreAllNullColumns 忽略 null 的行为)
248   - await _db.Updateable<WtPlEntity>()
249   - .SetColumns(it => it.Xh == input.xh)
  346 + EnsureMdfzIdsColumn();
  347 + var isOk = await _db.Updateable<WtPlEntity>()
  348 + .SetColumns(it => new WtPlEntity
  349 + {
  350 + Plmc = input.plmc,
  351 + Sfmdfl = input.sfmdfl,
  352 + Xh = input.xh,
  353 + MdfzIds = MdfzIdsToJsonStorage(input.mdfzIds, input.sfmdfl)
  354 + })
250 355 .Where(it => it.Id == id)
251 356 .ExecuteCommandAsync();
  357 + if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001);
252 358 }
253 359  
254 360 /// <summary>
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtXsckdService.cs
... ... @@ -1205,7 +1205,7 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
1205 1205 }
1206 1206  
1207 1207 /// <summary>
1208   - /// 从备注中解析原销售出库单号(如:原订单号:CHD202603300001
  1208 + /// 从备注中解析原销售出库单号(如:原订单号:CKD202603300001;历史单号可能为 CHD 前缀
1209 1209 /// </summary>
1210 1210 private static string TryParseYcddhFromRemark(string bz)
1211 1211 {
... ... @@ -1961,6 +1961,8 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
1961 1961 output.skmx = entity.Skmx;
1962 1962 output.fkmx = entity.Fkmx;
1963 1963 output.sy_pch = entity.SyPch;
  1964 + output.yddh = entity.Yddh;
  1965 + output.dyddh = entity.Dyddh;
1964 1966 EnrichInfoOutputYcddhFromRemarkIfNeeded(output, entity);
1965 1967 output.cjckId = entity.Cjck;
1966 1968  
... ... @@ -2235,6 +2237,7 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
2235 2237 (xsckd.Hysjh != null && xsckd.Hysjh.Contains(input.hysjh)) ||
2236 2238 (hy.Sjh != null && hy.Sjh.Contains(input.hysjh)))
2237 2239 .WhereIF(!string.IsNullOrEmpty(input.dyddh), (xsckd, hy) => xsckd.Dyddh != null && xsckd.Dyddh.Contains(input.dyddh))
  2240 + .WhereIF(!string.IsNullOrEmpty(input.yddh), (xsckd, hy) => xsckd.Yddh != null && xsckd.Yddh.Contains(input.yddh.Trim()))
2238 2241 .WhereIF(!string.IsNullOrEmpty(input.djlx), (xsckd, hy) => xsckd.Djlx.Contains(input.djlx))
2239 2242 .WhereIF(!string.IsNullOrEmpty(input.ly), (xsckd, hy) => xsckd.Ly.Equals(input.ly))
2240 2243 .WhereIF(!string.IsNullOrEmpty(input.djzt), (xsckd, hy) => xsckd.Djzt.Contains(input.djzt))
... ... @@ -2688,6 +2691,16 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
2688 2691 if (!string.IsNullOrWhiteSpace(row.zdr))
2689 2692 row.jsr = row.zdr;
2690 2693 }
  2694 +
  2695 + // 销售/零售出库:经手人列仍为空时回退显示制单人(收银台/抖音等常将业绩挂在制单账号,避免反审后列表经手人空白)
  2696 + foreach (var row in items)
  2697 + {
  2698 + var lx = row.djlx?.Trim() ?? "";
  2699 + if (lx != "销售出库单" && lx != "零售单") continue;
  2700 + if (!string.IsNullOrWhiteSpace(row.jsr)) continue;
  2701 + if (!string.IsNullOrWhiteSpace(row.zdr))
  2702 + row.jsr = row.zdr;
  2703 + }
2691 2704 }
2692 2705  
2693 2706 /// <summary>
... ... @@ -2705,6 +2718,12 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
2705 2718 else if (await _db.Queryable<WtHyEntity>().Where(h => h.Id == j).AnyAsync())
2706 2719 jsrLookup = entity.Zdr;
2707 2720 }
  2721 + else if ((string.Equals(entity.Djlx, "销售出库单", StringComparison.Ordinal) || string.Equals(entity.Djlx, "零售单", StringComparison.Ordinal))
  2722 + && string.IsNullOrWhiteSpace(jsrLookup) && !string.IsNullOrWhiteSpace(entity.Zdr))
  2723 + {
  2724 + // 与列表经手人列回退一致:未单独存经手人时用制单人展示,便于对账/提成核对
  2725 + jsrLookup = entity.Zdr;
  2726 + }
2708 2727  
2709 2728 var ids = new[] { jsrLookup, entity.Zdr, entity.Shr, entity.Shr1, entity.Shr2, entity.Gzr, entity.Fhr }
2710 2729 .Where(s => !string.IsNullOrWhiteSpace(s))
... ... @@ -2956,20 +2975,21 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
2956 2975 entity.SyPch = NormalizeSyPch(input.sy_pch);
2957 2976 Console.WriteLine($"[Create] 会员手机号码: input.hysjh={input.hysjh}, entity.Hysjh={entity.Hysjh}");
2958 2977  
2959   - // 生成每日递增单号,前缀根据djlx判断
  2978 + // 生成每日递增单号,前缀根据djlx判断(销售出库 CKD、销售退货 THD;零售单与销售出库单同前缀)
2960 2979 var today = DateTime.Now.ToString("yyyyMMdd");
2961   - string prefix = "CHD";
  2980 + string prefix = "CKD";
2962 2981 if (!string.IsNullOrEmpty(input.djlx)) {
2963 2982 if (input.djlx.Contains("采购入库单")) prefix = "RK";
2964 2983 else if (input.djlx.Contains("预售出库单")) prefix = "YC";
2965 2984 else if (input.djlx.Contains("预售退货单")) prefix = "YT";
  2985 + else if (input.djlx.Contains("销售退货单")) prefix = "THD";
2966 2986 else if (input.djlx.Contains("采购退货单")) prefix = "CT";
2967   - else if (input.djlx.Contains("销售出库单")) prefix = "CHD";
  2987 + else if (input.djlx.Contains("销售出库单") || input.djlx.Contains("零售单")) prefix = "CKD";
2968 2988 else if (input.djlx.Contains("委托代销发货单")) prefix = "WF";
2969 2989 else if (input.djlx.Contains("委托代销退货单")) prefix = "WT";
2970 2990 else if (input.djlx.Contains("委托代销结算单")) prefix = "WJ";
2971 2991 else if (input.djlx.Contains("现金费用单")) prefix = "XF";
2972   - else if (input.djlx.Contains("其他收入单")) prefix = "QT";
  2992 + else if (input.djlx.Contains("其他收入单")) prefix = "QTSRD";
2973 2993 else if (input.djlx.Contains("盘点单")) prefix = "PDD";
2974 2994 else if (input.djlx.Contains("报损单")) prefix = "BSD";
2975 2995 else if (input.djlx.Contains("赠送单")) prefix = "ZSD";
... ... @@ -2979,14 +2999,9 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
2979 2999 else if (input.djlx.Contains("报溢单")) prefix = "BYD";
2980 3000 // 可扩展其它类型
2981 3001 }
2982   - // 查询今日最大单号 - ✅ 获取所有今天的单号,然后在内存中按序列号排序
  3002 + // 查询本类型当日最大流水号:仅同一 prefix + 日期 内递增(不与出库/调拨/入库等其它类型混用序号)
2983 3003 var allTodayOrders = await _db.Queryable<WtXsckdEntity>()
2984   - .Where(x => (x.Id.StartsWith("CHD") || x.Id.StartsWith("YC") || x.Id.StartsWith("CT") ||
2985   - x.Id.StartsWith("RK") || x.Id.StartsWith("WF") || x.Id.StartsWith("WT") ||
2986   - x.Id.StartsWith("WJ") || x.Id.StartsWith("XF") || x.Id.StartsWith("QT") ||
2987   - x.Id.StartsWith("PDD") || x.Id.StartsWith("BSD") || x.Id.StartsWith("ZSD") || x.Id.StartsWith("TJD") ||
2988   - x.Id.StartsWith("BJD") || x.Id.StartsWith("HZD") || x.Id.StartsWith("BYD")) &&
2989   - x.Id.Contains(today)) // 包含今天的日期
  3004 + .Where(x => x.Id.StartsWith(prefix + today))
2990 3005 .Select(x => x.Id)
2991 3006 .ToListAsync();
2992 3007  
... ... @@ -3476,6 +3491,7 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
3476 3491 (xsckd.Hysjh != null && xsckd.Hysjh.Contains(input.hysjh)) ||
3477 3492 (hy.Sjh != null && hy.Sjh.Contains(input.hysjh)))
3478 3493 .WhereIF(!string.IsNullOrEmpty(input.dyddh), (xsckd, hy) => xsckd.Dyddh != null && xsckd.Dyddh.Contains(input.dyddh))
  3494 + .WhereIF(!string.IsNullOrEmpty(input.yddh), (xsckd, hy) => xsckd.Yddh != null && xsckd.Yddh.Contains(input.yddh.Trim()))
3479 3495 .WhereIF(!string.IsNullOrEmpty(input.djlx), (xsckd, hy) => xsckd.Djlx.Contains(input.djlx))
3480 3496 .WhereIF(!string.IsNullOrEmpty(input.ly), (xsckd, hy) => xsckd.Ly.Equals(input.ly))
3481 3497 .WhereIF(!string.IsNullOrEmpty(input.djzt), (xsckd, hy) => xsckd.Djzt.Contains(input.djzt))
... ... @@ -3541,19 +3557,20 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
3541 3557 public async Task<dynamic> GenerateBillNo([FromQuery] string djlx)
3542 3558 {
3543 3559 var today = DateTime.Now.ToString("yyyyMMdd");
3544   - string prefix = "CHD";
  3560 + string prefix = "CKD";
3545 3561 if (!string.IsNullOrEmpty(djlx))
3546 3562 {
3547 3563 if (djlx.Contains("采购入库单")) prefix = "RK";
3548 3564 else if (djlx.Contains("预售出库单")) prefix = "YC";
3549 3565 else if (djlx.Contains("预售退货单")) prefix = "YT";
  3566 + else if (djlx.Contains("销售退货单")) prefix = "THD";
3550 3567 else if (djlx.Contains("采购退货单")) prefix = "CT";
3551   - else if (djlx.Contains("销售出库单")) prefix = "CHD";
  3568 + else if (djlx.Contains("销售出库单") || djlx.Contains("零售单")) prefix = "CKD";
3552 3569 else if (djlx.Contains("委托代销发货单")) prefix = "WF";
3553 3570 else if (djlx.Contains("委托代销退货单")) prefix = "WT";
3554 3571 else if (djlx.Contains("委托代销结算单")) prefix = "WJ";
3555 3572 else if (djlx.Contains("现金费用单")) prefix = "XF";
3556   - else if (djlx.Contains("其他收入单")) prefix = "QT";
  3573 + else if (djlx.Contains("其他收入单")) prefix = "QTSRD";
3557 3574 else if (djlx.Contains("盘点单")) prefix = "PDD";
3558 3575 else if (djlx.Contains("报损单")) prefix = "BSD";
3559 3576 else if (djlx.Contains("赠送单")) prefix = "ZSD";
... ... @@ -3564,7 +3581,7 @@ WHERE d.djlx IN (&#39;销售退货单&#39;,&#39;预售退货单&#39;,&#39;委托代销退货单&#39;)
3564 3581 }
3565 3582  
3566 3583 var allTodayOrders = await _db.Queryable<WtXsckdEntity>()
3567   - .Where(x => x.Id.StartsWith(prefix) && x.Id.Contains(today))
  3584 + .Where(x => x.Id.StartsWith(prefix + today))
3568 3585 .Select(x => x.Id)
3569 3586 .ToListAsync();
3570 3587  
... ... @@ -5985,58 +6002,70 @@ LIMIT {offset}, {pageSize}&quot;;
5985 6002  
5986 6003 var code = productCode.Trim();
5987 6004  
5988   - // 标准化 productCode:允许前端传 F_Id / F_Spbm / F_Dyspid 任一种,统一到序列号表使用的 F_Id
5989   - // 序列号表 spbh 字段存的是 wt_sp.F_Id(商品主键)
5990   - var productCodeEncode = "";
5991   - var normalizedSpbh = code;
5992   - var spRow = await _db.Queryable<WtSpEntity>()
  6005 + // 标准化 productCode:允许前端传 F_Id / F_Spbm / F_Dyspid 任一种,统一到商品主表 F_Id;序列号表 spbh 须对应该 Id
  6006 + var spList = await _db.Queryable<WtSpEntity>()
5993 6007 .Where(p => p.Id == code || p.Spbm == code || p.Dyspid == code)
5994   - .Select(p => new { p.Id, p.Spbm })
5995   - .FirstAsync();
5996   - if (spRow != null)
  6008 + .ToListAsync();
  6009 + if (spList == null || spList.Count == 0)
  6010 + return new { serialNumbers = new List<object>() };
  6011 +
  6012 + WtSpEntity sp;
  6013 + if (spList.Count == 1)
5997 6014 {
5998   - if (!string.IsNullOrEmpty(spRow.Id)) normalizedSpbh = spRow.Id;
5999   - if (!string.IsNullOrEmpty(spRow.Spbm)) productCodeEncode = spRow.Spbm;
  6015 + sp = spList[0];
6000 6016 }
  6017 + else
  6018 + {
  6019 + // 多匹配时:优先 Id 精确、再 F_Spbm 精确,避免 F_Id/编码混用时误用另一条商品
  6020 + sp = spList.FirstOrDefault(p => p.Id == code)
  6021 + ?? spList.FirstOrDefault(p => string.Equals(p.Spbm, code, StringComparison.Ordinal))
  6022 + ?? spList[0];
  6023 + }
  6024 + if (sp == null || string.IsNullOrEmpty(sp.Id))
  6025 + return new { serialNumbers = new List<object>() };
  6026 +
  6027 + var normalizedSpId = sp.Id.Trim();
  6028 + var productCodeEncode = (sp.Spbm ?? string.Empty).Trim();
6001 6029  
6002   - // 使用标准化后的 F_Id 查询序列号表;避免 StringComparison 重载在 SqlSugar 下翻译不稳定
6003   - var baseQuery = _db.Queryable<WtSerialNumberEntity>()
6004   - .Where(s => s.Spbh == normalizedSpbh);
  6030 + // 与 wt_sp 内连接,仅返回 spbh=商品主键且能在商品表对上的在库行,避免脏数据/错误 spbh 混入其它货号
  6031 + var joinQ = _db.Queryable<WtSerialNumberEntity, WtSpEntity>(
  6032 + (s, p) => new JoinQueryInfos(JoinType.Inner, s.Spbh == p.Id))
  6033 + .Where((s, p) => p.Id == normalizedSpId);
6005 6034  
6006 6035 // 根据单据类型决定查询状态
6007   - // 退货单:查询已售出的序列号(Status = 1),因为要退货
6008   - // 出库单、发货单、结算单:查询在库的序列号(Status = 0),因为要出库
6009 6036 if (documentType == "销售退货单" || documentType == "预售退货单" || documentType == "委托代销退货单")
6010   - {
6011   - baseQuery = baseQuery.Where(s => s.Status == 1); // 已售出
6012   - }
  6037 + joinQ = joinQ.Where((s, p) => s.Status == 1);
6013 6038 else if (documentType == "采购退货单")
6014   - {
6015   - baseQuery = baseQuery.Where(s => s.Status == 0 || s.Status == 3);
6016   - }
  6039 + joinQ = joinQ.Where((s, p) => s.Status == 0 || s.Status == 3);
6017 6040 else
6018   - {
6019   - baseQuery = baseQuery.Where(s => s.Status == 0 || s.Status == 3);
6020   - }
  6041 + joinQ = joinQ.Where((s, p) => s.Status == 0 || s.Status == 3);
6021 6042  
6022   - // 构建带仓库过滤的查询
6023   - var queryWithWarehouse = baseQuery;
6024   - if (!string.IsNullOrEmpty(warehouse))
6025   - {
6026   - var warehouseTrim = warehouse.Trim();
6027   - queryWithWarehouse = queryWithWarehouse.Where(s => s.InWarehouse == warehouseTrim);
6028   - }
6029 6043 if (!string.IsNullOrEmpty(serialNumber))
6030 6044 {
6031   - queryWithWarehouse = queryWithWarehouse.Where(s => s.SerialNumber.Contains(serialNumber));
  6045 + var snTrim = serialNumber.Trim();
  6046 + joinQ = joinQ.Where((s, p) => s.SerialNumber.Contains(snTrim));
6032 6047 }
6033 6048  
6034   - // 先按商品+仓库过滤,如果没有结果,再退回到只按商品查询,避免因为仓库编码不一致导致查不到数据
6035   - var list1 = await queryWithWarehouse
6036   - .Select(s => new {
  6049 + var hasWarehouse = !string.IsNullOrEmpty(warehouse);
  6050 + var warehouseTrim = hasWarehouse ? warehouse.Trim() : null;
  6051 +
  6052 + // 销售/预售/委托等「指定出库仓库」的出库单:不要跨仓回退,避免可选条数>本仓账面库存、与其它仓串行
  6053 + var isOutboundWithWarehouse = hasWarehouse
  6054 + && (string.Equals(documentType, "销售出库单", StringComparison.Ordinal)
  6055 + || string.Equals(documentType, "预售出库单", StringComparison.Ordinal)
  6056 + || (documentType != null && documentType.Contains("委托", StringComparison.Ordinal) && (documentType.Contains("发货", StringComparison.Ordinal) || documentType.Contains("结算", StringComparison.Ordinal))));
  6057 +
  6058 + // 先按本仓库
  6059 + var qWh = hasWarehouse
  6060 + ? joinQ.Where((s, p) => s.InWarehouse == warehouseTrim)
  6061 + : joinQ;
  6062 +
  6063 + var list1 = await qWh
  6064 + .Select((s, p) => new
  6065 + {
6037 6066 serialNumber = s.SerialNumber,
6038 6067 productCode = s.Spbh,
6039   - productName = s.Spmc,
  6068 + productName = p.Spmc,
6040 6069 warehouse = s.InWarehouse,
6041 6070 status = s.Status,
6042 6071 inTime = s.InTime,
... ... @@ -6044,31 +6073,32 @@ LIMIT {offset}, {pageSize}&quot;;
6044 6073 })
6045 6074 .ToListAsync();
6046 6075  
6047   - var serialNumbers = list1.Select(s => new {
6048   - s.serialNumber,
6049   - s.productCode,
6050   - productCodeEncode,
6051   - s.productName,
6052   - s.warehouse,
6053   - s.status,
6054   - s.inTime,
6055   - s.outTime
6056   - }).ToList();
  6076 + var serialNumbers = list1
  6077 + .Where(x => x.productCode == normalizedSpId) // 内存再收一道,防 ORM/类型歧义
  6078 + .Select(s => new
  6079 + {
  6080 + s.serialNumber,
  6081 + s.productCode,
  6082 + productCodeEncode,
  6083 + productName = s.productName,
  6084 + s.warehouse,
  6085 + s.status,
  6086 + s.inTime,
  6087 + s.outTime
  6088 + })
  6089 + .ToList();
6057 6090  
6058   - // 如果按仓库过滤没有查到,但指定了仓库参数,则回退为忽略仓库,只按商品和状态查询
6059   - if (!serialNumbers.Any() && !string.IsNullOrEmpty(warehouse))
  6091 + if (!serialNumbers.Any() && hasWarehouse && !isOutboundWithWarehouse)
6060 6092 {
6061   - var fallbackQuery = baseQuery;
6062   - if (!string.IsNullOrEmpty(serialNumber))
6063   - {
6064   - fallbackQuery = fallbackQuery.Where(s => s.SerialNumber.Contains(serialNumber));
6065   - }
  6093 + // joinQ 已含状态与序列号模糊条件,此处仅去掉仓库限制做跨仓补偿(出库单见 isOutboundWithWarehouse 禁止补偿)
  6094 + var fallbackQ = joinQ;
6066 6095  
6067   - var list2 = await fallbackQuery
6068   - .Select(s => new {
  6096 + var list2 = await fallbackQ
  6097 + .Select((s, p) => new
  6098 + {
6069 6099 serialNumber = s.SerialNumber,
6070 6100 productCode = s.Spbh,
6071   - productName = s.Spmc,
  6101 + productName = p.Spmc,
6072 6102 warehouse = s.InWarehouse,
6073 6103 status = s.Status,
6074 6104 inTime = s.InTime,
... ... @@ -6076,16 +6106,20 @@ LIMIT {offset}, {pageSize}&quot;;
6076 6106 })
6077 6107 .ToListAsync();
6078 6108  
6079   - serialNumbers = list2.Select(s => new {
6080   - s.serialNumber,
6081   - s.productCode,
6082   - productCodeEncode,
6083   - s.productName,
6084   - s.warehouse,
6085   - s.status,
6086   - s.inTime,
6087   - s.outTime
6088   - }).ToList();
  6109 + serialNumbers = list2
  6110 + .Where(x => x.productCode == normalizedSpId)
  6111 + .Select(s => new
  6112 + {
  6113 + s.serialNumber,
  6114 + s.productCode,
  6115 + productCodeEncode,
  6116 + productName = s.productName,
  6117 + s.warehouse,
  6118 + s.status,
  6119 + s.inTime,
  6120 + s.outTime
  6121 + })
  6122 + .ToList();
6089 6123 }
6090 6124  
6091 6125 return new { serialNumbers = serialNumbers };
... ... @@ -7035,6 +7069,15 @@ LIMIT {offset}, {pageSize}&quot;;
7035 7069 if (!isAuthorized)
7036 7070 return new { success = false, message = "您没有反审权限,只有一级或二级审核人可以反审" };
7037 7071  
  7072 + if (string.Equals(entity.Djlx, "销售出库单", StringComparison.Ordinal)
  7073 + || string.Equals(entity.Djlx, "零售单", StringComparison.Ordinal)
  7074 + || string.Equals(entity.Djlx, "预售出库单", StringComparison.Ordinal))
  7075 + {
  7076 + var thCsv = await GetLinkedReturnBillIdsCsvAsync(id, entity.Djlx);
  7077 + if (!string.IsNullOrEmpty(thCsv))
  7078 + return new { success = false, message = $"该单据已存在关联退货单({thCsv}),无法反审" };
  7079 + }
  7080 +
7038 7081 _db.BeginTran();
7039 7082 try
7040 7083 {
... ... @@ -7077,6 +7120,53 @@ LIMIT {offset}, {pageSize}&quot;;
7077 7120 if (zsdMx.Count > 0)
7078 7121 await RollbackSpCostOnDelete(entity);
7079 7122 }
  7123 + else if ((string.Equals(entity.Djzt, "已审核", StringComparison.Ordinal)
  7124 + || string.Equals(entity.Djzt, "一级已审", StringComparison.Ordinal)
  7125 + || string.Equals(entity.Djzt, "一级已审/待二级", StringComparison.Ordinal))
  7126 + && !IsGiftAuditDocument(entity.Djlx)
  7127 + && !string.Equals(entity.Djlx, "采购入库单", StringComparison.Ordinal)
  7128 + && !string.Equals(entity.Djlx, "同价调拨单", StringComparison.Ordinal))
  7129 + {
  7130 + // 与审核不通过 RejectDocument 中 wasNotDraft 块对称:已生效单据反审需先冲回库存/成本/会员/序列号
  7131 + var lx0 = entity.Djlx ?? "";
  7132 + if (lx0 == "销售出库单" || lx0 == "零售单" || lx0 == "预售出库单" || lx0 == "委托代销发货单"
  7133 + || lx0.Contains("报损单"))
  7134 + {
  7135 + if (IsStockOutboundDocumentForSerialRevert(lx0))
  7136 + await RevertSerialNumbersForDeletedOutboundAsync(entity.Id);
  7137 + await DeleteWtXlhTrackingForDocumentAsync(entity.Id);
  7138 + await RollbackSpCostOnDelete(entity);
  7139 + if (lx0 == "销售出库单" || lx0 == "零售单" || lx0 == "预售出库单")
  7140 + await TryReverseSalesOutboundMemberAndCommissionAsync(entity, "反审");
  7141 + }
  7142 + else if (lx0 == "销售退货单" || lx0 == "预售退货单" || (lx0.Contains("委托代销退货单")))
  7143 + {
  7144 + await RevertSerialNumbersForDeletedReturnAsync(entity);
  7145 + await DeleteWtXlhTrackingForDocumentAsync(entity.Id);
  7146 + await RollbackSpCostOnDelete(entity);
  7147 + if (lx0 == "销售退货单" || lx0 == "预售退货单")
  7148 + await TryReverseReturnMemberRefundAsync(entity, "反审");
  7149 + }
  7150 + else if (lx0.Contains("报溢单"))
  7151 + {
  7152 + await DeleteWtXlhTrackingForDocumentAsync(entity.Id);
  7153 + await RollbackSpCostOnDelete(entity);
  7154 + }
  7155 + else if (lx0 == "采购退货单")
  7156 + {
  7157 + await RollbackSpCostOnDelete(entity);
  7158 + }
  7159 + else if (string.Equals(lx0, "变价调拨单", StringComparison.Ordinal))
  7160 + {
  7161 + var mxRev = await _db.Queryable<WtXsckdMxEntity>().Where(d => d.Djbh == id).ToListAsync();
  7162 + if (mxRev.Count > 0)
  7163 + {
  7164 + EnsureBjsxColumn();
  7165 + EnsureBjhcbColumn();
  7166 + await RollbackVariablePriceTransferCost(entity, mxRev);
  7167 + }
  7168 + }
  7169 + }
7080 7170  
7081 7171 var backToDraft = string.Equals(entity.Djlx, "同价调拨单", StringComparison.Ordinal)
7082 7172 || string.Equals(entity.Djlx, "采购入库单", StringComparison.Ordinal)
... ... @@ -7110,6 +7200,44 @@ LIMIT {offset}, {pageSize}&quot;;
7110 7200 }
7111 7201  
7112 7202 /// <summary>
  7203 + /// 撤回审核:赠送单/获赠单在「待审核」或一级/二级审核中 → 草稿(未过账,可再次编辑后提交)
  7204 + /// </summary>
  7205 + [HttpPost("WithdrawAudit/{id}")]
  7206 + public async Task<dynamic> WithdrawAudit(string id)
  7207 + {
  7208 + try
  7209 + {
  7210 + if (string.IsNullOrWhiteSpace(id))
  7211 + return new { success = false, message = "单据编号无效" };
  7212 +
  7213 + var entity = await _db.Queryable<WtXsckdEntity>().Where(x => x.Id == id).FirstAsync();
  7214 + if (entity == null)
  7215 + return new { success = false, message = "单据不存在" };
  7216 +
  7217 + if (!IsGiftAuditDocument(entity.Djlx))
  7218 + return new { success = false, message = "仅赠送单/获赠单支持撤回为草稿" };
  7219 +
  7220 + var z = entity.Djzt?.Trim() ?? "";
  7221 + if (z != "待审核" && z != "一级已审" && z != "待二级" && z != "一级已审/待二级")
  7222 + return new { success = false, message = $"当前状态「{entity.Djzt ?? "空"}」不可撤回,仅待审核或待二级审核中的单据可撤回" };
  7223 +
  7224 + entity.Djzt = "草稿";
  7225 + entity.Shr = null;
  7226 + entity.Shr1 = null;
  7227 + entity.Shr2 = null;
  7228 + await _db.Updateable(entity)
  7229 + .UpdateColumns(x => new { x.Djzt, x.Shr, x.Shr1, x.Shr2 })
  7230 + .ExecuteCommandAsync();
  7231 +
  7232 + return new { success = true, message = "已撤回为草稿,可继续编辑" };
  7233 + }
  7234 + catch (Exception ex)
  7235 + {
  7236 + return new { success = false, message = $"撤回失败: {ex.Message}" };
  7237 + }
  7238 + }
  7239 +
  7240 + /// <summary>
7113 7241 /// 获取状态文本
7114 7242 /// </summary>
7115 7243 /// <param name="status">状态码</param>
... ... @@ -7490,14 +7618,9 @@ LIMIT {offset}, {pageSize}&quot;;
7490 7618  
7491 7619 // 5. 生成新的销售出库单编号
7492 7620 var today = DateTime.Now.ToString("yyyyMMdd");
7493   - string prefix = "CHD";
  7621 + string prefix = "CKD";
7494 7622 var allTodayOrders = await _db.Queryable<WtXsckdEntity>()
7495   - .Where(x => (x.Id.StartsWith("CHD") || x.Id.StartsWith("YC") || x.Id.StartsWith("CT") ||
7496   - x.Id.StartsWith("RK") || x.Id.StartsWith("WF") || x.Id.StartsWith("WT") ||
7497   - x.Id.StartsWith("WJ") || x.Id.StartsWith("XF") || x.Id.StartsWith("QT") ||
7498   - x.Id.StartsWith("PDD") || x.Id.StartsWith("BSD") || x.Id.StartsWith("TJD") ||
7499   - x.Id.StartsWith("BJD") || x.Id.StartsWith("HZD") || x.Id.StartsWith("BYD")) &&
7500   - x.Id.Contains(today))
  7623 + .Where(x => x.Id.StartsWith(prefix + today))
7501 7624 .Select(x => x.Id)
7502 7625 .ToListAsync();
7503 7626  
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtYskzjjsService.cs
... ... @@ -242,41 +242,42 @@ namespace NCC.Extend.WtYskzjjs
242 242 }
243 243 ThrowIfInvalidForSubmit(input, input?.djzt);
244 244 var entity = input.Adapt<WtYskzjjsEntity>();
245   - entity.Id = await BuildCreateBillId(input);
246   - try
  245 + for (var attempt = 0; attempt < 10; attempt++)
247 246 {
248   - //开启事务
249   - _db.BeginTran();
250   -
251   - //新增应收款增加减少记录
252   - var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
253   -
254   - var wtYskzjjsMxEntityList = input.wtYskzjjsMxList.Adapt<List<WtYskzjjsMxEntity>>();
255   - if(wtYskzjjsMxEntityList != null)
  247 + try
256 248 {
257   - foreach (var item in wtYskzjjsMxEntityList)
  249 + _db.BeginTran();
  250 + entity.Id = await BuildCreateBillId(input);
  251 + var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
  252 +
  253 + var wtYskzjjsMxEntityList = input.wtYskzjjsMxList.Adapt<List<WtYskzjjsMxEntity>>();
  254 + if (wtYskzjjsMxEntityList != null)
258 255 {
259   - item.Id = YitIdHelper.NextId().ToString();
260   - item.Djbh = newEntity.Id;
  256 + foreach (var item in wtYskzjjsMxEntityList)
  257 + {
  258 + item.Id = YitIdHelper.NextId().ToString();
  259 + item.Djbh = newEntity.Id;
  260 + }
  261 + await _db.Insertable(wtYskzjjsMxEntityList).ExecuteCommandAsync();
261 262 }
262   - await _db.Insertable(wtYskzjjsMxEntityList).ExecuteCommandAsync();
263   - }
264   -
265   - await _wtDjzyService.UpsertFromYskzjjsAsync(newEntity.Id);
266 263  
267   - //关闭事务
268   - _db.CommitTran();
269   - }
270   - catch (Exception)
271   - {
272   - //回滚事务
273   - _db.RollbackTran();
274   - throw NCCException.Oh(ErrorCode.COM1000);
  264 + await _wtDjzyService.UpsertFromYskzjjsAsync(newEntity.Id);
  265 + _db.CommitTran();
  266 + return;
  267 + }
  268 + catch (Exception ex)
  269 + {
  270 + try { _db.RollbackTran(); } catch { /* ignore */ }
  271 + var msg = ex.Message ?? "";
  272 + if (msg.Contains("Duplicate", StringComparison.OrdinalIgnoreCase) && attempt < 9)
  273 + continue;
  274 + throw NCCException.Oh(ErrorCode.COM1000);
  275 + }
275 276 }
276 277 }
277 278  
278 279 /// <summary>
279   - /// 构建新增单据编号:转款单使用 ZK+日期+流水,其它单据保持原雪花 ID 规则不变
  280 + /// 构建新增单据编号:转款单 ZK+日期+流水,现金费用单 XJFYD+日期+流水,其他应收单 QTSRD+日期+流水(与 WtFysrd 其他收入单共用同日序列),其余沿用雪花 ID
280 281 /// </summary>
281 282 /// <param name="input">新增入参</param>
282 283 private async Task<string> BuildCreateBillId(WtYskzjjsCrInput input)
... ... @@ -304,6 +305,37 @@ namespace NCC.Extend.WtYskzjjs
304 305 return $"{cashPrefix}{cashToday}{cashSerial:D4}";
305 306 }
306 307  
  308 + // 其他应收单:与「其他收入单」表(WtFysrd)统一使用 QTSRD+yyyyMMdd+4 位流水。qt 页归并到此处后 djlx 为「其他应收单」,与独立「其他收入单」主表同前缀同序列。
  309 + if (input != null
  310 + && !string.IsNullOrWhiteSpace(input.djlx)
  311 + && input.djlx.Trim().Equals("其他应收单", StringComparison.Ordinal))
  312 + {
  313 + const string qtsrd = "QTSRD";
  314 + var ymd = DateTime.Now.ToString("yyyyMMdd");
  315 + var head = qtsrd + ymd;
  316 + var a = await _db.Queryable<WtFysrdEntity>()
  317 + .Where(x => x.Id != null && x.Id.StartsWith(head))
  318 + .Select(x => x.Id)
  319 + .ToListAsync();
  320 + var b = await _db.Queryable<WtYskzjjsEntity>()
  321 + .Where(x => x.Id != null && x.Id.StartsWith(head))
  322 + .Select(x => x.Id)
  323 + .ToListAsync();
  324 + var idRows = a.Concat(b);
  325 + var maxSerial = 0;
  326 + foreach (var bid in idRows)
  327 + {
  328 + if (string.IsNullOrEmpty(bid) || bid.Length < head.Length + 4) continue;
  329 + if (!bid.StartsWith(head, StringComparison.Ordinal)) continue;
  330 + var tail = bid.Substring(bid.Length - 4);
  331 + if (int.TryParse(tail, out var n) && n > maxSerial) maxSerial = n;
  332 + }
  333 + var next = maxSerial + 1;
  334 + if (next > 9999)
  335 + throw NCCException.Bah("当日 QTSRD 单号流水已超过 9999,请联系管理员");
  336 + return $"{head}{next:D4}";
  337 + }
  338 +
307 339 if (!IsTransferBill(input))
308 340 {
309 341 return YitIdHelper.NextId().ToString();
... ... @@ -1069,6 +1101,25 @@ namespace NCC.Extend.WtYskzjjs
1069 1101 return;
1070 1102 }
1071 1103  
  1104 + if (lx.Contains("现金费用单", StringComparison.Ordinal))
  1105 + {
  1106 + if (input.djrq == null) throw NCCException.Oh("请选择单据日期");
  1107 + if (string.IsNullOrWhiteSpace(input.jsr)) throw NCCException.Oh("请选择经手人");
  1108 + if (string.IsNullOrWhiteSpace(input.fkzh)) throw NCCException.Oh("请选择付款账户");
  1109 + if (input.wtYskzjjsMxList == null || input.wtYskzjjsMxList.Count == 0)
  1110 + throw NCCException.Oh("请至少添加一条明细");
  1111 + for (var i = 0; i < input.wtYskzjjsMxList.Count; i++)
  1112 + {
  1113 + var row = input.wtYskzjjsMxList[i];
  1114 + if (row == null) throw NCCException.Oh($"第 {i + 1} 行明细不能为空");
  1115 + if (string.IsNullOrWhiteSpace(row.fyxmmc)) throw NCCException.Oh($"第 {i + 1} 行费用项目为必填");
  1116 + if (row.je <= 0) throw NCCException.Oh($"第 {i + 1} 行原币金额须大于 0");
  1117 + }
  1118 + var sumCash = SumMxJe(input);
  1119 + if (sumCash <= 0) throw NCCException.Oh("明细原币金额合计须大于 0");
  1120 + return;
  1121 + }
  1122 +
1072 1123 if (input.djrq == null) throw NCCException.Oh("请选择单据日期");
1073 1124 if (string.IsNullOrWhiteSpace(input.jsr)) throw NCCException.Oh("请选择经手人");
1074 1125 if (string.IsNullOrWhiteSpace(input.wldw)) throw NCCException.Oh("请选择往来单位");
... ...
Antis.Erp.Plat/netcore/src/Modularity/VisualDev/NCC.VisualDev/DashboardService.cs
... ... @@ -107,12 +107,48 @@ namespace NCC.VisualDev
107 107 /// </summary>
108 108 /// <param name="currentPage">页码,从 1 开始</param>
109 109 /// <param name="pageSize">每页条数,默认 20,最大 100</param>
  110 + /// <param name="djmc">
  111 + /// 与「审核人员设置」wt_shrysz.djmc 一致;不传或 all=全部;_other_ 为待办 billType 未出现在 wt_shrysz 中的单据
  112 + /// </param>
  113 + /// <param name="includeStats">
  114 + /// 为 true 时一并返回左侧分类条数(与一次 GetExtendBillTodoListAsync 共用,避免与 MyFlowTodoStats 重复查库)
  115 + /// </param>
110 116 [HttpGet("MyFlowTodo")]
111   - public async Task<dynamic> GetMyFlowTodo([FromQuery] int currentPage = 1, [FromQuery] int pageSize = 20)
  117 + public async Task<dynamic> GetMyFlowTodo(
  118 + [FromQuery] int currentPage = 1,
  119 + [FromQuery] int pageSize = 20,
  120 + [FromQuery] string djmc = null,
  121 + [FromQuery] bool includeStats = false)
112 122 {
113 123 var userId = _userManager.UserId;
114 124 var userAccount = await ResolveUserAccountForTodoAsync(userId);
115   - var all = await GetExtendBillTodoListAsync(userId, userAccount);
  125 + var allFullTask = GetExtendBillTodoListAsync(userId, userAccount);
  126 + var djmcsTask = GetOrderedShryszDistinctDjmcsAsync();
  127 + await Task.WhenAll(allFullTask, djmcsTask);
  128 + var allFull = await allFullTask;
  129 + var djmcsFromShrysz = await djmcsTask;
  130 +
  131 + object statsBlock = null;
  132 + if (includeStats)
  133 + {
  134 + var (navTotal, navItems) = BuildMyFlowTodoNavModel(allFull, djmcsFromShrysz);
  135 + statsBlock = new { total = navTotal, items = navItems };
  136 + }
  137 +
  138 + var all = allFull;
  139 + if (!string.IsNullOrWhiteSpace(djmc) && !string.Equals(djmc.Trim(), "all", StringComparison.OrdinalIgnoreCase))
  140 + {
  141 + var p = djmc.Trim();
  142 + if (string.Equals(p, "_other_", StringComparison.OrdinalIgnoreCase) && djmcsFromShrysz.Count > 0)
  143 + {
  144 + all = all
  145 + .Where(x => !djmcsFromShrysz.Any(
  146 + c => BillTypeMatchesShryszDjmc(x.billType, c)))
  147 + .ToList();
  148 + }
  149 + else
  150 + all = all.Where(x => BillTypeMatchesShryszDjmc(x.billType, p)).ToList();
  151 + }
116 152  
117 153 if (currentPage < 1) currentPage = 1;
118 154 if (pageSize < 1) pageSize = 20;
... ... @@ -130,11 +166,160 @@ namespace NCC.VisualDev
130 166 total,
131 167 currentPage,
132 168 pageSize
133   - }
  169 + },
  170 + stats = statsBlock
134 171 };
135 172 }
136 173  
137 174 /// <summary>
  175 + /// 待办中心左侧条数(独立接口保留兼容;与 <see cref="GetMyFlowTodo"/> includeStats 复用同一段逻辑,仍会查库一次)
  176 + /// </summary>
  177 + [HttpGet("MyFlowTodoStats")]
  178 + public async Task<dynamic> GetMyFlowTodoStats()
  179 + {
  180 + var userId = _userManager.UserId;
  181 + var userAccount = await ResolveUserAccountForTodoAsync(userId);
  182 + var allTask = GetExtendBillTodoListAsync(userId, userAccount);
  183 + var djmcsTask = GetOrderedShryszDistinctDjmcsAsync();
  184 + await Task.WhenAll(allTask, djmcsTask);
  185 + var all = await allTask;
  186 + var djmcsFromShrysz = await djmcsTask;
  187 + var (total, items) = BuildMyFlowTodoNavModel(all, djmcsFromShrysz);
  188 + return new { total, items };
  189 + }
  190 +
  191 + /// <summary>左侧「单据类型」+ 条数(基于全量待办,不含 djmc 筛选)</summary>
  192 + private (int total, List<object> items) BuildMyFlowTodoNavModel(
  193 + List<FlowTodoOutput> all,
  194 + List<string> djmcsFromShrysz)
  195 + {
  196 + if (all == null) all = new List<FlowTodoOutput>();
  197 + if (djmcsFromShrysz == null) djmcsFromShrysz = new List<string>();
  198 +
  199 + var navFromConfig = djmcsFromShrysz.Count > 0;
  200 + var navKeys = navFromConfig
  201 + ? djmcsFromShrysz
  202 + : GetOrderedDistinctBillTypesFromTodoList(all);
  203 + var items = new List<object>();
  204 + foreach (var d in navKeys)
  205 + {
  206 + var count = all.Count(x => BillTypeMatchesShryszDjmc(x.billType, d));
  207 + items.Add(new
  208 + {
  209 + djmc = d,
  210 + label = ShryszDjmcToDisplayLabel(d),
  211 + count
  212 + });
  213 + }
  214 + if (navFromConfig)
  215 + {
  216 + var other = all.Count(x => !djmcsFromShrysz.Any(
  217 + c => BillTypeMatchesShryszDjmc(x.billType, c)));
  218 + if (other > 0)
  219 + {
  220 + items.Add(new
  221 + {
  222 + djmc = "_other_",
  223 + label = "其他",
  224 + count = other
  225 + });
  226 + }
  227 + }
  228 + return (all.Count, items);
  229 + }
  230 +
  231 + /// <summary>与 <c>wt_shrysz</c> 中「单据名称」顺序一致,与审核页 djmc 选项同序(<c>wtShrysz/djmcOptions.js</c>)</summary>
  232 + private static readonly string[] ShryszDjmcCanonicalOrder =
  233 + {
  234 + "同价调拨单", "销售出库单", "预售出库单", "销售退货单", "预售退货单", "采购入库单",
  235 + "委托代销发货单", "委托代销退货单", "委托代销结算单", "商品调价单",
  236 + "收款单", "付款单", "费用单", "现金费用单", "待摊费用摊销", "其他应收单", "其他收入单", "转款单",
  237 + "赠送单", "获赠单", "会员到期退款", "拆装单"
  238 + };
  239 +
  240 + private async Task<List<string>> GetOrderedShryszDistinctDjmcsAsync()
  241 + {
  242 + try
  243 + {
  244 + EnsureWtShryszAuditColumnsForTodo();
  245 + if (!_db.DbMaintenance.IsAnyTable("wt_shrysz")) return new List<string>();
  246 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(
  247 + "SELECT DISTINCT TRIM(djmc) AS djmc FROM wt_shrysz WHERE TRIM(IFNULL(djmc,'')) <> ''");
  248 + if (rows == null || rows.Count == 0) return new List<string>();
  249 + var rem = new List<string>();
  250 + foreach (var r in rows)
  251 + {
  252 + var s = Convert.ToString(r.djmc)?.Trim();
  253 + if (string.IsNullOrEmpty(s)) continue;
  254 + if (rem.Any(x => string.Equals(x, s, StringComparison.OrdinalIgnoreCase)))
  255 + continue;
  256 + rem.Add(s);
  257 + }
  258 + return OrderShryszDjmcsList(rem);
  259 + }
  260 + catch
  261 + {
  262 + return new List<string>();
  263 + }
  264 + }
  265 +
  266 + private static List<string> GetOrderedDistinctBillTypesFromTodoList(List<FlowTodoOutput> all)
  267 + {
  268 + if (all == null || all.Count == 0) return new List<string>();
  269 + var rem = all
  270 + .Select(x => (x.billType ?? string.Empty).Trim())
  271 + .Where(s => s.Length > 0)
  272 + .GroupBy(s => s, StringComparer.OrdinalIgnoreCase)
  273 + .Select(g => g.First())
  274 + .ToList();
  275 + return OrderShryszDjmcsList(rem);
  276 + }
  277 +
  278 + private static List<string> OrderShryszDjmcsList(List<string> inDb)
  279 + {
  280 + if (inDb == null || inDb.Count == 0) return new List<string>();
  281 + var rem = inDb.ToList();
  282 + var outl = new List<string>();
  283 + foreach (var canon in ShryszDjmcCanonicalOrder)
  284 + {
  285 + var idx = rem.FindIndex(x => string.Equals(x, canon, StringComparison.OrdinalIgnoreCase));
  286 + if (idx < 0) continue;
  287 + outl.Add(rem[idx]);
  288 + rem.RemoveAt(idx);
  289 + }
  290 + rem.Sort(StringComparer.OrdinalIgnoreCase);
  291 + outl.AddRange(rem);
  292 + return outl;
  293 + }
  294 +
  295 + /// <summary>待办行的 billType 与审核设置中 djmc 是否同一单据(含会员到期:收款/退款、大小写差异)</summary>
  296 + private static bool BillTypeMatchesShryszDjmc(string billType, string shryszDjmc)
  297 + {
  298 + if (string.IsNullOrWhiteSpace(billType) || string.IsNullOrWhiteSpace(shryszDjmc)) return false;
  299 + var t = billType.Trim();
  300 + var d = shryszDjmc.Trim();
  301 + if (string.Equals(t, d, StringComparison.OrdinalIgnoreCase)) return true;
  302 + if (IsHyDqShoukuan(t) && IsHyDqShoukuan(d)) return true;
  303 + return false;
  304 + }
  305 +
  306 + private static bool IsHyDqShoukuan(string s)
  307 + {
  308 + return string.Equals(s, "会员到期收款", StringComparison.OrdinalIgnoreCase)
  309 + || string.Equals(s, "会员到期退款", StringComparison.OrdinalIgnoreCase);
  310 + }
  311 +
  312 + private static string ShryszDjmcToDisplayLabel(string djm)
  313 + {
  314 + if (string.IsNullOrWhiteSpace(djm)) return djm;
  315 + if (string.Equals(djm.Trim(), "会员到期退款", StringComparison.OrdinalIgnoreCase))
  316 + return "会员到期收款";
  317 + if (string.Equals(djm, "_other_", StringComparison.OrdinalIgnoreCase))
  318 + return "其他";
  319 + return djm.Trim();
  320 + }
  321 +
  322 + /// <summary>
138 323 /// 获取未读邮件
139 324 /// </summary>
140 325 [HttpGet("Email")]
... ... @@ -389,7 +574,8 @@ namespace NCC.VisualDev
389 574 }
390 575  
391 576 /// <summary>
392   - /// 扩展业务待办:同价调拨单、销售/预售出库、销售/预售退货、采购入库单、委托代销发/退/结算(wt_xsckd);商品调价单(wt_tjd + wt_shrysz);拆装单(wt_czd)等
  577 + /// 扩展业务待办:同价调拨单、销售/预售出库、销售/预售退货、采购入库单、委托代销发/退/结算(wt_xsckd);商品调价单(wt_tjd + wt_shrysz);拆装单(wt_czd)等。
  578 + /// 各单据查询使用 Task.WhenAll 并行,避免串行累加延迟(原约 20+ 次往返易达数秒)。
393 579 /// </summary>
394 580 private async Task<List<FlowTodoOutput>> GetExtendBillTodoListAsync(string userId, string userAccount)
395 581 {
... ... @@ -398,33 +584,42 @@ namespace NCC.VisualDev
398 584 EnsureWtYskzjjsAuditColumnsForTodo();
399 585 EnsureWtCzdAuditColumnsForTodo();
400 586 var roleIds = await ResolveUserRoleIdsForTodoAsync(userId);
401   - var list = new List<FlowTodoOutput>();
402   - list.AddRange(await QueryWtXsckdAuditTodosAsync("同价调拨单", userId, userAccount, roleIds, null));
403   - // 仅单据来源为「后台」或未标记(ly 空)的销售出库单进待办;备注「抖音订单:」同后端免审规则
404   - list.AddRange(await QueryWtXsckdAuditTodosAsync("销售出库单", userId, userAccount, roleIds,
405   - @"( TRIM(IFNULL(d.ly, '')) = '' OR TRIM(IFNULL(d.ly, '')) = '后台' )
406   - AND NOT ( TRIM(IFNULL(d.bz, '')) <> '' AND TRIM(d.bz) LIKE '抖音订单:%' )"));
407   - // 与销售出库单一致:收银台/抖音等非后台来源不进入待办
408   - list.AddRange(await QueryWtXsckdAuditTodosAsync("预售出库单", userId, userAccount, roleIds,
409   - @"( TRIM(IFNULL(d.ly, '')) = '' OR TRIM(IFNULL(d.ly, '')) = '后台' )
410   - AND NOT ( TRIM(IFNULL(d.bz, '')) <> '' AND TRIM(d.bz) LIKE '抖音订单:%' )"));
411   - list.AddRange(await QueryWtXsckdAuditTodosAsync("销售退货单", userId, userAccount, roleIds, null));
412   - list.AddRange(await QueryWtXsckdAuditTodosAsync("预售退货单", userId, userAccount, roleIds, null));
413   - list.AddRange(await QueryWtXsckdAuditTodosAsync("采购入库单", userId, userAccount, roleIds, null));
414   - list.AddRange(await QueryWtXsckdAuditTodosAsync("委托代销发货单", userId, userAccount, roleIds, null));
415   - list.AddRange(await QueryWtXsckdAuditTodosAsync("委托代销退货单", userId, userAccount, roleIds, null));
416   - list.AddRange(await QueryWtXsckdAuditTodosAsync("委托代销结算单", userId, userAccount, roleIds, null));
417   - list.AddRange(await QueryWtXsckdAuditTodosAsync("赠送单", userId, userAccount, roleIds, null));
418   - list.AddRange(await QueryWtTjdAuditTodosAsync(userId, userAccount, roleIds));
419   - list.AddRange(await QueryWtFysrdQtsrAuditTodosAsync(userId, userAccount, roleIds));
420   - list.AddRange(await QueryWtYskzjjsQtAuditTodosAsync(userId, userAccount, roleIds));
421   - list.AddRange(await QueryWtYskzjjsXjAuditTodosAsync(userId, userAccount, roleIds));
422   - list.AddRange(await QueryWtYskzjjsHyAuditTodosAsync(userId, userAccount, roleIds));
423   - list.AddRange(await QueryWtYskzjjsZkAuditTodosAsync(userId, userAccount, roleIds));
424   - list.AddRange(await QueryWtCwdjSkdAuditTodosAsync(userId, userAccount, roleIds));
425   - list.AddRange(await QueryWtCwdjFydAuditTodosAsync(userId, userAccount, roleIds));
426   - list.AddRange(await QueryWtCwdjDtfytxAuditTodosAsync(userId, userAccount, roleIds));
427   - list.AddRange(await QueryWtCzdAuditTodosAsync(userId, userAccount, roleIds));
  587 + // 仅后台销售/预售出库进待办;抖音订单备注同免审规则
  588 + const string xsckLyForHoutai = @"( TRIM(IFNULL(d.ly, '')) = '' OR TRIM(IFNULL(d.ly, '')) = '后台' )
  589 + AND NOT ( TRIM(IFNULL(d.bz, '')) <> '' AND TRIM(d.bz) LIKE '抖音订单:%' )";
  590 +
  591 + var queryTasks = new Task<List<FlowTodoOutput>>[]
  592 + {
  593 + QueryWtXsckdAuditTodosAsync("同价调拨单", userId, userAccount, roleIds, null),
  594 + QueryWtXsckdAuditTodosAsync("销售出库单", userId, userAccount, roleIds, xsckLyForHoutai),
  595 + QueryWtXsckdAuditTodosAsync("预售出库单", userId, userAccount, roleIds, xsckLyForHoutai),
  596 + QueryWtXsckdAuditTodosAsync("销售退货单", userId, userAccount, roleIds, null),
  597 + QueryWtXsckdAuditTodosAsync("预售退货单", userId, userAccount, roleIds, null),
  598 + QueryWtXsckdAuditTodosAsync("采购入库单", userId, userAccount, roleIds, null),
  599 + QueryWtXsckdAuditTodosAsync("委托代销发货单", userId, userAccount, roleIds, null),
  600 + QueryWtXsckdAuditTodosAsync("委托代销退货单", userId, userAccount, roleIds, null),
  601 + QueryWtXsckdAuditTodosAsync("委托代销结算单", userId, userAccount, roleIds, null),
  602 + QueryWtXsckdAuditTodosAsync("赠送单", userId, userAccount, roleIds, null),
  603 + QueryWtXsckdAuditTodosAsync("获赠单", userId, userAccount, roleIds, null),
  604 + QueryWtTjdAuditTodosAsync(userId, userAccount, roleIds),
  605 + QueryWtFysrdQtsrAuditTodosAsync(userId, userAccount, roleIds),
  606 + QueryWtYskzjjsQtAuditTodosAsync(userId, userAccount, roleIds),
  607 + QueryWtYskzjjsXjAuditTodosAsync(userId, userAccount, roleIds),
  608 + QueryWtYskzjjsHyAuditTodosAsync(userId, userAccount, roleIds),
  609 + QueryWtYskzjjsZkAuditTodosAsync(userId, userAccount, roleIds),
  610 + QueryWtCwdjSkdAuditTodosAsync(userId, userAccount, roleIds),
  611 + QueryWtCwdjFkdAuditTodosAsync(userId, userAccount, roleIds),
  612 + QueryWtCwdjFydAuditTodosAsync(userId, userAccount, roleIds),
  613 + QueryWtCwdjDtfytxAuditTodosAsync(userId, userAccount, roleIds),
  614 + QueryWtCzdAuditTodosAsync(userId, userAccount, roleIds)
  615 + };
  616 +
  617 + var chunks = await Task.WhenAll(queryTasks);
  618 + var list = new List<FlowTodoOutput>(512);
  619 + foreach (var part in chunks)
  620 + {
  621 + if (part != null && part.Count > 0) list.AddRange(part);
  622 + }
428 623 return list.OrderByDescending(x => x.creatorTime).ToList();
429 624 }
430 625  
... ... @@ -523,6 +718,9 @@ namespace NCC.VisualDev
523 718 /// <summary>与 <c>wt_shrysz.djmc</c>、财务单据 djlx 一致</summary>
524 719 private const string CwdjSkdTodoBillName = "收款单";
525 720  
  721 + /// <summary>与 <c>wt_shrysz.djmc</c>、<c>wt_cwdj.djlx</c> 一致</summary>
  722 + private const string CwdjFkdTodoBillName = "付款单";
  723 +
526 724 /// <summary>与 <c>wt_shrysz.djmc</c>、<c>wt_cwdj.djlx</c> 一致(财务费用单,非现金费用单)</summary>
527 725 private const string CwdjFydTodoBillName = "费用单";
528 726  
... ... @@ -1154,6 +1352,66 @@ ORDER BY d.ldrq DESC&quot;;
1154 1352 }
1155 1353  
1156 1354 /// <summary>
  1355 + /// 付款单待办(wt_cwdj + wt_shrysz),与收款单同为一级审核
  1356 + /// </summary>
  1357 + private async Task<List<FlowTodoOutput>> QueryWtCwdjFkdAuditTodosAsync(string userId, string userAccount, HashSet<string> roleIds)
  1358 + {
  1359 + var result = new List<FlowTodoOutput>();
  1360 + const string sql = @"
  1361 +SELECT d.F_Id AS id, d.ldrq, d.djzt,
  1362 + IFNULL(NULLIF(TRIM(s.shr1), ''), s.shr) AS shr1_eff,
  1363 + TRIM(IFNULL(s.shr1Mode, '')) AS shr1Mode_eff,
  1364 + TRIM(IFNULL(s.shrRole1, '')) AS shrRole1_eff
  1365 +FROM wt_cwdj d
  1366 +LEFT JOIN wt_shrysz s ON TRIM(s.djmc) = @billName
  1367 +WHERE TRIM(IFNULL(d.djlx,'')) IN ('付款单')
  1368 + AND IFNULL(d.djzt, '') <> '草稿'
  1369 + AND IFNULL(d.djzt, '') <> '已审核'
  1370 + AND IFNULL(d.djzt, '') <> '审核不通过'
  1371 + AND (
  1372 + d.djzt IN ('待审核')
  1373 + OR d.djzt IS NULL
  1374 + OR TRIM(IFNULL(d.djzt, '')) = ''
  1375 + )
  1376 +ORDER BY d.ldrq DESC";
  1377 +
  1378 + var rows = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { billName = CwdjFkdTodoBillName });
  1379 + if (rows == null || rows.Count == 0) return result;
  1380 +
  1381 + foreach (var row in rows)
  1382 + {
  1383 + var configUsers = Convert.ToString(row.shr1_eff);
  1384 + var configRoles = Convert.ToString(row.shrRole1_eff);
  1385 + var configMode = Convert.ToString(row.shr1Mode_eff)?.Trim() ?? "";
  1386 + if (string.Equals(configMode, "user", StringComparison.OrdinalIgnoreCase))
  1387 + configRoles = "";
  1388 + else if (string.Equals(configMode, "role", StringComparison.OrdinalIgnoreCase))
  1389 + configUsers = "";
  1390 + if (!string.IsNullOrWhiteSpace(configUsers) || !string.IsNullOrWhiteSpace(configRoles))
  1391 + {
  1392 + if (!IsInAuditUsersOrRoles(configUsers, configRoles, userId, userAccount, roleIds))
  1393 + continue;
  1394 + }
  1395 +
  1396 + DateTime? createTime = null;
  1397 + var rawTime = Convert.ToString(row.ldrq);
  1398 + if (DateTime.TryParse(rawTime, out DateTime dt))
  1399 + createTime = dt;
  1400 +
  1401 + var id = Convert.ToString(row.id);
  1402 + result.Add(new FlowTodoOutput
  1403 + {
  1404 + id = id,
  1405 + fullName = $"{CwdjFkdTodoBillName} {id} - 一级审核待办",
  1406 + creatorTime = createTime,
  1407 + billType = CwdjFkdTodoBillName
  1408 + });
  1409 + }
  1410 +
  1411 + return result;
  1412 + }
  1413 +
  1414 + /// <summary>
1157 1415 /// 费用单待办(wt_cwdj.djlx = 费用单 + wt_shrysz),与收款单同为一级审核
1158 1416 /// </summary>
1159 1417 private async Task<List<FlowTodoOutput>> QueryWtCwdjFydAuditTodosAsync(string userId, string userAccount, HashSet<string> roleIds)
... ...
Antis.Erp.Plat/sy/css/pos-unified.css
... ... @@ -678,44 +678,115 @@ body.pos-page--embed .pos-embed-select {
678 678 box-sizing: border-box;
679 679 }
680 680  
681   -/* ========= 收银首页 home.html:页顶业务提示(WebView 常屏蔽 alert) ========= */
  681 +/* ========= 收银首页 home.html:业务提示(全屏遮罩 + 居中大卡片,与内联样式一致) ========= */
682 682 .pos-page .pos-home-toast {
683 683 position: fixed;
684   - top: max(10px, env(safe-area-inset-top, 0px));
685   - left: 50%;
686   - transform: translateX(-50%);
  684 + inset: 0;
687 685 z-index: 100050;
688 686 display: flex;
689   - align-items: flex-start;
690   - gap: 12px;
691   - max-width: min(560px, calc(100vw - 24px));
692   - padding: 14px 16px;
693   - border-radius: 12px;
694   - background: #fff;
695   - border: 1px solid var(--pos-border);
696   - box-shadow: 0 12px 40px rgba(15, 23, 42, 0.15);
  687 + align-items: center;
  688 + justify-content: center;
  689 + padding: max(20px, env(safe-area-inset-top, 0px)) 24px max(20px, env(safe-area-inset-bottom, 0px)) 24px;
697 690 box-sizing: border-box;
  691 + background: rgba(15, 23, 42, 0.52);
  692 + backdrop-filter: blur(4px);
  693 + -webkit-backdrop-filter: blur(4px);
698 694 }
699 695  
700   -.pos-page .pos-home-toast__text {
701   - flex: 1 1 auto;
  696 +.pos-page .pos-home-toast__panel {
  697 + position: relative;
  698 + display: block;
  699 + width: 100%;
  700 + min-width: min(360px, calc(100vw - 32px));
  701 + max-width: min(500px, calc(100vw - 32px));
  702 + max-height: min(72vh, 560px);
  703 + overflow-x: hidden;
  704 + overflow-y: auto;
  705 + padding: 0;
  706 + border-radius: 20px;
  707 + background: linear-gradient(180deg, #f8faff 0%, #ffffff 28%, #ffffff 100%);
  708 + border: 1px solid rgba(226, 232, 240, 0.95);
  709 + box-shadow:
  710 + 0 0 0 1px rgba(255, 255, 255, 0.6) inset,
  711 + 0 8px 12px -4px rgba(15, 23, 42, 0.08),
  712 + 0 24px 56px -12px rgba(15, 23, 42, 0.28),
  713 + 0 48px 80px -20px rgba(79, 108, 248, 0.12);
  714 + box-sizing: border-box;
  715 +}
  716 +
  717 +.pos-page .pos-home-toast__accent {
  718 + display: block;
  719 + height: 6px;
  720 + background: linear-gradient(90deg, #4f6cf8 0%, #5c7cfa 35%, #818cf8 70%, #a5b4fc 100%);
  721 +}
  722 +
  723 +.pos-page .pos-home-toast__main {
  724 + padding: 32px 36px 40px;
  725 + display: flex;
  726 + flex-direction: column;
  727 + align-items: center;
  728 + text-align: center;
  729 + gap: 24px;
  730 + box-sizing: border-box;
  731 +}
  732 +
  733 +.pos-page .pos-home-toast__icon {
  734 + width: 88px;
  735 + height: 88px;
  736 + border-radius: 50%;
  737 + background: linear-gradient(160deg, #eef2ff 0%, #e0e7ff 45%, #c7d2fe 100%);
  738 + color: var(--pos-blue-dark);
  739 + display: flex;
  740 + align-items: center;
  741 + justify-content: center;
  742 + box-shadow:
  743 + 0 0 0 10px rgba(92, 124, 250, 0.12),
  744 + 0 12px 28px rgba(79, 108, 248, 0.28);
  745 +}
  746 +
  747 +.pos-page .pos-home-toast__icon svg {
  748 + width: 44px;
  749 + height: 44px;
  750 + display: block;
  751 +}
  752 +
  753 +.pos-page .pos-home-toast__body {
  754 + width: 100%;
702 755 min-width: 0;
  756 +}
  757 +
  758 +.pos-page .pos-home-toast__label {
  759 + margin: 0 0 10px;
703 760 font-size: 14px;
704   - line-height: 1.5;
705 761 font-weight: 600;
  762 + letter-spacing: 0.2em;
  763 + color: var(--pos-muted);
  764 +}
  765 +
  766 +.pos-page .pos-home-toast__text {
  767 + margin: 0;
  768 + font-size: clamp(18px, 2.2vw, 22px);
  769 + line-height: 1.5;
  770 + font-weight: 700;
706 771 color: var(--pos-text);
  772 + letter-spacing: 0.02em;
  773 + overflow-wrap: anywhere;
  774 + word-break: break-word;
707 775 }
708 776  
709 777 .pos-page .pos-home-toast__close {
710   - flex-shrink: 0;
711   - width: 36px;
712   - height: 36px;
713   - margin: -4px -6px -4px 0;
  778 + position: absolute;
  779 + top: 12px;
  780 + right: 12px;
  781 + z-index: 2;
  782 + width: 44px;
  783 + height: 44px;
  784 + margin: 0;
714 785 border: none;
715   - border-radius: 10px;
716   - background: transparent;
  786 + border-radius: 12px;
  787 + background: rgba(255, 255, 255, 0.92);
717 788 color: var(--pos-muted);
718   - font-size: 22px;
  789 + font-size: 24px;
719 790 line-height: 1;
720 791 cursor: pointer;
721 792 touch-action: manipulation;
... ... @@ -724,11 +795,14 @@ body.pos-page--embed .pos-embed-select {
724 795 display: inline-flex;
725 796 align-items: center;
726 797 justify-content: center;
  798 + box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
  799 + transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
727 800 }
728 801  
729 802 .pos-page .pos-home-toast__close:hover {
730 803 background: #f1f5f9;
731 804 color: var(--pos-text);
  805 + box-shadow: 0 4px 12px rgba(15, 23, 42, 0.1);
732 806 }
733 807  
734 808 /* ========= 与 settlement 一致的弹层/卡片圆角(首页等复用 class 时生效) ========= */
... ...
Antis.Erp.Plat/sy/home.html
... ... @@ -5,7 +5,7 @@
5 5 <meta charset="UTF-8">
6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 7 <link rel="stylesheet" href="css/style.css?id=1">
8   - <link rel="stylesheet" href="css/pos-unified.css">
  8 + <link rel="stylesheet" href="css/pos-unified.css?v=20260424-3">
9 9 <script src="./js/vue.min.js"></script>
10 10 <script src="./axios-1.x/axios-1.x/dist/axios.js"></script>
11 11 <script src="./axios-1.x/axios-1.x/dist/axios.min.js"></script>
... ... @@ -45,6 +45,12 @@
45 45 animation: slideUp 0.3s ease;
46 46 }
47 47  
  48 + /* 选择会员弹窗:加宽并略放大,与收银视口比例协调 */
  49 + .modal-content.home-member-picker {
  50 + max-width: min(720px, 96vw) !important;
  51 + width: 100%;
  52 + }
  53 +
48 54 @keyframes slideUp {
49 55 from {
50 56 transform: translateY(20px);
... ... @@ -143,7 +149,7 @@
143 149 }
144 150  
145 151 .member-list {
146   - max-height: 400px;
  152 + max-height: min(420px, 50vh);
147 153 overflow-y: auto;
148 154 margin-top: 16px;
149 155 border: 1px solid #f0f0f0;
... ... @@ -268,6 +274,145 @@
268 274 box-shadow: 0 4px 12px rgba(222, 83, 44, 0.3);
269 275 }
270 276  
  277 + /*
  278 + * 业务 Toast:全屏遮罩 + 居中大卡(内联保底)。收银台主提示:顶栏渐变、大图标、主文案加粗,整体更「大气」。
  279 + */
  280 + body.pos-page .pos-home-toast {
  281 + position: fixed !important;
  282 + top: 0 !important;
  283 + left: 0 !important;
  284 + right: 0 !important;
  285 + bottom: 0 !important;
  286 + width: 100% !important;
  287 + height: 100% !important;
  288 + max-width: none !important;
  289 + margin: 0 !important;
  290 + transform: none !important;
  291 + display: flex !important;
  292 + align-items: center !important;
  293 + justify-content: center !important;
  294 + padding: max(20px, env(safe-area-inset-top, 0px)) 24px max(20px, env(safe-area-inset-bottom, 0px)) 24px !important;
  295 + box-sizing: border-box !important;
  296 + background: rgba(15, 23, 42, 0.52) !important;
  297 + backdrop-filter: blur(4px) !important;
  298 + -webkit-backdrop-filter: blur(4px) !important;
  299 + z-index: 100050 !important;
  300 + }
  301 +
  302 + body.pos-page .pos-home-toast__panel {
  303 + position: relative !important;
  304 + display: block !important;
  305 + overflow: hidden !important;
  306 + width: 100% !important;
  307 + min-width: min(360px, calc(100vw - 32px)) !important;
  308 + max-width: min(500px, calc(100vw - 32px)) !important;
  309 + max-height: min(72vh, 560px) !important;
  310 + overflow-y: auto !important;
  311 + padding: 0 !important;
  312 + border-radius: 20px !important;
  313 + background: linear-gradient(180deg, #f8faff 0%, #ffffff 28%, #ffffff 100%) !important;
  314 + border: 1px solid rgba(226, 232, 240, 0.95) !important;
  315 + box-shadow:
  316 + 0 0 0 1px rgba(255, 255, 255, 0.6) inset,
  317 + 0 8px 12px -4px rgba(15, 23, 42, 0.08),
  318 + 0 24px 56px -12px rgba(15, 23, 42, 0.28),
  319 + 0 48px 80px -20px rgba(79, 108, 248, 0.12) !important;
  320 + box-sizing: border-box !important;
  321 + }
  322 +
  323 + body.pos-page .pos-home-toast__accent {
  324 + display: block !important;
  325 + height: 6px !important;
  326 + background: linear-gradient(90deg, #4f6cf8 0%, #5c7cfa 35%, #818cf8 70%, #a5b4fc 100%) !important;
  327 + }
  328 +
  329 + body.pos-page .pos-home-toast__main {
  330 + padding: 32px 36px 40px !important;
  331 + display: flex !important;
  332 + flex-direction: column !important;
  333 + align-items: center !important;
  334 + text-align: center !important;
  335 + gap: 24px !important;
  336 + box-sizing: border-box !important;
  337 + }
  338 +
  339 + body.pos-page .pos-home-toast__icon-wrap {
  340 + flex: 0 0 auto !important;
  341 + }
  342 +
  343 + body.pos-page .pos-home-toast__icon {
  344 + width: 88px !important;
  345 + height: 88px !important;
  346 + border-radius: 50% !important;
  347 + background: linear-gradient(160deg, #eef2ff 0%, #e0e7ff 45%, #c7d2fe 100%) !important;
  348 + color: #4f6cf8 !important;
  349 + display: flex !important;
  350 + align-items: center !important;
  351 + justify-content: center !important;
  352 + box-shadow:
  353 + 0 0 0 10px rgba(92, 124, 250, 0.12),
  354 + 0 12px 28px rgba(79, 108, 248, 0.28) !important;
  355 + }
  356 +
  357 + body.pos-page .pos-home-toast__icon svg {
  358 + width: 44px !important;
  359 + height: 44px !important;
  360 + display: block !important;
  361 + }
  362 +
  363 + body.pos-page .pos-home-toast__body {
  364 + width: 100% !important;
  365 + min-width: 0 !important;
  366 + }
  367 +
  368 + body.pos-page .pos-home-toast__label {
  369 + margin: 0 0 10px !important;
  370 + font-size: 14px !important;
  371 + font-weight: 600 !important;
  372 + letter-spacing: 0.2em !important;
  373 + color: #64748b !important;
  374 + }
  375 +
  376 + body.pos-page .pos-home-toast__text {
  377 + margin: 0 !important;
  378 + font-size: clamp(18px, 2.2vw, 22px) !important;
  379 + line-height: 1.5 !important;
  380 + font-weight: 700 !important;
  381 + color: #0f172a !important;
  382 + letter-spacing: 0.02em !important;
  383 + overflow-wrap: anywhere !important;
  384 + word-break: break-word !important;
  385 + }
  386 +
  387 + body.pos-page .pos-home-toast__close {
  388 + position: absolute !important;
  389 + top: 12px !important;
  390 + right: 12px !important;
  391 + z-index: 2 !important;
  392 + width: 44px !important;
  393 + height: 44px !important;
  394 + margin: 0 !important;
  395 + border: none !important;
  396 + border-radius: 12px !important;
  397 + background: rgba(255, 255, 255, 0.92) !important;
  398 + color: #64748b !important;
  399 + font-size: 24px !important;
  400 + line-height: 1 !important;
  401 + cursor: pointer !important;
  402 + padding: 0 !important;
  403 + display: inline-flex !important;
  404 + align-items: center !important;
  405 + justify-content: center !important;
  406 + box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08) !important;
  407 + transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease !important;
  408 + }
  409 +
  410 + body.pos-page .pos-home-toast__close:hover {
  411 + background: #f1f5f9 !important;
  412 + color: #0f172a !important;
  413 + box-shadow: 0 4px 12px rgba(15, 23, 42, 0.1) !important;
  414 + }
  415 +
271 416 </style>
272 417 </head>
273 418  
... ... @@ -277,10 +422,30 @@
277 422 <div
278 423 v-if="posToastMessage"
279 424 class="pos-home-toast"
280   - role="alert"
  425 + role="alertdialog"
  426 + aria-modal="true"
  427 + aria-live="assertive"
  428 + @click.self="clearPosToast"
281 429 >
282   - <span class="pos-home-toast__text">{{ posToastMessage }}</span>
283   - <button type="button" class="pos-home-toast__close" @click="clearPosToast" aria-label="关闭">&times;</button>
  430 + <div class="pos-home-toast__panel" @click.stop>
  431 + <span class="pos-home-toast__accent" aria-hidden="true"></span>
  432 + <button type="button" class="pos-home-toast__close" @click="clearPosToast" aria-label="关闭">&times;</button>
  433 + <div class="pos-home-toast__main">
  434 + <div class="pos-home-toast__icon-wrap">
  435 + <div class="pos-home-toast__icon" aria-hidden="true">
  436 + <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" focusable="false">
  437 + <circle cx="12" cy="12" r="9.25" stroke="currentColor" stroke-width="1.5" fill="none" />
  438 + <circle cx="12" cy="8" r="1.2" fill="currentColor" />
  439 + <path d="M12 11.25V16.5" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" />
  440 + </svg>
  441 + </div>
  442 + </div>
  443 + <div class="pos-home-toast__body">
  444 + <div class="pos-home-toast__label">温馨提示</div>
  445 + <p class="pos-home-toast__text">{{ posToastMessage }}</p>
  446 + </div>
  447 + </div>
  448 + </div>
284 449 </div>
285 450  
286 451 <!-- 弹出层 -->
... ...
Antis.Erp.Plat/sy/orders.html
... ... @@ -2206,7 +2206,7 @@
2206 2206 ? this.promiseAllLimited(taskFns, 8)
2207 2207 : Promise.resolve();
2208 2208 },
2209   - /** 与后端备注「已转换为销售出库单 CHDxxx」一致 */
  2209 + /** 与后端备注「已转换为销售出库单 CKDxxx」一致(历史可能为 CHD) */
2210 2210 parsePresaleConvertedChdId(bz) {
2211 2211 if (bz == null || bz === '') return '';
2212 2212 const m = String(bz).match(/已转换为销售出库单\s+([A-Za-z0-9]+)/);
... ...