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 === '免税';
}