From cfed2ba734ef606b7f9223343a6b11b11c14eff4 Mon Sep 17 00:00:00 2001 From: nebula_chen Date: Tue, 24 Mar 2026 14:07:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=20-=20=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=B7=B2=E4=BD=9C=E5=BA=9F=E7=9B=AE=E5=BD=95=E5=8F=8A=E5=AD=90?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../miscprocurement/misc-material/api.ts | 8 - .../miscprocurement/misc-material/crud.tsx | 112 - .../miscprocurement/misc-material/index.vue | 24 - .../pisadmin/miscprocurement/misc-part/api.ts | 8 - .../miscprocurement/misc-part/crud.tsx | 203 -- .../miscprocurement/misc-part/index.vue | 22 - .../miscprocurement/misc-station/api.ts | 8 - .../miscprocurement/misc-station/crud.tsx | 192 -- .../miscprocurement/misc-station/index.vue | 24 - .../pricetemplate/SectionBuilder.vue | 529 ----- .../miscprocurement/pricetemplate/api.ts | 43 - .../miscprocurement/pricetemplate/crud.tsx | 793 ------- .../miscprocurement/pricetemplate/index.vue | 32 - .../pricetemplate/SectionBuilder.vue | 529 ----- .../views/procurement/pricetemplate/api.ts | 43 - .../views/procurement/pricetemplate/crud.tsx | 793 ------- .../views/procurement/pricetemplate/index.vue | 32 - .../views/procurement/rfqmiscellaneous/api.ts | 22 - .../procurement/rfqmiscellaneous/crud.tsx | 317 --- .../procurement/rfqmiscellaneous/index.vue | 2034 ----------------- 20 files changed, 5768 deletions(-) delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-material/api.ts delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-material/crud.tsx delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-material/index.vue delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-part/api.ts delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-part/crud.tsx delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-part/index.vue delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-station/api.ts delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-station/crud.tsx delete mode 100644 web/src/views/pisadmin/miscprocurement/misc-station/index.vue delete mode 100644 web/src/views/pisadmin/miscprocurement/pricetemplate/SectionBuilder.vue delete mode 100644 web/src/views/pisadmin/miscprocurement/pricetemplate/api.ts delete mode 100644 web/src/views/pisadmin/miscprocurement/pricetemplate/crud.tsx delete mode 100644 web/src/views/pisadmin/miscprocurement/pricetemplate/index.vue delete mode 100644 web/src/views/procurement/pricetemplate/SectionBuilder.vue delete mode 100644 web/src/views/procurement/pricetemplate/api.ts delete mode 100644 web/src/views/procurement/pricetemplate/crud.tsx delete mode 100644 web/src/views/procurement/pricetemplate/index.vue delete mode 100644 web/src/views/procurement/rfqmiscellaneous/api.ts delete mode 100644 web/src/views/procurement/rfqmiscellaneous/crud.tsx delete mode 100644 web/src/views/procurement/rfqmiscellaneous/index.vue diff --git a/web/src/views/pisadmin/miscprocurement/misc-material/api.ts b/web/src/views/pisadmin/miscprocurement/misc-material/api.ts deleted file mode 100644 index 1d52ac8..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-material/api.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { request } from '/@/utils/service' - -const apiPrefix = '/api/pisadmin/miscprocurement/misc_materials/' - -export const GetList = (params: any) => request({ url: apiPrefix, method: 'get', params }) -export const AddObj = (data: any) => request({ url: apiPrefix, method: 'post', data }) -export const UpdateObj = (data: any) => request({ url: apiPrefix + data.id + '/', method: 'put', data }) -export const DelObj = (id: string | number) => request({ url: apiPrefix + id + '/', method: 'delete' }) diff --git a/web/src/views/pisadmin/miscprocurement/misc-material/crud.tsx b/web/src/views/pisadmin/miscprocurement/misc-material/crud.tsx deleted file mode 100644 index a64e83c..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-material/crud.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' -import * as api from './api' -import { GetCompanies } from '../../basicinfo/currency/api' - -const statusDict = [ - { value: 1, label: '可用' }, - { value: 0, label: '不可用' } -] - -const loadCompanyOptions = async () => { - try { - const res = await GetCompanies({ page: 1, page_size: 1000, pageSize: 1000 }) - const list = - res?.data?.data?.results || - res?.data?.results || - res?.data?.list || - res?.data || - res?.results || - res?.list || - [] - return (Array.isArray(list) ? list : []).map((c: any) => ({ - company_code: c.company_code, - company_short_name: c.company_short_name || c.company_code || c.company_name - })) - } catch (e) { - console.warn('加载公司列表失败', e) - return [] - } -} - -void loadCompanyOptions() - -export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { - void crudExpose - return { - crudOptions: { - form: { - labelWidth: '110px' - }, - request: { - pageRequest: async (query) => api.GetList(query), - addRequest: async ({ form }) => api.AddObj(form), - editRequest: async ({ form, row }) => api.UpdateObj({ ...form, id: row.id }), - delRequest: async ({ row }) => api.DelObj(row.id) - }, - table: { - rowKey: 'id' - }, - actionbar: { - buttons: { - add: { show: true } - } - }, - rowHandle: { - fixed: 'right' - }, - columns: { - factory: { - title: '交易厂区', - type: 'dict-select', - dict: dict({ - cache: false, - value: 'company_code', - label: 'company_short_name', - getData: async () => { - return loadCompanyOptions() - } - }), - search: { show: true }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - materialtype: { - title: '材质', - type: 'input', - search: { show: true, component: { props: { placeholder: '请输入材质', clearable: true } } }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - density: { - title: '比重', - type: 'input', - form: { rules: [{ required: true, message: '请输入比重' }] }, - column: { width: 120, showOverflowTooltip: true } - }, - price: { - title: '单价', - type: 'number', - form: { rules: [{ required: true, message: '请输入单价' }], component: { props: { precision: 2 } } }, - column: { width: 120 } - }, - status: { - title: '可用状态', - type: 'dict-switch', - dict: dict({ data: statusDict }), - form: { value: 1 }, - column: { width: 120 } - }, - create_datetime: { - title: '创建时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - }, - update_datetime: { - title: '更新时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - } - } - } - } -} diff --git a/web/src/views/pisadmin/miscprocurement/misc-material/index.vue b/web/src/views/pisadmin/miscprocurement/misc-material/index.vue deleted file mode 100644 index 113c7e6..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-material/index.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - - - diff --git a/web/src/views/pisadmin/miscprocurement/misc-part/api.ts b/web/src/views/pisadmin/miscprocurement/misc-part/api.ts deleted file mode 100644 index dfe1a5c..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-part/api.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { request } from '/@/utils/service' - -const apiPrefix = '/api/pisadmin/miscprocurement/misc_parts/' - -export const GetList = (params: any) => request({ url: apiPrefix, method: 'get', params }) -export const AddObj = (data: any) => request({ url: apiPrefix, method: 'post', data }) -export const UpdateObj = (data: any) => request({ url: apiPrefix + data.id + '/', method: 'put', data }) -export const DelObj = (id: string | number) => request({ url: apiPrefix + id + '/', method: 'delete' }) diff --git a/web/src/views/pisadmin/miscprocurement/misc-part/crud.tsx b/web/src/views/pisadmin/miscprocurement/misc-part/crud.tsx deleted file mode 100644 index bfef5b0..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-part/crud.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' -import * as api from './api' -import { GetCompanies } from '../../basicinfo/currency/api' -import { GetList as GetUnits } from '../../basicinfo/unit/api' -import { ElMessage } from 'element-plus' - -const statusDict = [ - { value: 1, label: '启用' }, - { value: 0, label: '禁用' } -] - -const categoryDict = [ - { value: 1, label: '模治具' }, - { value: 2, label: '石墨' }, - { value: 3, label: '其他' } -] - -const loadCompanyOptions = async () => { - try { - const res = await GetCompanies({ page: 1, page_size: 1000, pageSize: 1000 }) - const list = - res?.data?.data?.results || - res?.data?.results || - res?.data?.list || - res?.data || - res?.results || - res?.list || - [] - return (Array.isArray(list) ? list : []).map((c: any) => ({ - company_code: c.company_code, - company_short_name: c.company_short_name || c.company_code || c.company_name - })) - } catch (e) { - console.warn('加载公司列表失败', e) - return [] - } -} - -const loadUnitOptions = async (companyCode?: string) => { - try { - const params: any = { page: 1, page_size: 500, pageSize: 500 } - if (companyCode) params.factory = companyCode - const res = await GetUnits(params) - const list = - res?.data?.data?.results || - res?.data?.results || - res?.data?.list || - res?.data || - res?.results || - res?.list || - [] - return (Array.isArray(list) ? list : []).map((u: any) => ({ - value: u.unitcode || u.unit_code, - label: u.unitname || u.unit_name || u.unitcode - })) - } catch (e) { - console.warn('加载单位列表失败', e) - return [] - } -} - -void loadCompanyOptions() - -export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { - void crudExpose - - const ensurePartUnique = async (companyCode: string, partId: string, currentId?: number) => { - if (!companyCode || !partId) return - const res = await api.GetList({ company_code: companyCode, partid: partId, page: 1, page_size: 1, pageSize: 1 }) - const list = - res?.data?.data?.results || - res?.data?.results || - res?.data?.list || - res?.data || - res?.results || - res?.list || - [] - const exists = Array.isArray(list) - ? list.find((item: any) => item.company_code === companyCode && item.partid === partId) - : null - if (exists && (!currentId || exists.id !== currentId)) { - throw new Error('同一交易厂区下料号已存在,不可重复') - } - } - return { - crudOptions: { - form: { - labelWidth: '110px' - }, - request: { - pageRequest: async (query) => api.GetList(query), - addRequest: async ({ form }) => { - try { - await ensurePartUnique(form.company_code, form.partid) - return await api.AddObj(form) - } catch (err: any) { - ElMessage.error(err?.message || '保存失败') - throw err - } - }, - editRequest: async ({ form, row }) => { - try { - await ensurePartUnique(form.company_code, form.partid, row.id) - return await api.UpdateObj({ ...form, id: row.id }) - } catch (err: any) { - ElMessage.error(err?.message || '保存失败') - throw err - } - }, - delRequest: async ({ row }) => api.DelObj(row.id) - }, - table: { - rowKey: 'id' - }, - actionbar: { - buttons: { - add: { show: true } - } - }, - rowHandle: { - fixed: 'right' - }, - columns: { - company_code: { - title: '交易厂区', - type: 'dict-select', - dict: dict({ - cache: false, - value: 'company_code', - label: 'company_short_name', - getData: async () => loadCompanyOptions() - }), - search: { show: true }, - form: { - component: { - on: { - change({ form, value }: any) { - form.unit = undefined - form.company_code = value - } - } - } - }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - partid: { - title: '料号', - type: 'input', - search: { show: true, component: { props: { placeholder: '请输入料号', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入料号' }] }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - partid_name: { - title: '物料说明', - type: 'input', - form: { rules: [{ required: true, message: '请输入物料说明' }] }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - specification: { - title: '品名规格', - type: 'input', - form: { rules: [{ required: true, message: '请输入品名规格' }] }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - unit: { - title: '单位', - type: 'dict-select', - dict: dict({ - cache: false, - getData: async ({ form }: any = {}) => loadUnitOptions(form?.company_code) - }), - form: { rules: [{ required: true, message: '请选择单位' }], component: { props: { placeholder: '先选择厂区' } } }, - column: { width: 140, showOverflowTooltip: true } - }, - partid_category_id: { - title: '物料分类', - type: 'dict-select', - dict: dict({ data: categoryDict }), - column: { width: 140, showOverflowTooltip: true } - }, - status: { - title: '启用否', - type: 'dict-switch', - dict: dict({ data: statusDict }), - form: { value: 1 }, - column: { width: 120 } - }, - create_datetime: { - title: '创建时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - }, - update_datetime: { - title: '更新时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - } - } - } - } -} diff --git a/web/src/views/pisadmin/miscprocurement/misc-part/index.vue b/web/src/views/pisadmin/miscprocurement/misc-part/index.vue deleted file mode 100644 index 6981059..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-part/index.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/web/src/views/pisadmin/miscprocurement/misc-station/api.ts b/web/src/views/pisadmin/miscprocurement/misc-station/api.ts deleted file mode 100644 index 4286ba3..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-station/api.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { request } from '/@/utils/service' - -const apiPrefix = '/api/pisadmin/miscprocurement/misc_stations/' - -export const GetList = (params: any) => request({ url: apiPrefix, method: 'get', params }) -export const AddObj = (data: any) => request({ url: apiPrefix, method: 'post', data }) -export const UpdateObj = (data: any) => request({ url: apiPrefix + data.id + '/', method: 'put', data }) -export const DelObj = (id: string | number) => request({ url: apiPrefix + id + '/', method: 'delete' }) diff --git a/web/src/views/pisadmin/miscprocurement/misc-station/crud.tsx b/web/src/views/pisadmin/miscprocurement/misc-station/crud.tsx deleted file mode 100644 index 609485c..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-station/crud.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' -import * as api from './api' -import { GetCompanies } from '../../basicinfo/currency/api' -import { GetList as GetUnits } from '../../basicinfo/unit/api' -import { ElMessage } from 'element-plus' - -const statusDict = [ - { value: 1, label: '可用' }, - { value: 0, label: '不可用' } -] - -const stationTypeDict = [ - { value: 1, label: '模治具' }, - { value: 2, label: '石墨' } -] - -const loadCompanyOptions = async () => { - try { - const res = await GetCompanies({ page: 1, page_size: 1000, pageSize: 1000 }) - const list = - res?.data?.data?.results || - res?.data?.results || - res?.data?.list || - res?.data || - res?.results || - res?.list || - [] - return (Array.isArray(list) ? list : []).map((c: any) => ({ - company_code: c.company_code, - company_short_name: c.company_short_name || c.company_code || c.company_name - })) - } catch (e) { - console.warn('加载公司列表失败', e) - return [] - } -} - -const loadUnitOptions = async () => { - try { - const res = await GetUnits({ page: 1, page_size: 1000, pageSize: 1000 }) - const list = - res?.data?.data?.results || - res?.data?.results || - res?.data?.list || - res?.data || - res?.results || - res?.list || - [] - return (Array.isArray(list) ? list : []).map((u: any) => ({ - value: u.unitcode || u.unit_code || u.unit || u.code, - label: u.unitname || u.unit_name || u.unit || u.code - })) - } catch (e) { - console.warn('加载计量单位失败', e) - return [] - } -} - -void loadCompanyOptions() - -export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { - void crudExpose - - const ensureStationCodeUnique = async (companyCode: string, stationCode: string, currentId?: number) => { - if (!companyCode || !stationCode) return - const res = await api.GetList({ company_code: companyCode, stationcode: stationCode, page: 1, page_size: 1, pageSize: 1 }) - const list = - res?.data?.data?.results || - res?.data?.results || - res?.data?.list || - res?.data || - res?.results || - res?.list || - [] - const exists = Array.isArray(list) - ? list.find((item: any) => item.company_code === companyCode && item.stationcode === stationCode) - : null - if (exists && (!currentId || exists.id !== currentId)) { - throw new Error('同一交易厂区下工站代码已存在,不可重复') - } - } - return { - crudOptions: { - form: { - labelWidth: '110px' - }, - request: { - pageRequest: async (query) => api.GetList(query), - addRequest: async ({ form }) => { - try { - await ensureStationCodeUnique(form.company_code, form.stationcode) - return await api.AddObj(form) - } catch (err: any) { - ElMessage.error(err?.message || '保存失败') - throw err - } - }, - editRequest: async ({ form, row }) => { - try { - await ensureStationCodeUnique(form.company_code, form.stationcode, row.id) - return await api.UpdateObj({ ...form, id: row.id }) - } catch (err: any) { - ElMessage.error(err?.message || '保存失败') - throw err - } - }, - delRequest: async ({ row }) => api.DelObj(row.id) - }, - table: { - rowKey: 'id' - }, - actionbar: { - buttons: { - add: { show: true } - } - }, - rowHandle: { - fixed: 'right' - }, - columns: { - company_code: { - title: '交易厂区', - type: 'dict-select', - dict: dict({ - cache: false, - value: 'company_code', - label: 'company_short_name', - getData: async () => loadCompanyOptions() - }), - search: { show: true }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - stationcode: { - title: '工站代码', - type: 'input', - search: { show: true, component: { props: { placeholder: '请输入工站代码', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入工站代码' }] }, - column: { minWidth: 140, showOverflowTooltip: true } - }, - stationname: { - title: '工站名称', - type: 'input', - search: { show: true, component: { props: { placeholder: '请输入工站名称', clearable: true } } }, - form: { rules: [{ required: true, message: '请输入工站名称' }] }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - stationtype: { - title: '工站类型', - type: 'dict-select', - dict: dict({ data: stationTypeDict }), - form: { rules: [{ required: true, message: '请选择工站类型' }] }, - column: { width: 140, showOverflowTooltip: true } - }, - unit: { - title: '计量单位', - type: 'dict-select', - dict: dict({ - cache: false, - getData: async () => loadUnitOptions() - }), - form: { rules: [{ required: true, message: '请选择计量单位' }] }, - column: { width: 140, showOverflowTooltip: true } - }, - rate: { - title: '费率', - type: 'number', - form: { rules: [{ required: true, message: '请输入费率' }], component: { props: { precision: 2 } } }, - column: { width: 120 } - }, - status: { - title: '可用状态', - type: 'dict-switch', - dict: dict({ data: statusDict }), - form: { value: 1 }, - column: { width: 120 } - }, - create_datetime: { - title: '创建时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - }, - update_datetime: { - title: '更新时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - } - } - } - } -} diff --git a/web/src/views/pisadmin/miscprocurement/misc-station/index.vue b/web/src/views/pisadmin/miscprocurement/misc-station/index.vue deleted file mode 100644 index 3dd337f..0000000 --- a/web/src/views/pisadmin/miscprocurement/misc-station/index.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - - - diff --git a/web/src/views/pisadmin/miscprocurement/pricetemplate/SectionBuilder.vue b/web/src/views/pisadmin/miscprocurement/pricetemplate/SectionBuilder.vue deleted file mode 100644 index 4da1254..0000000 --- a/web/src/views/pisadmin/miscprocurement/pricetemplate/SectionBuilder.vue +++ /dev/null @@ -1,529 +0,0 @@ - - - - - diff --git a/web/src/views/pisadmin/miscprocurement/pricetemplate/api.ts b/web/src/views/pisadmin/miscprocurement/pricetemplate/api.ts deleted file mode 100644 index e28147e..0000000 --- a/web/src/views/pisadmin/miscprocurement/pricetemplate/api.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { request } from '/@/utils/service' -import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud' - -// 成本结构模板(独立表):t_CostEstimate_Template_Head/Body -export const apiPrefix = '/api/pisadmin/miscprocurement/cost_template/' - -export function GetList(query: PageQuery) { - return request({ - url: apiPrefix, - method: 'get', - params: query - }) -} - -export function GetObj(id: InfoReq) { - return request({ - url: apiPrefix + id + '/', - method: 'get' - }) -} - -export function AddObj(obj: AddReq) { - return request({ - url: apiPrefix, - method: 'post', - data: obj - }) -} - -export function UpdateObj(obj: EditReq) { - return request({ - url: apiPrefix + obj.id + '/', - method: 'put', - data: obj - }) -} - -export function DelObj(id: DelReq) { - return request({ - url: apiPrefix + id + '/', - method: 'delete' - }) -} diff --git a/web/src/views/pisadmin/miscprocurement/pricetemplate/crud.tsx b/web/src/views/pisadmin/miscprocurement/pricetemplate/crud.tsx deleted file mode 100644 index 4bb7d93..0000000 --- a/web/src/views/pisadmin/miscprocurement/pricetemplate/crud.tsx +++ /dev/null @@ -1,793 +0,0 @@ -import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' -import * as api from './api' -import SectionBuilder from './SectionBuilder.vue' - -// 后端 cost_template.procurement_category:1=策采;2=杂采 -const procurementDict = [ - { value: '1', label: '策采' }, - { value: '2', label: '杂采' } -] - -const logPT = (...args: any[]) => console.log('[CostTemplate]', ...args) - -type SupplierBehavior = - | 'prefill_locked' - | 'prefill_editable' - | 'hidden_required' - | 'hidden_optional' - | 'required' - | 'optional' - -type FieldOpts = { supplierEditable?: boolean; supplierRequired?: boolean; supplierBehavior?: SupplierBehavior } - -const supplierBehaviorCodeMap: Record = { - prefill_locked: 1, - prefill_editable: 2, - hidden_required: 3, - hidden_optional: 4, - required: 5, - optional: 6 -} - -const legacySupplierBehavior = (purchaserRequired: boolean, supplierRequired: boolean, supplierEditable: boolean): SupplierBehavior => { - if (purchaserRequired) { - if (supplierRequired && !supplierEditable) return 'prefill_locked' - if (supplierRequired && supplierEditable) return 'prefill_editable' - if (!supplierRequired && !supplierEditable) return 'hidden_required' - return 'hidden_optional' - } - return supplierRequired ? 'required' : 'optional' -} - -const normalizeSupplierBehavior = ( - raw: unknown, - purchaserRequired = false, - supplierRequired = false, - supplierEditable = true -): SupplierBehavior => { - if (typeof raw === 'string' && raw in supplierBehaviorCodeMap) { - return raw as SupplierBehavior - } - const code = Number(raw) - if (Number.isInteger(code) && code >= 1 && code <= 6) { - return (Object.keys(supplierBehaviorCodeMap) as SupplierBehavior[]).find((key) => supplierBehaviorCodeMap[key] === code) || 'optional' - } - return legacySupplierBehavior(purchaserRequired, supplierRequired, supplierEditable) -} - -const getSupplierRequiredCode = (field: any) => - field?.autoFill - ? 0 - : - supplierBehaviorCodeMap[ - normalizeSupplierBehavior( - field?.supplier_required ?? field?.supplierRequiredCode ?? field?.supplierBehavior, - !!field?.purchaserRequired, - !!field?.supplierRequired, - field?.supplierEditable !== false - ) - ] || 0 - -const createField = ( - label: string, - key: string, - type: 'text' | 'select' | 'number', - formula = '', - opts: FieldOpts = {} -) => { - const supplierRequired = opts.supplierRequired === true - const supplierEditable = opts.supplierEditable !== false - const supplierBehavior = - opts.supplierBehavior || legacySupplierBehavior(false, supplierRequired, supplierEditable) - return { - label, - key, - type, - formula, - fixed: true, - purchaserRequired: false, - supplierBehavior, - builtIn: true - } -} - -const materialsFieldsMisc = [ - createField('材质', 'material', 'text'), - createField('长', 'length', 'number'), - createField('宽', 'width', 'number'), - createField('高', 'height', 'number'), - createField('比重', 'specificgravity', 'number', '根据材质自动带出', { supplierEditable: false }), - createField('数量', 'qty', 'number', '', { supplierRequired: true }), - createField('重量', 'weight', 'number', '长*宽*高*比重*数量', { supplierEditable: false }), - createField('单价', 'unitPrice', 'number'), - createField('材料费用', 'material_cost', 'number', '重量*单价', { supplierEditable: false }) -] - -const materialsFieldsStrategic = [ - createField('材质', 'material', 'text'), - createField('用量/重量', 'weight', 'number'), - createField('单价', 'unitPrice', 'number'), - createField('单位', 'unit', 'text'), - createField('损耗率', 'loss_rate', 'number'), - createField('材料费用', 'material_cost', 'number', '重量*单价', { supplierEditable: false }), - createField('备注', 'remark', 'text') -] - -const getMaterialsFields = (procurementCategory?: string) => - procurementCategory === '1' ? materialsFieldsStrategic : materialsFieldsMisc - -const processFieldsMisc = [ - createField('加工工站', 'process_station', 'select'), - createField('单位', 'unit', 'text', '', { supplierEditable: false }), - createField('费率', 'unitrate', 'number', '根据工站自动带出', { supplierEditable: false }), - createField('加工计量', 'processqty', 'number'), - createField('加工费', 'processprice', 'number', '费率*加工计量', { supplierEditable: false }), - createField('备注', 'remark', 'text', '', { supplierEditable: false }) -] - -const processFieldsStrategic = [ - createField('加工工站', 'process_station', 'select'), - createField('加工时间', 'process_time', 'number'), - createField('加工单价', 'unit_price', 'number'), - createField('单位', 'unit', 'text', '', { supplierEditable: false }), - createField('损耗率', 'loss_rate', 'number', '', { supplierEditable: false }), - createField('加工费', 'process_cost', 'number', '', { supplierEditable: false }), - createField('备注', 'remark', 'text', '', { supplierEditable: false }) -] - -const getProcessFields = (procurementCategory?: string) => - procurementCategory === '1' ? processFieldsStrategic : processFieldsMisc - -const normalizeSections = (value: unknown) => { - if (value === null || value === undefined || (typeof value === 'string' && value.trim() === '')) { - return [] - } - if (typeof value === 'string') { - try { - const parsed = JSON.parse(value) - return Array.isArray(parsed) ? parsed : [] - } catch (e) { - return [] - } - } - if (Array.isArray(value)) { - return value - } - return [] -} - -const baseSections: Record = { - 产品明细: { - id: 'productDetail', - title: '产品明细', - enabled: true, - supplierCanAddRow: true, - allowAddField: true, - fields: [ - createField('料号', 'partNo', 'text'), - createField('规格描述', 'desc', 'text'), - createField('数量', 'qty', 'number'), - createField('单价', 'price', 'number'), - createField('合计价格', 'amount', 'number', '数量*单价', { supplierEditable: false }) - ] - }, - 材料成本: { - id: 'materials', - title: '材料成本', - enabled: true, - supplierCanAddRow: true, - allowAddField: true, - fields: [] - }, - 加工成本: { - id: 'process', - title: '加工成本', - enabled: true, - supplierCanAddRow: true, - allowAddField: true, - fields: [] - }, - 管销研费用: { - id: 'sgna', - title: '管销研费用', - enabled: true, - supplierCanAddRow: false, - allowAddField: false, - fields: [createField('费用', 'fee', 'number')] - }, - 其它成本: { - id: 'others', - title: '其它成本', - enabled: true, - supplierCanAddRow: true, - allowAddField: false, - fields: [ - createField('包装费', 'packaging_cost', 'number'), - createField('运输费', 'transportation_cost', 'number') - ] - }, - 利润: { - id: 'profit', - title: '利润', - enabled: true, - supplierCanAddRow: false, - allowAddField: false, - fields: [ - createField('利润率', 'profitRate', 'number') - ] - }, - 税金: { - id: 'tax', - title: '税金', - enabled: true, - supplierCanAddRow: false, - allowAddField: false, - fields: [ - createField('税率', 'taxRate', 'number', '根据交易厂区自动带出', { supplierEditable: false }) - ] - } -} - -const deepCopy = (obj: T): T => JSON.parse(JSON.stringify(obj)) - -const buildDefaultSections = (procurementCategory?: string) => { - const sections = Object.values(baseSections).map((s) => deepCopy(s)) - const materials = sections.find((s) => s.title === '材料成本') - if (materials) { - materials.fields = deepCopy(getMaterialsFields(procurementCategory)) - } - const process = sections.find((s) => s.title === '加工成本') - if (process) { - process.fields = deepCopy(getProcessFields(procurementCategory)) - } - return sections -} - -const replaceBuiltInFields = (section: any, defaultFields: any[]) => { - const customFields = Array.isArray(section?.fields) ? section.fields.filter((field: any) => !field?.builtIn) : [] - const existingByKey = new Map() - ;(section?.fields || []).forEach((f: any) => { - if (f?.key) existingByKey.set(String(f.key), f) - }) - const mergedDefaults = defaultFields.map((df: any) => { - const copy = deepCopy(df) - const existing = df?.key ? existingByKey.get(String(df.key)) : null - if (existing) { - copy.label = existing.label ?? copy.label - copy.nameCn = existing.nameCn ?? existing.label ?? copy.label - copy.nameEn = existing.nameEn ?? copy.nameEn ?? '' - copy.nameVn = existing.nameVn ?? copy.nameVn ?? '' - // 内置字段也允许编辑这些业务属性,合并默认字段时必须保留用户修改值 - if (existing.fixed !== undefined) copy.fixed = existing.fixed - if (existing.autoFill !== undefined) copy.autoFill = existing.autoFill - if (existing.purchaserRequired !== undefined) copy.purchaserRequired = existing.purchaserRequired - if (existing.supplierBehavior !== undefined) copy.supplierBehavior = existing.supplierBehavior - if (existing.supplierRequiredCode !== undefined) copy.supplierRequiredCode = existing.supplierRequiredCode - if (existing.supplierRequired !== undefined) copy.supplierRequired = existing.supplierRequired - if (existing.supplierEditable !== undefined) copy.supplierEditable = existing.supplierEditable - if (existing.remark !== undefined) copy.remark = existing.remark - } else { - copy.nameCn = copy.nameCn ?? copy.label ?? '' - copy.nameEn = copy.nameEn ?? '' - copy.nameVn = copy.nameVn ?? '' - } - return copy - }) - section.fields = [...mergedDefaults, ...customFields] -} - -const isBuiltInField = (sectionTitle: string, fieldKey: string, procurementCategory?: string) => { - if (!sectionTitle || !fieldKey) return false - const defaults = buildDefaultSections(procurementCategory) - const section = defaults.find((item) => item.title === sectionTitle) - return !!section?.fields?.some((field: any) => field?.key === fieldKey && field?.builtIn) -} - -const titleAliases: Record = { - 其它: '其它成本', - 其他: '其它成本', - 其他费用: '其它成本', - 税率: '税金', - 管销研: '管销研费用', - 管销研成本: '管销研费用' -} - -const normalizeTitleKey = (title?: string) => { - if (!title) return '' - return titleAliases[title] || title -} - -const applyEnabledFlags = (sections: any[], titles: string[]) => { - const set = new Set(titles.map((t) => normalizeTitleKey(t))) - sections.forEach((s) => { - s.enabled = set.has(normalizeTitleKey(s?.title || s?.name)) - }) -} - -const visibleTitles = (procurementCategory?: string, enableCostStructure?: boolean) => { - // 杂采(2) 不显示“管销研费用” - if (procurementCategory === '2') { - return enableCostStructure - ? ['材料成本', '加工成本', '其它成本', '利润', '税金'] - : ['产品明细', '利润', '税金'] - } - return enableCostStructure - ? ['材料成本', '加工成本', '其它成本', '管销研费用', '利润', '税金'] - : ['产品明细', '利润', '税金'] -} - -const ensureAllSections = (form: any, titlesForEnabled?: string[], procurementCategory?: string) => { - const existing = normalizeSections(form?.sections) - const map = new Map() - existing.forEach((s: any) => { - const key = normalizeTitleKey(s?.title || s?.name) - if (key) map.set(key, s) - }) - const defaults = buildDefaultSections(procurementCategory || form?.procurement_category) - defaults.forEach((base) => { - if (!map.has(base.title)) { - map.set(base.title, deepCopy(base)) - } - }) - const arr = Array.from(map.values()) - const currentCategory = procurementCategory || form?.procurement_category - // Always replace内置材料成本字段按采购类别,保留用户新增字段 - const materials = arr.find((s: any) => s?.title === '材料成本') - if (materials) { - replaceBuiltInFields(materials, getMaterialsFields(currentCategory)) - } - // Always replace内置加工成本字段按采购类别,保留用户新增字段 - const process = arr.find((s: any) => s?.title === '加工成本') - if (process) { - replaceBuiltInFields(process, getProcessFields(currentCategory)) - } - if (titlesForEnabled && titlesForEnabled.length) { - applyEnabledFlags(arr, titlesForEnabled) - } - return arr -} - -const refreshSectionsIfNeeded = (form: any, overrides: any = {}) => { - const titles = visibleTitles( - overrides.procurement_category ?? form?.procurement_category, - overrides.enable_cost_structure ?? (form?.is_bom || 'Y') === 'Y' - ) - const procurementCategory = overrides.procurement_category ?? form?.procurement_category - form.sections = ensureAllSections({ ...form, ...overrides }, titles, procurementCategory) - form.__visibleTitles = titles - if (Array.isArray(form.sections)) { - ;(form.sections as any).__visibleTitles = titles - } - latestSectionDraft = normalizeSections(form.sections) - logPT('refreshSections', { - procurement_category: overrides.procurement_category ?? form?.procurement_category, - enable_cost_structure: overrides.enable_cost_structure ?? (form?.is_bom || 'Y') === 'Y', - titles, - sectionsCount: Array.isArray(form.sections) ? form.sections.length : 0, - draftSectionsCount: latestSectionDraft.length - }) -} - -const titleToCostCategory: Record = { - 材料成本: '1', - 加工成本: '2', - 其它成本: '3', - 其他成本: '3', - 管销研费用: '4', - 利润: '5', - 税金: '6', - 产品明细: '7' -} - -const costCategoryToTitle: Record = { - '1': '材料成本', - '2': '加工成本', - '3': '其它成本', - '4': '管销研费用', - '5': '利润', - '6': '税金', - '7': '产品明细' -} - -// 绕过 fast-crud 对自定义组件深层回写偶发失效的问题: -// SectionBuilder 会把当前界面的最新分段草稿主动回传到这里,保存时优先使用这份草稿。 -let latestSectionDraft: any[] = [] - -const countSectionFields = (sections: any[]) => - normalizeSections(sections).reduce((total, section: any) => total + (Array.isArray(section?.fields) ? section.fields.length : 0), 0) - -const resolveSectionsForSubmit = (form: any) => { - const draftSections = normalizeSections(latestSectionDraft) - const currentSections = normalizeSections(form.sections) - if (!draftSections.length) return currentSections - if (!currentSections.length) return draftSections - // 优先使用 latestSectionDraft(SectionBuilder 实时回传的用户编辑),避免 valueResolve 用旧 items 覆盖后提交 - const draftCount = countSectionFields(draftSections) - const currentCount = countSectionFields(currentSections) - return draftCount >= currentCount ? draftSections : currentSections -} - -const sectionsToItems = (sectionsRaw: any[], allowTitles?: string[]) => { - const sections = normalizeSections(sectionsRaw) - const allowList = - Array.isArray(allowTitles) && allowTitles.length - ? allowTitles - : Array.isArray((sectionsRaw as any)?.__visibleTitles) && (sectionsRaw as any).__visibleTitles.length - ? (sectionsRaw as any).__visibleTitles - : [] - const items: any[] = [] - let order = 1 - sections - .filter((s: any) => s && s.enabled !== false) - .filter((s: any) => !allowList.length || allowList.includes(s.title || s.name || '')) - .forEach((sec: any) => { - const title = sec.title || sec.name || '' - const cost_category = titleToCostCategory[title] || '1' - ;(sec.fields || []).forEach((f: any) => { - items.push({ - cost_category, - item_order: order++, - item_no: f.key || '', - item_name_cn: f.nameCn || f.label || '', - item_name_en: f.nameEn || '', - item_name_vn: f.nameVn || '', - is_fixed: f.fixed ? 1 : 0, - is_computed: f.autoFill ? 1 : 0, - purchaser_required: f.autoFill ? 0 : f.purchaserRequired ? 1 : 0, - supplier_required: getSupplierRequiredCode(f), - remark: f.remark || '' - }) - }) - }) - return items -} - -const itemsToSections = (items: any[], head?: any) => { - const grouped = new Map() - ;(items || []).forEach((it: any) => { - const title = costCategoryToTitle[String(it.cost_category ?? '')] || '其它成本' - if (!grouped.has(title)) grouped.set(title, []) - grouped.get(title)!.push(it) - }) - const defaults = buildDefaultSections(head?.procurement_category) - defaults.forEach((sec: any) => { - const title = sec.title - const rows = grouped.get(title) || [] - if (rows.length) { - sec.enabled = true - sec.fields = rows - .slice() - .sort((a: any, b: any) => (a.item_order || 0) - (b.item_order || 0)) - .map((r: any) => ({ - id: r.id, - key: r.item_no, - label: r.item_name_cn, - nameCn: r.item_name_cn, - nameEn: r.item_name_en, - nameVn: r.item_name_vn, - fixed: Number(r.is_fixed ?? r.item_category ?? 0) === 1, - autoFill: Number(r.is_computed || 0) === 1, - purchaserRequired: Number(r.is_computed || 0) === 1 ? false : Number(r.purchaser_required || 0) === 1, - supplierRequiredCode: Number(r.supplier_required || 0) || 0, - supplierBehavior: normalizeSupplierBehavior( - Number(r.is_computed || 0) === 1 ? '' : r.supplier_behavior ?? r.supplier_required, - Number(r.is_computed || 0) === 1 ? false : Number(r.purchaser_required || 0) === 1 - ), - supplierRequired: Number(r.is_computed || 0) === 1 ? false : [1, 2, 5].includes(Number(r.supplier_required || 0)), - remark: r.remark || '', - builtIn: isBuiltInField(title, r.item_no, head?.procurement_category) - })) - } - if (title === '材料成本') sec.supplierCanAddRow = Number(head?.is_can_add_materials || 0) === 1 - if (title === '加工成本') sec.supplierCanAddRow = Number(head?.is_can_add_process || 0) === 1 - }) - const allowedTitles = visibleTitles(head?.procurement_category, (head?.is_bom || 'Y') === 'Y') - applyEnabledFlags(defaults, allowedTitles) - return defaults -} - -export const createCrudOptions = function ({ context }: CreateCrudOptionsProps): CreateCrudOptionsRet { - return { - crudOptions: { - form: { - labelWidth: '96px', - col: { span: 12 }, - wrapper: { - is: 'el-dialog', - width: '95vw', - top: '5vh' - }, - group: { - type: 'tab', - base: { - label: '基础信息', - columns: ['template_name', 'procurement_category', 'template_desc'] - }, - controls: { - label: '启用设置', - columns: ['acti', 'is_bom'] - }, - sections: { - label: '成本结构', - columns: ['sections'] - } - }, - async onOpened(ctx: any) { - if (ctx.mode === 'add') { - ctx.form.procurement_category = '2' - ctx.form.is_bom = 'Y' - ctx.form.acti = 'Y' - refreshSectionsIfNeeded(ctx.form) - latestSectionDraft = normalizeSections(ctx.form.sections) - return - } - // 查看/编辑时:列表行可能不包含 items,强制拉详情回填,避免 sections/items 不同步导致页面展示不完整 - if ((ctx.mode === 'edit' || ctx.mode === 'view') && ctx.form?.id) { - try { - const res: any = await api.GetObj(ctx.form.id) - const detail = res?.data?.data || res?.data || res - if (detail && typeof detail === 'object') { - Object.assign(ctx.form, detail) - if (Array.isArray(detail.items)) { - const secs = itemsToSections(detail.items, detail) - const vis = visibleTitles(detail.procurement_category, (detail.is_bom || 'Y') === 'Y') - ;(secs as any).__visibleTitles = vis - ctx.form.sections = secs - ctx.form.__visibleTitles = vis - latestSectionDraft = normalizeSections(secs) - } - } - } catch (e) { - console.warn('加载成本模板详情失败', e) - } - } - }, - }, - request: { - pageRequest: async (query) => api.GetList(query), - addRequest: async ({ form }) => { - const sections = resolveSectionsForSubmit(form) - const allowTitles = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - const payload: any = { - template_no: form.template_no || undefined, - template_name: form.template_name, - procurement_category: form.procurement_category, - is_bom: form.is_bom || 'Y', - acti: form.acti || 'Y', - template_desc: form.template_desc || '', - is_can_add_materials: Number(sections.find((s: any) => s?.title === '材料成本')?.supplierCanAddRow) ? 1 : 0, - is_can_add_process: Number(sections.find((s: any) => s?.title === '加工成本')?.supplierCanAddRow) ? 1 : 0, - items: sectionsToItems(sections, allowTitles) - } - const categoryStats = payload.items.reduce((acc: Record, item: any) => { - const key = String(item.cost_category || '') - acc[key] = (acc[key] || 0) + 1 - return acc - }, {}) - const computedStats = payload.items.map((item: any) => ({ - cost_category: item.cost_category, - item_no: item.item_no, - is_computed: item.is_computed - })) - console.log('[CostTemplate] add payload.items stats', { total: payload.items?.length || 0, categories: categoryStats, allowTitles }) - console.log('[CostTemplate] add payload.items computed', computedStats) - return api.AddObj(payload) - }, - editRequest: async ({ form, row }) => { - const sections = resolveSectionsForSubmit(form) - const allowTitles = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - const payload: any = { - id: row.id, - template_no: form.template_no || row.template_no, - template_name: form.template_name, - procurement_category: form.procurement_category, - is_bom: form.is_bom || 'Y', - acti: form.acti || 'Y', - template_desc: form.template_desc || '', - is_can_add_materials: Number(sections.find((s: any) => s?.title === '材料成本')?.supplierCanAddRow) ? 1 : 0, - is_can_add_process: Number(sections.find((s: any) => s?.title === '加工成本')?.supplierCanAddRow) ? 1 : 0, - items: sectionsToItems(sections, allowTitles) - } - const categoryStats = payload.items.reduce((acc: Record, item: any) => { - const key = String(item.cost_category || '') - acc[key] = (acc[key] || 0) + 1 - return acc - }, {}) - const computedStats = payload.items.map((item: any) => ({ - cost_category: item.cost_category, - item_no: item.item_no, - is_computed: item.is_computed - })) - console.log('[CostTemplate] edit payload.items stats', { total: payload.items?.length || 0, categories: categoryStats, allowTitles }) - console.log('[CostTemplate] edit payload.items computed', computedStats) - return api.UpdateObj(payload) - }, - delRequest: async ({ row }) => api.DelObj(row.id) - }, - table: { - rowKey: 'id' - }, - actionbar: { - buttons: { - add: { show: true } - } - }, - rowHandle: { - fixed: 'right', - minWidth: 220, - buttons: { - view: { - show: true - } - } - }, - columns: { - template_name: { - title: '模板名称', - type: 'input', - search: { - show: true, - component: { - props: { placeholder: '请输入模板名称', clearable: true } - } - }, - form: { - col: { span: 12 }, - rules: [{ required: true, message: '请输入模板名称' }] - }, - column: { minWidth: 160 } - }, - template_no: { - title: '模板编号', - type: 'input', - search: { - show: true, - component: { props: { placeholder: '模板编号', clearable: true } } - }, - form: { show: false }, - column: { width: 160, showOverflowTooltip: true } - }, - procurement_category: { - title: '采购类别', - type: 'dict-select', - dict: dict({ data: procurementDict }), - column: { width: 120 }, - search: { show: true }, - form: { - col: { span: 12 }, - value: '2', - rules: [{ required: true, message: '请选择采购类别' }], - valueChange({ form, value }) { - refreshSectionsIfNeeded(form, { procurement_category: value }) - logPT('onChange procurement_category', value, { visibleTitles: form.__visibleTitles }) - logPT('sections titles now', Array.isArray(form.sections) ? form.sections.map((s: any) => s.title) : []) - } - }, - editForm: { - component: { props: { disabled: true } } - } - }, - acti: { - title: '有效', - type: 'dict-select', - dict: dict({ data: [ - { value: 'Y', label: '是' }, - { value: 'N', label: '否' } - ] }), - column: { width: 90 }, - search: { show: true }, - form: { value: 'Y' } - }, - is_bom: { - title: '启用BOM', - type: 'dict-select', - dict: dict({ data: [ - { value: 'Y', label: '是' }, - { value: 'N', label: '否' } - ] }), - column: { width: 100 }, - search: { show: true }, - form: { - value: 'Y', - valueChange({ form, value }) { - refreshSectionsIfNeeded(form, { enable_cost_structure: value === 'Y' }) - logPT('onChange is_bom', value, { visibleTitles: form.__visibleTitles }) - logPT('sections titles now', Array.isArray(form.sections) ? form.sections.map((s: any) => s.title) : []) - } - } - }, - template_desc: { - title: '备注', - type: 'textarea', - column: { minWidth: 180, showOverflowTooltip: true }, - form: { - col: { span: 12 }, - component: { props: { rows: 2 } } - } - }, - sections: { - title: '', - type: 'text', - column: { show: false }, - form: { - label: '', - labelWidth: '0px', - component: { - name: SectionBuilder, - props: { - hideTitle: true, - showTitleInput: false, - defaultSections: buildDefaultSections('2'), - visibleTitles: [], - onDraftChange: (sections: any[]) => { - latestSectionDraft = normalizeSections(sections) - } - } - }, - value: () => { - const vis = visibleTitles('2', true) - const defaults = ensureAllSections({ sections: buildDefaultSections('2') }, vis, '2') - ;(defaults as any).__visibleTitles = vis - return defaults - }, - col: { span: 24 }, - itemProps: { labelWidth: 0, class: 'fc-section-item' }, - wrapper: { class: 'price-template-fs-dialog' } - }, - valueBuilder({ form }) { - // 编辑态优先以后端返回的 Body 明细(items) 还原 UI,不能再回退到默认 sections, - // 否则会把数据库里的 is_computed 等真实值覆盖成前端预设默认值。 - if (Array.isArray(form.items) && form.items.length) { - const secs = itemsToSections(form.items, form) - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - ;(secs as any).__visibleTitles = vis - form.sections = secs - form.__visibleTitles = vis - latestSectionDraft = normalizeSections(secs) - logPT('valueBuilder(items)', { visibleTitles: form.__visibleTitles, sections: form.sections?.length, items: form.items.length }) - return - } - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - const ensured = ensureAllSections(form, vis, form.procurement_category) - ;(ensured as any).__visibleTitles = vis - form.sections = ensured - form.__visibleTitles = vis - latestSectionDraft = normalizeSections(ensured) - logPT('valueBuilder', { visibleTitles: form.__visibleTitles, sections: form.sections?.length }) - }, - valueResolve({ form }) { - const currentSections = normalizeSections(form.sections) - const currentSectionFieldCount = countSectionFields(currentSections) - // 若当前已存在可编辑 sections(用户可能已修改),不要再被 form.items 覆盖 - if (currentSections.length && currentSectionFieldCount > 0) { - latestSectionDraft = currentSections - logPT('valueResolve', { source: 'sections', sections: currentSections.length, fields: currentSectionFieldCount }) - return - } - // 初次回填时(尚未形成 sections),再从 items 转换 - if (Array.isArray(form.items) && form.items.length) { - const secs = itemsToSections(form.items, form) - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - ;(secs as any).__visibleTitles = vis - form.sections = secs - form.__visibleTitles = vis - latestSectionDraft = normalizeSections(secs) - logPT('valueResolve', { source: 'items', sections: secs.length, items: form.items.length }) - return - } - const ensured = ensureAllSections(form, form.__visibleTitles || [], form.procurement_category) - form.sections = ensured - latestSectionDraft = normalizeSections(ensured) - logPT('valueResolve', { source: 'ensured', sections: form.sections?.length }) - } - }, - update_datetime: { - title: '更新时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - } - } - } - } -} diff --git a/web/src/views/pisadmin/miscprocurement/pricetemplate/index.vue b/web/src/views/pisadmin/miscprocurement/pricetemplate/index.vue deleted file mode 100644 index a944afa..0000000 --- a/web/src/views/pisadmin/miscprocurement/pricetemplate/index.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/web/src/views/procurement/pricetemplate/SectionBuilder.vue b/web/src/views/procurement/pricetemplate/SectionBuilder.vue deleted file mode 100644 index 4da1254..0000000 --- a/web/src/views/procurement/pricetemplate/SectionBuilder.vue +++ /dev/null @@ -1,529 +0,0 @@ - - - - - diff --git a/web/src/views/procurement/pricetemplate/api.ts b/web/src/views/procurement/pricetemplate/api.ts deleted file mode 100644 index 99f0bce..0000000 --- a/web/src/views/procurement/pricetemplate/api.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { request } from '/@/utils/service' -import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud' - -// 成本结构模板(独立表):t_CostEstimate_Template_Head/Body -export const apiPrefix = '/api/procurement/cost_template/' - -export function GetList(query: PageQuery) { - return request({ - url: apiPrefix, - method: 'get', - params: query - }) -} - -export function GetObj(id: InfoReq) { - return request({ - url: apiPrefix + id + '/', - method: 'get' - }) -} - -export function AddObj(obj: AddReq) { - return request({ - url: apiPrefix, - method: 'post', - data: obj - }) -} - -export function UpdateObj(obj: EditReq) { - return request({ - url: apiPrefix + obj.id + '/', - method: 'put', - data: obj - }) -} - -export function DelObj(id: DelReq) { - return request({ - url: apiPrefix + id + '/', - method: 'delete' - }) -} diff --git a/web/src/views/procurement/pricetemplate/crud.tsx b/web/src/views/procurement/pricetemplate/crud.tsx deleted file mode 100644 index 4bb7d93..0000000 --- a/web/src/views/procurement/pricetemplate/crud.tsx +++ /dev/null @@ -1,793 +0,0 @@ -import { dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' -import * as api from './api' -import SectionBuilder from './SectionBuilder.vue' - -// 后端 cost_template.procurement_category:1=策采;2=杂采 -const procurementDict = [ - { value: '1', label: '策采' }, - { value: '2', label: '杂采' } -] - -const logPT = (...args: any[]) => console.log('[CostTemplate]', ...args) - -type SupplierBehavior = - | 'prefill_locked' - | 'prefill_editable' - | 'hidden_required' - | 'hidden_optional' - | 'required' - | 'optional' - -type FieldOpts = { supplierEditable?: boolean; supplierRequired?: boolean; supplierBehavior?: SupplierBehavior } - -const supplierBehaviorCodeMap: Record = { - prefill_locked: 1, - prefill_editable: 2, - hidden_required: 3, - hidden_optional: 4, - required: 5, - optional: 6 -} - -const legacySupplierBehavior = (purchaserRequired: boolean, supplierRequired: boolean, supplierEditable: boolean): SupplierBehavior => { - if (purchaserRequired) { - if (supplierRequired && !supplierEditable) return 'prefill_locked' - if (supplierRequired && supplierEditable) return 'prefill_editable' - if (!supplierRequired && !supplierEditable) return 'hidden_required' - return 'hidden_optional' - } - return supplierRequired ? 'required' : 'optional' -} - -const normalizeSupplierBehavior = ( - raw: unknown, - purchaserRequired = false, - supplierRequired = false, - supplierEditable = true -): SupplierBehavior => { - if (typeof raw === 'string' && raw in supplierBehaviorCodeMap) { - return raw as SupplierBehavior - } - const code = Number(raw) - if (Number.isInteger(code) && code >= 1 && code <= 6) { - return (Object.keys(supplierBehaviorCodeMap) as SupplierBehavior[]).find((key) => supplierBehaviorCodeMap[key] === code) || 'optional' - } - return legacySupplierBehavior(purchaserRequired, supplierRequired, supplierEditable) -} - -const getSupplierRequiredCode = (field: any) => - field?.autoFill - ? 0 - : - supplierBehaviorCodeMap[ - normalizeSupplierBehavior( - field?.supplier_required ?? field?.supplierRequiredCode ?? field?.supplierBehavior, - !!field?.purchaserRequired, - !!field?.supplierRequired, - field?.supplierEditable !== false - ) - ] || 0 - -const createField = ( - label: string, - key: string, - type: 'text' | 'select' | 'number', - formula = '', - opts: FieldOpts = {} -) => { - const supplierRequired = opts.supplierRequired === true - const supplierEditable = opts.supplierEditable !== false - const supplierBehavior = - opts.supplierBehavior || legacySupplierBehavior(false, supplierRequired, supplierEditable) - return { - label, - key, - type, - formula, - fixed: true, - purchaserRequired: false, - supplierBehavior, - builtIn: true - } -} - -const materialsFieldsMisc = [ - createField('材质', 'material', 'text'), - createField('长', 'length', 'number'), - createField('宽', 'width', 'number'), - createField('高', 'height', 'number'), - createField('比重', 'specificgravity', 'number', '根据材质自动带出', { supplierEditable: false }), - createField('数量', 'qty', 'number', '', { supplierRequired: true }), - createField('重量', 'weight', 'number', '长*宽*高*比重*数量', { supplierEditable: false }), - createField('单价', 'unitPrice', 'number'), - createField('材料费用', 'material_cost', 'number', '重量*单价', { supplierEditable: false }) -] - -const materialsFieldsStrategic = [ - createField('材质', 'material', 'text'), - createField('用量/重量', 'weight', 'number'), - createField('单价', 'unitPrice', 'number'), - createField('单位', 'unit', 'text'), - createField('损耗率', 'loss_rate', 'number'), - createField('材料费用', 'material_cost', 'number', '重量*单价', { supplierEditable: false }), - createField('备注', 'remark', 'text') -] - -const getMaterialsFields = (procurementCategory?: string) => - procurementCategory === '1' ? materialsFieldsStrategic : materialsFieldsMisc - -const processFieldsMisc = [ - createField('加工工站', 'process_station', 'select'), - createField('单位', 'unit', 'text', '', { supplierEditable: false }), - createField('费率', 'unitrate', 'number', '根据工站自动带出', { supplierEditable: false }), - createField('加工计量', 'processqty', 'number'), - createField('加工费', 'processprice', 'number', '费率*加工计量', { supplierEditable: false }), - createField('备注', 'remark', 'text', '', { supplierEditable: false }) -] - -const processFieldsStrategic = [ - createField('加工工站', 'process_station', 'select'), - createField('加工时间', 'process_time', 'number'), - createField('加工单价', 'unit_price', 'number'), - createField('单位', 'unit', 'text', '', { supplierEditable: false }), - createField('损耗率', 'loss_rate', 'number', '', { supplierEditable: false }), - createField('加工费', 'process_cost', 'number', '', { supplierEditable: false }), - createField('备注', 'remark', 'text', '', { supplierEditable: false }) -] - -const getProcessFields = (procurementCategory?: string) => - procurementCategory === '1' ? processFieldsStrategic : processFieldsMisc - -const normalizeSections = (value: unknown) => { - if (value === null || value === undefined || (typeof value === 'string' && value.trim() === '')) { - return [] - } - if (typeof value === 'string') { - try { - const parsed = JSON.parse(value) - return Array.isArray(parsed) ? parsed : [] - } catch (e) { - return [] - } - } - if (Array.isArray(value)) { - return value - } - return [] -} - -const baseSections: Record = { - 产品明细: { - id: 'productDetail', - title: '产品明细', - enabled: true, - supplierCanAddRow: true, - allowAddField: true, - fields: [ - createField('料号', 'partNo', 'text'), - createField('规格描述', 'desc', 'text'), - createField('数量', 'qty', 'number'), - createField('单价', 'price', 'number'), - createField('合计价格', 'amount', 'number', '数量*单价', { supplierEditable: false }) - ] - }, - 材料成本: { - id: 'materials', - title: '材料成本', - enabled: true, - supplierCanAddRow: true, - allowAddField: true, - fields: [] - }, - 加工成本: { - id: 'process', - title: '加工成本', - enabled: true, - supplierCanAddRow: true, - allowAddField: true, - fields: [] - }, - 管销研费用: { - id: 'sgna', - title: '管销研费用', - enabled: true, - supplierCanAddRow: false, - allowAddField: false, - fields: [createField('费用', 'fee', 'number')] - }, - 其它成本: { - id: 'others', - title: '其它成本', - enabled: true, - supplierCanAddRow: true, - allowAddField: false, - fields: [ - createField('包装费', 'packaging_cost', 'number'), - createField('运输费', 'transportation_cost', 'number') - ] - }, - 利润: { - id: 'profit', - title: '利润', - enabled: true, - supplierCanAddRow: false, - allowAddField: false, - fields: [ - createField('利润率', 'profitRate', 'number') - ] - }, - 税金: { - id: 'tax', - title: '税金', - enabled: true, - supplierCanAddRow: false, - allowAddField: false, - fields: [ - createField('税率', 'taxRate', 'number', '根据交易厂区自动带出', { supplierEditable: false }) - ] - } -} - -const deepCopy = (obj: T): T => JSON.parse(JSON.stringify(obj)) - -const buildDefaultSections = (procurementCategory?: string) => { - const sections = Object.values(baseSections).map((s) => deepCopy(s)) - const materials = sections.find((s) => s.title === '材料成本') - if (materials) { - materials.fields = deepCopy(getMaterialsFields(procurementCategory)) - } - const process = sections.find((s) => s.title === '加工成本') - if (process) { - process.fields = deepCopy(getProcessFields(procurementCategory)) - } - return sections -} - -const replaceBuiltInFields = (section: any, defaultFields: any[]) => { - const customFields = Array.isArray(section?.fields) ? section.fields.filter((field: any) => !field?.builtIn) : [] - const existingByKey = new Map() - ;(section?.fields || []).forEach((f: any) => { - if (f?.key) existingByKey.set(String(f.key), f) - }) - const mergedDefaults = defaultFields.map((df: any) => { - const copy = deepCopy(df) - const existing = df?.key ? existingByKey.get(String(df.key)) : null - if (existing) { - copy.label = existing.label ?? copy.label - copy.nameCn = existing.nameCn ?? existing.label ?? copy.label - copy.nameEn = existing.nameEn ?? copy.nameEn ?? '' - copy.nameVn = existing.nameVn ?? copy.nameVn ?? '' - // 内置字段也允许编辑这些业务属性,合并默认字段时必须保留用户修改值 - if (existing.fixed !== undefined) copy.fixed = existing.fixed - if (existing.autoFill !== undefined) copy.autoFill = existing.autoFill - if (existing.purchaserRequired !== undefined) copy.purchaserRequired = existing.purchaserRequired - if (existing.supplierBehavior !== undefined) copy.supplierBehavior = existing.supplierBehavior - if (existing.supplierRequiredCode !== undefined) copy.supplierRequiredCode = existing.supplierRequiredCode - if (existing.supplierRequired !== undefined) copy.supplierRequired = existing.supplierRequired - if (existing.supplierEditable !== undefined) copy.supplierEditable = existing.supplierEditable - if (existing.remark !== undefined) copy.remark = existing.remark - } else { - copy.nameCn = copy.nameCn ?? copy.label ?? '' - copy.nameEn = copy.nameEn ?? '' - copy.nameVn = copy.nameVn ?? '' - } - return copy - }) - section.fields = [...mergedDefaults, ...customFields] -} - -const isBuiltInField = (sectionTitle: string, fieldKey: string, procurementCategory?: string) => { - if (!sectionTitle || !fieldKey) return false - const defaults = buildDefaultSections(procurementCategory) - const section = defaults.find((item) => item.title === sectionTitle) - return !!section?.fields?.some((field: any) => field?.key === fieldKey && field?.builtIn) -} - -const titleAliases: Record = { - 其它: '其它成本', - 其他: '其它成本', - 其他费用: '其它成本', - 税率: '税金', - 管销研: '管销研费用', - 管销研成本: '管销研费用' -} - -const normalizeTitleKey = (title?: string) => { - if (!title) return '' - return titleAliases[title] || title -} - -const applyEnabledFlags = (sections: any[], titles: string[]) => { - const set = new Set(titles.map((t) => normalizeTitleKey(t))) - sections.forEach((s) => { - s.enabled = set.has(normalizeTitleKey(s?.title || s?.name)) - }) -} - -const visibleTitles = (procurementCategory?: string, enableCostStructure?: boolean) => { - // 杂采(2) 不显示“管销研费用” - if (procurementCategory === '2') { - return enableCostStructure - ? ['材料成本', '加工成本', '其它成本', '利润', '税金'] - : ['产品明细', '利润', '税金'] - } - return enableCostStructure - ? ['材料成本', '加工成本', '其它成本', '管销研费用', '利润', '税金'] - : ['产品明细', '利润', '税金'] -} - -const ensureAllSections = (form: any, titlesForEnabled?: string[], procurementCategory?: string) => { - const existing = normalizeSections(form?.sections) - const map = new Map() - existing.forEach((s: any) => { - const key = normalizeTitleKey(s?.title || s?.name) - if (key) map.set(key, s) - }) - const defaults = buildDefaultSections(procurementCategory || form?.procurement_category) - defaults.forEach((base) => { - if (!map.has(base.title)) { - map.set(base.title, deepCopy(base)) - } - }) - const arr = Array.from(map.values()) - const currentCategory = procurementCategory || form?.procurement_category - // Always replace内置材料成本字段按采购类别,保留用户新增字段 - const materials = arr.find((s: any) => s?.title === '材料成本') - if (materials) { - replaceBuiltInFields(materials, getMaterialsFields(currentCategory)) - } - // Always replace内置加工成本字段按采购类别,保留用户新增字段 - const process = arr.find((s: any) => s?.title === '加工成本') - if (process) { - replaceBuiltInFields(process, getProcessFields(currentCategory)) - } - if (titlesForEnabled && titlesForEnabled.length) { - applyEnabledFlags(arr, titlesForEnabled) - } - return arr -} - -const refreshSectionsIfNeeded = (form: any, overrides: any = {}) => { - const titles = visibleTitles( - overrides.procurement_category ?? form?.procurement_category, - overrides.enable_cost_structure ?? (form?.is_bom || 'Y') === 'Y' - ) - const procurementCategory = overrides.procurement_category ?? form?.procurement_category - form.sections = ensureAllSections({ ...form, ...overrides }, titles, procurementCategory) - form.__visibleTitles = titles - if (Array.isArray(form.sections)) { - ;(form.sections as any).__visibleTitles = titles - } - latestSectionDraft = normalizeSections(form.sections) - logPT('refreshSections', { - procurement_category: overrides.procurement_category ?? form?.procurement_category, - enable_cost_structure: overrides.enable_cost_structure ?? (form?.is_bom || 'Y') === 'Y', - titles, - sectionsCount: Array.isArray(form.sections) ? form.sections.length : 0, - draftSectionsCount: latestSectionDraft.length - }) -} - -const titleToCostCategory: Record = { - 材料成本: '1', - 加工成本: '2', - 其它成本: '3', - 其他成本: '3', - 管销研费用: '4', - 利润: '5', - 税金: '6', - 产品明细: '7' -} - -const costCategoryToTitle: Record = { - '1': '材料成本', - '2': '加工成本', - '3': '其它成本', - '4': '管销研费用', - '5': '利润', - '6': '税金', - '7': '产品明细' -} - -// 绕过 fast-crud 对自定义组件深层回写偶发失效的问题: -// SectionBuilder 会把当前界面的最新分段草稿主动回传到这里,保存时优先使用这份草稿。 -let latestSectionDraft: any[] = [] - -const countSectionFields = (sections: any[]) => - normalizeSections(sections).reduce((total, section: any) => total + (Array.isArray(section?.fields) ? section.fields.length : 0), 0) - -const resolveSectionsForSubmit = (form: any) => { - const draftSections = normalizeSections(latestSectionDraft) - const currentSections = normalizeSections(form.sections) - if (!draftSections.length) return currentSections - if (!currentSections.length) return draftSections - // 优先使用 latestSectionDraft(SectionBuilder 实时回传的用户编辑),避免 valueResolve 用旧 items 覆盖后提交 - const draftCount = countSectionFields(draftSections) - const currentCount = countSectionFields(currentSections) - return draftCount >= currentCount ? draftSections : currentSections -} - -const sectionsToItems = (sectionsRaw: any[], allowTitles?: string[]) => { - const sections = normalizeSections(sectionsRaw) - const allowList = - Array.isArray(allowTitles) && allowTitles.length - ? allowTitles - : Array.isArray((sectionsRaw as any)?.__visibleTitles) && (sectionsRaw as any).__visibleTitles.length - ? (sectionsRaw as any).__visibleTitles - : [] - const items: any[] = [] - let order = 1 - sections - .filter((s: any) => s && s.enabled !== false) - .filter((s: any) => !allowList.length || allowList.includes(s.title || s.name || '')) - .forEach((sec: any) => { - const title = sec.title || sec.name || '' - const cost_category = titleToCostCategory[title] || '1' - ;(sec.fields || []).forEach((f: any) => { - items.push({ - cost_category, - item_order: order++, - item_no: f.key || '', - item_name_cn: f.nameCn || f.label || '', - item_name_en: f.nameEn || '', - item_name_vn: f.nameVn || '', - is_fixed: f.fixed ? 1 : 0, - is_computed: f.autoFill ? 1 : 0, - purchaser_required: f.autoFill ? 0 : f.purchaserRequired ? 1 : 0, - supplier_required: getSupplierRequiredCode(f), - remark: f.remark || '' - }) - }) - }) - return items -} - -const itemsToSections = (items: any[], head?: any) => { - const grouped = new Map() - ;(items || []).forEach((it: any) => { - const title = costCategoryToTitle[String(it.cost_category ?? '')] || '其它成本' - if (!grouped.has(title)) grouped.set(title, []) - grouped.get(title)!.push(it) - }) - const defaults = buildDefaultSections(head?.procurement_category) - defaults.forEach((sec: any) => { - const title = sec.title - const rows = grouped.get(title) || [] - if (rows.length) { - sec.enabled = true - sec.fields = rows - .slice() - .sort((a: any, b: any) => (a.item_order || 0) - (b.item_order || 0)) - .map((r: any) => ({ - id: r.id, - key: r.item_no, - label: r.item_name_cn, - nameCn: r.item_name_cn, - nameEn: r.item_name_en, - nameVn: r.item_name_vn, - fixed: Number(r.is_fixed ?? r.item_category ?? 0) === 1, - autoFill: Number(r.is_computed || 0) === 1, - purchaserRequired: Number(r.is_computed || 0) === 1 ? false : Number(r.purchaser_required || 0) === 1, - supplierRequiredCode: Number(r.supplier_required || 0) || 0, - supplierBehavior: normalizeSupplierBehavior( - Number(r.is_computed || 0) === 1 ? '' : r.supplier_behavior ?? r.supplier_required, - Number(r.is_computed || 0) === 1 ? false : Number(r.purchaser_required || 0) === 1 - ), - supplierRequired: Number(r.is_computed || 0) === 1 ? false : [1, 2, 5].includes(Number(r.supplier_required || 0)), - remark: r.remark || '', - builtIn: isBuiltInField(title, r.item_no, head?.procurement_category) - })) - } - if (title === '材料成本') sec.supplierCanAddRow = Number(head?.is_can_add_materials || 0) === 1 - if (title === '加工成本') sec.supplierCanAddRow = Number(head?.is_can_add_process || 0) === 1 - }) - const allowedTitles = visibleTitles(head?.procurement_category, (head?.is_bom || 'Y') === 'Y') - applyEnabledFlags(defaults, allowedTitles) - return defaults -} - -export const createCrudOptions = function ({ context }: CreateCrudOptionsProps): CreateCrudOptionsRet { - return { - crudOptions: { - form: { - labelWidth: '96px', - col: { span: 12 }, - wrapper: { - is: 'el-dialog', - width: '95vw', - top: '5vh' - }, - group: { - type: 'tab', - base: { - label: '基础信息', - columns: ['template_name', 'procurement_category', 'template_desc'] - }, - controls: { - label: '启用设置', - columns: ['acti', 'is_bom'] - }, - sections: { - label: '成本结构', - columns: ['sections'] - } - }, - async onOpened(ctx: any) { - if (ctx.mode === 'add') { - ctx.form.procurement_category = '2' - ctx.form.is_bom = 'Y' - ctx.form.acti = 'Y' - refreshSectionsIfNeeded(ctx.form) - latestSectionDraft = normalizeSections(ctx.form.sections) - return - } - // 查看/编辑时:列表行可能不包含 items,强制拉详情回填,避免 sections/items 不同步导致页面展示不完整 - if ((ctx.mode === 'edit' || ctx.mode === 'view') && ctx.form?.id) { - try { - const res: any = await api.GetObj(ctx.form.id) - const detail = res?.data?.data || res?.data || res - if (detail && typeof detail === 'object') { - Object.assign(ctx.form, detail) - if (Array.isArray(detail.items)) { - const secs = itemsToSections(detail.items, detail) - const vis = visibleTitles(detail.procurement_category, (detail.is_bom || 'Y') === 'Y') - ;(secs as any).__visibleTitles = vis - ctx.form.sections = secs - ctx.form.__visibleTitles = vis - latestSectionDraft = normalizeSections(secs) - } - } - } catch (e) { - console.warn('加载成本模板详情失败', e) - } - } - }, - }, - request: { - pageRequest: async (query) => api.GetList(query), - addRequest: async ({ form }) => { - const sections = resolveSectionsForSubmit(form) - const allowTitles = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - const payload: any = { - template_no: form.template_no || undefined, - template_name: form.template_name, - procurement_category: form.procurement_category, - is_bom: form.is_bom || 'Y', - acti: form.acti || 'Y', - template_desc: form.template_desc || '', - is_can_add_materials: Number(sections.find((s: any) => s?.title === '材料成本')?.supplierCanAddRow) ? 1 : 0, - is_can_add_process: Number(sections.find((s: any) => s?.title === '加工成本')?.supplierCanAddRow) ? 1 : 0, - items: sectionsToItems(sections, allowTitles) - } - const categoryStats = payload.items.reduce((acc: Record, item: any) => { - const key = String(item.cost_category || '') - acc[key] = (acc[key] || 0) + 1 - return acc - }, {}) - const computedStats = payload.items.map((item: any) => ({ - cost_category: item.cost_category, - item_no: item.item_no, - is_computed: item.is_computed - })) - console.log('[CostTemplate] add payload.items stats', { total: payload.items?.length || 0, categories: categoryStats, allowTitles }) - console.log('[CostTemplate] add payload.items computed', computedStats) - return api.AddObj(payload) - }, - editRequest: async ({ form, row }) => { - const sections = resolveSectionsForSubmit(form) - const allowTitles = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - const payload: any = { - id: row.id, - template_no: form.template_no || row.template_no, - template_name: form.template_name, - procurement_category: form.procurement_category, - is_bom: form.is_bom || 'Y', - acti: form.acti || 'Y', - template_desc: form.template_desc || '', - is_can_add_materials: Number(sections.find((s: any) => s?.title === '材料成本')?.supplierCanAddRow) ? 1 : 0, - is_can_add_process: Number(sections.find((s: any) => s?.title === '加工成本')?.supplierCanAddRow) ? 1 : 0, - items: sectionsToItems(sections, allowTitles) - } - const categoryStats = payload.items.reduce((acc: Record, item: any) => { - const key = String(item.cost_category || '') - acc[key] = (acc[key] || 0) + 1 - return acc - }, {}) - const computedStats = payload.items.map((item: any) => ({ - cost_category: item.cost_category, - item_no: item.item_no, - is_computed: item.is_computed - })) - console.log('[CostTemplate] edit payload.items stats', { total: payload.items?.length || 0, categories: categoryStats, allowTitles }) - console.log('[CostTemplate] edit payload.items computed', computedStats) - return api.UpdateObj(payload) - }, - delRequest: async ({ row }) => api.DelObj(row.id) - }, - table: { - rowKey: 'id' - }, - actionbar: { - buttons: { - add: { show: true } - } - }, - rowHandle: { - fixed: 'right', - minWidth: 220, - buttons: { - view: { - show: true - } - } - }, - columns: { - template_name: { - title: '模板名称', - type: 'input', - search: { - show: true, - component: { - props: { placeholder: '请输入模板名称', clearable: true } - } - }, - form: { - col: { span: 12 }, - rules: [{ required: true, message: '请输入模板名称' }] - }, - column: { minWidth: 160 } - }, - template_no: { - title: '模板编号', - type: 'input', - search: { - show: true, - component: { props: { placeholder: '模板编号', clearable: true } } - }, - form: { show: false }, - column: { width: 160, showOverflowTooltip: true } - }, - procurement_category: { - title: '采购类别', - type: 'dict-select', - dict: dict({ data: procurementDict }), - column: { width: 120 }, - search: { show: true }, - form: { - col: { span: 12 }, - value: '2', - rules: [{ required: true, message: '请选择采购类别' }], - valueChange({ form, value }) { - refreshSectionsIfNeeded(form, { procurement_category: value }) - logPT('onChange procurement_category', value, { visibleTitles: form.__visibleTitles }) - logPT('sections titles now', Array.isArray(form.sections) ? form.sections.map((s: any) => s.title) : []) - } - }, - editForm: { - component: { props: { disabled: true } } - } - }, - acti: { - title: '有效', - type: 'dict-select', - dict: dict({ data: [ - { value: 'Y', label: '是' }, - { value: 'N', label: '否' } - ] }), - column: { width: 90 }, - search: { show: true }, - form: { value: 'Y' } - }, - is_bom: { - title: '启用BOM', - type: 'dict-select', - dict: dict({ data: [ - { value: 'Y', label: '是' }, - { value: 'N', label: '否' } - ] }), - column: { width: 100 }, - search: { show: true }, - form: { - value: 'Y', - valueChange({ form, value }) { - refreshSectionsIfNeeded(form, { enable_cost_structure: value === 'Y' }) - logPT('onChange is_bom', value, { visibleTitles: form.__visibleTitles }) - logPT('sections titles now', Array.isArray(form.sections) ? form.sections.map((s: any) => s.title) : []) - } - } - }, - template_desc: { - title: '备注', - type: 'textarea', - column: { minWidth: 180, showOverflowTooltip: true }, - form: { - col: { span: 12 }, - component: { props: { rows: 2 } } - } - }, - sections: { - title: '', - type: 'text', - column: { show: false }, - form: { - label: '', - labelWidth: '0px', - component: { - name: SectionBuilder, - props: { - hideTitle: true, - showTitleInput: false, - defaultSections: buildDefaultSections('2'), - visibleTitles: [], - onDraftChange: (sections: any[]) => { - latestSectionDraft = normalizeSections(sections) - } - } - }, - value: () => { - const vis = visibleTitles('2', true) - const defaults = ensureAllSections({ sections: buildDefaultSections('2') }, vis, '2') - ;(defaults as any).__visibleTitles = vis - return defaults - }, - col: { span: 24 }, - itemProps: { labelWidth: 0, class: 'fc-section-item' }, - wrapper: { class: 'price-template-fs-dialog' } - }, - valueBuilder({ form }) { - // 编辑态优先以后端返回的 Body 明细(items) 还原 UI,不能再回退到默认 sections, - // 否则会把数据库里的 is_computed 等真实值覆盖成前端预设默认值。 - if (Array.isArray(form.items) && form.items.length) { - const secs = itemsToSections(form.items, form) - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - ;(secs as any).__visibleTitles = vis - form.sections = secs - form.__visibleTitles = vis - latestSectionDraft = normalizeSections(secs) - logPT('valueBuilder(items)', { visibleTitles: form.__visibleTitles, sections: form.sections?.length, items: form.items.length }) - return - } - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - const ensured = ensureAllSections(form, vis, form.procurement_category) - ;(ensured as any).__visibleTitles = vis - form.sections = ensured - form.__visibleTitles = vis - latestSectionDraft = normalizeSections(ensured) - logPT('valueBuilder', { visibleTitles: form.__visibleTitles, sections: form.sections?.length }) - }, - valueResolve({ form }) { - const currentSections = normalizeSections(form.sections) - const currentSectionFieldCount = countSectionFields(currentSections) - // 若当前已存在可编辑 sections(用户可能已修改),不要再被 form.items 覆盖 - if (currentSections.length && currentSectionFieldCount > 0) { - latestSectionDraft = currentSections - logPT('valueResolve', { source: 'sections', sections: currentSections.length, fields: currentSectionFieldCount }) - return - } - // 初次回填时(尚未形成 sections),再从 items 转换 - if (Array.isArray(form.items) && form.items.length) { - const secs = itemsToSections(form.items, form) - const vis = visibleTitles(form.procurement_category, (form.is_bom || 'Y') === 'Y') - ;(secs as any).__visibleTitles = vis - form.sections = secs - form.__visibleTitles = vis - latestSectionDraft = normalizeSections(secs) - logPT('valueResolve', { source: 'items', sections: secs.length, items: form.items.length }) - return - } - const ensured = ensureAllSections(form, form.__visibleTitles || [], form.procurement_category) - form.sections = ensured - latestSectionDraft = normalizeSections(ensured) - logPT('valueResolve', { source: 'ensured', sections: form.sections?.length }) - } - }, - update_datetime: { - title: '更新时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - } - } - } - } -} diff --git a/web/src/views/procurement/pricetemplate/index.vue b/web/src/views/procurement/pricetemplate/index.vue deleted file mode 100644 index a944afa..0000000 --- a/web/src/views/procurement/pricetemplate/index.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/web/src/views/procurement/rfqmiscellaneous/api.ts b/web/src/views/procurement/rfqmiscellaneous/api.ts deleted file mode 100644 index e24515d..0000000 --- a/web/src/views/procurement/rfqmiscellaneous/api.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { request } from '/@/utils/service' - -const baseUrl = '/api/procurement/inquiry/' - -export const GetList = (params: any) => request({ url: baseUrl, method: 'get', params }) -export const GetObj = (id: string | number) => request({ url: baseUrl + id + '/', method: 'get' }) -export const AddObj = (data: any) => request({ url: baseUrl, method: 'post', data }) -export const UpdateObj = (data: any) => request({ url: baseUrl + data.id + '/', method: 'put', data }) -export const DelObj = (id: string | number) => request({ url: baseUrl + id + '/', method: 'delete' }) -export const ConfirmObj = (id: string | number) => request({ url: `${baseUrl}${id}/confirm/`, method: 'put' }) -export const RestoreObj = (id: string | number) => request({ url: `${baseUrl}${id}/restore/`, method: 'put' }) -export const PublishObj = (id: string | number) => request({ url: `${baseUrl}${id}/publish/`, method: 'put' }) -export const UploadFile = (data: FormData) => - request({ - url: '/api/system/file/', - method: 'post', - data, - timeout: 60000, - headers: { - 'Content-Type': 'multipart/form-data' - } - }) diff --git a/web/src/views/procurement/rfqmiscellaneous/crud.tsx b/web/src/views/procurement/rfqmiscellaneous/crud.tsx deleted file mode 100644 index 3514a16..0000000 --- a/web/src/views/procurement/rfqmiscellaneous/crud.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import { compute, dict, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud' -import { ElMessage, ElMessageBox } from 'element-plus' -import * as api from './api' - -const statusDict = [ - { value: 1, label: '开立' }, - { value: 2, label: '确认' }, - { value: 3, label: '发布' }, - { value: 4, label: '报价中' }, - { value: 5, label: '报价结束' }, - { value: 6, label: '比议价中' }, - { value: 7, label: '价格审核' }, - { value: 8, label: '核价通过(结束)' }, - { value: 9, label: '落标(结束)' }, - { value: 0, label: '作废' } -] - -const paymentMethods = [ - { value: 1, label: '月结30天' }, - { value: 2, label: '月结60天' }, - { value: 3, label: '不到付款' }, - { value: 4, label: '预付30%' }, - { value: 5, label: '预付50%' }, - { value: 6, label: '余款至生产' }, - { value: 7, label: '价格审核' }, - { value: 8, label: '价格结算(运费)' }, - { value: 9, label: '新建(结束)' } -] - -const formatQuoteDeadlineDisplay = (value: unknown) => { - if (!value) return '' - const text = String(value).trim() - if (!text) return '' - const matched = text.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2})/) - if (matched) { - return `${matched[1]} ${matched[2]}:00` - } - const dateOnly = text.match(/^(\d{4}-\d{2}-\d{2})$/) - if (dateOnly) { - return `${dateOnly[1]} 00:00` - } - return text -} - -const normalizeList = (value: unknown, label: string) => { - if (value === null || value === undefined || value === '') return [] - if (typeof value === 'string') { - const parsed = JSON.parse(value) - if (!Array.isArray(parsed)) throw new Error(`${label} 需为 JSON 数组`) - return parsed - } - if (Array.isArray(value)) return value - throw new Error(`${label} 需为 JSON 数组`) -} - -export const normalizeDict = (value: unknown, label: string) => { - if (value === null || value === undefined || value === '') return {} - if (typeof value === 'string') { - const parsed = JSON.parse(value) - if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new Error(`${label} 需为 JSON 对象`) - } - return parsed - } - if (typeof value === 'object') return value as Record - throw new Error(`${label} 需为 JSON 对象`) -} - -type ExtraHooks = { - onAdd?: () => void - onEdit?: (row: any) => void - onView?: (row: any) => void -} - -const STATUS_OPEN = 1 -const STATUS_CONFIRMED = 2 -const STATUS_PUBLISHED = 3 - -const getRowStatus = (row: any) => Number(row?.status) -const isOpenStatus = (row: any) => getRowStatus(row) === STATUS_OPEN -const isConfirmedStatus = (row: any) => getRowStatus(row) === STATUS_CONFIRMED -const canPublishStatus = (row: any) => isConfirmedStatus(row) -const getErrorMessage = (err: any, fallback: string) => err?.msg || err?.message || err?.response?.data?.msg || fallback - -export const createCrudOptions = function ({ context, crudExpose, onAdd, onEdit, onView }: Partial & ExtraHooks): CreateCrudOptionsRet { - void context - return { - crudOptions: { - form: { - labelWidth: '120px', - col: { span: 12 }, - wrapper: { is: 'el-dialog', width: '960px', top: '6vh', title: '询价单' }, - group: { - type: 'tab', - base: { label: '基础信息', columns: ['code', 'title', 'product_category', 'template', 'part_no', 'part_name', 'quote_deadline', 'purchase_qty', 'buyer', 'currency', 'plant', 'target_price', 'lead_time_days', 'payment_term', 'status', 'remark'] }, - cost: { label: '成本结构', columns: ['cost_items'] }, - vendors: { label: '供应商名单', columns: ['vendors'] }, - attachments: { label: '附件', columns: ['attachments'] } - } - }, - request: { - pageRequest: async (query) => api.GetList(query), - // 新版:新增/编辑由 index.vue 自定义弹窗负责(嵌套子表一次提交) - addRequest: async ({ form }) => api.AddObj(form), - editRequest: async ({ form, row }) => api.UpdateObj({ ...form, id: row.id }), - delRequest: async ({ row }) => { - try { - return await api.DelObj(row.id) - } catch (err: any) { - ElMessage.error(getErrorMessage(err, '删除失败')) - throw err - } - } - }, - table: { - rowKey: 'id' - }, - actionbar: { - buttons: { - add: { - show: true, - text: '新建询价单', - click() { - onAdd && onAdd() - } - } - } - }, - rowHandle: { - fixed: 'right', - width: 420, - buttons: { - view: { show: false }, - edit: { show: false }, - remove: { - text: '删除', - type: 'danger', - order: 1, - show: compute(({ row }) => isOpenStatus(row)) - }, - customView: { - text: '查看', - type: 'info', - order: 0, - show: true, - click({ row }) { - onView && onView(row) - } - }, - customEdit: { - text: '编辑', - type: 'primary', - order: 1.5, - show: compute(({ row }) => isOpenStatus(row)), - click({ row }) { - onEdit && onEdit(row) - } - }, - confirm: { - text: '确认', - type: 'success', - order: 2, - show: compute(({ row }) => isOpenStatus(row)), - async click({ row }) { - try { - await ElMessageBox.confirm('【确认】状态仅支持查看,不允许编辑或删除;如需修改请点击【还原】', '提示', { - type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' - }) - const res = await api.ConfirmObj(row.id) - crudExpose?.doRefresh?.() - return res - } catch (err: any) { - if (err === 'cancel' || err === 'close') return - ElMessage.error(getErrorMessage(err, '确认失败')) - throw err - } - } - }, - restore: { - text: '还原', - type: 'primary', - order: 2.5, - show: compute(({ row }) => isConfirmedStatus(row)), - async click({ row }) { - try { - await ElMessageBox.confirm('确认将状态还原为【开立】?', '提示', { - type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' - }) - const res = await api.RestoreObj(row.id) - crudExpose?.doRefresh?.() - return res - } catch (err: any) { - if (err === 'cancel' || err === 'close') return - ElMessage.error(getErrorMessage(err, '还原失败')) - throw err - } - } - }, - publish: { - text: '发布', - type: 'warning', - order: 3, - show: compute(({ row }) => canPublishStatus(row)), - async click({ row }) { - try { - await ElMessageBox.confirm('确认将状态改为【发布】?', '提示', { - type: 'warning', - confirmButtonText: '确定', - cancelButtonText: '取消' - }) - const res = await api.PublishObj(row.id) - crudExpose?.doRefresh?.() - return res - } catch (err: any) { - if (err === 'cancel' || err === 'close') return - ElMessage.error(getErrorMessage(err, '发布失败')) - throw err - } - } - } - } - }, - columns: { - inquiry_no: { - title: '询价单号', - type: 'input', - search: { - show: true, - component: { props: { placeholder: '请输入询价单号', clearable: true } } - }, - form: { - show: false - }, - column: { minWidth: 140 } - }, - title: { - title: '询价单名称', - type: 'input', - search: { - show: true, - component: { props: { placeholder: '请输入询价单名称', clearable: true } } - }, - form: { rules: [{ required: true, message: '请输入询价单名称' }] }, - column: { minWidth: 160, showOverflowTooltip: true } - }, - template: { - title: '询价模版', - type: 'input', - column: { minWidth: 140, showOverflowTooltip: true } - }, - quote_deadline: { - title: '报价截止时', - type: 'datetime', - column: { - width: 160, - formatter: ({ value }: { value: unknown }) => formatQuoteDeadlineDisplay(value) - } - }, - buyer: { - title: '采购负责人', - type: 'input', - search: { - show: true, - component: { props: { placeholder: '请输入采购负责人', clearable: true } } - }, - form: { rules: [{ required: true, message: '请输入采购负责人' }] }, - column: { minWidth: 120 } - }, - currency: { - title: '币别', - type: 'dict-select', - dict: dict({ data: [ - { value: 'CNY', label: 'CNY' }, - { value: 'USD', label: 'USD' } - ] }), - column: { width: 100 }, - form: { value: 'CNY' } - }, - lead_time_days: { - title: '交货周期(天)', - type: 'number', - column: { width: 120 }, - form: { component: { props: { precision: 0 } } } - }, - payment_method: { - title: '付款方式', - type: 'dict-select', - dict: dict({ data: paymentMethods }), - column: { width: 140, showOverflowTooltip: true }, - form: { show: false } - }, - status: { - title: '状态', - type: 'dict-select', - dict: dict({ data: statusDict }), - column: { width: 140 } - }, - remark: { - title: '备注', - type: 'textarea', - column: { minWidth: 180, showOverflowTooltip: true }, - form: { component: { props: { rows: 3 } } } - }, - update_datetime: { - title: '更新时间', - type: 'datetime', - form: { show: false }, - column: { width: 180 } - } - } - } - } -} diff --git a/web/src/views/procurement/rfqmiscellaneous/index.vue b/web/src/views/procurement/rfqmiscellaneous/index.vue deleted file mode 100644 index 9d3cb49..0000000 --- a/web/src/views/procurement/rfqmiscellaneous/index.vue +++ /dev/null @@ -1,2034 +0,0 @@ - - - - -