Commit 41e33d6bb6770c71d5ca1e7cbe0b442804e50b7f
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
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 "@/views/wtYsckd/detail-view.vue"; |
| 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 "@/views/wtYskzjjs_hy/detail-view.vue"; |
| 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 '@/router' |
| 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) => { |
| 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(() => { |
| 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 () => { |
| 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) => { |
| 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(() => { |
| 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 = '816913803978474757' |
| 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 = '816913803978474757' |
| 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 => { |
| 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 'vue-router' |
| 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<any | null>(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) => { |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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 ('销售退货单','预售退货单','委托代销退货单') |
| 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}"; |
| 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}"; |
| 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}"; |
| 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}"; |
| 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}"; |
| 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}"; |
| 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}"; |
| 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"; |
| 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="关闭">×</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="关闭">×</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]+)/); | ... | ... |