支付的页面和各种细节计算
This commit is contained in:
parent
88fb060c67
commit
8a0580a5a6
|
|
@ -10,6 +10,7 @@ import wpProductCheckers from './wpProduct';
|
|||
import abstractCheckers from './abstractChecker';
|
||||
import withdrawAccounts from './withdrawAccount';
|
||||
import refundCheckers from './refund';
|
||||
import withdrawTransferCheckers from './withdrawTransfer';
|
||||
|
||||
const checkers = [
|
||||
...refundCheckers,
|
||||
|
|
@ -21,6 +22,7 @@ const checkers = [
|
|||
...applicationCheckers,
|
||||
...offlineAccountCheckers,
|
||||
...wpProductCheckers,
|
||||
...withdrawTransferCheckers,
|
||||
] as Checker<EntityDict, keyof EntityDict, RuntimeCxt>[];
|
||||
|
||||
export default checkers;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,25 @@ const checkers: Checker<EntityDict, 'withdrawTransfer', RuntimeCxt>[] = [
|
|||
action: 'succeed',
|
||||
checker(operation, context) {
|
||||
const { data } = operation as EntityDict['pay']['Update'];
|
||||
const { externalId } = data;
|
||||
if (!externalId) {
|
||||
throw new OakAttrNotNullException('pay', ['externalId']);
|
||||
if (data) {
|
||||
const { externalId } = data;
|
||||
if (!externalId) {
|
||||
throw new OakAttrNotNullException('pay', ['externalId']);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
entity: 'withdrawTransfer',
|
||||
type: 'data',
|
||||
action: 'fail',
|
||||
checker(operation, context) {
|
||||
const { data } = operation as EntityDict['pay']['Update'];
|
||||
if (data) {
|
||||
const { reason } = data;
|
||||
if (!reason) {
|
||||
throw new OakAttrNotNullException('pay', ['reason']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export default OakComponent({
|
|||
},
|
||||
},
|
||||
filter: {
|
||||
iState: 'refunding',
|
||||
iState: 'transferring',
|
||||
withdraw: {
|
||||
account: {
|
||||
ofSystemId: systemId,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default function render(props: WebComponentProps<EntityDict, 'withdrawAcc
|
|||
{
|
||||
isBank &&
|
||||
<Form.Item
|
||||
label={t('label.bank.org')}
|
||||
label={t('withdraw::account.bank.org')}
|
||||
>
|
||||
<Input
|
||||
value={withdrawAccount.org!}
|
||||
|
|
@ -51,7 +51,7 @@ export default function render(props: WebComponentProps<EntityDict, 'withdrawAcc
|
|||
</Form.Item>
|
||||
}
|
||||
<Form.Item
|
||||
label={isBank ? t('label.bank.name') : t('label.others.name')}
|
||||
label={isBank ? t('withdraw::account.bank.name') : t('withdraw::account.others.name')}
|
||||
>
|
||||
<Input
|
||||
value={withdrawAccount.name!}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import assert from 'assert';
|
||||
import { EntityDict } from '../../../oak-app-domain';
|
||||
import { ToYuan, ThousandCont } from 'oak-domain/lib/utils/money';
|
||||
|
||||
export default OakComponent({
|
||||
entity: 'withdrawTransfer',
|
||||
isList: true,
|
||||
actions: ['succeed', 'fail'],
|
||||
projection: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
|
|
@ -32,6 +35,9 @@ export default OakComponent({
|
|||
}
|
||||
},
|
||||
withdrawAccount: {
|
||||
org: 1,
|
||||
name: 1,
|
||||
code: 1,
|
||||
channel: {
|
||||
entity: 1,
|
||||
entityId: 1,
|
||||
|
|
@ -47,25 +53,28 @@ export default OakComponent({
|
|||
},
|
||||
formData({ data }) {
|
||||
return {
|
||||
refunds: data?.map(
|
||||
transfers: data?.map(
|
||||
(ele) => {
|
||||
const { creator, price, loss, operator, withdrawAccount, ...rest } = ele;
|
||||
const { creator, operator, withdrawAccount, ...rest } = ele;
|
||||
const { entity, offlineAccount } = withdrawAccount!.channel!;
|
||||
const channel = entity === 'offlineAccount' ? this.t(`withdraw::channel.offlineAccount.${offlineAccount?.type}`) : this.t(`withdraw::channel.${entity}`);
|
||||
return {
|
||||
...rest,
|
||||
price: ThousandCont(ToYuan(price!), 2),
|
||||
loss: ThousandCont(ToYuan(loss!), 2),
|
||||
creatorName: creator?.name || creator?.nickname || '-',
|
||||
creatorMobile: creator?.mobile$user?.[0]?.mobile || '-',
|
||||
operatorName: operator?.name || operator?.nickname || '-',
|
||||
operatorMobile: operator?.mobile$user?.[0]?.mobile || '-',
|
||||
withdrawAccount,
|
||||
channel,
|
||||
};
|
||||
}
|
||||
),
|
||||
};
|
||||
},
|
||||
data: {
|
||||
updateId: '',
|
||||
sysAccountAmount: 0,
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
filter() {
|
||||
|
|
@ -81,4 +90,35 @@ export default OakComponent({
|
|||
}
|
||||
}
|
||||
],
|
||||
methods: {
|
||||
async setUpdateId(id: string, action: string) {
|
||||
this.clean();
|
||||
this.setState({
|
||||
updateId: id,
|
||||
});
|
||||
if (id) {
|
||||
this.updateItem({}, id, action);
|
||||
|
||||
const row = (this.state.transfers as EntityDict['withdrawTransfer']['Schema'][]).find(
|
||||
ele => ele.id === id
|
||||
);
|
||||
|
||||
// 每次都刷新这个系统账户当前余额吧
|
||||
const { entity, entityId } = row!.withdrawAccount!.channel;
|
||||
const { data: [sysAccount] } = await this.features.cache.refresh(entity as 'offlineAccount', {
|
||||
data: {
|
||||
id: 1,
|
||||
price: 1,
|
||||
},
|
||||
filter: {
|
||||
id: entityId,
|
||||
}
|
||||
});
|
||||
assert(sysAccount);
|
||||
this.setState({
|
||||
sysAccountAmount: sysAccount.price!,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
{
|
||||
"label": {
|
||||
"cn": "创建人",
|
||||
"on": "操作者",
|
||||
"channel": "提现渠道"
|
||||
}
|
||||
"channel": "提现渠道",
|
||||
"transferActualPrice": "实际转账金额",
|
||||
"sysAccountAmount": "系统账户余额",
|
||||
"cn": "创建者",
|
||||
"on": "操作者"
|
||||
},
|
||||
"saNotEnough": "系统中此账户的余额不足,请及时同步数据和实际账户一致"
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
.spCon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 18px;
|
||||
|
||||
.btn {
|
||||
margin-top: 13px;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.entityDetail {
|
||||
font-size: small;
|
||||
color: var(--oak-color-primary);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
color: var(--oak-color-primary);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.actualPrice {
|
||||
font-weight: bold;
|
||||
color: var(--oak-color-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
@ -6,22 +6,29 @@ import Styles from './web.pc.module.less';
|
|||
import classNames from 'classnames';
|
||||
import ListPro from 'oak-frontend-base/es/components/listPro';
|
||||
import { FilterPanel } from '../../../components/AbstractComponents';
|
||||
import { ToYuan } from 'oak-domain/lib/utils/money';
|
||||
import { ThousandCont, ToYuan } from 'oak-domain/lib/utils/money';
|
||||
import dayJs from 'dayjs';
|
||||
import assert from 'assert';
|
||||
|
||||
export default function Render(props: WebComponentProps<EntityDict, 'pay', false, {
|
||||
refunds?: (RowWithActions<EntityDict, 'refund'> & {
|
||||
transfers?: (RowWithActions<EntityDict, 'withdrawTransfer'> & {
|
||||
creatorName: string;
|
||||
creatorMobile: string;
|
||||
operatorName: string;
|
||||
operatorMobile: string;
|
||||
channel: string;
|
||||
})[];
|
||||
updateId: string;
|
||||
sysAccountAmount: number;
|
||||
}, {
|
||||
setUpdateId: (id: string, action: string) => void;
|
||||
}>) {
|
||||
const { refunds, oakFullpath } = props.data;
|
||||
const { t } = props.methods;
|
||||
const { transfers, oakFullpath, oakExecutable, updateId, sysAccountAmount } = props.data;
|
||||
const { t, updateItem, execute, clean, setMessage, setUpdateId } = props.methods;
|
||||
|
||||
if (refunds) {
|
||||
const [updateAction, setUpdateAction] = useState('');
|
||||
if (transfers) {
|
||||
const updateRow = updateId && transfers.find(ele => ele.id === updateId);
|
||||
return (
|
||||
<div>
|
||||
<FilterPanel
|
||||
|
|
@ -38,23 +45,29 @@ export default function Render(props: WebComponentProps<EntityDict, 'pay', false
|
|||
/>
|
||||
<ListPro
|
||||
entity="withdrawTransfer"
|
||||
data={refunds}
|
||||
data={transfers}
|
||||
|
||||
attributes={[
|
||||
{
|
||||
path: 'price',
|
||||
label: t('order:attr.price'),
|
||||
label: t('withdrawTransfer:attr.price'),
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
return `${t('common::pay.symbol')} ${ThousandCont(ToYuan(row!.price), 2)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'loss',
|
||||
label: t('order:attr.loss'),
|
||||
label: t('withdrawTransfer:attr.loss'),
|
||||
width: 100,
|
||||
render: (row) => {
|
||||
return `${t('common::pay.symbol')} ${ThousandCont(ToYuan(row!.loss), 2)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'iState',
|
||||
width: 80,
|
||||
label: t('order:attr.iState'),
|
||||
label: t('withdrawTransfer:attr.iState'),
|
||||
},
|
||||
{
|
||||
path: '$$createAt$$',
|
||||
|
|
@ -97,16 +110,123 @@ export default function Render(props: WebComponentProps<EntityDict, 'pay', false
|
|||
},
|
||||
{
|
||||
path: 'externalId',
|
||||
label: t('order:attr.externalId'),
|
||||
label: t('withdrawTransfer:attr.externalId'),
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
path: 'reason',
|
||||
label: t('order:attr.reason'),
|
||||
label: t('withdrawTransfer:attr.reason'),
|
||||
width: 200,
|
||||
}
|
||||
]}
|
||||
|
||||
onAction={(row, action) => {
|
||||
setUpdateId(row.id, action);
|
||||
setUpdateAction(action);
|
||||
}}
|
||||
/>
|
||||
{updateRow && <Modal
|
||||
open={!!updateId}
|
||||
onCancel={() => {
|
||||
setUpdateId('', '');
|
||||
setUpdateAction('');
|
||||
}}
|
||||
onOk={async () => {
|
||||
await execute();
|
||||
setUpdateId('', '');
|
||||
setUpdateAction('');
|
||||
}}
|
||||
destroyOnClose
|
||||
closeIcon={null}
|
||||
okText={t('common::confirm')}
|
||||
cancelText={t('common::action.cancel')}
|
||||
okButtonProps={{
|
||||
disabled: oakExecutable !== true,
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 12 }}
|
||||
layout="horizontal"
|
||||
style={{ width: '100%', marginTop: 12 }}
|
||||
>
|
||||
<Form.Item
|
||||
label={t('label.channel')}
|
||||
>
|
||||
{updateRow!.channel}
|
||||
</Form.Item>
|
||||
{updateRow.withdrawAccount?.org && <Form.Item
|
||||
label={t('withdraw::account.bank.org')}
|
||||
>
|
||||
{updateRow!.withdrawAccount?.org}
|
||||
</Form.Item>}
|
||||
{updateRow.withdrawAccount?.name && <Form.Item
|
||||
label={updateRow.withdrawAccount?.channel?.entity === 'bank' ?
|
||||
t('withdraw::account.bank.name') :
|
||||
t('withdraw::account.others.name')}
|
||||
>
|
||||
{updateRow!.withdrawAccount?.name}
|
||||
</Form.Item>}
|
||||
{updateRow.withdrawAccount?.code && <Form.Item
|
||||
label={t('withdrawAccount:attr.code')}
|
||||
>
|
||||
{updateRow!.withdrawAccount?.code}
|
||||
</Form.Item>}
|
||||
<Form.Item
|
||||
label={t('label.sysAccountAmount')}
|
||||
help={
|
||||
(updateAction === 'succeed' && sysAccountAmount < updateRow!.price! - updateRow.loss!) ?
|
||||
<span className={Styles.warning}>{t('saNotEnough')}</span> :
|
||||
undefined
|
||||
}
|
||||
>
|
||||
<span className={(updateAction === 'succeed' && sysAccountAmount < updateRow!.price! - updateRow.loss!) ? Styles.warning : undefined}>
|
||||
{`${t('common::pay.symbol')} ${ThousandCont(ToYuan(sysAccountAmount), 2)}`}
|
||||
</span>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('label.transferActualPrice')}
|
||||
>
|
||||
<span className={Styles.actualPrice}>
|
||||
{`${t('common::pay.symbol')} ${ThousandCont(ToYuan(updateRow!.price! - updateRow.loss!), 2)}`}
|
||||
</span>
|
||||
</Form.Item>
|
||||
{
|
||||
updateAction === 'succeed' && (
|
||||
<Form.Item
|
||||
label={t('withdrawTransfer:attr.externalId')}
|
||||
>
|
||||
<Input
|
||||
value={updateRow!.externalId!}
|
||||
onChange={({ currentTarget }) => {
|
||||
const { value } = currentTarget;
|
||||
updateItem({
|
||||
externalId: value,
|
||||
}, updateId);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
{
|
||||
updateAction === 'fail' && (
|
||||
<Form.Item
|
||||
label={t('withdrawTransfer:attr.reason')}
|
||||
>
|
||||
<Input
|
||||
value={updateRow!.reason!}
|
||||
onChange={({ currentTarget }) => {
|
||||
const { value } = currentTarget;
|
||||
updateItem({
|
||||
reason: value,
|
||||
}, updateId);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
</Form>
|
||||
</Modal>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -572,10 +572,13 @@ const i18ns: I18n[] = [
|
|||
position: "src/components/withdrawTransfer/list",
|
||||
data: {
|
||||
"label": {
|
||||
"cn": "创建人",
|
||||
"on": "操作者",
|
||||
"channel": "提现渠道"
|
||||
}
|
||||
"channel": "提现渠道",
|
||||
"transferActualPrice": "实际转账金额",
|
||||
"sysAccountAmount": "系统账户余额",
|
||||
"cn": "创建者",
|
||||
"on": "操作者"
|
||||
},
|
||||
"saNotEnough": "系统中此账户的余额不足,请及时同步数据和实际账户一致"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -813,6 +816,15 @@ const i18ns: I18n[] = [
|
|||
"others": "其它渠道"
|
||||
},
|
||||
"wpAccount": "微信支付(到零钱)"
|
||||
},
|
||||
"account": {
|
||||
"bank": {
|
||||
"org": "银行及所属支行",
|
||||
"name": "户主姓名"
|
||||
},
|
||||
"others": {
|
||||
"name": "收款人真实姓名"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const entityDesc: EntityDesc<Schema, Action, '', {
|
|||
loss: '损耗',
|
||||
withdraw: '关联提现',
|
||||
meta: 'metadata',
|
||||
externalId: '外部退款ID',
|
||||
externalId: '外部退款流水号',
|
||||
iState: '状态',
|
||||
creator: '创建者',
|
||||
reason: '原因',
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export const entityDesc: EntityDesc<Schema, Action, '', {
|
|||
operator: '操作者',
|
||||
creator: '创建者',
|
||||
iState: '状态',
|
||||
externalId: '外部Id',
|
||||
externalId: '外部转账流水号',
|
||||
meta: 'metadata',
|
||||
reason: '原因',
|
||||
sysOpers: '账户操作'
|
||||
|
|
@ -65,8 +65,8 @@ export const entityDesc: EntityDesc<Schema, Action, '', {
|
|||
},
|
||||
},
|
||||
action: {
|
||||
succeed: '提现成功',
|
||||
fail: '提现失败',
|
||||
succeed: '转账成功',
|
||||
fail: '转账失败',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,5 +22,14 @@
|
|||
"others": "其它渠道"
|
||||
},
|
||||
"wpAccount": "微信支付(到零钱)"
|
||||
},
|
||||
"account": {
|
||||
"bank": {
|
||||
"org": "银行及所属支行",
|
||||
"name": "户主姓名"
|
||||
},
|
||||
"others": {
|
||||
"name": "收款人真实姓名"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -155,8 +155,7 @@ const triggers: Trigger<EntityDict, 'withdraw', BRC>[] = [
|
|||
]
|
||||
return 1;
|
||||
},
|
||||
} as CreateTriggerInTxn<EntityDict, 'withdraw', BRC>,
|
||||
|
||||
} as CreateTriggerInTxn<EntityDict, 'withdraw', BRC>,
|
||||
{
|
||||
name: '当withdraw失败时,将total和avail还回到帐户',
|
||||
entity: 'withdraw',
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@ const triggers: Trigger<EntityDict, 'withdrawTransfer', BRC>[] = [
|
|||
}, {});
|
||||
|
||||
cnt ++;
|
||||
if (tax) {
|
||||
// 如果转账有手续费,由system的account来承受
|
||||
assert(tax > 0);
|
||||
if (tax || loss) {
|
||||
const systemId = withdrawAccount!.ofSystemId!;
|
||||
const [account] = await context.select('account', {
|
||||
data: {
|
||||
|
|
@ -73,22 +71,46 @@ const triggers: Trigger<EntityDict, 'withdrawTransfer', BRC>[] = [
|
|||
entityId: systemId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
|
||||
await context.operate('accountOper', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
|
||||
if (loss) {
|
||||
// loss也进system account的账户
|
||||
await context.operate('accountOper', {
|
||||
id: await generateNewIdAsync(),
|
||||
accountId: account!.id,
|
||||
type: 'tax',
|
||||
totalPlus: -tax,
|
||||
availPlus: -tax,
|
||||
entity: 'withdrawTransfer',
|
||||
entityId: transfer.id!,
|
||||
},
|
||||
}, {});
|
||||
|
||||
cnt++;
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
accountId: account!.id,
|
||||
type: 'earn',
|
||||
totalPlus: loss,
|
||||
availPlus: loss,
|
||||
entity: 'withdrawTransfer',
|
||||
entityId: transfer.id!,
|
||||
},
|
||||
}, {});
|
||||
|
||||
cnt++;
|
||||
}
|
||||
|
||||
if (tax) {
|
||||
// 如果转账有手续费,由system的account来承受
|
||||
assert(tax > 0);
|
||||
|
||||
await context.operate('accountOper', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'create',
|
||||
data: {
|
||||
id: await generateNewIdAsync(),
|
||||
accountId: account!.id,
|
||||
type: 'tax',
|
||||
totalPlus: -tax,
|
||||
availPlus: -tax,
|
||||
entity: 'withdrawTransfer',
|
||||
entityId: transfer.id!,
|
||||
},
|
||||
}, {});
|
||||
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue