import React from 'react'; import { Form, AutoComplete, Select, Button, Input, Spin, Tooltip } from 'kts-components-antd-x3'; import { InputProps } from 'kts-components-antd-x3/lib/input'; import { WrappedFormUtils } from 'kts-components-antd-x3/lib/form/Form'; import { IGood, LineAttributeType } from '../../../../../InvoiceController'; import Invoice from '../../../../..'; import RowMenu from './ui/RowMenu'; import TitleText from './ui/TitleText'; import ItemNameInput from './ui/ItemNameInput'; import ItemCodeInput from './ui/ItemCodeInput'; import { onChangeQuantity, onChangePriceIncludeTax, onChangePriceExcludeTax, onChangeLineAmountIncludeTax, onChangeLineAmountExcludeTax, onChangeTaxRate, onChangeTaxAmount, onChangeItemName, onChangeItemCode, clearCalculatingField, } from './autoFillFn'; import { getItemNameWithShorthand } from '../../../../../tools/itemName'; import Drag from './ui/Drag'; import { nonScientificNotation, countTaxAmount } from '../../../../../tools/calculate'; import Expand from './ui/Expand'; export default (form: WrappedFormUtils) => { const { getFieldDecorator, getFieldValue } = form; const controller = Invoice.useInvoiceController(); const rootElement = controller.useMemo(s => s.rootElement, []); /** 是否含税 */ const isTaxIncluded = controller.useMemo(e => e.goodsListState.isTaxIncluded, []); /** 组件模式 */ const model = controller.useMemo(e => e.model, []); /** 计算类型 */ const calculateType = controller.useMemo(e => e.goodsListState.calculateType, []); /** 是否启用拖拽 */ const isStart = controller.useMemo((e) => e.goodsListState.drag.isStart, []); /** 是否显示我方 */ const isMyShow = controller.useMemo(e => e.goodsListState.isMyShow, []); /** 正在编辑的货物 */ const editGood = controller.useMemo((e) => e.goodsListState.editGood, []); /** 正在编辑的货物 */ const goodsList = controller.useMemo((e) => e.goodsListState.goodsList, []); /** 商品表格隐藏列 */ const columnshide = controller.useMemo((e) => e.goodsListState.columnshide, []); /** 搜索条件 */ const searchValue = controller.useMemo((e) => e.goodsListState.searchValue, []); /** 税率列表 */ const taxRateList = controller.useMemo((e) => e.goodsListState.taxRateList, []); /** 单位列表 */ const unitList = controller.useMemo((e) => e.goodsListState.unitList, []); /** 商品表格补充配置 */ const columnsReplenish = controller.useMemo((e) => e.goodsListState.columnsReplenish, []); /** 扣除额 */ const deduction = controller.useMemo((e) => e.goodsListState.deduction, []); /** 计算中启动字段 */ const changeField = controller.useMemo((e) => e.calculatingField, []); /** 计算中启动字段 */ const setChangeField = React.useCallback((value: string) => controller.run(async s => { s.calculatingField = value }), []); const onNumberValueChange = React.useCallback((e) => { const value = e?.target?.value; if (value) { return value.replace(/[^0-9-\.]/g, ''); } }, []) /** 获取补充校验规则 */ const getReplenishRules = React.useCallback((id: string) => { return columnsReplenish[id] ? columnsReplenish[id].rules || [] : [] }, [columnsReplenish]) /** 金额整数位限制,不传默认9位 */ const priceIntegerDigit = controller.useMemo((e) => e.priceIntegerDigit || 9, []); /** 表头 */ const columns = React.useMemo(() => { return [ { title: ' ', key: 'drag', width: 40, align: 'center', render: (_: any, record: IGood) => }, { title: ' ', key: 'expand', width: 50, align: 'center', render: (_: any, record: IGood) => }, { title: '序号', key: 'serialNo', dataIndex: 'serialNo', width: 50, render: (e: number) => {e}, }, { title: '商品编码', key: 'itemCode', width: 119, render: (_: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('itemCode', { initialValue: editGood.itemCode, rules: [ ...getReplenishRules('itemCode'), { pattern: /^.{1,19}$/, message: '商品编码长度不能超过19位' }, ] })( { await onChangeItemCode(controller, form, record); // await onChangeLineAmountIncludeTax(controller, form, record); }} /> )} ) } else { return {record.itemCode} } } }, { title: 项目名称, key: 'itemName', render: (_: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return (
{getFieldDecorator('itemName', { initialValue: record.itemName, rules: [ ...getReplenishRules('itemName'), { validator: async (_, __, callback) => { await controller.wait(); const value = controller.state.goodsListState.editGood; if (!value?.itemName && !value?.itemNameSelf) { callback('项目名称不能为空'); } else { return; } } } ] })( { onChangeItemName(controller, form, record); }} /> )}
{controller.getGoodsList && model !== 'readOnly' && (
); } else { return ( ) } }, }, { title: 规格型号, key: 'itemModelName', width: 119, render: (_: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('itemModelName', { initialValue: isMyShow ? editGood.itemModelNameSelf : editGood.itemModelName, rules: getReplenishRules('itemModelName'), })( { await controller.wait() const key = isMyShow ? 'itemModelNameSelf' : 'itemModelName'; const value = {} as any; value[key] = form.getFieldsValue().itemModelName; controller.setEditGood(value); }} onBlur={async () => { await controller.wait(); const key = isMyShow ? 'itemModelNameSelf' : 'itemModelName'; const rawValue = form.getFieldsValue().itemModelName; const trimmedValue = rawValue?.replace(/^\s+|\s+$/g, '') ?? ''; // 失焦时才回写干净的值 form.setFieldsValue({ itemModelName: trimmedValue }); controller.setEditGood({ [key]: trimmedValue }); }} />, )} ); } else { return ( ) } }, }, { title: 单位, key: 'unit', width: 70, render: (_: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('unit', { initialValue: editGood.unit, rules: getReplenishRules('unit'), })( rootElement || document.body} onChange={async () => { await controller.wait() controller.setEditGood({ unit: form.getFieldsValue().unit }); }} />, )} ); } else { return {record.unit}; } }, }, { title: 数量, dataIndex: 'quantity', key: 'quantity', align: 'right', width: 149, render: (value: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('quantity', { initialValue: nonScientificNotation(editGood.quantity), getValueFromEvent: onNumberValueChange, rules: [ ...getReplenishRules('quantity'), { pattern: /^[+-]?(0|([1-9]\d*))(\.\d+)?$/, message: '数量必须为数字' }, { validator: async (_, value, callback) => { await controller.wait(); const isvalue = !!value || value === 0; const isPrice = !!getFieldValue(isTaxIncluded ? 'priceIncludeTax' : 'priceExcludeTax') || getFieldValue(isTaxIncluded ? 'priceIncludeTax' : 'priceExcludeTax') === 0; if (isvalue || isPrice === isvalue) return; callback('请输入数量'); } } ], })( { setChangeField('quantity'); await onChangeQuantity(controller, form, record); }} />, )} ); } else { return {nonScientificNotation(value)}; } }, }, { title: 单价(含税), dataIndex: 'priceIncludeTax', key: 'priceIncludeTax', align: 'right', width: 149, render: (value: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('priceIncludeTax', { initialValue: nonScientificNotation(editGood.priceIncludeTax, 8), getValueFromEvent: onNumberValueChange, rules: [ ...getReplenishRules('priceIncludeTax'), // { pattern: /^[+-]?(0|([1-9]\d*))(\.\d+)?$/, message: '单价必须为数字' }, // { pattern: /^[+-]?(0|([1-9]\d*))(\.\d+)?$/, message: '金额必须为数字' }, { pattern: /^[+-]?(0|([1-9]\d*))(\.\d{1,8})?$/, message: '单价必须为数字且最多保留8位小数' }, { validator: async (_, value, callback) => { await controller.wait(); // const isQuantity = !!getFieldValue('quantity') || getFieldValue('quantity') === 0; // const isvalue = !!value || value === 0; // if (isvalue || isQuantity === isvalue) return; // callback('请输入单价'); const quantity = getFieldValue('quantity'); const isQuantityValid = quantity !== undefined && quantity !== null && quantity !== ''; const isValueValid = value !== undefined && value !== null && value !== ''; // if (isValueValid && isQuantityValid) return; // 允许两者都为空 if (!isValueValid && !isQuantityValid) return; if (!isValueValid) return callback('金额不能为空'); if (!isQuantityValid) return callback('数量不能为空'); } } ], })( { setChangeField('priceIncludeTax'); onChangePriceIncludeTax(controller, form, record); }} />, )} ); } else { return {nonScientificNotation(value, 8)}; } }, }, { title: 单价(不含税), dataIndex: 'priceExcludeTax', key: 'priceExcludeTax', align: 'right', width: 149, render: (value: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('priceExcludeTax', { initialValue: nonScientificNotation(editGood.priceExcludeTax, 8), getValueFromEvent: onNumberValueChange, rules: [ ...getReplenishRules('priceExcludeTax'), // { pattern: /^[+-]?(0|([1-9]\d*))(\.\d+)?$/, message: '单价必须为数字' }, { pattern: /^[+-]?(0|([1-9]\d*))(\.\d{1,8})?$/, message: '单价必须为数字且最多保留8位小数' }, { validator: async (_, value, callback) => { await controller.wait(); // const isQuantity = !!getFieldValue('quantity') || getFieldValue('quantity') === 0; // const isvalue = !!value || value === 0; // if (isvalue || isQuantity === isvalue) return; // callback('请输入单价'); const quantity = getFieldValue('quantity'); const isQuantityValid = quantity !== undefined && quantity !== null && quantity !== ''; const isValueValid = value !== undefined && value !== null && value !== ''; // if (isValueValid && isQuantityValid) return; // 允许两者都为空 if (!isValueValid && !isQuantityValid) return; if (!isValueValid) return callback('金额不能为空'); if (!isQuantityValid) return callback('数量不能为空'); } } ], })( { setChangeField('priceExcludeTax'); onChangePriceExcludeTax(controller, form, record); }} />, )} ); } else { return {nonScientificNotation(value, 8)}; } }, }, { title: 金额(含税), dataIndex: 'lineAmountIncludeTax', key: 'lineAmountIncludeTax', width: 119, align: 'right', render: (value: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('lineAmountIncludeTax', { initialValue: editGood.lineAmountIncludeTax, getValueFromEvent: onNumberValueChange, rules: [ ...getReplenishRules('lineAmountIncludeTax'), { required: true, message: '金额不能为空' }, { pattern: /^[+-]?(0|([1-9]\d*))(\.\d+)?$/, message: '金额必须为数字' }, { validator: async (_, value, callback) => { if (`${value}`.split('.')[0].length > priceIntegerDigit) { callback(`金额整数部分不能大于${priceIntegerDigit}位,小数点后最多2位`); } } }, { validator: async (_, value, callback) => { if (deduction && parseFloat(value) <= deduction) { callback('扣除额不能大于等于价税合计'); } } }, ], })( { setChangeField('lineAmountIncludeTax'); onChangeLineAmountIncludeTax(controller, form, record); }} />, )} ); } else { return {formatSearch(parseFloat(value).toFixed(2), searchValue)}; } }, }, { title: 金额(不含税), dataIndex: 'lineAmountExcludeTax', key: 'lineAmountExcludeTax', align: 'right', width: 119, render: (value: string, record: IGood) => { if (editGood?.$index === record.$index && model !== 'prefab') { return ( {getFieldDecorator('lineAmountExcludeTax', { initialValue: editGood.lineAmountExcludeTax, getValueFromEvent: onNumberValueChange, rules: [ ...getReplenishRules('lineAmountExcludeTax'), { required: true, message: '金额不能为空' }, { pattern: /^[+-]?(0|([1-9]\d*))(\.\d+)?$/, message: '金额必须为数字' }, { validator: async (_, value: string, callback) => { if (`${value}`.split('.')[0].length > priceIntegerDigit) { callback(`金额整数部分不能大于${priceIntegerDigit}位,小数点后最多2位`); } } }, ], })( { setChangeField('lineAmountExcludeTax'); onChangeLineAmountExcludeTax(controller, form, record); }} />, )} ); } else { return {value === '' ? '' : formatSearch(parseFloat(value).toFixed(2), searchValue)}; } }, }, { title: 税率, dataIndex: 'taxRate', key: 'taxRate', align: 'right', width: 75, render: (value: string, record: IGood) => { if (editGood?.$index === record.$index && !(model === 'prefab' && calculateType === '3')) { return ( {getFieldDecorator('taxRate', { initialValue: editGood.taxRate, rules: [ ...getReplenishRules('taxRate'), { required: true, message: '请选择税率' }, { pattern: /^[+-]?(0|([1-9]\d*))(\.\d+)?$/, message: '请选择正确税率' }, ], })( , )} ); } else { return {isDutyFree(record) ? '免税' : value === '' ? '' : `${value}%`}; } }, }, { title: 税额, dataIndex: 'taxAmount', key: 'taxAmount', align: 'right', width: 119, render: (value: string, record: IGood) => { if (editGood?.$index === record.$index) { return ( {getFieldDecorator('taxAmount', { initialValue: editGood.taxAmount, getValueFromEvent: onNumberValueChange, rules: [ ...getReplenishRules('taxAmount'), { pattern: /^[+-]?(0|([1-9]\d*))(\.\d{1,2})?$/, message: '税额必须为数字且最多保留2位小数', }, { validator: async (_, taxAmountValue, callback) => { await controller.wait(); const amount = getFieldValue(isTaxIncluded ? 'lineAmountIncludeTax' : 'lineAmountExcludeTax'); const taxRate = getFieldValue('taxRate'); if (!amount || amount === '' || !taxRate || taxRate === '' || !taxAmountValue || taxAmountValue === '') { return; } const amountNum = parseFloat(amount); const taxRateNum = parseFloat(taxRate); const taxAmountNum = parseFloat(taxAmountValue); // 根据是否含税使用不同的计算方式 let calculatedTaxAmount: number | undefined; if (isTaxIncluded) { // 含税:税额 = (含税金额 - 扣除额) / (1 + 税率) * 税率 calculatedTaxAmount = countTaxAmount(amountNum, deduction || 0, taxRateNum); } else { // 不含税:税额 = 不含税金额 * 税率 / 100 calculatedTaxAmount = amountNum * taxRateNum / 100; } if (calculatedTaxAmount === undefined || isNaN(calculatedTaxAmount)) { return; } // 先四舍五入保留两位小数再比较差额 const roundedCalc = parseFloat(calculatedTaxAmount.toFixed(2)); const roundedInput = parseFloat(taxAmountNum.toFixed(2)); const diff = parseFloat(Math.abs(roundedCalc - roundedInput).toFixed(2)); if (diff > 0.06) { callback(`税额与计算值(${roundedCalc.toFixed(2)})的差额(${diff.toFixed(2)})超过0.06,请调整`); } } } ], })( { setChangeField('taxAmount'); onChangeTaxAmount(controller, form, record); }} />, )} ); } else { return {isDutyFree(record) ? '***' : value === '' ? '' : parseFloat(value).toFixed(2)}; } }, }, { title: '行性质', dataIndex: 'lineAttribute', key: 'lineAttribute', width: 70, render: (e: any) => { switch (e) { case LineAttributeType.折扣行: return 折扣行 case LineAttributeType.被折扣行: return 被折扣行 case LineAttributeType.赠品行: return 赠品行 case LineAttributeType.折让行: return 折让行 case LineAttributeType.正常: return 正常行 default: return {e} } } }, { title: '操作', key: 'operating', align: 'right', width: 130, fixed: 'right', render: (_value: string, record: IGood) => , }, ] // 筛选隐藏 .filter(e => { return e.key ? columnshide.indexOf(e.key) < 0 : false; }) // 含税不含税 .filter((e) => { if (isTaxIncluded) { return !(e.key === 'priceExcludeTax' || e.key === 'lineAmountExcludeTax'); } else { return !(e.key === 'priceIncludeTax' || e.key === 'lineAmountIncludeTax'); } }) // 是否启动拖拽 .filter(e => e.key !== 'drag' || isStart) // 是否启动展开 .filter(e => e.key === 'expand' ? goodsList.some(e => !!e.children) : true) // 只读 .filter(e => { if (model === 'readOnly') { return e.key !== 'operating'; } else { return true; } }) .map((e) => { return { ...e, ellipsis: true, }; }) as any[]; }, [isTaxIncluded, editGood, goodsList, controller, changeField, deduction, isMyShow, searchValue, model, columnsReplenish, columnshide, isStart]); return controller.setColumnsConfig ? controller.setColumnsConfig(columns) : columns; }; /** 字段 */ function isCipher(name?: string, field?: string) { if (!name || !field) return false; return name !== field; } class MyInput extends React.Component { render() { if (this.props.loading) { return ( ) } else { return } } } class MyDiv extends React.Component<{ value?: any, loading?: boolean }> { render() { if (this.props.loading) { return ( {this.props.value} ) } else { return {this.props.value} } } } class MyItemNameDiv extends React.Component<{ valueT?: React.ReactNode, valueF?: React.ReactNode, isMyShow: boolean }> { render(): React.ReactNode { const { isMyShow, valueT, valueF } = this.props; if (isMyShow) { if (valueT) { return ( {valueT} ) } else { return ( {valueF} ) } } else { if (valueF) { return ( {valueF} ) } else { return ( {valueT} ) } } } } /** 格式搜索结果 */ function formatSearch(value?: string, search?: string) { if (!value || !search) return value; const __html = ucoding(value).split(new RegExp(ucoding(search), 'g')).map(e => dcoding(e)).join(`${search}`); return } /** 编码 */ function ucoding(v: string): string { return v.split('').map(e => `U${e.charCodeAt(0)}E`).join(''); } /** 解码 */ function dcoding(v: string): string { return v.split('U').map(e => e ? String.fromCharCode(parseInt(e.replace('E', ''))) : '').join(''); } /** 是否免税 */ function isDutyFree(record: IGood): boolean { return (record.taxFreeType as any) === 1 && record.favouredPolicyName === '免税'; }