// 我想要评论!!!
// 我想要赞!!!
// 看过的帅哥美女们,点一下,谢谢!!!!
import React, { Component } from 'react';
import { Button, message, Spin } from 'antd';
import { connect } from 'react-redux';
import { CrModal, CrTable } from 'creams-ui';
import cx from 'classnames';
import shortid from 'shortid';
import { getBuildingsId, deleteBuildingsPortfoliosId } from 'redux/modules/swaggerGen/Buildings';
import {
postReceipt,
postReceiptPrint,
putReceiptStatus,
getReceiptTemplateIdCurrentNum,
getReceiptSendRecycleNumberForReceipt,
getReceiptGetNumbersIncludeManualForReceipt,
} from 'redux/modules/swaggerGen/Receipt';
import { getOssUploadStsToken, getOssDownloadStsToken } from 'redux/modules/swaggerGen/OSS';
import { changeLoadingStatus } from 'redux/modules/ui/spinLoading';
import { refreshBox } from 'redux/modules/ui/global';
import { getOaSelection } from 'redux/modules/swaggerGen/OA';
import split from '../util';
import { OaStartModal } from '../../../creams-web-AuditSetting';
import InvoiceSplitModal from '../../BodyAccount/AccountModal/InvoiceSplitModal/Modal';
import EditModal from '../EditModal';
import generateColumns from './config';
import styles from './index.less';
const editActiveId = Symbol('editActiveId');
const form = Symbol('form');
let receiptNumRanger = [];
const promiseAdd = (request, action) => new Promise((res, rej) => {
action(
{
...request,
success: data => res(data),
error: e => rej(e),
},
);
});
const formatBill = arr => arr.reduce((target, { id, value }) => {
target.push({
billId: id,
amount: value,
});
return target;
}, []);
const unique = (array) => {
const hash = {};
return array.filter((element) => {
const newKey = typeof element + JSON.stringify(element);
if (!hash.hasOwnProperty(newKey)) {
hash[newKey] = true;
return true;
}
return false;
});
};
/**
* 拆分 合并 要操作 relateBills receiptNum
*/
@connect(
state => ({
customer: state.user.customerInfo,
}),
{
postReceipt,
refreshBox,
postReceiptPrint,
getOssUploadStsToken,
getOssDownloadStsToken,
changeLoadingStatus,
putReceiptStatus,
getReceiptTemplateIdCurrentNum,
getReceiptSendRecycleNumberForReceipt,
getReceiptGetNumbersIncludeManualForReceipt,
getBuildingsId,
getOaSelection,
},
)
class CreateModal extends Component {
static defaultProps = {
data: [{}],
};
constructor(props) {
super();
// const dataItem = { ...props.data[0] };
this.state = {
splitModalVisible: false,
editModalVisible: false,
previewModalVisible: false,
listData: new Map(),
disabledAll: false,
receiptNumRanger: [],
startOa: { show: false },
loading: false,
saveLoading: false,
clickEditRow: {}, // table被点击的行
selectedRowKeys: [], // 被select选中的行集合 keys
selectedRows: [], // 被select选中的行集合
splitedRows: {}, // 被拆分的行数据
};
this[editActiveId] = 0;
this[form] = {
amount: 0,
buildingId: props.buildingId,
buildingName: props.buildingName,
customerId: props.customer && props.customer.id || '',
memo: '',
monetaryUnit: '',
paymentUnit: '',
billTypeAlias: '',
relateBills: [
{
amount: 0,
billId: 0,
billType: props.billType,
},
],
receiver: {
address: '',
companyName: '',
contacts: '',
tel: '',
},
payer: {
address: '',
companyName: '',
contacts: '',
tel: '',
},
receiptDetails: [{
amount: 0,
count: 0,
paymentName: '',
unitPrice: 0,
roomNumber: '',
}],
remittanceMethod: '',
};
}
componentDidMount() {
this.initDataSource();
}
onSelectChange = (selectedRowKeys, selectedRows) => {
this.setState({ selectedRowKeys, selectedRows });
};
onAllmount = data => data.reduce((total, { payedAmount, receiptAmount }) => {
let _total = total;
_total += payedAmount - receiptAmount;
return _total;
}, 0);
// 新建收据 && 修改收据状态 如果需要审核则发送审核
asyncPostCreateReceipt = async (body) => {
const requests = body.map((item) => {
if (item.receiver) {
const { id, buildingId, ...rest } = item.receiver;
item.receiver = { ...rest };
}
item.serialNumberRule = item.prefix;
delete item.prefix;
delete item.receiptDetails;
delete item.billType;
delete item.disabled;
delete item.generateId;
delete item.billId;
delete item.orderNo;
return item;
});
const buildingIds = body[0].buildingId;
this.setState({ saveLoading: true });
try {
const resPostReceiptData = await promiseAdd({ body: { requests } }, this.props.postReceipt);
const ids = resPostReceiptData.receiptPrintRequests.map(item => item.receiptId);
const query = { ids, status: 'NEW_CREATE' };
await promiseAdd({ query }, this.props.putReceiptStatus);
message.success('收据新建成功');
this.props.handleVisible(false);
const objectIds = resPostReceiptData.receiptPrintRequests.map(item => item.receiptId);
const dataOaSelection = await promiseAdd({ query: { buildingIds } }, this.props.getOaSelection);
const enableApproval = dataOaSelection.items[0].auditElements.includes('RECEIPT__NEW_APPROVAL');
const startOa = {
show: true,
description: '',
objectIds,
buildingIds: [{ buildingId: buildingIds }],
subDomainType: 'NEW_APPROVAL',
domainType: 'RECEIPT',
title: '开具收据审核流程',
onClose: () => { this.setState({ startOa: { show: false } }); },
success: () => { this.props.handleReceiptModalVisible(false); },
enableApproval,
};
this.setState({ startOa, saveLoading: false });
} catch (e) {
this.setState({ saveLoading: false });
this.props.handleReceiptModalVisible(false);
message.error(e);
}
};
// 收据编辑
handleEditItem = obj => () => {
this[editActiveId] = obj.splitId;
this.toggleModals('edit', true)();
};
handleSubmit = () => {
const { selectedRows } = this.state;
if (!selectedRows.length) {
return message.error('请选择要保存的收据');
}
this.asyncPostCreateReceipt(selectedRows);
};
// 更改 modal状态
toggleModals = (type, bool) => () => {
this.setState({
[`${type}ModalVisible`]: bool,
});
};
// 初始化数据
// 收据开具的编号 需求有很大逻辑问题
initDataSource = async () => {
this.setState({ loading: true });
try {
let numbers,
prefix;
const { data } = this.props;
const buildingId = this[form].buildingId;
const requestReceipt = {
buildingId,
reqSize: data.length,
};
const resBuilding = await promiseAdd({ id: buildingId }, this.props.getBuildingsId);
// 如果设置 收据规则 则获取收据编号 填充数据
if (resBuilding.serialNumberRule) {
const resRecieptNumber = await promiseAdd({ query: requestReceipt }, this.props.getReceiptGetNumbersIncludeManualForReceipt);
numbers = resRecieptNumber.numbers;
prefix = resRecieptNumber.prefix;
}
const listData = data.reduce((map, currVal, currIndex) => {
const generateId = shortid.generate();
const billId = typeof this.props.billId === 'number' ? this.props.billId : this.props.billId[currIndex];
const { payedAmount, receiptAmount, other, monetaryUnit, billType, orderNo, roomNumber, payee, collectingCompany} = currVal;
const amount = payedAmount - receiptAmount;
const { payer, receiver, ...restProps } = this[form];
// 给每个数据增加一个 generateId prefix 收据编号前缀 number 收据编号 disabled 是否禁用
const receiptNum = resBuilding.serialNumberRule ? numbers[currIndex].split(prefix)[1] : '';
const result = {
...restProps,
amount,
monetaryUnit,
paymentUnit: other,
billType,
// billId,
orderNo,
receiptDetails: [{ amount, paymentName: currVal.billType, roomNumber }],
payer: { ...payer, companyName: other },
relateBills: [{ amount, billId, billType }],
receiver: resBuilding.receiptReceiverSettingBuildingModel,
generateId,
prefix,
serialNumberRule: resBuilding.serialNumberRule,
receiptNum,
disabled: false, // 是否选中
};
map.set(generateId, result);
return map;
}, new Map());
this.setState({
listData,
disabledAll: false,
loading: false,
});
} catch (e) {
message.error(e);
}
}
// CrTableHandle EditModal's data
handleCrTableClick = (clickEditRow) => {
this.setState({
clickEditRow,
}, () => {
this.toggleModals('edit', true)();
});
}
// splitButton CrTable's data
handleSplitButtonClick = () => {
// 点击拆分 设置拆分的数据
const { selectedRowKeys, listData } = this.state;
if (selectedRowKeys.length !== 1) {
return false;
}
this.setState({
splitedRows: listData.get(selectedRowKeys[0]),
});
this.toggleModals('split', true)();
}
// mergeButton CrTable's data
handleMergeButtonClick = () => {
const { selecedLength, listData, selectedRowKeys, selectedRows } = this.state;
const paymentUnits = selectedRows.map(item => item.paymentUnit);
const payers = selectedRows.map(item => item.payer.companyName);
const receivers = selectedRows.map(item => item.payer.companyName);
const data = new Map(listData);
const generateId = selectedRowKeys[0];
const hasReceiptNumbers = selectedRows.filter(item => item.serialNumberRule);
const mergeData = selectedRows[0];
// 关联账单
const bills = this.state.selectedRows.reduce((mapData, { relateBills }) => {
relateBills.map(({ billId, amount }) => {
if (mapData.has(billId)) {
const currObj = mapData.get(billId);
currObj.amount += amount;
mapData.set(billId, currObj);
} else {
mapData.set(billId, {
amount,
billId,
});
}
return false;
});
return mapData;
}, new Map());
const relateBills = [...bills.values()];
const reducer = (accumulator, currentValue) => accumulator + currentValue.amount;
const amount = selectedRows.reduce(reducer, 0);
let receiptNum = '';
if (selecedLength <= 1) {
return false;
}
// 租客名称 收款方 付款方 一致才可以合并
if ([...new Set(paymentUnits)].length > 1) { return message.error('不同租客无法进行收据合并'); }
if ([...new Set(payers)].length > 1) { return message.error('不同付款方无法进行收据合并'); }
if ([...new Set(receivers)].length > 1) { return message.error('不同收款方无法进行收据合并'); }
// 合并 要取得最小编号
if (hasReceiptNumbers.length) {
const numbers = hasReceiptNumbers.map(num => num.receiptNum);
receiptNum = Math.min(...numbers);
}
selectedRowKeys.forEach((row, index) => { if (index !== 0) data.delete(row); });
data.set(generateId, { ...mergeData, amount, receiptNum, generateId, relateBills });
this.setState({
listData: data,
selectedRowKeys: [],
selectedRows: [],
});
}
// EditModalHandle's submit
handleEditModalSubmit = (value) => {
const { clickEditRow: { generateId }, listData } = this.state;
const data = new Map(listData);
data.set(generateId, { ...data.get(generateId), ...value });
this.setState({
listData: data,
});
}
// InvoiceSplitModal's submit
handleSplitSubmit = async ({ matchPrice, number, resPrice }) => {
// 如果有编号 则把编号放入数据 没有 编号则为空
// 要拆分的编号是 receiptNum = 191 拆分number= 3 则请求 reqSize= 2 拆分后的编号应为 191 192 193 保留原来的191
const { splitedRows, listData } = this.state;
const { buildingId, serialNumberRule, prefix } = splitedRows;
const oldGenerateId = splitedRows.generateId;
const requestReceipt = { buildingId, reqSize: resPrice > 0 ? number : number - 1 };
const data = new Map();
const valuesData = [...listData.values()];
const splitIndex = [...listData.keys()].findIndex(item => item === splitedRows.generateId);
let newRecieptNumber;
const relateBills = splitedRows.relateBills.slice();
const newRelateBills = relateBills.reduce((arr, { billId, amount }) => {
arr.push({
id: billId,
value: amount,
});
return arr;
}, []);
const { splitValue, restValue } = split(
newRelateBills,
number,
matchPrice,
);
const splitBills = formatBill(splitValue);
const restBills = formatBill(restValue);
try {
if (serialNumberRule) {
const resRecieptNumber = await promiseAdd({ query: requestReceipt }, this.props.getReceiptGetNumbersIncludeManualForReceipt);
newRecieptNumber = resRecieptNumber.numbers;
}
const splitData = new Array(resPrice > 0 ? number + 1 : number).fill(0).map((num, index) => {
let newRowData;
const generateId = shortid.generate();
const recieptNum = serialNumberRule && index > 0 ? newRecieptNumber[index - 1].split(prefix)[1] : '';
newRowData = { ...splitedRows, generateId, recieptNum, amount: matchPrice, relateBills: unique(splitBills) };
if (index === 0) {
newRowData = { ...newRowData, generateId: oldGenerateId, recieptNum: splitedRows.recieptNum };
}
if (resPrice > 0 && index === number) {
newRowData = { ...splitedRows, generateId, recieptNum, amount: resPrice, relateBills: unique(restBills) };
}
return newRowData;
});
valuesData.splice(splitIndex, 1, ...splitData);
valuesData.map(value => data.set(value.generateId, value));
this.setState({
listData: data,
selectedRowKeys: [],
selectedRows: [],
});
} catch (e) {
message.error(e);
}
}
renderFooter = () => [
<Button
type={'primary'}
onClick={this.handleSubmit}
disabled={!this.state.selectedRowKeys.length}
key={'saveBtn'}
loading={this.state.saveLoading}
>
保存
</Button>,
];
render() {
const { visible, handleReceiptModalVisible, buildingId, data } = this.props;
const {
selectedRowKeys,
splitModalVisible,
listData,
editModalVisible,
disabledAll,
startOa,
clickEditRow,
loading,
splitedRows,
} = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
getCheckboxProps: () => ({
disabled: disabledAll,
}),
};
const selecedLength = selectedRowKeys.length;
return (
<div>
<CrModal
title="添加收据"
width={1000}
show={visible}
handleCancel={() => handleReceiptModalVisible(false)}
wrapClassName={styles.modal}
footer={this.renderFooter()}
>
{splitModalVisible && (
<InvoiceSplitModal
visible={splitModalVisible}
handleCloseModal={this.toggleModals('split', false)}
splitedRows={splitedRows}
title="收据"
handleSplitSubmit={this.handleSplitSubmit}
/>
)}
{editModalVisible && (
<EditModal
close={this.toggleModals('edit', false)}
visible={editModalVisible}
buildingId={buildingId}
data={clickEditRow}
callback={this.handleEditModalSubmit}
receiptNumRanger={{
min: [].concat(receiptNumRanger).shift(),
middle: [].concat(receiptNumRanger).pop(),
max: [].concat(this.state.receiptNumRanger).pop(),
}}
/>
)}
<div className={styles.header}>
<div className={styles.text}>
<span>开据数:{listData.size}</span>
{' '}
<span>开据金额: {this.onAllmount(data)}</span>
</div>
<div className={styles.end}>
<span
className={cx(`${styles.btn}`, {
[`${styles.active}`]: selecedLength === 1,
})}
onClick={this.handleSplitButtonClick}
>
拆分
</span>
<span
className={cx(`${styles.btn}`, {
[`${styles.active}`]: selecedLength > 1,
})}
onClick={this.handleMergeButtonClick}
>
合并
</span>
</div>
</div>
<div className={styles.table}>
<Spin spinning={loading}>
<CrTable
rowKey={'generateId'}
columns={generateColumns({
onClick: this.handleCrTableClick,
})}
rowSelection={rowSelection}
dataSource={[...listData.values()]}
pagination={false}
/>
</Spin>
</div>
</CrModal>
<OaStartModal
{...startOa}
/>
</div>
);
}
}
export default CreateModal;
// utils.js
const split = (items, size = 1, value = 13) => {
const total = items.reduce((t, item) => t + item.value, 0);
if (total >= size * value) {
return getTargets(items, value, size);
}
}
function getTargets(items, value, totalSize) {
const arr = [];
items.forEach(({ id, value: itemValue }) => {
if (Math.floor(itemValue / value) > 0) {
arr.push({
id,
size: Math.floor(itemValue / value),
value: itemValue,
})
} else {
arr.push({
id,
size: 0,
value: itemValue,
});
}
})
const currentSize = arr.reduce((s, arrItem) => {
if (s + arrItem.size <= totalSize) {
arrItem.use = arrItem.size;
return s + arrItem.size;
}
arrItem.use = totalSize - s;
return totalSize;
}, 0)
let size = 0;
let newArr = arr.map(item => ({ id: item.id, value: item.value - item.use * value }));
let moreTargets = [];
if (currentSize < totalSize) {
while (size < totalSize - currentSize) {
const target = [];
newArr.forEach((item, index) => {
const currentValue = getTargetValue(target);
if (currentValue < value) {
target.push(item);
if (currentValue + item.value >= value) {
const originValue = target[target.length - 1].value;
target[target.length - 1].value = originValue - getTargetValue(target) + value;
size += 1;
newArr = [{ ...target[target.length - 1], value: originValue - target[target.length - 1].value, use: 0 }, ...newArr.slice(index + 1)];
}
if (index + 1 === newArr.length) size += 1;
}
});
moreTargets.push(target);
}
}
return {
splitValue: arr.reduce((targets, item) => {
const newTargets = [];
for (let i = 0; i < item.use; i++) {
newTargets.push({
id: item.id,
value,
});
}
return [...targets, ...newTargets];
}, moreTargets),
restValue: newArr,
};
}
function getTargetValue(target) {
return target.reduce((total, item) => total + item.value, 0);
}
export default split;
/**
* 总结 set map 使用业务场景
* set用于没有重复项的数组类型数据
* map用于value是数组 需要通过给定keys 来区分数组value 对数组value进行操作 的业务场景 很方便 很实用
* */