From cd204a780822fd9518d61234b1314f130b0cde57 Mon Sep 17 00:00:00 2001 From: xiaojibeier Date: Fri, 17 Jul 2026 22:06:10 +0800 Subject: [PATCH] 20260717 --- jsconfig.json | 3 +- src/api/company.js | 6 +- src/api/costShareListed.js | 18 +- src/api/customer.js | 70 +- src/api/customerPrice.js | 427 +++++- src/api/globalTabRuntime.js | 37 +- src/api/prCost.js | 90 +- src/api/pricingTable.js | 23 + src/api/supplyCollectionGoods.js | 19 +- src/api/supplyCoordCollection.js | 172 ++- src/api/supplySupplier.js | 70 +- src/api/warehouseAlloc.js | 48 +- src/api/warehouseDeploy.js | 47 +- src/api/warehouseFdc.js | 27 + src/api/warehouseFdcAlloc.js | 188 +++ src/api/warehouseFreight.js | 90 +- src/api/warehouseLaborCost.js | 113 ++ src/api/warehousePurchase.js | 64 +- src/api/warehouseRdc.js | 41 + src/api/warehouseRdcDeploy.js | 30 +- src/api/warehouseWaste.js | 30 +- src/api/warehouseZone.js | 2 +- .../global-tab/GlobalTabCostPanel.vue | 62 +- .../global-tab/GlobalTabWarehousePanel.vue | 562 ++++--- .../scene/SceneGoodsAdvancedSearchDlg.vue | 2 +- .../warehouse/WhWarehouseLogAddDlg.vue | 587 ++++++++ src/components/warehouse/WopsGoodsListDlg.vue | 144 ++ src/components/warehouse/WopsPageHeader.vue | 27 +- .../warehouse/WopsQuadViewToggle.vue | 25 + src/components/warehouse/WopsScopeToggle.vue | 23 + .../warehouse/WopsTripleViewToggle.vue | 24 + src/composables/useWopsPageScope.js | 45 + src/composables/useWopsQuadView.js | 30 + src/composables/useWopsShareEmbed.js | 12 + src/composables/useWopsTripleView.js | 30 + src/config/buildId.json | 4 +- src/constants/orgTaxType.js | 78 + src/constants/whGoodsZone.js | 184 +++ src/constants/whOrgType.js | 22 + src/data/appChangelog.js | 642 +++++++- src/router/index.js | 33 +- src/store/modules/configConstants.js | 56 +- src/styles/warehouseOpsFlexTable.css | 162 +- src/utils/costShareListedDisplay.js | 6 +- src/utils/fieldVisibility.js | 62 + src/utils/financeBillTypes.js | 36 +- src/utils/globalTabRuntime.js | 35 +- src/utils/goodsCategorys.js | 75 + src/utils/goodsGlobalPayload.js | 30 +- src/utils/logisticsGlobalTabSchema.js | 250 ++++ src/utils/supplyBillDerive.js | 24 +- src/utils/supplyCollectionConflictCompare.js | 683 +++++++++ src/utils/warehouseGlobalTabSchema.js | 152 +- src/utils/warehouseOpsDisplay.js | 26 +- src/utils/whRdcDeployCandidatesTree.js | 82 +- src/utils/whSidebarGeoTree.js | 57 +- src/utils/whSrfGoodsSearch.js | 5 + src/utils/wopsGoodsList.js | 28 + src/views/accrual/AccrualList.vue | 1 - src/views/base/AllocShareListed.vue | 106 +- src/views/base/BizTypeManage.vue | 10 +- src/views/base/ConfigManage.vue | 32 +- src/views/base/CostShareListedPage.vue | 1 - src/views/base/DeployShareListed.vue | 82 +- src/views/base/DeptManage.vue | 71 +- src/views/base/FixedAssetsShareListed.vue | 33 +- src/views/base/FreightShareListed.vue | 91 +- .../base/LowValueConsumablesShareListed.vue | 33 +- src/views/base/OrgManage.vue | 91 +- src/views/base/PrCost.vue | 67 +- src/views/base/PrCostShareListed.vue | 215 ++- src/views/base/PurchasingLaborShareListed.vue | 44 +- src/views/base/SalesDocLaborShareListed.vue | 33 +- src/views/base/WarehouseLaborShareListed.vue | 46 +- src/views/base/WarehouseLocationTriPane.vue | 788 +++++++--- src/views/base/WarehouseManage.vue | 425 ++++-- src/views/base/WasteShareListed.vue | 60 +- src/views/base/WhAlloc.vue | 279 ++-- src/views/base/WhFdcLinkRdcDialog.vue | 79 +- src/views/base/WhFreight.vue | 119 +- src/views/base/WhPurchase.vue | 204 ++- src/views/base/WhRdcDeployLinkDialog.vue | 72 +- src/views/base/WhRdcRegionPickDialog.vue | 1 + src/views/base/WhSceneGoodsFilterDlg.vue | 1 + src/views/base/WhSceneLinkGoodsDlg.vue | 1 + src/views/base/WhSrfFdcAllocPane.vue | 409 +++++ src/views/base/WhSrfFdcPane.vue | 176 ++- src/views/base/WhSrfFdcRightPane.vue | 61 + src/views/base/WhSrfGoodsZoneCell.vue | 93 ++ src/views/base/WhSrfGoodsZonePickDlg.vue | 110 ++ src/views/base/WhSrfRdcDeployPane.vue | 230 ++- src/views/base/WhSrfRdcPane.vue | 167 ++- src/views/base/WhTransfer.vue | 282 ++-- src/views/base/WhWarehouseLaborCost.vue | 656 +++++++++ src/views/base/WhWarehouseScenePane.vue | 51 +- src/views/base/WhWaste.vue | 97 +- src/views/dealer/DealerPriceBook.vue | 608 ++++++-- .../dealer/DealerPriceCategoryGoodsDlg.vue | 1 + src/views/dealer/DealerPriceSceneGoodsDlg.vue | 1 + .../dealer/DealerPriceSpecialPriceDlg.vue | 1310 ++++++++++++----- src/views/material/AssemblyManage.vue | 9 +- src/views/material/MaterialCategory.vue | 24 +- .../material/MaterialFormTabPaneBody.vue | 66 +- src/views/material/MaterialList.vue | 273 +++- src/views/material/SceneManage.vue | 31 +- src/views/material/TabBuilder.vue | 6 +- src/views/material/TabManage.vue | 151 +- src/views/material/mdt/ColConfigPanel.vue | 2 +- src/views/material/mdt/FormulaEditor.vue | 2 +- src/views/material/mdt/RecordDrawer.vue | 2 +- src/views/supply/CompanyOrgViewDlg.vue | 21 +- src/views/supply/SupplierList.vue | 364 ++++- src/views/supply/SupplyAuthPackageDlg.vue | 1 + .../SupplyCollectionConflictCompareDlg.vue | 497 +++++++ src/views/supply/SupplyCollectionGoodsDlg.vue | 375 ++++- .../supply/SupplyCoordCollectionManage.vue | 491 ++++-- src/views/supply/SupplyRebateAccept.vue | 50 +- src/views/supply/SupplySupplierFormDlg.vue | 276 +++- .../supply/SupplyTargetRebateProductScope.vue | 2 +- .../supply/SupplyTargetRebateRuleDlg.vue | 1 + 120 files changed, 13229 insertions(+), 2572 deletions(-) create mode 100644 src/api/warehouseFdcAlloc.js create mode 100644 src/api/warehouseLaborCost.js create mode 100644 src/components/warehouse/WhWarehouseLogAddDlg.vue create mode 100644 src/components/warehouse/WopsGoodsListDlg.vue create mode 100644 src/components/warehouse/WopsQuadViewToggle.vue create mode 100644 src/components/warehouse/WopsScopeToggle.vue create mode 100644 src/components/warehouse/WopsTripleViewToggle.vue create mode 100644 src/composables/useWopsPageScope.js create mode 100644 src/composables/useWopsQuadView.js create mode 100644 src/composables/useWopsShareEmbed.js create mode 100644 src/composables/useWopsTripleView.js create mode 100644 src/constants/orgTaxType.js create mode 100644 src/constants/whGoodsZone.js create mode 100644 src/constants/whOrgType.js create mode 100644 src/utils/logisticsGlobalTabSchema.js create mode 100644 src/utils/supplyCollectionConflictCompare.js create mode 100644 src/utils/wopsGoodsList.js create mode 100644 src/views/base/WhSrfFdcAllocPane.vue create mode 100644 src/views/base/WhSrfFdcRightPane.vue create mode 100644 src/views/base/WhSrfGoodsZoneCell.vue create mode 100644 src/views/base/WhSrfGoodsZonePickDlg.vue create mode 100644 src/views/base/WhWarehouseLaborCost.vue create mode 100644 src/views/supply/SupplyCollectionConflictCompareDlg.vue diff --git a/jsconfig.json b/jsconfig.json index 693b34a..c272b29 100755 --- a/jsconfig.json +++ b/jsconfig.json @@ -6,7 +6,7 @@ }, "target": "es2020", "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "jsx": "preserve", "strict": true, "allowJs": true, @@ -14,6 +14,7 @@ "esModuleInterop": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, + "ignoreDeprecations": "6.0", "lib": ["esnext", "dom", "dom.iterable", "scripthost"] }, "include": [ diff --git a/src/api/company.js b/src/api/company.js index c38f20e..f019944 100755 --- a/src/api/company.js +++ b/src/api/company.js @@ -131,7 +131,7 @@ export const COMPANY_ORG_TYPE_SUPPLIER = 2 export const COMPANY_ORG_TYPE_CUSTOMER = 3 export const COMPANY_ORG_TYPE_LOGISTICS = 4 -/** POST /api/company/listed — 按主体 type 筛选组织(如运费供应公司 type=2) */ +/** POST /api/company/listed — 按主体 type 筛选组织(如运费供应组织 type=2) */ export async function companyListedByOrgType(orgType) { const type = Number(orgType) if (!Number.isFinite(type) || type <= 0) return [] @@ -336,7 +336,7 @@ export function pickCompanyFullName(row) { return '' } -/** 运费管理等:组织行 → 供应公司下拉/Tab 选项 */ +/** 运费管理等:组织行 → 供应组织下拉/Tab 选项 */ export function mapSupplyCompanyOption(row) { const supply_company_code = String(row?.company_code || '').trim() if (!supply_company_code) return null @@ -351,7 +351,7 @@ export function mapSupplyCompanyOption(row) { } } -/** 采购管理等:组织行 → 供应公司(supplier_company_code)选项 */ +/** 采购管理等:组织行 → 供应组织(supplier_company_code)选项 */ export function mapSupplierCompanyOption(row) { const base = mapSupplyCompanyOption(row) if (!base) return null diff --git a/src/api/costShareListed.js b/src/api/costShareListed.js index 01c2b16..5cb99c7 100644 --- a/src/api/costShareListed.js +++ b/src/api/costShareListed.js @@ -1,4 +1,5 @@ import request from '@/utils/request' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function str(v) { return String(v ?? '').trim() @@ -32,6 +33,18 @@ export function pickApiErrorMessage(err, fallback = '操作失败') { ).trim() || fallback } +/** cost 模块 share/listed 行:物料名与分类路径(有 goods_categorys 才带出) */ +export function normalizeCostShareListedRow(raw) { + if (!raw || typeof raw !== 'object') return null + const goods_display_name = pickGoodsNameFromListedRow(raw) + || str(raw.goods_name ?? raw.goods_display_name) + return { + ...raw, + goods_display_name, + goods_category_path: pickGoodsCategoryPathText(raw), + } +} + function buildShareListedPayload(body = {}, filterKeys = []) { const payload = { ...body } if (payload.page != null) payload.page = Math.max(1, Number(payload.page) || 1) @@ -61,7 +74,10 @@ function createCostShareListed(path, filterKeys) { const payload = buildShareListedPayload(body, filterKeys) const res = await request.post(path, payload) const { list, total } = unwrapListed(res) - return { list: Array.isArray(list) ? list : [], total } + return { + list: (Array.isArray(list) ? list : []).map(normalizeCostShareListedRow).filter(Boolean), + total, + } } } diff --git a/src/api/customer.js b/src/api/customer.js index e7e1fd3..be85a2d 100644 --- a/src/api/customer.js +++ b/src/api/customer.js @@ -4,6 +4,7 @@ import { buildSupplierBlocAddPayload, buildSupplierBlocModifyPayload, buildSupplierFormFields, + buildSupplierFormStateFromRow, buildSupplierGroupTree, buildSupplierModifyPayload, buildSupplierModifyPayloadFromRow, @@ -22,7 +23,64 @@ import { resolveOrgEffectiveSceneCodes, } from '@/api/supplySupplier' +/** 客户类型:0 内部 / 1 C端 / 2 B端 */ +export const CUSTOMER_TYPE_INTERNAL = 0 +export const CUSTOMER_TYPE_C_END = 1 +export const CUSTOMER_TYPE_B_END = 2 + +export const CUSTOMER_TYPE_OPTIONS = [ + { value: CUSTOMER_TYPE_INTERNAL, label: '内部客户' }, + { value: CUSTOMER_TYPE_C_END, label: 'C端客户' }, + { value: CUSTOMER_TYPE_B_END, label: 'B端客户' }, +] + +/** listed / 表单 → customer_type(0/1/2,默认内部) */ +export function normalizeCustomerType(raw) { + const n = Number(raw) + if (n === CUSTOMER_TYPE_C_END) return CUSTOMER_TYPE_C_END + if (n === CUSTOMER_TYPE_B_END) return CUSTOMER_TYPE_B_END + return CUSTOMER_TYPE_INTERNAL +} + +/** customer_type → 展示文案 */ +export function formatCustomerTypeLabel(raw) { + const t = normalizeCustomerType(raw) + const opt = CUSTOMER_TYPE_OPTIONS.find((o) => o.value === t) + return opt?.label ?? '内部客户' +} + +export function isBEndCustomerType(raw) { + return normalizeCustomerType(raw) === CUSTOMER_TYPE_B_END +} + +/** listed 行解析 customer_type */ +export function pickCustomerType(row) { + if (!row || typeof row !== 'object') return CUSTOMER_TYPE_INTERNAL + const r = /** @type {Record} */ (row) + const field = r.field && typeof r.field === 'object' ? r.field : null + const fields = r.fields && typeof r.fields === 'object' ? r.fields : null + const raw = r.customer_type ?? field?.customer_type ?? fields?.customer_type + if (raw === null || raw === undefined || raw === '') return CUSTOMER_TYPE_INTERNAL + return normalizeCustomerType(raw) +} + +/** 客户组织编辑弹窗:listed 行 → 表单 */ +export function buildCustomerFormStateFromRow(row) { + const base = buildSupplierFormStateFromRow(row) + return { + ...base, + customer_type: pickCustomerType(row), + relationship_maintenance_commission_rate: pickCustomerRelationshipMaintenanceCommissionRate(row), + } +} + function buildCustomerOrgExtraFields(formOrRow) { + if (!isBEndCustomerType(pickCustomerType(formOrRow))) { + return { + special_delivery_commission_rate: 0, + relationship_maintenance_commission_rate: 0, + } + } const specialRate = pickSupplierSpecialDeliveryCommissionRate(formOrRow) const relationshipRate = pickCustomerRelationshipMaintenanceCommissionRate(formOrRow) return { @@ -145,27 +203,33 @@ export function buildCustomerAddPayload(form, { blocCode }) { const body = { ...buildSupplierAddPayload(form, { blocCode }), company_type: 3, - ...buildCustomerSpecialDeliveryFields(form), + customer_type: normalizeCustomerType(form?.customer_type), + ...buildCustomerOrgExtraFields(form), } delete body.is_support_receipt + delete body.sale_type return body } export function buildCustomerModifyPayload(form, companyCode, opts = {}) { const body = { ...buildSupplierModifyPayload(form, companyCode, opts), - ...buildCustomerSpecialDeliveryFields(form), + customer_type: normalizeCustomerType(form?.customer_type), + ...buildCustomerOrgExtraFields(form), } delete body.is_support_receipt + delete body.sale_type return body } export function buildCustomerModifyPayloadFromRow(row, opts = {}) { const body = { ...buildSupplierModifyPayloadFromRow(row, opts), - ...buildCustomerSpecialDeliveryFieldsFromRow(row), + customer_type: pickCustomerType(row), + ...buildCustomerOrgExtraFieldsFromRow(row), } delete body.is_support_receipt + delete body.sale_type return body } diff --git a/src/api/customerPrice.js b/src/api/customerPrice.js index aee6ea3..fe50149 100644 --- a/src/api/customerPrice.js +++ b/src/api/customerPrice.js @@ -1,5 +1,7 @@ import request from '@/utils/request' -import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { normalizeOrgSaleType, ORG_SALE_TYPE_CHANNEL } from '@/api/company' +import { FINANCE_BILL_TYPE_EXPENSE, normalizeFinanceBillType, shouldDisplayBillTax } from '@/utils/financeBillTypes' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function unwrapListed(res) { const data = res?.data @@ -103,7 +105,12 @@ function parseCustomerPriceCustomRaw(raw) { export function pickCustomerPriceSpecialAmount(...sources) { for (const src of sources) { if (!src || typeof src !== 'object') continue - for (const key of ['special_amount', 'special_price', 'special_sale_price']) { + for (const key of [ + 'special_general_amount', + 'special_amount', + 'special_price', + 'special_sale_price', + ]) { const v = src[key] if (v == null || v === '') continue const n = Number(v) @@ -113,6 +120,61 @@ export function pickCustomerPriceSpecialAmount(...sources) { return 0 } +function pickOptionalPriceAmount(src, key) { + if (!src || typeof src !== 'object') return null + const v = src[key] + if (v == null || v === '') return null + const n = Number(v) + return Number.isFinite(n) ? n : null +} + +function pickFirstOptionalAmount(sources, key) { + for (const src of sources) { + const n = pickOptionalPriceAmount(src, key) + if (n != null) return n + } + return null +} + +/** 特殊售价 · 批量值(主记录 general/small/individual_amount) */ +export function pickCustomerPriceBatchSpecialAmounts(row) { + const src = row && typeof row === 'object' ? row : {} + return { + general: pickOptionalPriceAmount(src, 'general_amount'), + small: pickOptionalPriceAmount(src, 'small_amount'), + receipt: pickOptionalPriceAmount(src, 'individual_amount'), + } +} + +/** 特殊售价 · 自定义计算(custom_general/small/individual_amount) */ +export function pickCustomerPriceCustomCalcSpecialAmounts(row) { + const src = row && typeof row === 'object' ? row : {} + const custom = parseCustomerPriceCustomRaw(src.custom) + || (src.custom && typeof src.custom === 'object' ? src.custom : null) + const sources = custom ? [src, custom] : [src] + return { + general: pickFirstOptionalAmount(sources, 'custom_general_amount'), + small: pickFirstOptionalAmount(sources, 'custom_small_amount'), + receipt: pickFirstOptionalAmount(sources, 'custom_individual_amount'), + } +} + +/** 特殊售价 · 手动输入(modify_custom 保存 special_*;兼容旧 special_amount → 一般人) */ +export function pickCustomerPriceManualSpecialAmounts(...sources) { + let general = pickFirstOptionalAmount(sources, 'special_general_amount') + const small = pickFirstOptionalAmount(sources, 'special_small_amount') + const receipt = pickFirstOptionalAmount(sources, 'special_individual_amount') + if (general == null) { + const legacy = pickCustomerPriceSpecialAmount(...sources) + if (Number.isFinite(legacy) && legacy !== 0) general = legacy + } + return { + general: general ?? 0, + small: small ?? 0, + receipt: receipt ?? 0, + } +} + /** modify_custom 分桶 key(bill_label + bill_tax) */ export function customerPriceBillBucketKey({ bill_label, bill_tax, bill_code } = {}) { const label = String(bill_label ?? '').trim() @@ -128,9 +190,9 @@ export function formatCustomerPriceBillBucketLabel(key, meta) { const label = String(meta?.bill_label ?? '').trim() if (label) { const tax = meta?.bill_tax - if (tax != null && tax !== '') { + if (tax != null && tax !== '' && shouldDisplayBillTax(tax)) { const n = Number(tax) - if (Number.isFinite(n) && n !== -1) { + if (Number.isFinite(n)) { return `${label}(${n}%)` } } @@ -142,7 +204,7 @@ export function formatCustomerPriceBillBucketLabel(key, meta) { const [name, tax] = k.split('|') if (name) { const n = Number(tax) - if (tax !== '' && tax != null && Number.isFinite(n) && n !== -1) { + if (shouldDisplayBillTax(tax)) { return `${name}(${n}%)` } return name @@ -266,12 +328,12 @@ export function parseBenefitGroups(src) { return [] } -/** 列表展示:0.05 → 5% */ +/** 列表展示:接口 rate 多为百分点(1→1%);小数比率(0.05→5%)仅 |n|<1 时 ×100 */ export function formatCustomerPriceRate(rate) { if (rate == null || rate === '') return '—' const n = Number(rate) if (!Number.isFinite(n)) return '—' - const pct = n !== 0 && Math.abs(n) <= 1 ? n * 100 : n + const pct = n !== 0 && Math.abs(n) < 1 ? n * 100 : n const text = Number.isInteger(pct) ? String(pct) : String(Number(pct.toFixed(4))).replace(/\.?0+$/, '') @@ -284,6 +346,15 @@ export function formatCustomerPriceMetricAmount(v) { return Number.isFinite(n) ? String(n) : '—' } +/** 列表报价列:金额保留 2 位小数 */ +export function formatCustomerPriceQuoteAmount(v) { + if (v == null || v === '') return '—' + const n = Number(v) + if (!Number.isFinite(n)) return '—' + const text = n.toFixed(2).replace(/\.?0+$/, '') + return `${text}元` +} + /** 展示用:金额 + 元 */ export function formatCustomerPriceAmount(v) { const text = formatCustomerPriceMetricAmount(v) @@ -343,6 +414,218 @@ export function summarizeCustomerPriceMetricGroups(groups, opts = {}) { } } +function pickCustomerPriceMetricGroupLabel(g, billNameMap = {}) { + if (g?.bill_label) return formatCustomerPriceBillBucketLabel(g.bill_code, g) + const c = String(g?.bill_code || '').trim() + if (!c) return '默认' + return billNameMap[c] || formatCustomerPriceBillBucketLabel(c, g) +} + +/** + * 列表/弹窗:按接口 groups 逐条展示(动态长度,不折叠) + * @param {Array} groups + * @param {{ amountKey?: string, billNameMap?: Record }} [opts] + */ +export function listCustomerPriceMetricGroupDisplayRows(groups, opts = {}) { + const amountKey = opts.amountKey || 'amount' + const billNameMap = opts.billNameMap || {} + const list = Array.isArray(groups) ? groups : [] + if (!list.length) { + return [{ + key: '', + label: '', + display: '—', + displayRate: '—', + displayAmount: '—', + }] + } + const showLabel = list.length > 1 || list.some((g) => String(g?.bill_label ?? '').trim()) + return list.map((g, idx) => { + const key = String(g?.bill_code ?? customerPriceBillBucketKey(g) ?? idx).trim() || String(idx) + const label = showLabel ? pickCustomerPriceMetricGroupLabel(g, billNameMap) : '' + return { + key, + label, + display: formatCustomerPriceMetricPair(g, amountKey), + displayRate: formatCustomerPriceRate(g.rate), + displayAmount: formatCustomerPriceAmount(g[amountKey]), + } + }) +} + +function pickBatchBillTaxText(meta) { + const tax = meta?.bill_tax + if (tax == null || tax === '') return '' + if (!shouldDisplayBillTax(tax)) return '' + const n = Number(tax) + return Number.isFinite(n) ? `${n}%` : '' +} + +function splitBatchBillDisplayParts(meta, fallbackLabel) { + const billName = String(meta?.bill_label ?? '').trim() + const billTaxText = pickBatchBillTaxText(meta) + if (billName) return { billName, billTaxText } + const text = String(fallbackLabel || '').trim() + const matched = text.match(/^(.+?)\s*[((](\d+(?:\.\d+)?)%[))]$/) + if (matched) { + return { + billName: matched[1].trim(), + billTaxText: `${Number(matched[2])}%`, + } + } + return { billName: text || '默认', billTaxText: '' } +} + +/** + * 批量值对照表:超额绩效 + 好处费按 bill 合并为多行 + * @param {Array} perfGroups + * @param {Array} benefitGroups + * @param {{ billNameMap?: Record }} [opts] + */ +export function listCustomerPriceMetricTableRows(perfGroups, benefitGroups, opts = {}) { + const billNameMap = opts.billNameMap || {} + /** @type {Map} */ + const map = new Map() + const add = (g, kind) => { + const key = String(g?.bill_code ?? customerPriceBillBucketKey(g) ?? '').trim() + if (!map.has(key)) map.set(key, {}) + map.get(key)[kind] = g + } + for (const g of perfGroups || []) add(g, 'perf') + for (const g of benefitGroups || []) add(g, 'benefit') + if (!map.size) { + return [{ + key: '', + label: '默认', + billName: '默认', + billTaxText: '', + perfDisplay: '—', + benefitDisplay: '—', + }] + } + const keys = [...map.keys()].filter((k) => k !== '' || map.size === 1) + if (!keys.length) keys.push('') + return keys.map((key) => { + const entry = map.get(key) || {} + const meta = entry.perf || entry.benefit || {} + const label = pickCustomerPriceMetricGroupLabel(meta, billNameMap) + const { billName, billTaxText } = splitBatchBillDisplayParts(meta, label) + return { + key, + label: label || '默认', + billName, + billTaxText, + perfDisplay: entry.perf ? formatCustomerPriceMetricPair(entry.perf, 'amount') : '—', + benefitDisplay: entry.benefit ? formatCustomerPriceMetricPair(entry.benefit, 'fee') : '—', + } + }) +} + +/** 费用票据 type=4 → 分桶 key 与展示 meta */ +export function customerPriceExpenseBillMeta(bill) { + const bill_label = String(bill?.name ?? bill?.bill_label ?? '').trim() + const taxRaw = bill?.tax ?? bill?.rate + const hasTax = taxRaw != null && taxRaw !== '' && Number.isFinite(Number(taxRaw)) + const bill_tax = hasTax ? toPriceNum(taxRaw) : undefined + const key = customerPriceBillBucketKey({ bill_label, bill_tax, bill_code: bill?.bill_code }) + return { key, bill_label, bill_tax } +} + +function findCustomerPriceBucketEntry(buckets, bill_label, bill_tax) { + const src = buckets && typeof buckets === 'object' ? buckets : {} + const targetKey = customerPriceBillBucketKey({ bill_label, bill_tax }) + if (src[targetKey]) return { key: targetKey, bucket: src[targetKey] } + const taxNum = bill_tax == null || bill_tax === '' ? 0 : toPriceNum(bill_tax) + for (const [k, bucket] of Object.entries(src)) { + const meta = bucketBillMeta(k, bucket) + if (meta.bill_label !== bill_label) continue + const metaTax = meta.bill_tax == null || meta.bill_tax === '' ? 0 : toPriceNum(meta.bill_tax) + if (metaTax === taxNum) return { key: k, bucket } + } + return null +} + +/** + * 自定义编辑:按 type=4 费用票据补全分桶(保留已保存值) + * @param {Record} excessBuckets + * @param {Record} benefitBuckets + * @param {Array} expenseBills + */ +export function applyCustomerPriceExpenseBillsToCustomBuckets( + excessBuckets, + benefitBuckets, + expenseBills, +) { + const bills = Array.isArray(expenseBills) ? expenseBills.filter(Boolean) : [] + if (!bills.length) { + const perf = { ...(excessBuckets || {}) } + const ben = { ...(benefitBuckets || {}) } + mergeCustomerPriceBucketKeys(perf, ben) + return { excess_performance_buckets: perf, benefit_buckets: ben } + } + + /** @type {Record} */ + const perf = {} + /** @type {Record} */ + const ben = {} + for (const bill of bills) { + const { key, bill_label, bill_tax } = customerPriceExpenseBillMeta(bill) + if (!key) continue + const foundPerf = findCustomerPriceBucketEntry(excessBuckets, bill_label, bill_tax) + const foundBen = findCustomerPriceBucketEntry(benefitBuckets, bill_label, bill_tax) + perf[key] = { + ...(foundPerf?.bucket || createEmptyExcessPerformanceBucket()), + bill_label, + bill_tax, + } + ben[key] = { + ...(foundBen?.bucket || createEmptyBenefitBucket()), + bill_label, + bill_tax, + } + } + mergeCustomerPriceBucketKeys(perf, ben) + return { excess_performance_buckets: perf, benefit_buckets: ben } +} + +/** + * 自定义分桶对照表:超额绩效 + 好处费按 bill 合并为多行(供编辑表格绑定 key) + * @param {Record} excessBuckets + * @param {Record} benefitBuckets + * @param {Array} [expenseBills] type=4 费用票据,有则按票种顺序出表 + */ +export function listCustomerPriceCustomBucketTableRows(excessBuckets, benefitBuckets, expenseBills) { + const bills = Array.isArray(expenseBills) ? expenseBills.filter(Boolean) : [] + if (bills.length) { + return bills.map((bill) => { + const { key, bill_label, bill_tax } = customerPriceExpenseBillMeta(bill) + if (!key) return null + const perf = excessBuckets?.[key] + const benefit = benefitBuckets?.[key] + const meta = { bill_label, bill_tax, ...(perf || benefit || {}) } + const label = formatCustomerPriceBillBucketLabel(key, meta) + const { billName, billTaxText } = splitBatchBillDisplayParts(meta, label) + return { key, billName, billTaxText } + }).filter(Boolean) + } + + const keys = listCustomerPriceJsonBucketKeys(excessBuckets, benefitBuckets) + const useKeys = keys.length ? keys : [''] + return useKeys.map((key) => { + const perf = excessBuckets?.[key] + const benefit = benefitBuckets?.[key] + const meta = perf || benefit || {} + const label = formatCustomerPriceBillBucketLabel(key, meta) + const { billName, billTaxText } = splitBatchBillDisplayParts(meta, label) + return { key, billName, billTaxText } + }) +} + +/** @param {unknown} type */ +export function isCustomerPriceExpenseBillType(type) { + return normalizeFinanceBillType(type) === FINANCE_BILL_TYPE_EXPENSE +} + /** 超额绩效 JSON:{ rate, amount } */ export function normalizeExcessPerformance(raw) { const json = parseJsonObject(raw) @@ -593,15 +876,30 @@ export function buildCustomerPriceWriteBody(form, extras = {}) { if (pc) body.customer_price_code = pc if (cc) body.company_code = cc if (gc) body.goods_code = gc + if (extras.sale_type != null) { + body.sale_type = normalizeOrgSaleType(extras.sale_type) + } else if (form?.sale_type != null) { + body.sale_type = normalizeOrgSaleType(form.sale_type) + } return body } +/** listed 行解析 sale_type(1 渠道 / 2 零售) */ +export function pickCustomerPriceSaleType(row) { + if (!row || typeof row !== 'object') return ORG_SALE_TYPE_CHANNEL + const r = row + return normalizeOrgSaleType(r.sale_type ?? r.sales_type) +} + /** POST /api/customer/price/listed */ export async function customerPriceListed(body = {}) { const payload = { ...(body || {}) } if (payload.company_code != null) { payload.company_code = String(payload.company_code ?? '').trim() || undefined } + if (payload.rdc_warehouse_code != null) { + payload.rdc_warehouse_code = String(payload.rdc_warehouse_code ?? '').trim() || undefined + } if (payload.page != null) payload.page = Math.max(1, Number(payload.page) || 1) if (payload.limit != null) payload.limit = Math.max(1, Number(payload.limit) || 20) const res = await request.post('/api/customer/price/listed', payload) @@ -642,7 +940,7 @@ export function buildCustomerPriceAddItem(company_code, goods_code, overrides = * @param {string} company_code * @param {Array} goodsList */ -export async function persistCustomerPriceGoods(company_code, goodsList) { +export async function persistCustomerPriceGoods(company_code, goodsList, overrides = {}) { const cc = String(company_code ?? '').trim() const codes = [...new Set( (Array.isArray(goodsList) ? goodsList : []) @@ -652,7 +950,8 @@ export async function persistCustomerPriceGoods(company_code, goodsList) { if (!cc) throw new Error('缺少客户编码') if (!codes.length) throw new Error('请先选择物料') - const items = codes.map((goods_code) => buildCustomerPriceAddItem(cc, goods_code)) + const o = overrides && typeof overrides === 'object' ? overrides : {} + const items = codes.map((goods_code) => buildCustomerPriceAddItem(cc, goods_code, o)) if (items.length === 1) { await customerPriceAdd(items[0]) } else { @@ -719,6 +1018,12 @@ export async function persistCustomerPriceModify(rows, form) { }) .filter(Boolean) if (!list.length) throw new Error('缺少 customer_price_code,无法修改') + const isBatch = list.length > 1 + if (isBatch) { + for (const item of list) { + delete item.is_special_price + } + } if (list.length === 1) { await customerPriceModify(list[0]) } else { @@ -738,6 +1043,62 @@ export function customerPriceModifyCustom(body) { return request.post('/api/customer/price/modify_custom', body) } +/** POST /api/customer/price/warehouse_custom_overview — 按客户+商品查关联 RDC 自定义价格概览 */ +export async function customerPriceWarehouseCustomOverview(body = {}) { + const payload = { + company_code: String(body?.company_code ?? '').trim(), + goods_code: String(body?.goods_code ?? '').trim(), + } + const res = await request.post('/api/customer/price/warehouse_custom_overview/', payload) + return res?.data ?? res ?? {} +} + +/** warehouse_custom_overview → 按 warehouse_code 索引 */ +export function parseCustomerPriceWarehouseCustomOverview(raw) { + const data = raw?.data ?? raw ?? {} + const warehouses = Array.isArray(data.warehouses) ? data.warehouses : [] + /** @type {Record} */ + const byWarehouseCode = {} + for (const item of warehouses) { + const code = String(item?.warehouse_code ?? '').trim() + if (!code) continue + const profitRateRaw = item?.profit_rate ?? item?.trade_profit_rate + const profitAmountRaw = item?.profit_amount ?? item?.trade_profit_amount + byWarehouseCode[code] = { + has_custom: item?.has_custom === true + || item?.has_custom === 1 + || item?.has_custom === '1', + has_special: item?.has_special === true + || item?.has_special === 1 + || item?.has_special === '1', + profit_rate: profitRateRaw == null || profitRateRaw === '' + ? null + : (Number.isFinite(Number(profitRateRaw)) ? Number(profitRateRaw) : null), + profit_amount: profitAmountRaw == null || profitAmountRaw === '' + ? null + : (Number.isFinite(Number(profitAmountRaw)) ? Number(profitAmountRaw) : null), + } + } + return { + customer_price_code: String(data.customer_price_code ?? '').trim(), + has_customer_price: data.has_customer_price === true, + has_any_special: data.has_any_special === true, + warehouses, + byWarehouseCode, + } +} + +/** warehouse_custom_overview · warehouses → 左侧 RDC 选项(warehouse_name + warehouse_code) */ +export function mapCustomerPriceWarehouseOverviewToRdcOptions(warehouses) { + return (Array.isArray(warehouses) ? warehouses : []).map((item) => { + const code = String(item?.warehouse_code ?? item?.warehouse_rdc_code ?? '').trim() + const name = String( + item?.warehouse_name ?? item?.warehouseName ?? item?.name ?? '', + ).trim() + return { code, name: name || code } + }).filter((item) => item.code) +} + /** POST /api/customer/price/remove_custom */ export function customerPriceRemoveCustom(body) { const customer_price_code = pickCustomerPriceCode(body) @@ -749,6 +1110,7 @@ function normalizeCustomerPriceCustom(raw) { const src = raw const excess_performance_groups = parseExcessPerformanceGroups(src) const benefit_groups = parseBenefitGroups(src) + const manual = pickCustomerPriceManualSpecialAmounts(src) return { ...src, sales_policy_type: src.sales_policy_type == null ? 0 : toPriceNum(src.sales_policy_type), @@ -764,27 +1126,33 @@ function normalizeCustomerPriceCustom(raw) { excess_profit_rate: toPriceNum(src.excess_profit_rate), excess_profit_amount: toPriceNum(src.excess_profit_amount), habitual_bargain_ratio: toPriceNum(src.habitual_bargain_ratio), - special_amount: pickCustomerPriceSpecialAmount(src), + special_general_amount: manual.general, + special_small_amount: manual.small, + special_individual_amount: manual.receipt, + special_amount: manual.general, } } -/** 列表刷新后合并刚保存的 custom(listed 未回传 special_amount 时兜底) */ +/** 列表刷新后合并刚保存的 custom(listed 未回传 special_* 时兜底) */ export function applyCustomerPriceCustomSnapshot(row, snapshot) { if (!row || typeof row !== 'object' || !snapshot || typeof snapshot !== 'object') return row const customRaw = { ...(row.custom && typeof row.custom === 'object' ? row.custom : {}), ...snapshot, - special_amount: pickCustomerPriceSpecialAmount(snapshot, row.custom, row, snapshot), } const custom = normalizeCustomerPriceCustom(customRaw) if (!custom) return row + const manual = pickCustomerPriceManualSpecialAmounts(snapshot, custom, row.custom, row) return { ...row, has_custom: true, custom, custom_excess_performance_groups: custom.excess_performance_groups ?? [], custom_benefit_groups: custom.benefit_groups ?? [], - special_amount: custom.special_amount, + special_general_amount: manual.general, + special_small_amount: manual.small, + special_individual_amount: manual.receipt, + special_amount: manual.general, } } @@ -800,7 +1168,9 @@ export function createEmptyCustomerPriceCustomForm() { excess_profit_amount: 0, habitual_bargain_ratio: 0, benefit_buckets: groupsToBenefitBuckets([]), - special_amount: 0, + special_general_amount: 0, + special_small_amount: 0, + special_individual_amount: 0, } } @@ -825,6 +1195,7 @@ export function customerPriceCustomFormFromRow(row) { : parseBenefitGroups(customSrc), ) mergeCustomerPriceBucketKeys(excess_performance_buckets, benefit_buckets) + const manual = pickCustomerPriceManualSpecialAmounts(customSrc, src) return { ...createEmptyCustomerPriceCustomForm(), sales_policy_type: customSrc.sales_policy_type == null ? 0 : toPriceNum(customSrc.sales_policy_type), @@ -838,7 +1209,9 @@ export function customerPriceCustomFormFromRow(row) { excess_profit_amount: toPriceNum(customSrc.excess_profit_amount), habitual_bargain_ratio: toPriceNum(customSrc.habitual_bargain_ratio), benefit_buckets, - special_amount: pickCustomerPriceSpecialAmount(src, customSrc), + special_general_amount: manual.general, + special_small_amount: manual.small, + special_individual_amount: manual.receipt, } } @@ -856,7 +1229,9 @@ export function buildCustomerPriceCustomWriteBody(form, { customer_price_code } excess_profit_amount: toPriceNum(form?.excess_profit_amount), habitual_bargain_ratio: toPriceNum(form?.habitual_bargain_ratio), benefit: serializeBenefitBucketsAsCustomArray(form?.benefit_buckets), - special_amount: toPriceNum(form?.special_amount), + special_general_amount: toPriceNum(form?.special_general_amount), + special_small_amount: toPriceNum(form?.special_small_amount), + special_individual_amount: toPriceNum(form?.special_individual_amount), } } @@ -931,9 +1306,12 @@ export function normalizeCustomerPriceRow(raw) { const custom = customRaw ? normalizeCustomerPriceCustom({ ...customRaw, - special_amount: pickCustomerPriceSpecialAmount(src, customRaw), + ...pickCustomerPriceManualSpecialAmounts(src, customRaw), }) : null + const batchSpecialAmounts = pickCustomerPriceBatchSpecialAmounts(src) + const customCalcAmounts = pickCustomerPriceCustomCalcSpecialAmounts({ ...src, custom: customRaw }) + const manualAmounts = pickCustomerPriceManualSpecialAmounts(src, customRaw, custom) return { ...src, customer_price_code, @@ -941,6 +1319,7 @@ export function normalizeCustomerPriceRow(raw) { company_code, goods_code, category_code: String(src.category_code ?? '').trim(), + sale_type: pickCustomerPriceSaleType(src), sales_policy_type: src.sales_policy_type == null ? 0 : toPriceNum(src.sales_policy_type), pos: src.pos == null || src.pos === '' ? null : toPriceNum(src.pos), excess_performance_groups, @@ -957,7 +1336,17 @@ export function normalizeCustomerPriceRow(raw) { credit_period_months: toPriceNum(src.credit_period_months), goods_categorys_list, goods_categorys_text, - special_amount: pickCustomerPriceSpecialAmount(src, customRaw, custom), + goods_category_path: pickGoodsCategoryPathText(src) || goods_categorys_text, + general_amount: batchSpecialAmounts.general, + small_amount: batchSpecialAmounts.small, + individual_amount: batchSpecialAmounts.receipt, + custom_general_amount: customCalcAmounts.general, + custom_small_amount: customCalcAmounts.small, + custom_individual_amount: customCalcAmounts.receipt, + special_general_amount: manualAmounts.general, + special_small_amount: manualAmounts.small, + special_individual_amount: manualAmounts.receipt, + special_amount: manualAmounts.general, goods_name: pickGoodsNameFromListedRow(src) || String(src.goods_name ?? src.goods?.name ?? src.name ?? '').trim(), company_name: String( @@ -987,6 +1376,7 @@ export function createEmptyCustomerPriceForm() { goods_code: '', category_code: '', goods_categorys_text: '', + sale_type: ORG_SALE_TYPE_CHANNEL, sales_policy_type: 0, is_support_acceptance: false, rebate_delay_base_months: 0, @@ -1014,6 +1404,7 @@ export function customerPriceFormFromRow(row) { goods_code: src.goods_code || '', category_code: src.category_code || '', goods_categorys_text: src.goods_categorys_text || formatCustomerPriceGoodsDisplay(src) || '', + sale_type: pickCustomerPriceSaleType(src), sales_policy_type: src.sales_policy_type == null ? 0 : toPriceNum(src.sales_policy_type), is_support_acceptance: src.is_support_acceptance === true || src.is_support_acceptance === 1 || src.is_support_acceptance === '1', rebate_delay_base_months: toPriceNum(src.rebate_delay_base_months), diff --git a/src/api/globalTabRuntime.js b/src/api/globalTabRuntime.js index a3e0589..6789748 100644 --- a/src/api/globalTabRuntime.js +++ b/src/api/globalTabRuntime.js @@ -1,5 +1,34 @@ import { globalTabManageApi } from '@/api/tabManageApi' -import { mapGlobalTabRowsToRuntime } from '@/utils/globalTabRuntime' +import { + mapGlobalTabRowsToRuntime, + normalizeGlobalTabStorageKey, + reinforceLogisticsInTabList, +} from '@/utils/globalTabRuntime' +import { + LOGISTICS_TAB_CODE, + buildLogisticsDefaultGroups, +} from '@/utils/logisticsGlobalTabSchema' + +function ensureLogisticsRuntimeTab(list) { + const rows = reinforceLogisticsInTabList(Array.isArray(list) ? list : []) + const has = rows.some((t) => normalizeGlobalTabStorageKey(t?.code || t?.tab_code) === 'logistics') + if (has) return rows + return [ + ...rows, + { + code: LOGISTICS_TAB_CODE, + id: LOGISTICS_TAB_CODE, + tab_code: LOGISTICS_TAB_CODE, + name: '物流配置', + desc: '', + groups: buildLogisticsDefaultGroups(), + pos: 35, + type: 'global', + global: true, + classifyName: '', + }, + ] +} /** 拉取全部有效全局页签(跨分类扁平) */ export async function fetchGlobalTabRuntimeList() { @@ -17,7 +46,7 @@ export async function fetchGlobalTabRuntimeList() { } }), ) - return chunks.flat().sort((a, b) => (a.pos || 0) - (b.pos || 0)) + return ensureLogisticsRuntimeTab(chunks.flat().sort((a, b) => (a.pos || 0) - (b.pos || 0))) } /** 会话内缓存,避免分类 / 物料重复拉取 */ @@ -25,8 +54,8 @@ let cached = /** @type {object[]|null} */ (null) let loading = /** @type {Promise|null} */ (null) export async function getGlobalTabRuntimeListCached(force = false) { - if (!force && cached) return cached - if (!force && loading) return loading + if (!force && cached) return reinforceLogisticsInTabList(cached) + if (!force && loading) return loading.then((rows) => reinforceLogisticsInTabList(rows)) loading = fetchGlobalTabRuntimeList() .then((rows) => { cached = rows diff --git a/src/api/prCost.js b/src/api/prCost.js index 55a12ea..1176a56 100644 --- a/src/api/prCost.js +++ b/src/api/prCost.js @@ -1,5 +1,8 @@ import request from '@/utils/request' -import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { formatCompanyNameWithOrgType } from '@/api/pricingTable' +import { orgTaxTypeLabel, parseOrgTaxType } from '@/constants/orgTaxType' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { normalizeWopsGoodsItem } from '@/utils/wopsGoodsList' function str(v) { return String(v ?? '').trim() } @@ -40,6 +43,80 @@ export function pickPrCostShareGoodsName(raw) { return pickGoodsNameFromListedRow(raw) || '' } +function pickSupplierCompanyOrgTypeLabel(raw) { + const compObj = raw?.supplier_company && typeof raw.supplier_company === 'object' + ? raw.supplier_company + : null + const sources = [raw, compObj].filter((s) => s && typeof s === 'object') + for (const src of sources) { + for (const key of ['supplier_company_org_type', 'supply_company_org_type', 'org_type']) { + if (src[key] == null || src[key] === '') continue + if (!parseOrgTaxType(src[key])) continue + const label = orgTaxTypeLabel(src[key]) + if (label) return label + } + } + return '' +} + +function pickSupplierCompanyDisplay(raw) { + const compObj = raw?.supplier_company && typeof raw.supplier_company === 'object' + ? raw.supplier_company + : null + const supplier_company_name = str( + raw.supplier_company_name + ?? compObj?.name + ?? compObj?.company_name + ?? compObj?.supplier_company_name, + ) + const supplier_company_org_type_label = pickSupplierCompanyOrgTypeLabel(raw) + const supplier_company_display = formatCompanyNameWithOrgType( + supplier_company_name, + supplier_company_org_type_label, + ) + return { + supplier_company_name, + supplier_company_org_type_label, + supplier_company_display: supplier_company_display || supplier_company_name || '', + } +} + +function pickExpenseDate(raw) { + const v = raw?.expense_date + if (v == null || v === '') return null + const s = String(v).trim() + if (!s) return null + return s.includes('T') ? s.slice(0, 10) : s.slice(0, 10) +} + +/** POST /api/pr/cost/listed — 费用行归一化 */ +export function normalizePrCostRow(raw) { + if (!raw || typeof raw !== 'object') return null + const goods = Array.isArray(raw.goods) + ? raw.goods.map(normalizeWopsGoodsItem).filter(Boolean) + : [] + const expense_code = pickPrExpenseCode(raw) + return { + ...raw, + ...pickSupplierCompanyDisplay(raw), + expense_code, + pr_expense_code: expense_code, + supplier_bloc_code: str(raw.supplier_bloc_code), + supplier_bloc_name: str(raw.supplier_bloc_name), + supplier_company_code: str(raw.supplier_company_code), + bill_name: str(raw.bill_name), + bill_code: str(raw.bill_code), + bill_tax: num(raw.bill_tax), + amount: num(raw.amount), + expense_date: pickExpenseDate(raw), + remark: str(raw.remark), + goods, + goods_count: goods.length, + is_amortized: raw.is_amortized === true || !!str(raw.amortized_time ?? raw.amortizedTime), + amortized_time: str(raw.amortized_time ?? raw.amortizedTime), + } +} + /** POST /api/pr/cost/share/listed — 分摊结果行归一化 */ export function normalizePrCostShareRow(raw) { if (!raw || typeof raw !== 'object') return null @@ -69,6 +146,7 @@ export function normalizePrCostShareRow(raw) { return { ...raw, + ...pickSupplierCompanyDisplay(raw), purchase_count, purchase_amount, total_amount: share_total_amount, @@ -81,6 +159,7 @@ export function normalizePrCostShareRow(raw) { targeted_ratio, non_targeted_ratio, goods_display_name: pickPrCostShareGoodsName(raw), + goods_category_path: pickGoodsCategoryPathText(raw), share_time: str(raw.share_time ?? raw.amortize_time ?? raw.ctime ?? raw.create_time), } } @@ -96,10 +175,13 @@ export async function prCostShareListed(body = {}) { if (body.bill_tax != null && body.bill_tax !== '') payload.bill_tax = Number(body.bill_tax) if (body.day_id != null && body.day_id !== '') payload.day_id = Number(body.day_id) const res = await request.post('/api/pr/cost/share/listed', payload) + const root = res?.data ?? res const { list, total } = unwrapListed(res) return { list: list.map(normalizePrCostShareRow).filter(Boolean), total, + total_targeted_amount: num(root?.total_targeted_amount), + total_non_targeted_amount: num(root?.total_non_targeted_amount), } } @@ -122,7 +204,11 @@ export async function prCostListed(body = {}) { if (str(body.start_date)) payload.start_date = str(body.start_date) if (str(body.end_date)) payload.end_date = str(body.end_date) const res = await request.post('/api/pr/cost/listed', payload) - return unwrapListed(res) + const { list, total } = unwrapListed(res) + return { + list: list.map(normalizePrCostRow).filter(Boolean), + total, + } } /** POST /api/pr/cost/add */ diff --git a/src/api/pricingTable.js b/src/api/pricingTable.js index 5c64c77..a4fc0af 100644 --- a/src/api/pricingTable.js +++ b/src/api/pricingTable.js @@ -1,4 +1,5 @@ import request from '@/utils/request' +import { orgTaxTypeLabel } from '@/constants/orgTaxType' import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function str(v) { @@ -74,16 +75,28 @@ function pickMoney(raw, ...keys) { return null } +/** 组织名称后接企业类型,如「创意元素(一般纳税人)」 */ +export function formatCompanyNameWithOrgType(name, orgTypeLabel) { + const n = String(name ?? '').trim() + const t = String(orgTypeLabel ?? '').trim() + if (n && t) return `${n}(${t})` + return n || t || null +} + /** 定价决策表行 → 成本围栏表格(字段对齐 /api/pricing/table) */ export function normalizePricingTableRow(raw, idx = 0) { if (!raw || typeof raw !== 'object') return null const supplier_bloc_name = pickText(raw, 'supplier_bloc_name') const supplier_company_name = pickText(raw, 'supplier_company_name') + const supplier_org_type = pickRate(raw, 'supplier_org_type') + const supplier_org_type_label = orgTaxTypeLabel(supplier_org_type) || null const goods_name = pickGoodsNameFromListedRow(raw) || pickText(raw, 'goods_name') const bill_type = pickText(raw, 'bill_name', 'bill_type') const input_tax_rate = pickRate(raw, 'bill_tax', 'input_tax_rate') const source_company_name = pickText(raw, 'receive_company_name', 'source_company_name') + const receive_org_type = pickRate(raw, 'receive_org_type') + const receive_org_type_label = orgTaxTypeLabel(receive_org_type) || null const receive_company_code = pickText( raw, 'receive_company_code', @@ -115,12 +128,18 @@ export function normalizePricingTableRow(raw, idx = 0) { key: pickText(raw, 'key', 'id') ?? `${bloc || 'bloc'}-${supplier_company_code || idx}`, supplier_bloc_name, supplier_company_name, + supplier_company_display: formatCompanyNameWithOrgType(supplier_company_name, supplier_org_type_label), + supplier_org_type, + supplier_org_type_label, supplier_company_code, supply_collection_code, goods_name, bill_type, input_tax_rate, source_company_name, + source_company_display: formatCompanyNameWithOrgType(source_company_name, receive_org_type_label), + receive_org_type, + receive_org_type_label, receive_company_code, internal_min_price, internal_bill_type, @@ -160,9 +179,13 @@ export function buildPricingTablePayload(body = {}, opts = {}) { const supply_collection_code = str(body.supply_collection_code) const receive_company_code = str(body.receive_company_code) const customer_code = str(body.customer_code) + const source_warehouse_code = str(body.source_warehouse_code || body.rdc_warehouse_code) + const supplier_company_code = str(body.supplier_company_code || body.company_code) if (supply_collection_code) payload.supply_collection_code = supply_collection_code if (receive_company_code) payload.receive_company_code = receive_company_code if (customer_code) payload.customer_code = customer_code + if (source_warehouse_code) payload.source_warehouse_code = source_warehouse_code + if (supplier_company_code) payload.supplier_company_code = supplier_company_code if (opts.debug === true || body.debug === true) payload.debug = true return payload } diff --git a/src/api/supplyCollectionGoods.js b/src/api/supplyCollectionGoods.js index 0a8bb2f..d0ff823 100755 --- a/src/api/supplyCollectionGoods.js +++ b/src/api/supplyCollectionGoods.js @@ -322,7 +322,8 @@ export function extraFeeFieldLabel(log, fields) { const hit = registry.find((f) => String(f.bill_code || f.key || '').trim() === code) if (hit) { const tax = String(hit.taxLabel ?? hit.bill_tax ?? '').trim() - return tax && tax !== '—' ? `${hit.label} ${tax}%` : hit.label + const taxNum = Number(tax) + return shouldDisplayBillTax(tax) ? `${hit.label} ${taxNum}%` : hit.label } } return extraFeeBillTaxLabel(log?.bill_tax) @@ -944,23 +945,23 @@ export async function fetchCollectionGoods(supplyCollectionCode, options = {}) { } /** POST /api/supply/collection/goods/add */ -export function supplyCollectionGoodsAdd(body) { - return request.post('/api/supply/collection/goods/add', body) +export function supplyCollectionGoodsAdd(body, config = {}) { + return request.post('/api/supply/collection/goods/add', body, config) } /** POST /api/supply/collection/goods/remove */ -export function supplyCollectionGoodsRemove(body) { - return request.post('/api/supply/collection/goods/remove', body) +export function supplyCollectionGoodsRemove(body, config = {}) { + return request.post('/api/supply/collection/goods/remove', body, config) } /** POST /api/supply/collection/goods/batch_add */ -export function supplyCollectionGoodsBatchAdd(body) { - return request.post('/api/supply/collection/goods/batch_add', body) +export function supplyCollectionGoodsBatchAdd(body, config = {}) { + return request.post('/api/supply/collection/goods/batch_add', body, config) } /** POST /api/supply/collection/goods/update */ -export function supplyCollectionGoodsUpdate(body) { - return request.post('/api/supply/collection/goods/update', body) +export function supplyCollectionGoodsUpdate(body, config = {}) { + return request.post('/api/supply/collection/goods/update', body, config) } /** POST /api/supply/collection/copy_goods — 复制物料到其他统筹集合 */ diff --git a/src/api/supplyCoordCollection.js b/src/api/supplyCoordCollection.js index 427d1d9..ce1be16 100755 --- a/src/api/supplyCoordCollection.js +++ b/src/api/supplyCoordCollection.js @@ -1,4 +1,5 @@ import request from '@/utils/request' +import { normalizeCollectionOrgTypeForApi, orgTaxTypeLabel } from '@/constants/orgTaxType' import { fetchCompanyInfo, fetchCompanyListedByCode, pickCompanyFullName, fetchCompanyClassifyByCode } from '@/api/company' import { fetchSupplierGroupTreeListed, @@ -58,7 +59,8 @@ export function buildSupplyCollectionListedBody(filters = {}) { export async function supplyCoordCollectionListed(filters = {}) { const body = buildSupplyCollectionListedBody(filters) const res = await request.post('/api/supply/collection/listed', body) - return extractCoordCollectionRows(unwrapResponseData(res)) + const rows = extractCoordCollectionRows(unwrapResponseData(res)) + return rows } /** @@ -107,6 +109,56 @@ export async function supplyCoordCollectionCopy(body = {}) { return unwrapResponseData(res) } +/** @param {unknown} data */ +export function extractCollectionInfoRows(data) { + if (Array.isArray(data)) return data + if (data && typeof data === 'object') { + const row = /** @type {Record} */ (data) + for (const key of ['list', 'collections', 'items', 'records', 'rows']) { + const v = row[key] + if (Array.isArray(v)) return v + } + const nested = row.data + if (Array.isArray(nested)) return nested + if (nested && typeof nested === 'object') { + return extractCollectionInfoRows(nested) + } + const values = Object.values(row).filter( + (v) => v && typeof v === 'object' && !Array.isArray(v), + ) + if ( + values.length + && values.some((v) => pickCollectionRowCode(/** @type {object} */ (v))) + ) { + return values + } + } + return [] +} + +/** + * POST /api/supply/collection/info — 按编码批量拉供货集及商品信息(多集合对比) + * @param {string[]|string} codes + * @param {{ skipErrorNotify?: boolean }} [opts] + */ +export async function fetchSupplyCoordCollectionInfo(codes, opts = {}) { + const supply_collection_codes = [ + ...new Set( + (Array.isArray(codes) ? codes : [codes]) + .map((c) => String(c || '').trim()) + .filter(Boolean), + ), + ] + if (!supply_collection_codes.length) return [] + const body = supply_collection_codes.length === 1 + ? { supply_collection_code: supply_collection_codes[0] } + : { supply_collection_codes } + const res = await request.post('/api/supply/collection/info', body, { + skipErrorNotify: !!opts.skipErrorNotify, + }) + return extractCollectionInfoRows(unwrapResponseData(res)) +} + /** @param {unknown} list */ function joinNameList(list, pick) { if (!Array.isArray(list)) return '' @@ -136,24 +188,85 @@ export function collectSceneCodesFromRow(row) { return [...new Set(codes)] } +/** @param {unknown} row listed / modify 行中的直供仓对象列表(含 name) */ +export function collectWarehouseRowsFromRow(row) { + const r = row && typeof row === 'object' ? row : {} + /** @type {object[]} */ + const rows = [] + const push = (raw) => { + if (raw == null || raw === '') return + if (typeof raw === 'string') { + const code = raw.trim() + if (code) rows.push({ warehouse_code: code, name: code }) + return + } + if (typeof raw !== 'object') return + const code = String( + raw.warehouse_code + ?? raw.code + ?? raw.warehouse_rdc_code + ?? raw.rdc_code + ?? '', + ).trim() + if (!code) return + rows.push({ + ...raw, + warehouse_code: code, + name: String(raw.name || raw.warehouse_name || code).trim(), + }) + } + for (const key of ['warehouses', 'warehouse_codes', 'warehouse_list', 'direct_warehouses']) { + const arr = r[key] + if (Array.isArray(arr)) arr.forEach(push) + } + if (!rows.length && typeof r.warehouses === 'string') { + try { + const parsed = JSON.parse(r.warehouses) + if (Array.isArray(parsed)) parsed.forEach(push) + } catch { + r.warehouses.split(/[,,、\s]+/).forEach(push) + } + } + const map = new Map() + for (const wh of rows) { + const code = String(wh.warehouse_code || '').trim() + if (code) map.set(code, wh) + } + return [...map.values()] +} + /** @param {unknown} row */ export function collectWarehouseCodesFromRow(row) { const r = row && typeof row === 'object' ? row : {} const codes = [] - const fromArr = Array.isArray(r.warehouses) ? r.warehouses : [] - for (const item of fromArr) { - if (typeof item === 'string') { - const c = item.trim() + const push = (raw) => { + if (raw == null || raw === '') return + if (typeof raw === 'string') { + const c = raw.trim() if (c) codes.push(c) - } else if (item && typeof item === 'object') { - const c = String(item.warehouse_code || item.code || '').trim() + return + } + if (typeof raw === 'object') { + const c = String( + raw.warehouse_code + ?? raw.code + ?? raw.warehouse_rdc_code + ?? raw.rdc_code + ?? '', + ).trim() if (c) codes.push(c) } } - if (!codes.length && Array.isArray(r.warehouse_codes)) { - for (const c of r.warehouse_codes) { - const code = String(c || '').trim() - if (code) codes.push(code) + for (const key of ['warehouses', 'warehouse_codes', 'warehouse_list', 'direct_warehouses']) { + const arr = r[key] + if (Array.isArray(arr)) arr.forEach(push) + } + if (!codes.length && typeof r.warehouses === 'string') { + try { + const parsed = JSON.parse(r.warehouses) + if (Array.isArray(parsed)) parsed.forEach(push) + } catch { + r.warehouses.split(/[,,、\s]+/).forEach(push) } } return [...new Set(codes)] @@ -441,9 +554,10 @@ export async function resolveCollectionSummaryOrgTypes(collection, opts = {}) { supplierRow = row supplierOrgType = await resolveOrgMajorTaxTypeForRow(row) } catch { - supplierOrgType = String( - collection.supplierOrgType || collection.raw?.supplier_org_type || '', - ).trim() + const saved = collection.supplierOrgType ?? collection.raw?.supplier_org_type + if (saved !== '' && saved != null) { + supplierOrgType = orgTaxTypeLabel(saved) || String(saved).trim() + } } } } @@ -471,6 +585,12 @@ export async function resolveCollectionSummaryOrgTypes(collection, opts = {}) { if (!partnerOrgType && classifyName) { partnerOrgType = resolveOrgTypeLabel({ classify_name: classifyName }) } + if (!partnerOrgType) { + const saved = collection.partnerOrgType ?? collection.raw?.partner_org_type + if (saved !== '' && saved != null) { + partnerOrgType = orgTaxTypeLabel(saved) || String(saved).trim() + } + } } const savedBillName = String( @@ -601,6 +721,8 @@ export function mapCoordCollectionListRow(raw, enrich = {}) { } const status = String(row.status || row.state || 'normal').trim() const partnerOrgTypeLive = resolveOrgTypeFromClassify(null, ourClassifyLabel) + const supplierOrgTypeSaved = row.supplier_org_type + const partnerOrgTypeSaved = row.partner_org_type return { code, name: String(row.name || '').trim() || code || '—', @@ -625,8 +747,14 @@ export function mapCoordCollectionListRow(raw, enrich = {}) { || companyCode || '', ).trim() || '—', - supplierOrgType: '', - partnerOrgType: partnerOrgTypeLive || '', + supplierOrgType: + supplierOrgTypeSaved !== '' && supplierOrgTypeSaved != null + ? supplierOrgTypeSaved + : '', + partnerOrgType: + partnerOrgTypeSaved !== '' && partnerOrgTypeSaved != null + ? partnerOrgTypeSaved + : (partnerOrgTypeLive || ''), billName: '', partnerLabel: pickCollectionPartnerLabel(row, receiveCompanyCode, enrich), sceneCodes, @@ -775,9 +903,15 @@ export function buildSupplyCollectionAddBody(parts) { ?? 0, is_valid: p.is_valid !== false, pos: Number.isFinite(Number(p.pos)) ? Number(p.pos) : 0, + bill_name: String(p.bill_name || '').trim(), + supplier_org_type: normalizeCollectionOrgTypeForApi(p.supplier_org_type), + partner_org_type: normalizeCollectionOrgTypeForApi(p.partner_org_type), } if (!body.receive_company_code) delete body.receive_company_code if (!body.owner_code) delete body.owner_code + if (!body.bill_name) delete body.bill_name + if (body.supplier_org_type == null) delete body.supplier_org_type + if (body.partner_org_type == null) delete body.partner_org_type return body } @@ -810,8 +944,8 @@ export function buildCoordCollectionSaveBody(parts) { partner_company_code: String(p.partner_company_code || '').trim(), scene_codes: Array.isArray(p.scene_codes) ? [...p.scene_codes] : [], bill_name: String(p.bill_name || '').trim(), - supplier_org_type: String(p.supplier_org_type || '').trim(), - partner_org_type: String(p.partner_org_type || '').trim(), + supplier_org_type: normalizeCollectionOrgTypeForApi(p.supplier_org_type), + partner_org_type: normalizeCollectionOrgTypeForApi(p.partner_org_type), warehouse_codes: Array.isArray(p.warehouse_codes) ? [...p.warehouse_codes] : [], payment_months: resolveCollectionPaymentMonthsValue(p), acceptance_payment: String(p.acceptance_payment || '').trim(), @@ -825,5 +959,7 @@ export function buildCoordCollectionSaveBody(parts) { } const code = String(p.coord_collection_code || '').trim() if (code) body.coord_collection_code = code + if (body.supplier_org_type == null) delete body.supplier_org_type + if (body.partner_org_type == null) delete body.partner_org_type return body } diff --git a/src/api/supplySupplier.js b/src/api/supplySupplier.js index f9192e4..1b24d5e 100755 --- a/src/api/supplySupplier.js +++ b/src/api/supplySupplier.js @@ -1,5 +1,15 @@ import request from '@/utils/request' import { normalizeOrgSaleType, ORG_SALE_TYPE_CHANNEL } from '@/api/company' +import { + ORG_TAX_TYPE_GENERAL, + ORG_TAX_TYPE_OPTIONS, + ORG_TAX_TYPE_RECEIPT, + ORG_TAX_TYPE_SMALL, + isOrgTaxTypeGeneralOrSmall, + isOrgTaxTypeReceipt, + normalizeOrgTaxTypeForApi, + parseOrgTaxType, +} from '@/constants/orgTaxType' /** @param {unknown} res */ function unwrapResponseData(res) { @@ -12,7 +22,7 @@ function unwrapResponseData(res) { * POST /api/supply/supplier/listed * data 为集团数组;每项 children[] 为旗下组织(不再嵌套)。 * 组织上可有 partners[](关联编码/简称)、ignore_scenes[];可供货场景继承集团 bloc/scene。 - * partners[] 常不含 org_type;展示时按 company_code 走 company/listed 补全(含中文 org_type)。 + * partners[] 常不含 org_type;展示时按 company_code 走 company/listed 补全。 * @param {object} [body] */ export async function supplySupplierListed(body = {}) { @@ -182,7 +192,7 @@ export function pickPartnerOrgDisplayName(row) { return pickSupplierCompanyCode(row) || '—' } -/** partners[] 单项:组织类型(后端中文 org_type,原样展示) */ +/** partners[] 单项:组织类型(org_type:1/2/3 或历史中文) */ export function pickPartnerOrgType(partner) { if (!partner || typeof partner !== 'object') return '' const r = /** @type {Record} */ (partner) @@ -281,21 +291,16 @@ export function findSupplierOrgInListedTree(listedOrTree, orgCompanyCode, blocCo return null } -/** 纳税类型:一般纳税人、小规模纳税人、个人/收据 */ -export const SUPPLIER_ORG_TYPE_OPTIONS = [ - { value: '一般纳税人', label: '一般纳税人' }, - { value: '小规模纳税人', label: '小规模纳税人' }, - { value: '个人/收据', label: '个人/收据' }, -] +/** 纳税类型下拉:1 一般纳税人 / 2 小规模纳税人 / 3 收据组织 */ +export const SUPPLIER_ORG_TYPE_OPTIONS = ORG_TAX_TYPE_OPTIONS -export const SUPPLIER_ORG_TYPE_GENERAL = '一般纳税人' -export const SUPPLIER_ORG_TYPE_SMALL = '小规模纳税人' -export const SUPPLIER_ORG_TYPE_RECEIPT = '个人/收据' +export const SUPPLIER_ORG_TYPE_GENERAL = ORG_TAX_TYPE_GENERAL +export const SUPPLIER_ORG_TYPE_SMALL = ORG_TAX_TYPE_SMALL +export const SUPPLIER_ORG_TYPE_RECEIPT = ORG_TAX_TYPE_RECEIPT /** 表单下拉 key → 落库 output_vat_rate(13/9/6/0) */ export function resolveSupplierOutputVatRate(form) { - const orgType = String(form?.org_type || '').trim() - if (orgType === SUPPLIER_ORG_TYPE_RECEIPT) return 0 + if (isOrgTaxTypeReceipt(form?.org_type)) return 0 const key = String(form?.output_vat_rate_key || '').trim() if (key === 'receipt' || key === 'in_0') return 0 if (key === '13' || key === '9' || key === '6') return Number(key) @@ -306,15 +311,13 @@ export function resolveSupplierOutputVatRate(form) { /** 是否支持收据(仅一般/小规模;个人/收据固定 false)— 开关:是否允许选「收据」 */ export function resolveSupplierIsSupportReceipt(form) { - const orgType = String(form?.org_type || '').trim() - if (!supplierOrgTypeShowsReceiptSwitch(orgType)) return false + if (!supplierOrgTypeShowsReceiptSwitch(form?.org_type)) return false return form?.is_support_receipt === true } /** 当前增值税率是否选「收据」(与普票 0 同为 output_vat_rate=0,须单独落库) */ export function resolveSupplierIsOutputReceipt(form) { - const orgType = String(form?.org_type || '').trim() - if (!supplierOrgTypeShowsVatRateSelect(orgType)) return false + if (!supplierOrgTypeShowsVatRateSelect(form?.org_type)) return false return String(form?.output_vat_rate_key || '').trim() === 'receipt' } @@ -373,12 +376,12 @@ export function pickSupplierIsOutputReceipt(row) { * @param {boolean} _isSupportReceipt - 保留参数签名兼容旧调用,不再影响下拉内容 */ export function buildSupplierOutputVatRateOptions(orgType, _isSupportReceipt = false) { - const t = String(orgType || '').trim() - if (t === SUPPLIER_ORG_TYPE_RECEIPT) return [] - if (t === SUPPLIER_ORG_TYPE_SMALL) { + const t = parseOrgTaxType(orgType) + if (t === ORG_TAX_TYPE_RECEIPT) return [] + if (t === ORG_TAX_TYPE_SMALL) { return [{ value: 'in_0', label: '0% 普票进项增值税率' }] } - if (t === SUPPLIER_ORG_TYPE_GENERAL) { + if (t === ORG_TAX_TYPE_GENERAL) { return [ { value: '13', label: '13% 销项增值税率' }, { value: '9', label: '9% 销项增值税率' }, @@ -397,8 +400,8 @@ export function hydrateSupplierOutputVatRateKey(opts = {}) { if (storedKey && ['13', '9', '6', 'in_0', 'receipt'].includes(storedKey)) { return storedKey } - const t = String(opts.orgType || '').trim() - if (t === SUPPLIER_ORG_TYPE_RECEIPT) return '' + const t = parseOrgTaxType(opts.orgType) + if (t === ORG_TAX_TYPE_RECEIPT) return '' const rateNum = Number(opts.rateRaw) if (!Number.isFinite(rateNum)) return '' const rate = Math.round(rateNum) @@ -425,7 +428,7 @@ export function hydrateSupplierOrgTaxFields(row) { output_vat_rate_key = defaultSupplierOutputVatRateKey(orgType) } return { - org_type: orgType, + org_type: parseOrgTaxType(orgType) ?? '', is_support_receipt: isSupportReceipt, output_vat_rate_key, } @@ -434,7 +437,7 @@ export function hydrateSupplierOrgTaxFields(row) { /** 详情区展示:按 org_type + output_vat_rate 显示税率文案 */ export function formatSupplierOutputVatRateLabel(row) { const orgType = pickSupplierOrgType(row) - if (orgType === SUPPLIER_ORG_TYPE_RECEIPT) return '0(收据)' + if (isOrgTaxTypeReceipt(orgType)) return '0(收据)' const isSupportReceipt = pickSupplierIsSupportReceipt(row) const isOutputReceipt = pickSupplierIsOutputReceipt(row) const rate = pickSupplierOutputVatRate(row) @@ -519,7 +522,7 @@ export function buildSupplierFormStateFromRow(row) { /** 纳税类型变更时同步增值税率 key(仅用户手动改类型时调用,回填勿调用) */ export function syncSupplierVatRateKeyForOrgType(form, orgType, { preserveIfValid = false } = {}) { - const t = String(orgType || '').trim() + const t = parseOrgTaxType(orgType) if (!supplierOrgTypeShowsVatRateSelect(t)) { form.output_vat_rate_key = '' form.is_support_receipt = false @@ -540,15 +543,14 @@ export function syncSupplierVatRateKeyForReceiptSwitch(_form) { } export function defaultSupplierOutputVatRateKey(orgType) { - const t = String(orgType || '').trim() - if (t === SUPPLIER_ORG_TYPE_GENERAL) return '13' - if (t === SUPPLIER_ORG_TYPE_SMALL) return 'in_0' + const t = parseOrgTaxType(orgType) + if (t === ORG_TAX_TYPE_GENERAL) return '13' + if (t === ORG_TAX_TYPE_SMALL) return 'in_0' return '' } export function supplierOrgTypeShowsVatRateSelect(orgType) { - const t = String(orgType || '').trim() - return t === SUPPLIER_ORG_TYPE_GENERAL || t === SUPPLIER_ORG_TYPE_SMALL + return isOrgTaxTypeGeneralOrSmall(orgType) } export function supplierOrgTypeShowsReceiptSwitch(orgType) { @@ -621,7 +623,7 @@ function pickSupplierRowNumber(row, key, fallback = 0) { /** @param {object} form */ export function buildSupplierFormFields(form) { - const org_type = String(form?.org_type || '').trim() + const org_type = normalizeOrgTaxTypeForApi(form?.org_type) return { short_name: String(form?.short_name || '').trim(), full_name: String(form?.full_name || '').trim(), @@ -644,8 +646,8 @@ export function buildSupplierFormFields(form) { function buildSupplierOrgModifyBody(formOrRow, { ignoreScenes, useFormResolvers = false } = {}) { const src = formOrRow && typeof formOrRow === 'object' ? formOrRow : {} const org_type = useFormResolvers - ? String(src.org_type || '').trim() - : pickSupplierOrgType(src) + ? normalizeOrgTaxTypeForApi(src.org_type) + : normalizeOrgTaxTypeForApi(pickSupplierOrgType(src)) const output_vat_rate = useFormResolvers ? resolveSupplierOutputVatRate(src) : (pickSupplierOutputVatRate(src) ?? pickSupplierRowNumber(src, 'output_vat_rate')) diff --git a/src/api/warehouseAlloc.js b/src/api/warehouseAlloc.js index e1a0daa..f1e18f6 100644 --- a/src/api/warehouseAlloc.js +++ b/src/api/warehouseAlloc.js @@ -1,5 +1,5 @@ import request from '@/utils/request' -import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function unwrapListed(res) { const data = res?.data @@ -113,6 +113,7 @@ export function normalizeAllocAttrRow(raw) { ?? raw?.goods?.goods_name ?? raw.name, ), + goods_category_path: pickGoodsCategoryPathText(raw), fees: num(raw.fees ?? raw.total_fees ?? raw.fee ?? raw.amount), count: num(raw.count ?? raw.total_count ?? raw.qty), } @@ -158,6 +159,7 @@ export function normalizeAllocLogRow(raw) { ?? raw?.goods?.name ?? raw?.goods?.goods_name, ), + goods_category_path: pickGoodsCategoryPathText(raw), fees: num(raw.fees ?? raw.fee), count: num(raw.count ?? raw.qty), remark: str(raw.remark ?? raw.desc), @@ -176,6 +178,29 @@ function buildListedPayload(body = {}) { return payload } +/** POST /api/warehouse/alloc/collect/listed */ +export async function allocCollectListed(body = {}) { + const payload = buildListedPayload({ + goods_code: str(body.goods_code) || undefined, + rdc_warehouse_code: str(body.rdc_warehouse_code) || undefined, + fdc_warehouse_code: str(body.fdc_warehouse_code) || undefined, + bill_tax: body.bill_tax != null && body.bill_tax !== '' ? Number(body.bill_tax) : undefined, + page: body.page, + limit: body.limit, + }) + const res = await request.post('/api/warehouse/alloc/collect/listed', payload) + const { list, total } = unwrapListed(res) + return { + list: list.map(normalizeAllocCollectRow).filter(Boolean), + total, + } +} + +/** collect 汇总行(含配货属性信息,字段与 attr 列表对齐) */ +export function normalizeAllocCollectRow(raw) { + return normalizeAllocAttrRow(raw) +} + /** POST /api/warehouse/alloc/attr/listed */ export async function allocAttrListed(body = {}) { const res = await request.post('/api/warehouse/alloc/attr/listed', buildListedPayload(body)) @@ -213,15 +238,28 @@ export async function allocLogsListed(body = {}) { } } -/** POST /api/warehouse/alloc/logs/add */ +function normalizeLogAddGoodsList(goods, legacy) { + const list = (Array.isArray(goods) ? goods : []) + .map((g) => ({ + goods_code: str(g?.goods_code), + count: Number(g?.count), + })) + .filter((g) => g.goods_code) + if (list.length) return list + const goods_code = str(legacy?.goods_code) + if (!goods_code) return [] + return [{ goods_code, count: Number(legacy?.count) }] +} + +/** POST /api/warehouse/alloc/logs/add — goods: [{ goods_code, count }],fees 为总费用按 logistics_size 占比拆分 */ export async function allocLogsAdd(body) { + const goods = normalizeLogAddGoodsList(body?.goods, body) const payload = { - goods_code: str(body?.goods_code), + goods, rdc_warehouse_code: str(body?.rdc_warehouse_code), fdc_warehouse_code: str(body?.fdc_warehouse_code), bill_code: str(body?.bill_code), fees: Number(body?.fees), - count: Number(body?.count), remark: str(body?.remark) || undefined, } const res = await request.post('/api/warehouse/alloc/logs/add', payload) @@ -265,6 +303,7 @@ function normalizeAllocShareRow(raw) { unit_amount: num(raw.unit_amount ?? raw.share_unit_amount ?? raw.unit_share_amount), ratio: num(raw.ratio ?? raw.share_ratio ?? raw.amount_ratio), goods_display_name: pickGoodsNameFromListedRow(raw), + goods_category_path: pickGoodsCategoryPathText(raw), share_time: str(raw.share_time ?? raw.amortize_time ?? raw.ctime ?? raw.create_time), } } @@ -274,7 +313,6 @@ export async function allocShareListed(body = {}) { const payload = buildListedPayload({ goods_code: str(body.goods_code) || undefined, rdc_warehouse_code: str(body.rdc_warehouse_code) || undefined, - fdc_warehouse_code: str(body.fdc_warehouse_code) || undefined, bill_tax: body.bill_tax != null && body.bill_tax !== '' ? Number(body.bill_tax) : undefined, day_id: body.day_id != null && body.day_id !== '' ? Number(body.day_id) : undefined, page: body.page, diff --git a/src/api/warehouseDeploy.js b/src/api/warehouseDeploy.js index 367a65c..031e57c 100644 --- a/src/api/warehouseDeploy.js +++ b/src/api/warehouseDeploy.js @@ -1,5 +1,5 @@ import request from '@/utils/request' -import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function unwrapListed(res) { const data = res?.data @@ -115,6 +115,7 @@ export function normalizeDeployAttrRow(raw) { ?? raw?.goods?.goods_name ?? raw.name, ), + goods_category_path: pickGoodsCategoryPathText(raw), fees: num(raw.fees ?? raw.total_fees ?? raw.fee ?? raw.amount), count: num(raw.count ?? raw.total_count ?? raw.qty), } @@ -160,6 +161,7 @@ export function normalizeDeployLogRow(raw) { ?? raw?.goods?.name ?? raw?.goods?.goods_name, ), + goods_category_path: pickGoodsCategoryPathText(raw), fees: num(raw.fees ?? raw.fee), count: num(raw.count ?? raw.qty), remark: str(raw.remark ?? raw.desc), @@ -178,6 +180,29 @@ function buildListedPayload(body = {}) { return payload } +/** POST /api/warehouse/deploy/collect/listed */ +export async function deployCollectListed(body = {}) { + const payload = buildListedPayload({ + goods_code: str(body.goods_code) || undefined, + source_warehouse_code: str(body.source_warehouse_code ?? body.out_warehouse_code) || undefined, + self_warehouse_code: str(body.self_warehouse_code ?? body.in_warehouse_code) || undefined, + bill_tax: body.bill_tax != null && body.bill_tax !== '' ? Number(body.bill_tax) : undefined, + page: body.page, + limit: body.limit, + }) + const res = await request.post('/api/warehouse/deploy/collect/listed', payload) + const { list, total } = unwrapListed(res) + return { + list: list.map(normalizeDeployCollectRow).filter(Boolean), + total, + } +} + +/** collect 汇总行(含调货属性信息,字段与 attr 列表对齐) */ +export function normalizeDeployCollectRow(raw) { + return normalizeDeployAttrRow(raw) +} + /** POST /api/warehouse/deploy/attr/listed */ export async function deployAttrListed(body = {}) { const res = await request.post('/api/warehouse/deploy/attr/listed', buildListedPayload(body)) @@ -198,15 +223,28 @@ export async function deployLogsListed(body = {}) { } } -/** POST /api/warehouse/deploy/logs/add */ +function normalizeLogAddGoodsList(goods, legacy) { + const list = (Array.isArray(goods) ? goods : []) + .map((g) => ({ + goods_code: str(g?.goods_code), + count: Number(g?.count), + })) + .filter((g) => g.goods_code) + if (list.length) return list + const goods_code = str(legacy?.goods_code) + if (!goods_code) return [] + return [{ goods_code, count: Number(legacy?.count) }] +} + +/** POST /api/warehouse/deploy/logs/add — goods: [{ goods_code, count }],fees 为总费用按 logistics_size 占比拆分 */ export async function deployLogsAdd(body) { + const goods = normalizeLogAddGoodsList(body?.goods, body) const payload = { - goods_code: str(body?.goods_code), + goods, source_warehouse_code: str(body?.source_warehouse_code ?? body?.out_warehouse_code), self_warehouse_code: str(body?.self_warehouse_code ?? body?.in_warehouse_code), bill_code: str(body?.bill_code), fees: Number(body?.fees), - count: Number(body?.count), remark: str(body?.remark) || undefined, } const res = await request.post('/api/warehouse/deploy/logs/add', payload) @@ -250,6 +288,7 @@ function normalizeDeployShareRow(raw) { unit_amount: num(raw.unit_amount ?? raw.share_unit_amount ?? raw.unit_share_amount), ratio: num(raw.ratio ?? raw.share_ratio ?? raw.amount_ratio), goods_display_name: pickGoodsNameFromListedRow(raw), + goods_category_path: pickGoodsCategoryPathText(raw), share_time: str(raw.share_time ?? raw.amortize_time ?? raw.ctime ?? raw.create_time), } } diff --git a/src/api/warehouseFdc.js b/src/api/warehouseFdc.js index d9fd941..affff8d 100755 --- a/src/api/warehouseFdc.js +++ b/src/api/warehouseFdc.js @@ -3,6 +3,10 @@ import { postWarehouseBatch, WAREHOUSE_BATCH_TIMEOUT_MS } from '@/api/warehouseB import { parseWarehouseRdcFdcListedRows } from '@/api/warehouseRdcFdc' import { pickGoodsDisplayName, pickFdcLinkedRdcWarehouseCode } from '@/utils/rdcGoodsLink' import { isValidWhLogType } from '@/constants/whLotType' +import { + prepareWarehouseSetZonePayload, + validateWarehouseSetZonePayload, +} from '@/constants/whGoodsZone' function unwrapApiPayload(res) { if (res == null || typeof res !== 'object') return res @@ -347,6 +351,29 @@ export function warehouseFdcSetLotType(body) { }) } +/** POST /api/warehouse/fdc/set_zone — body: { warehouse_code, goods_code, warehouse_zone_code, rows, cols };地面 cols=0 */ +export function warehouseFdcSetZone(body) { + const { + warehouse_code, + goods_code, + warehouse_zone_code, + rows, + cols, + is_ground, + } = prepareWarehouseSetZonePayload(body) + const err = validateWarehouseSetZonePayload({ + warehouse_code, + goods_code, + warehouse_zone_code, + rows, + cols, + is_ground, + }) + if (err) return Promise.reject(new Error(err)) + const payload = { warehouse_code, goods_code, warehouse_zone_code, rows, cols } + return request.post('/api/warehouse/fdc/set_zone', payload) +} + /** POST /api/warehouse/fdc/find_rdc — body: { warehouse_code, list: [{ goods_code }] } */ export function warehouseFdcFindRdc(body) { const warehouse_code = String(body?.warehouse_code ?? '').trim() diff --git a/src/api/warehouseFdcAlloc.js b/src/api/warehouseFdcAlloc.js new file mode 100644 index 0000000..964a111 --- /dev/null +++ b/src/api/warehouseFdcAlloc.js @@ -0,0 +1,188 @@ +import request from '@/utils/request' +import { normalizeWhOrgType } from '@/constants/whOrgType' +import { formatRdcWhNodeLabel, pickFdcLinkAnchorKey } from '@/utils/whFdcFindRdcTree' + +function unwrapListed(res) { + if (Array.isArray(res)) return res + if (res && typeof res === 'object') { + if (Array.isArray(res.data)) return res.data + if (Array.isArray(res.list)) return res.list + if (res.data && typeof res.data === 'object') { + for (const key of ['list', 'rows', 'items', 'records']) { + if (Array.isArray(res.data[key])) return res.data[key] + } + } + } + return [] +} + +export function withAllocOrgType(payload, orgType) { + return { ...payload, org_type: normalizeWhOrgType(orgType ?? payload?.org_type, 1) } +} + +/** 上级 RDC 仓编码(读响应兼容旧字段) */ +export function pickAllocRdcWarehouseCode(raw) { + return String( + raw?.rdc_warehouse_code + ?? raw?.to_warehouse_code + ?? raw?.warehouse_rdc_code + ?? raw?.rdc_code + ?? raw?.warehouse_code + ?? '', + ).trim() +} + +export function normalizeFdcAllocRow(raw) { + if (!raw || typeof raw !== 'object') return null + const rdc_warehouse_code = pickAllocRdcWarehouseCode(raw) + if (!rdc_warehouse_code) return null + const rdc_warehouse_name = String( + raw?.rdc_warehouse_name + ?? raw?.warehouse_rdc_name + ?? raw?.warehouse_name + ?? raw?.name + ?? '', + ).trim() + const label = formatRdcWhNodeLabel(rdc_warehouse_name || rdc_warehouse_code, rdc_warehouse_code) + return { + rdc_warehouse_code, + rdc_warehouse_name: rdc_warehouse_name || rdc_warehouse_code, + label, + row: raw, + } +} + +export function buildFdcAllocLocateItem(fdcWarehouseCode, goodsCode) { + const fdc_warehouse_code = String(fdcWarehouseCode ?? '').trim() + const goods_code = String(goodsCode ?? '').trim() + if (!fdc_warehouse_code || !goods_code) return null + return { fdc_warehouse_code, goods_code } +} + +function normalizeAllocToListItem(raw) { + const rdc_warehouse_code = pickAllocRdcWarehouseCode(raw) + if (!rdc_warehouse_code) return null + return { rdc_warehouse_code } +} + +/** POST /api/warehouse/fdc/alloc — 已关联上级 RDC 列表 */ +export async function warehouseFdcAllocListed(body) { + const fdc_warehouse_code = String(body?.fdc_warehouse_code ?? body?.warehouse_code ?? '').trim() + const goods_code = String(body?.goods_code ?? '').trim() + if (!fdc_warehouse_code || !goods_code) return [] + const payload = withAllocOrgType({ fdc_warehouse_code, goods_code }, body?.org_type) + const res = await request.post('/api/warehouse/fdc/alloc', payload) + return unwrapListed(res).map(normalizeFdcAllocRow).filter(Boolean) +} + +/** POST /api/warehouse/fdc/alloc/set — 按 org_type 全量替换 */ +export function warehouseFdcAllocSet(body) { + const list = (Array.isArray(body?.list) ? body.list : []) + .map((item) => buildFdcAllocLocateItem( + item?.fdc_warehouse_code ?? item?.warehouse_code, + item?.goods_code, + )) + .filter(Boolean) + const to_list = (Array.isArray(body?.to_list) ? body.to_list : []) + .map(normalizeAllocToListItem) + .filter(Boolean) + if (!list.length) { + return Promise.reject(new Error('fdc/alloc/set 缺少 list(物料)')) + } + const payload = withAllocOrgType({ list, to_list }, body?.org_type) + return request.post('/api/warehouse/fdc/alloc/set', payload).then((res) => res) +} + +function serializeAllocMutateBody(body) { + const fdc_warehouse_code = String(body?.fdc_warehouse_code ?? body?.warehouse_code ?? '').trim() + const rdc_warehouse_code = pickAllocRdcWarehouseCode(body) + const goods_code = String(body?.goods_code ?? '').trim() + if (!fdc_warehouse_code || !rdc_warehouse_code || !goods_code) return null + return withAllocOrgType({ fdc_warehouse_code, rdc_warehouse_code, goods_code }, body?.org_type) +} + +/** POST /api/warehouse/fdc/alloc/link */ +export function warehouseFdcAllocLink(body) { + const payload = serializeAllocMutateBody(body) + if (!payload) { + return Promise.reject(new Error('fdc/alloc/link 缺少 fdc_warehouse_code / rdc_warehouse_code / goods_code')) + } + return request.post('/api/warehouse/fdc/alloc/link', payload).then((res) => res) +} + +/** POST /api/warehouse/fdc/alloc/unlink */ +export function warehouseFdcAllocUnlink(body) { + const payload = serializeAllocMutateBody(body) + if (!payload) { + return Promise.reject(new Error('fdc/alloc/unlink 缺少 fdc_warehouse_code / rdc_warehouse_code / goods_code')) + } + return request.post('/api/warehouse/fdc/alloc/unlink', payload).then((res) => res) +} + +/** find_rdc 树 → 单物料可选的 RDC 仓平铺列表 */ +export function listFdcAllocCandidatesForGoods(findRdcTree, goodsCode) { + const gc = String(goodsCode ?? '').trim() + if (!gc) return [] + const rows = [] + const seen = new Set() + for (const wh of findRdcTree || []) { + if (wh?.nodeType !== 'wh') continue + const warehouse_code = String(wh?.warehouse_code ?? '').trim() + if (!warehouse_code) continue + const goodsList = Array.isArray(wh?.children) ? wh.children : [] + const matched = goodsList.some((g) => String(g?.goods_code ?? '').trim() === gc) + if (!matched || seen.has(warehouse_code)) continue + seen.add(warehouse_code) + rows.push({ + key: `rdc:${warehouse_code}`, + rdc_warehouse_code: warehouse_code, + label: String(wh?.label ?? formatRdcWhNodeLabel(wh?.warehouse_name, warehouse_code)).trim() + || warehouse_code, + node: wh, + }) + } + return rows +} + +export async function persistFdcAllocSetForRows(rows, fdcToRdcMap, fdcWarehouseCode, pickAnchorKey, orgType = 1) { + const pickKey = typeof pickAnchorKey === 'function' ? pickAnchorKey : () => '' + let count = 0 + for (const row of rows || []) { + const anchorKey = pickKey(row) + const pick = anchorKey ? fdcToRdcMap?.[anchorKey] : null + const rdc_warehouse_code = pickAllocRdcWarehouseCode(pick) + const locate = buildFdcAllocLocateItem(fdcWarehouseCode, row?.goods_code) + if (!locate || !rdc_warehouse_code) continue + await warehouseFdcAllocSet({ + org_type: orgType, + list: [locate], + to_list: [{ rdc_warehouse_code }], + }) + count += 1 + } + return count +} + +/** 按目标 RDC 仓分组 alloc/set(批量弹窗) */ +export function groupFdcAllocSetRequests(rows, fdcToRdcMap, fdcWarehouseCode, orgType = 1) { + const fdc_warehouse_code = String(fdcWarehouseCode ?? '').trim() + const groups = new Map() + for (const row of rows || []) { + const anchorKey = pickFdcLinkAnchorKey(row) + const pick = anchorKey ? fdcToRdcMap?.[anchorKey] : null + const rdc_warehouse_code = pickAllocRdcWarehouseCode(pick) + const goods_code = String(row?.goods_code ?? '').trim() + if (!fdc_warehouse_code || !rdc_warehouse_code || !goods_code) continue + if (!groups.has(rdc_warehouse_code)) groups.set(rdc_warehouse_code, []) + const bucket = groups.get(rdc_warehouse_code) + if (!bucket.some((item) => item.goods_code === goods_code)) { + bucket.push({ fdc_warehouse_code, goods_code }) + } + } + return [...groups.entries()] + .filter(([, list]) => list.length) + .map(([rdc_warehouse_code, list]) => withAllocOrgType({ + list, + to_list: [{ rdc_warehouse_code }], + }, orgType)) +} diff --git a/src/api/warehouseFreight.js b/src/api/warehouseFreight.js index 319f773..04f9517 100644 --- a/src/api/warehouseFreight.js +++ b/src/api/warehouseFreight.js @@ -1,5 +1,7 @@ import request from '@/utils/request' -import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { formatCompanyNameWithOrgType } from '@/api/pricingTable' +import { orgTaxTypeLabel } from '@/constants/orgTaxType' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function unwrapListed(res) { const data = res?.data @@ -52,9 +54,9 @@ function pickRdcSide(raw) { } function pickSupplyCompanySide(raw) { - const attr = raw?.freight_attr ?? raw?.alloc_attr + const attr = raw?.freight_attr ?? raw?.alloc_attr ?? raw?.purchase_attr const sources = [raw, attr, raw?.goods].filter((s) => s && typeof s === 'object') - const compObj = raw?.supply_company ?? attr?.supply_company + const compObj = raw?.supply_company ?? attr?.supply_company ?? raw?.supplier_company ?? attr?.supplier_company let code = '' let name = '' @@ -62,6 +64,7 @@ function pickSupplyCompanySide(raw) { if (!code) { code = str( src?.supply_company_code + ?? src?.supplier_company_code ?? src?.supplyCompanyCode ?? src?.org_company_code ?? src?.company_code, @@ -70,6 +73,7 @@ function pickSupplyCompanySide(raw) { if (!name) { name = str( src?.supply_company_name + ?? src?.supplier_company_name ?? src?.supplyCompanyName ?? src?.company_name ?? src?.name, @@ -79,24 +83,66 @@ function pickSupplyCompanySide(raw) { if (!code) { code = str( compObj?.supply_company_code + ?? compObj?.supplier_company_code ?? compObj?.org_company_code ?? compObj?.company_code ?? compObj?.code, ) } - if (!name) name = str(compObj?.name ?? compObj?.company_name ?? compObj?.supply_company_name) + if (!name) { + name = str( + compObj?.name + ?? compObj?.company_name + ?? compObj?.supply_company_name + ?? compObj?.supplier_company_name, + ) + } if (name && code && name === code) name = '' return { code, name } } +function pickDirectOrgType(sources, keys) { + for (const src of sources) { + if (!src || typeof src !== 'object') continue + for (const key of keys) { + const v = src[key] + if (v !== '' && v != null) return v + } + } + return null +} + +function pickOrgTypeLabelFromSources(sources, keys) { + const direct = pickDirectOrgType(sources, keys) + if (direct != null) { + const label = orgTaxTypeLabel(direct) + if (label) return label + } + return '' +} + +function pickSupplyCompanyOrgTypeLabel(raw) { + const attr = raw?.freight_attr ?? raw?.alloc_attr ?? raw?.purchase_attr + const compObj = raw?.supply_company ?? attr?.supply_company ?? raw?.supplier_company ?? attr?.supplier_company + const sources = [raw, attr, compObj].filter((s) => s && typeof s === 'object') + return pickOrgTypeLabelFromSources( + sources, + ['supplier_company_org_type', 'supply_company_org_type', 'org_type'], + ) +} + function pickRdcSupplyPair(raw) { const rdc = pickRdcSide(raw) const supply = pickSupplyCompanySide(raw) + const supply_company_org_type_label = pickSupplyCompanyOrgTypeLabel(raw) + const supply_company_display = formatCompanyNameWithOrgType(supply.name, supply_company_org_type_label) return { rdc_warehouse_code: rdc.code, rdc_warehouse_name: rdc.name, supply_company_code: supply.code, supply_company_name: supply.name, + supply_company_org_type_label, + supply_company_display: supply_company_display || supply.name || '', } } @@ -132,6 +178,7 @@ export function normalizeFreightAttrRow(raw) { ?? raw?.goods?.goods_name ?? raw.name, ), + goods_category_path: pickGoodsCategoryPathText(raw), fees: num(raw.fees ?? raw.total_fees ?? raw.fee ?? raw.amount), count: num(raw.count ?? raw.total_count ?? raw.qty), } @@ -176,6 +223,7 @@ export function normalizeFreightLogRow(raw) { ?? raw?.goods?.name ?? raw?.goods?.goods_name, ), + goods_category_path: pickGoodsCategoryPathText(raw), fees: num(raw.fees ?? raw.fee), count: num(raw.count ?? raw.qty), remark: str(raw.remark ?? raw.desc), @@ -194,6 +242,29 @@ function buildListedPayload(body = {}) { return payload } +/** POST /api/warehouse/freight/collect/listed */ +export async function freightCollectListed(body = {}) { + const payload = buildListedPayload({ + goods_code: str(body.goods_code) || undefined, + rdc_warehouse_code: str(body.rdc_warehouse_code) || undefined, + supply_company_code: str(body.supply_company_code) || undefined, + bill_tax: body.bill_tax != null && body.bill_tax !== '' ? Number(body.bill_tax) : undefined, + page: body.page, + limit: body.limit, + }) + const res = await request.post('/api/warehouse/freight/collect/listed', payload) + const { list, total } = unwrapListed(res) + return { + list: list.map(normalizeFreightCollectRow).filter(Boolean), + total, + } +} + +/** collect 汇总行(含运货属性信息,字段与 attr 列表对齐) */ +export function normalizeFreightCollectRow(raw) { + return normalizeFreightAttrRow(raw) +} + /** POST /api/warehouse/freight/attr/listed */ export async function freightAttrListed(body = {}) { const res = await request.post('/api/warehouse/freight/attr/listed', buildListedPayload(body)) @@ -253,9 +324,17 @@ export function normalizeFreightShareRow(raw) { ?? raw.freight_total, ) + const wh = pickRdcSupplyPair(raw) + const supplier_company_name = str(raw.supplier_company_name) || wh.supply_company_name + const supplier_company_display = wh.supply_company_display + || formatCompanyNameWithOrgType(supplier_company_name, wh.supply_company_org_type_label) + || supplier_company_name + return { ...raw, - ...pickRdcSupplyPair(raw), + ...wh, + supplier_company_name, + supplier_company_display, total_count: purchase_count, purchase_count, purchase_amount, @@ -269,6 +348,7 @@ export function normalizeFreightShareRow(raw) { unit_amount: num(raw.unit_amount ?? raw.share_unit_amount ?? raw.unit_share_amount), ratio: num(raw.ratio ?? raw.share_ratio ?? raw.amount_ratio), goods_display_name: pickGoodsNameFromListedRow(raw), + goods_category_path: pickGoodsCategoryPathText(raw), share_time: str(raw.share_time ?? raw.amortize_time ?? raw.ctime ?? raw.create_time), } } diff --git a/src/api/warehouseLaborCost.js b/src/api/warehouseLaborCost.js new file mode 100644 index 0000000..1806797 --- /dev/null +++ b/src/api/warehouseLaborCost.js @@ -0,0 +1,113 @@ +import request from '@/utils/request' + +function str(v) { + return String(v ?? '').trim() +} + +function num(v) { + if (v == null || v === '') return null + const n = Number(v) + return Number.isFinite(n) ? n : null +} + +function unwrapListed(res) { + const root = res?.data ?? res + if (Array.isArray(root)) { + return { list: root, total: root.length } + } + if (root && typeof root === 'object') { + const list = Array.isArray(root.list) + ? root.list + : Array.isArray(root.data) + ? root.data + : Array.isArray(root.rows) + ? root.rows + : [] + const total = Number(root.total ?? root.count ?? list.length) + return { list, total: Number.isFinite(total) && total >= 0 ? total : list.length } + } + return { list: [], total: 0 } +} + +/** 列表行主键 */ +export function pickWarehouseLaborCostCode(row) { + if (row == null) return '' + if (typeof row === 'string' || typeof row === 'number') return str(row) + return str(row.warehouse_labor_cost_code ?? row.code) +} + +export function normalizeWarehouseLaborCostRow(raw) { + if (!raw || typeof raw !== 'object') return null + const warehouse_code = str(raw.warehouse_code) + const user_code = str(raw.user_code) + return { + ...raw, + warehouse_labor_cost_code: pickWarehouseLaborCostCode(raw), + warehouse_code, + warehouse_name: str(raw.warehouse_name), + user_code, + user_name: str(raw.user_name), + salary: num(raw.salary), + pay_time: str(raw.pay_time), + cuser: str(raw.cuser), + ctime: str(raw.ctime), + } +} + +export function pickApiErrorMessage(err, fallback = '操作失败') { + return String( + err?.data?.message + || err?.data?.data?.message + || err?.message + || fallback, + ).trim() || fallback +} + +/** POST /api/cost/warehouse_labor_cost/listed */ +export async function warehouseLaborCostListed(body = {}) { + const payload = { + page: Math.max(1, Number(body.page) || 1), + limit: Math.min(50, Math.max(1, Number(body.limit) || 20)), + } + if (str(body.warehouse_code)) payload.warehouse_code = str(body.warehouse_code) + if (str(body.user_code)) payload.user_code = str(body.user_code) + if (str(body.pay_time_start)) payload.pay_time_start = str(body.pay_time_start) + if (str(body.pay_time_end)) payload.pay_time_end = str(body.pay_time_end) + const res = await request.post('/api/cost/warehouse_labor_cost/listed', payload) + const { list, total } = unwrapListed(res) + return { + list: list.map(normalizeWarehouseLaborCostRow).filter(Boolean), + total, + } +} + +/** POST /api/cost/warehouse_labor_cost/add */ +export async function warehouseLaborCostAdd(body = {}) { + const payload = { + warehouse_code: str(body.warehouse_code), + user_code: str(body.user_code), + salary: Number(body.salary), + pay_time: str(body.pay_time), + } + const res = await request.post('/api/cost/warehouse_labor_cost/add', payload) + return res?.data ?? res +} + +/** POST /api/cost/warehouse_labor_cost/modify */ +export async function warehouseLaborCostModify(body = {}) { + const payload = { warehouse_labor_cost_code: str(body.warehouse_labor_cost_code) } + if (str(body.warehouse_code)) payload.warehouse_code = str(body.warehouse_code) + if (str(body.user_code)) payload.user_code = str(body.user_code) + if (body.salary != null && body.salary !== '') payload.salary = Number(body.salary) + if (str(body.pay_time)) payload.pay_time = str(body.pay_time) + const res = await request.post('/api/cost/warehouse_labor_cost/modify', payload) + return res?.data ?? res +} + +/** POST /api/cost/warehouse_labor_cost/remove */ +export async function warehouseLaborCostRemove(warehouse_labor_cost_code) { + const res = await request.post('/api/cost/warehouse_labor_cost/remove', { + warehouse_labor_cost_code: str(warehouse_labor_cost_code), + }) + return res?.data ?? res +} diff --git a/src/api/warehousePurchase.js b/src/api/warehousePurchase.js index 6529721..b7bceff 100644 --- a/src/api/warehousePurchase.js +++ b/src/api/warehousePurchase.js @@ -1,5 +1,7 @@ import request from '@/utils/request' -import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { formatCompanyNameWithOrgType } from '@/api/pricingTable' +import { orgTaxTypeLabel } from '@/constants/orgTaxType' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function unwrapListed(res) { const data = res?.data @@ -105,20 +107,67 @@ function pickCompanySide(raw) { if (!code) code = str(compObj?.company_code ?? compObj?.code) if (!name) name = str(compObj?.name ?? compObj?.company_name ?? compObj?.full_name) if (name && code && name === code) name = '' - return { code, name } + return { code, name, compObj } +} + +function pickDirectOrgType(sources, keys) { + for (const src of sources) { + if (!src || typeof src !== 'object') continue + for (const key of keys) { + const v = src[key] + if (v !== '' && v != null) return v + } + } + return null +} + +function pickOrgTypeLabelFromSources(sources, keys) { + const direct = pickDirectOrgType(sources, keys) + if (direct != null) { + const label = orgTaxTypeLabel(direct) + if (label) return label + } + return '' +} + +function pickCompanyOrgTypeLabel(raw) { + const attr = raw?.purchase_attr ?? raw?.alloc_attr + const compObj = raw?.company && typeof raw.company === 'object' ? raw.company : attr?.company + const sources = [raw, attr, compObj].filter((s) => s && typeof s === 'object') + return pickOrgTypeLabelFromSources( + sources, + ['company_org_type', 'receive_org_type', 'partner_org_type', 'org_type'], + ) +} + +function pickSupplierCompanyOrgTypeLabel(raw) { + const attr = raw?.purchase_attr ?? raw?.alloc_attr + const compObj = raw?.supplier_company ?? attr?.supplier_company ?? raw?.supply_company ?? attr?.supply_company + const sources = [raw, attr, compObj].filter((s) => s && typeof s === 'object') + return pickOrgTypeLabelFromSources( + sources, + ['supplier_company_org_type', 'supply_company_org_type', 'org_type'], + ) } function pickPurchaseContext(raw) { const rdc = pickRdcSide(raw) const supplier = pickSupplierCompanySide(raw) const company = pickCompanySide(raw) + const company_org_type_label = pickCompanyOrgTypeLabel(raw) + const supplier_company_org_type_label = pickSupplierCompanyOrgTypeLabel(raw) + const supplier_company_display = formatCompanyNameWithOrgType(supplier.name, supplier_company_org_type_label) return { rdc_warehouse_code: rdc.code, rdc_warehouse_name: rdc.name, supplier_company_code: supplier.code, supplier_company_name: supplier.name, + supplier_company_org_type_label, + supplier_company_display: supplier_company_display || supplier.name || '', company_code: company.code, company_name: company.name, + company_org_type_label, + company_display: formatCompanyNameWithOrgType(company.name, company_org_type_label), } } @@ -186,6 +235,7 @@ export function normalizePurchaseCollectRow(raw) { goods_code, goods_name: pickGoodsNameFromListedRow(raw) || str(raw.goods_name ?? raw?.goods?.name ?? raw?.goods?.goods_name), + goods_category_path: pickGoodsCategoryPathText(raw), fees: pickPurchaseFeesDisplay(raw), count: num(raw.count ?? raw.total_count ?? raw.qty), } @@ -216,6 +266,7 @@ export function normalizePurchaseLogRow(raw) { goods_code, goods_name: pickGoodsNameFromListedRow(raw) || str(raw.goods_name ?? raw?.goods?.name ?? raw?.goods?.goods_name), + goods_category_path: pickGoodsCategoryPathText(raw), fees: pickPurchaseFeesDisplay(raw), count: num(raw.count ?? raw.qty), log_time: str(raw.log_time ?? raw.ctime ?? raw.create_time ?? raw.created_at), @@ -310,6 +361,12 @@ export function normalizePurchaseOrderRow(raw) { if (!raw || typeof raw !== 'object') return null const code = pickPurchaseOrderCode(raw) if (!code && raw.id == null) return null + const supplier_company_name = str(raw.supplier_company_name) + const supplier_company_org_type_label = pickSupplierCompanyOrgTypeLabel(raw) + const supplier_company_display = formatCompanyNameWithOrgType( + supplier_company_name, + supplier_company_org_type_label, + ) || supplier_company_name const goods = Array.isArray(raw.goods) ? raw.goods.map((g) => ({ ...g, @@ -319,6 +376,9 @@ export function normalizePurchaseOrderRow(raw) { return { ...raw, purchase_order_code: code || raw.purchase_order_code, + supplier_company_name, + supplier_company_org_type_label, + supplier_company_display, goods, } } diff --git a/src/api/warehouseRdc.js b/src/api/warehouseRdc.js index 6df4b7d..4c852b8 100755 --- a/src/api/warehouseRdc.js +++ b/src/api/warehouseRdc.js @@ -2,6 +2,10 @@ import request from '@/utils/request' import { postWarehouseBatch, WAREHOUSE_BATCH_TIMEOUT_MS } from '@/api/warehouseBatch' import { parseWarehouseRdcFdcListedRows } from '@/api/warehouseRdcFdc' import { isValidWhLogType } from '@/constants/whLotType' +import { + prepareWarehouseSetZonePayload, + validateWarehouseSetZonePayload, +} from '@/constants/whGoodsZone' function unwrapApiPayload(res) { if (res == null || typeof res !== 'object') return res @@ -360,3 +364,40 @@ export function warehouseRdcSetLotType(body) { lot_type, }) } + +/** POST /api/warehouse/rdc/set_zone — body: { warehouse_code, goods_code, warehouse_zone_code, rows, cols };地面 cols=0 */ +export function warehouseRdcSetZone(body) { + const { + warehouse_code, + goods_code, + warehouse_zone_code, + rows, + cols, + is_ground, + } = prepareWarehouseSetZonePayload(body) + const err = validateWarehouseSetZonePayload({ + warehouse_code, + goods_code, + warehouse_zone_code, + rows, + cols, + is_ground, + }) + if (err) return Promise.reject(new Error(err)) + const payload = { warehouse_code, goods_code, warehouse_zone_code, rows, cols } + return request.post('/api/warehouse/rdc/set_zone', payload) +} + +/** POST /api/warehouse/rdc/match_zone — body: { warehouse_code, goods_code } */ +export async function warehouseRdcMatchZone(body) { + const warehouse_code = String(body?.warehouse_code ?? '').trim() + const goods_code = String(body?.goods_code ?? '').trim() + if (!warehouse_code || !goods_code) { + return Promise.reject(new Error('match_zone 缺少 warehouse_code / goods_code')) + } + const res = await request.post('/api/warehouse/rdc/match_zone', { + warehouse_code, + goods_code, + }) + return res +} diff --git a/src/api/warehouseRdcDeploy.js b/src/api/warehouseRdcDeploy.js index e154bfd..fb86e70 100755 --- a/src/api/warehouseRdcDeploy.js +++ b/src/api/warehouseRdcDeploy.js @@ -1,5 +1,6 @@ import request from '@/utils/request' import { pickRdcCodeFromGoodsRow } from '@/api/warehouseRdc' +import { normalizeWhOrgType } from '@/constants/whOrgType' function unwrapListed(res) { if (Array.isArray(res)) return res @@ -15,6 +16,15 @@ function unwrapListed(res) { return [] } +/** deploy 查询 / link / unlink / set 共用 org_type */ +export function pickDeployOrgType(body) { + return normalizeWhOrgType(body?.org_type, 1) +} + +export function withDeployOrgType(payload, orgType) { + return { ...payload, org_type: normalizeWhOrgType(orgType ?? payload?.org_type, 1) } +} + /** 所在仓 RDC 编码(读响应兼容 form / 旧字段) */ export function pickSelfWarehouseCode(raw) { return String( @@ -157,11 +167,11 @@ function normalizeDeployToListItem(raw) { return { source_warehouse_code } } -export function buildRdcDeployLinkBody(goodsRow, warehouseCode, deployTarget) { +export function buildRdcDeployLinkBody(goodsRow, warehouseCode, deployTarget, orgType = 1) { const base = buildRdcDeployQueryBody(goodsRow, warehouseCode) const source_warehouse_code = pickSourceWarehouseCode(deployTarget) if (!base || !source_warehouse_code) return null - return { ...base, source_warehouse_code } + return withDeployOrgType({ ...base, source_warehouse_code }, orgType) } export function buildRdcDeployUnlinkBody(goodsRow, warehouseCode, deployTarget) { @@ -193,7 +203,7 @@ export async function warehouseRdcDeployListed(body) { const self_warehouse_code = pickSelfWarehouseCode(body) const goods_code = String(body?.goods_code ?? '').trim() if (!self_warehouse_code || !goods_code) return [] - const payload = { self_warehouse_code, goods_code } + const payload = withDeployOrgType({ self_warehouse_code, goods_code }, body?.org_type) const res = await request.post('/api/warehouse/rdc/deploy', payload) return unwrapListed(res).map(normalizeRdcDeployRow).filter(Boolean) } @@ -234,33 +244,33 @@ export function subtractRdcDeploySetList(existingRows, removeRows) { export async function buildRdcDeploySetPayloadForMaterial( goodsRow, warehouseCode = '', - { mergePick = null, removeTargets = null } = {}, + { mergePick = null, removeTargets = null, orgType = 1 } = {}, ) { const material = buildRdcDeployGoodsLocateItem(goodsRow, warehouseCode) const query = buildRdcDeployQueryBody(goodsRow, warehouseCode) if (!material || !query) return null - const existing = await warehouseRdcDeployListed(query) + const existing = await warehouseRdcDeployListed({ ...query, org_type: orgType }) let to_list = mergeRdcDeploySetList(existing, []) if (removeTargets?.length) { to_list = subtractRdcDeploySetList(existing, removeTargets) } else if (mergePick) { to_list = mergeRdcDeploySetList(existing, [mergePick]) } - return { list: [material], to_list } + return withDeployOrgType({ list: [material], to_list }, orgType) } /** * 批量关联源采购仓:按物料分别 deploy/set(避免不同物料互相覆盖)。 */ -export async function persistRdcDeploySetForRows(rows, anchorToDeployMap, warehouseCode, pickAnchorKey) { +export async function persistRdcDeploySetForRows(rows, anchorToDeployMap, warehouseCode, pickAnchorKey, orgType = 1) { const pickKey = typeof pickAnchorKey === 'function' ? pickAnchorKey : () => '' let count = 0 for (const row of rows || []) { const anchorKey = pickKey(row) const pick = anchorKey ? anchorToDeployMap?.[anchorKey] : null if (!pick) continue - const payload = await buildRdcDeploySetPayloadForMaterial(row, warehouseCode, { mergePick: pick }) + const payload = await buildRdcDeploySetPayloadForMaterial(row, warehouseCode, { mergePick: pick, orgType }) if (!payload) continue await warehouseRdcDeploySet(payload) count += 1 @@ -279,7 +289,7 @@ export function warehouseRdcDeploySet(body) { if (!list.length) { return Promise.reject(new Error('deploy/set 缺少 list(物料)')) } - const payload = { list, to_list } + const payload = withDeployOrgType({ list, to_list }, body?.org_type) return request.post('/api/warehouse/rdc/deploy/set', payload).then((res) => res) } @@ -290,7 +300,7 @@ function serializeDeployMutateBody(body) { if (!self_warehouse_code || !source_warehouse_code || !goods_code) { return null } - return { self_warehouse_code, source_warehouse_code, goods_code } + return withDeployOrgType({ self_warehouse_code, source_warehouse_code, goods_code }, body?.org_type) } /** POST /api/warehouse/rdc/deploy/link — 新增源采购仓关联 */ diff --git a/src/api/warehouseWaste.js b/src/api/warehouseWaste.js index 80e5060..674c703 100644 --- a/src/api/warehouseWaste.js +++ b/src/api/warehouseWaste.js @@ -1,5 +1,5 @@ import request from '@/utils/request' -import { pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' +import { pickGoodsCategoryPathText, pickGoodsNameFromListedRow } from '@/utils/goodsCategorys' function unwrapListed(res) { const data = res?.data @@ -101,6 +101,7 @@ function pickGoodsFields(raw) { ?? raw?.goods?.name ?? raw?.goods?.goods_name, ), + goods_category_path: pickGoodsCategoryPathText(raw), } } @@ -205,22 +206,35 @@ function normalizeWasteShareRow(raw) { const goods_code = goods.goods_code if (!goods_code && !wh.rdc_warehouse_code) return null const waste_count = num( - raw.waste_count + raw.total_loss_count + ?? raw.waste_count ?? raw.rdc_waste_count ?? raw.rdc_waste ?? raw.waste_num, ) const rdc_in_count = num( - raw.rdc_in_count + raw.total_inbound_count + ?? raw.rdc_in_count ?? raw.total_rdc_in ?? raw.rdc_inbound_count ?? raw.in_count, ) - const share_total_amount = num( + const pre_price = num(raw.pre_price ?? raw.price ?? raw.cost_price) + const unit_amount = num(raw.unit_amount ?? raw.share_unit_amount ?? raw.unit_share_amount) + let share_total_amount = num( raw.total_amount ?? raw.share_total_amount - ?? raw.share_amount, + ?? raw.share_amount + ?? raw.loss_total_amount + ?? raw.loss_amount + ?? raw.total_loss_amount, ) + if (share_total_amount == null && waste_count != null && pre_price != null) { + share_total_amount = waste_count * pre_price + } + if (share_total_amount == null && waste_count != null && unit_amount != null) { + share_total_amount = waste_count * unit_amount + } return { ...raw, ...wh, @@ -228,9 +242,9 @@ function normalizeWasteShareRow(raw) { goods_display_name: pickGoodsNameFromListedRow(raw) || goods.goods_name, waste_count, rdc_in_count, - ratio: num(raw.ratio ?? raw.share_ratio ?? raw.waste_ratio), - pre_price: num(raw.pre_price ?? raw.price ?? raw.cost_price), - unit_amount: num(raw.unit_amount ?? raw.share_unit_amount ?? raw.unit_share_amount), + ratio: num(raw.loss_rate ?? raw.ratio ?? raw.share_ratio ?? raw.waste_ratio), + pre_price, + unit_amount, share_total_amount, share_time: str(raw.share_time ?? raw.amortize_time ?? raw.ctime ?? raw.create_time), day_id: raw.day_id != null && raw.day_id !== '' ? Number(raw.day_id) : undefined, diff --git a/src/api/warehouseZone.js b/src/api/warehouseZone.js index 7101420..5f218ac 100755 --- a/src/api/warehouseZone.js +++ b/src/api/warehouseZone.js @@ -202,7 +202,7 @@ export function warehouseZoneGridSet(body) { }) } -/** 解析 zone/listed 的格子类型 → Map<`${zoneCode}:${x}:${y}`, store_type> */ +/** 解析 zone/listed 的格子 store_type → Map<`${zoneCode}:${x}:${y}`, goods_code> */ export function parseZoneGridStoreTypes(storeTypes, zoneCode) { const map = new Map() const zc = String(zoneCode || '').trim() diff --git a/src/components/global-tab/GlobalTabCostPanel.vue b/src/components/global-tab/GlobalTabCostPanel.vue index 78133dd..d12b303 100644 --- a/src/components/global-tab/GlobalTabCostPanel.vue +++ b/src/components/global-tab/GlobalTabCostPanel.vue @@ -5,7 +5,7 @@ @@ -136,11 +160,14 @@ import { } from '@/utils/financeBillTypes' import { WAREHOUSE_CHANNELS, + WAREHOUSE_COMMISSION_SECTION, + WAREHOUSE_RDC_PROFIT_KEY, WAREHOUSE_SECTIONS, - createEmptyRdcBucket, + createEmptyCommissionBillBucket, + createEmptyProfitSection, isLegacyRdcMetricsBucket, - mapGoodsFdcsToOptions, mapGoodsRdcsToOptions, + normalizeProfitSection, normalizeWarehouseGoodsBucket, } from '@/utils/warehouseGlobalTabSchema' @@ -149,8 +176,6 @@ const props = defineProps({ readonly: { type: Boolean, default: false }, showSchemaHint: { type: Boolean, default: false }, rdcList: { type: Array, default: () => [] }, - /** 物料 /goods/view 返回的 fdcs,与 RDC 共用 warehouse[warehouse_code] 分桶 */ - fdcList: { type: Array, default: () => [] }, }) const emit = defineEmits(['update:modelValue']) @@ -163,12 +188,12 @@ const billsLoaded = ref(false) /** @type {import('vue').Ref} */ const allBills = ref([]) const activeWarehouseCode = ref('') +const activeBillCode = ref('') const panelLoading = computed(() => billLoading.value) const rdcOptions = computed(() => mapGoodsRdcsToOptions(props.rdcList)) -const fdcOptions = computed(() => mapGoodsFdcsToOptions(props.fdcList)) -const warehouseOptions = computed(() => [...rdcOptions.value, ...fdcOptions.value]) +const warehouseOptions = computed(() => rdcOptions.value) const bucket = computed({ get: () => normalizeWarehouseGoodsBucket(props.modelValue), @@ -182,6 +207,12 @@ const expenseBills = computed(() => { }) }) +const activeBill = computed(() => { + const code = normBillCode(activeBillCode.value) + if (!code) return null + return expenseBills.value.find((b) => normBillCode(b.bill_code) === code) || null +}) + const showLegacyRdcHint = computed(() => { if (!activeWarehouseCode.value) return false const wh = bucket.value[activeWarehouseCode.value] @@ -202,16 +233,33 @@ function warehouseDisplaySub(wh) { return '' } -function sectionBlockClass(sectionKey) { - if (sectionKey === 'commission') return 'is-commission' - if (sectionKey === 'profit') return 'is-profit' - return '' -} - function billTabParts(bill) { return expenseBillTabParts(bill) } +function billTabTitle(bill) { + const parts = billTabParts(bill) + return parts.tax ? `${parts.name}(${parts.tax}%)` : parts.name +} + +function pickInitialBillCode(bills) { + if (!bills.length) return '' + const cur = normBillCode(activeBillCode.value) + if (cur && bills.some((b) => normBillCode(b.bill_code) === cur)) return cur + return normBillCode(bills[0].bill_code) +} + +function selectBill(billCode) { + const code = normBillCode(billCode) + if (!code || code === activeBillCode.value) return + activeBillCode.value = code + const wh = normWarehouseCode(activeWarehouseCode.value) + if (wh && !props.readonly) { + ensureRdcProfit(wh) + ensureBillBucket(wh, code) + } +} + function normBillCode(code) { return String(code ?? '').trim() } @@ -220,9 +268,41 @@ function normWarehouseCode(code) { return String(code ?? '').trim() } +function migrateLegacyRdcEntry(whEntry) { + if (!isLegacyRdcMetricsBucket(whEntry)) return whEntry + return { + [WAREHOUSE_RDC_PROFIT_KEY]: normalizeProfitSection(whEntry.profit), + } +} + +function ensureRdcEntry(warehouseCode) { + const wh = normWarehouseCode(warehouseCode) + if (!wh || props.readonly) return null + const next = { ...bucket.value } + let whEntry = migrateLegacyRdcEntry(next[wh]) + if (!whEntry || typeof whEntry !== 'object' || Array.isArray(whEntry)) { + whEntry = { [WAREHOUSE_RDC_PROFIT_KEY]: createEmptyProfitSection() } + } + if (!whEntry[WAREHOUSE_RDC_PROFIT_KEY]) { + whEntry = { + ...whEntry, + [WAREHOUSE_RDC_PROFIT_KEY]: createEmptyProfitSection(), + } + } + next[wh] = whEntry + bucket.value = next + return whEntry +} + +function ensureRdcProfit(warehouseCode) { + const whEntry = ensureRdcEntry(warehouseCode) + return whEntry?.[WAREHOUSE_RDC_PROFIT_KEY] || null +} + function ensureAllBillBucketsForWarehouse(warehouseCode) { const wh = normWarehouseCode(warehouseCode) if (!wh || props.readonly) return + ensureRdcProfit(wh) for (const bill of expenseBills.value) { ensureBillBucket(wh, bill.bill_code) } @@ -250,47 +330,68 @@ function ensureBillBucket(warehouseCode, billCode) { const wh = normWarehouseCode(warehouseCode) const bill = normBillCode(billCode) if (!wh || !bill || props.readonly) return null + const whEntry = ensureRdcEntry(wh) + if (!whEntry) return null const next = { ...bucket.value } - let whEntry = next[wh] - if (isLegacyRdcMetricsBucket(whEntry)) { - whEntry = {} - next[wh] = whEntry + if (!whEntry[bill]) { + whEntry[bill] = createEmptyCommissionBillBucket() + next[wh] = { ...whEntry } + bucket.value = next } - if (!whEntry || typeof whEntry !== 'object' || Array.isArray(whEntry)) { - whEntry = {} - next[wh] = whEntry - } - if (!whEntry[bill]) whEntry[bill] = createEmptyRdcBucket() - bucket.value = next return next[wh][bill] } -function billBucketFor(warehouseCode, billCode) { - const wh = bucket.value[normWarehouseCode(warehouseCode)] - const bill = normBillCode(billCode) - if (!wh || !bill) return null - if (isLegacyRdcMetricsBucket(wh)) return wh - return wh[bill] || null +function rdcEntryForActiveWarehouse() { + const wh = bucket.value[normWarehouseCode(activeWarehouseCode.value)] + if (!wh || typeof wh !== 'object') return null + if (isLegacyRdcMetricsBucket(wh)) return null + return wh } -function metricValue(sectionKey, channelKey, metricKey, billCode) { - const b = billBucketFor(activeWarehouseCode.value, billCode) - const v = b?.[sectionKey]?.[channelKey]?.[metricKey] +function profitMetricValue(channelKey, metricKey) { + const profit = rdcEntryForActiveWarehouse()?.[WAREHOUSE_RDC_PROFIT_KEY] + const v = profit?.[channelKey]?.[metricKey] return v === null || v === undefined ? null : Number(v) } -function setMetric(sectionKey, channelKey, metricKey, raw, billCode) { +function setProfitMetric(channelKey, metricKey, raw) { + const warehouseCode = normWarehouseCode(activeWarehouseCode.value) + if (props.readonly || !warehouseCode) return + const profit = ensureRdcProfit(warehouseCode) + if (!profit) return + if (!profit[channelKey]) profit[channelKey] = { rate: null, amount: null } + const n = raw === null || raw === undefined || raw === '' ? null : Number(raw) + profit[channelKey][metricKey] = Number.isFinite(n) ? n : null + emit('update:modelValue', normalizeWarehouseGoodsBucket({ ...bucket.value })) +} + +function commissionBillBucketFor(warehouseCode, billCode) { + const wh = bucket.value[normWarehouseCode(warehouseCode)] + const bill = normBillCode(billCode) + if (!wh || !bill || isLegacyRdcMetricsBucket(wh)) return null + return wh[bill] || null +} + +function commissionMetricValue(channelKey, metricKey, billCode) { + const b = commissionBillBucketFor(activeWarehouseCode.value, billCode) + const v = b?.[WAREHOUSE_COMMISSION_SECTION]?.[channelKey]?.[metricKey] + return v === null || v === undefined ? null : Number(v) +} + +function setCommissionMetric(channelKey, metricKey, raw, billCode) { const warehouseCode = normWarehouseCode(activeWarehouseCode.value) const bill = normBillCode(billCode) if (props.readonly || !warehouseCode || !bill) return const billBucket = ensureBillBucket(warehouseCode, bill) if (!billBucket) return - if (!billBucket[sectionKey]) billBucket[sectionKey] = createEmptyRdcBucket()[sectionKey] - if (!billBucket[sectionKey][channelKey]) { - billBucket[sectionKey][channelKey] = { rate: null, amount: null } + if (!billBucket[WAREHOUSE_COMMISSION_SECTION]) { + billBucket[WAREHOUSE_COMMISSION_SECTION] = createEmptyProfitSection() + } + if (!billBucket[WAREHOUSE_COMMISSION_SECTION][channelKey]) { + billBucket[WAREHOUSE_COMMISSION_SECTION][channelKey] = { rate: null, amount: null } } const n = raw === null || raw === undefined || raw === '' ? null : Number(raw) - billBucket[sectionKey][channelKey][metricKey] = Number.isFinite(n) ? n : null + billBucket[WAREHOUSE_COMMISSION_SECTION][channelKey][metricKey] = Number.isFinite(n) ? n : null emit('update:modelValue', normalizeWarehouseGoodsBucket({ ...bucket.value })) } @@ -300,15 +401,18 @@ async function loadBillList() { const res = await request.post('/api/bill/listed', { page: 1, limit: 500, type: FINANCE_BILL_TYPE_EXPENSE }) const list = Array.isArray(res?.data) ? res.data : (Array.isArray(res?.data?.list) ? res.data.list : []) allBills.value = list.filter((b) => normalizeFinanceBillType(b?.type) === FINANCE_BILL_TYPE_EXPENSE) + activeBillCode.value = pickInitialBillCode(expenseBills.value) syncActiveWarehouseBuckets() } catch { allBills.value = [] + activeBillCode.value = '' } finally { billLoading.value = false } } -watch(expenseBills, () => { +watch(expenseBills, (bills) => { + activeBillCode.value = pickInitialBillCode(bills) syncActiveWarehouseBuckets() }) @@ -343,7 +447,7 @@ watch( ) watch( - () => [props.rdcList, props.fdcList], + () => props.rdcList, () => { const opts = warehouseOptions.value if (!opts.length) { @@ -431,15 +535,12 @@ defineExpose({ overflow: hidden; } .gt-wh-wh-pane { - flex: 1 1 0; - min-height: 120px; + flex: 1 1 auto; + min-height: 0; display: flex; flex-direction: column; overflow: hidden; } -.gt-wh-wh-pane--fdc { - border-top: 1px solid #ebeef5; -} .gt-wh-wh-pane-body { flex: 1; min-height: 0; @@ -460,11 +561,6 @@ defineExpose({ background: #ecf5ff; border-bottom-color: #d9ecff; } -.gt-wh-rdc-hd--fdc { - color: #67c23a; - background: #f0f9eb; - border-bottom-color: #e1f3d8; -} .gt-wh-rdc-nav-item { display: block; width: 100%; @@ -486,9 +582,6 @@ defineExpose({ .gt-wh-rdc-nav-item--rdc.is-active { border-left-color: #409eff; } -.gt-wh-rdc-nav-item--fdc.is-active { - border-left-color: #67c23a; -} .gt-wh-rdc-nav-name { display: block; font-size: 13px; @@ -500,10 +593,6 @@ defineExpose({ color: #409eff; font-weight: 600; } -.gt-wh-rdc-nav-item--fdc.is-active .gt-wh-rdc-nav-name { - color: #67c23a; - font-weight: 600; -} .gt-wh-rdc-nav-sub { display: block; margin-top: 2px; @@ -528,125 +617,157 @@ defineExpose({ flex-direction: column; overflow: hidden; } -.gt-wh-bills-scroll { - flex: 1; +.gt-wh-bill-tabs { min-width: 0; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - padding: 10px 10px 12px; - display: flex; - flex-direction: column; - gap: 12px; -} -.gt-wh-bill-group { - flex-shrink: 0; - display: flex; - flex-direction: column; - background: #fafbfc; - border: 1px solid #ebeef5; - border-radius: 8px; - overflow: hidden; -} -.gt-wh-bill-group-title { - flex-shrink: 0; display: flex; align-items: center; - flex-wrap: wrap; - gap: 8px; - padding: 8px 14px 6px; - font-size: 12px; - font-weight: 600; - color: #8b9ab0; - letter-spacing: 0.5px; - border-bottom: 1px solid #ebeef5; - background: #fafbfc; + gap: 4px; + overflow-x: auto; + padding: 0; + flex-shrink: 0; + min-height: 28px; + scrollbar-width: thin; + scrollbar-color: #e4e7ed transparent; } -.gt-wh-bill-group-name { - color: #606266; - font-size: 13px; -} -.gt-wh-bill-group-tax { - padding: 0 6px; - height: 18px; - line-height: 18px; +.gt-wh-bill-tabs::-webkit-scrollbar { height: 4px; } +.gt-wh-bill-tabs::-webkit-scrollbar-thumb { background: #e4e7ed; border-radius: 2px; } +.gt-wh-bill-tab { + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 8px; + border: 1px solid #e4e7ed; border-radius: 4px; + background: #f5f7fa; + color: #606266; font-size: 11px; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, color 0.15s; + max-width: 120px; +} +.gt-wh-bill-tab:hover { + border-color: #c6d8f5; + background: #ecf5ff; + color: #409eff; +} +.gt-wh-bill-tab.is-active { + border-color: #409eff; + background: #ecf5ff; + color: #409eff; + font-weight: 600; +} +.gt-wh-bill-tab-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} +.gt-wh-bill-tab-tax { + flex-shrink: 0; + padding: 0 4px; + height: 14px; + line-height: 14px; + border-radius: 3px; + font-size: 10px; font-weight: 600; background: #fef0f0; color: #f56c6c; } -.gt-wh-bill-group-body { - padding: 12px; +.gt-wh-metrics-split { + flex: 1; + min-height: 0; + display: flex; + flex-direction: row; + align-items: stretch; + overflow: hidden; + --gt-wh-channel-cols: 32px minmax(0, 1fr) minmax(0, 1fr); } -.gt-wh-sections-row { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px; - align-items: start; -} -.gt-wh-section-block { +.gt-wh-pane { + flex: 1 1 50%; + min-width: 0; + min-height: 0; display: flex; flex-direction: column; - gap: 10px; - min-width: 0; - padding: 10px 10px 12px; - border-radius: 8px; - border: 1px solid transparent; + overflow: hidden; } -.gt-wh-section-block.is-commission { - background: #f7fafd; - border-color: #e6eef8; +.gt-wh-pane--commission { + border-right: 1px solid #ebeef5; + background: #fcfdff; } -.gt-wh-section-block.is-profit { - background: #f8fbf6; - border-color: #e5efe5; +.gt-wh-pane--profit { + background: #fcfffc; } -.gt-wh-section-title { +.gt-wh-pane-head { + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 6px; + padding: 8px 10px 6px; + border-bottom: 1px solid #ebeef5; + background: #fafbfc; +} +.gt-wh-bill-tabs-spacer { + flex-shrink: 0; + min-height: 28px; + width: 100%; +} +.gt-wh-pane-title { + flex-shrink: 0; font-size: 13px; font-weight: 600; - color: #303133; - padding-left: 2px; + letter-spacing: 0.5px; } -.gt-wh-section-block.is-commission .gt-wh-section-title { +.gt-wh-pane-title--commission { color: #4a6fa5; } -.gt-wh-section-block.is-profit .gt-wh-section-title { +.gt-wh-pane-title--profit { color: #5a8f5a; } -.gt-wh-channel-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; +.gt-wh-pane-body { + flex: 1; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + padding: 8px 10px; } -.gt-wh-channel-card { - padding: 10px 8px; +.gt-wh-channel-stack { + display: flex; + flex-direction: column; + gap: 6px; +} +.gt-wh-channel-row { + display: grid; + grid-template-columns: var(--gt-wh-channel-cols); + align-items: center; + gap: 6px; + padding: 5px 8px; border: 1px solid #ebeef5; border-radius: 6px; background: #fff; - min-width: 0; + min-height: 34px; } -.gt-wh-channel-title { +.gt-wh-pane--commission .gt-wh-channel-row { + border-color: #e6eef8; +} +.gt-wh-pane--profit .gt-wh-channel-row { + border-color: #e5efe5; +} +.gt-wh-channel-row-label { font-size: 12px; font-weight: 600; - color: #303133; - margin-bottom: 8px; - padding-bottom: 6px; - border-bottom: 1px dashed #ebeef5; + color: #606266; + white-space: nowrap; } -.gt-wh-metrics { - display: flex; - flex-direction: column; - gap: 8px; -} -.gt-wh-metric { +.gt-wh-channel-row-field { display: flex; align-items: center; - gap: 6px; + gap: 4px; + min-width: 0; } .gt-wh-metric-label { - flex: 0 0 18px; - font-size: 12px; + flex: 0 0 auto; + font-size: 11px; color: #909399; } .gt-wh-metric-input { @@ -658,7 +779,7 @@ defineExpose({ text-align: left; } .gt-wh-metric-suffix { - font-size: 12px; + font-size: 11px; color: #909399; flex-shrink: 0; } @@ -675,11 +796,12 @@ defineExpose({ border-right: none; border-bottom: 1px solid #ebeef5; } - .gt-wh-sections-row { - grid-template-columns: 1fr; + .gt-wh-metrics-split { + flex-direction: column; } - .gt-wh-channel-grid { - grid-template-columns: 1fr; + .gt-wh-pane--commission { + border-right: none; + border-bottom: 1px solid #ebeef5; } } diff --git a/src/components/scene/SceneGoodsAdvancedSearchDlg.vue b/src/components/scene/SceneGoodsAdvancedSearchDlg.vue index f1c4953..bf2f764 100755 --- a/src/components/scene/SceneGoodsAdvancedSearchDlg.vue +++ b/src/components/scene/SceneGoodsAdvancedSearchDlg.vue @@ -1330,7 +1330,7 @@ watch( (open) => { if (open) panelLoading.value = true }, - { flush: 'sync' }, + { flush: 'sync', immediate: true }, ) watch(mainFilterEngaged, (engaged, prev) => { diff --git a/src/components/warehouse/WhWarehouseLogAddDlg.vue b/src/components/warehouse/WhWarehouseLogAddDlg.vue new file mode 100644 index 0000000..69957dc --- /dev/null +++ b/src/components/warehouse/WhWarehouseLogAddDlg.vue @@ -0,0 +1,587 @@ + + + + + + + diff --git a/src/components/warehouse/WopsGoodsListDlg.vue b/src/components/warehouse/WopsGoodsListDlg.vue new file mode 100644 index 0000000..9ff7e39 --- /dev/null +++ b/src/components/warehouse/WopsGoodsListDlg.vue @@ -0,0 +1,144 @@ + + + + + + + diff --git a/src/components/warehouse/WopsPageHeader.vue b/src/components/warehouse/WopsPageHeader.vue index b9a9e40..02f1a53 100644 --- a/src/components/warehouse/WopsPageHeader.vue +++ b/src/components/warehouse/WopsPageHeader.vue @@ -8,11 +8,15 @@ > - 共 {{ count }} 条
+