This commit is contained in:
Xu Chang 2024-06-14 20:18:02 +08:00
parent b7324538a0
commit 673cdc4924
22 changed files with 340 additions and 79 deletions

View File

@ -7,6 +7,7 @@ import wpProductCheckers from './wpProduct';
import abstractCheckers from './abstractChecker';
import withdrawAccounts from './withdrawAccount';
import refundCheckers from './refund';
import withdrawTransferCheckers from './withdrawTransfer';
const checkers = [
...refundCheckers,
...withdrawAccounts,
@ -17,5 +18,6 @@ const checkers = [
...applicationCheckers,
...offlineAccountCheckers,
...wpProductCheckers,
...withdrawTransferCheckers,
];
export default checkers;

View File

@ -6,9 +6,25 @@ const checkers = [
action: 'succeed',
checker(operation, context) {
const { data } = operation;
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;
if (data) {
const { reason } = data;
if (!reason) {
throw new OakAttrNotNullException('pay', ['reason']);
}
}
}
}

View File

@ -121,7 +121,7 @@ export default OakComponent({
},
},
filter: {
iState: 'refunding',
iState: 'transferring',
withdraw: {
account: {
ofSystemId: systemId,

View File

@ -9,13 +9,13 @@ export default function render(props) {
<Select disabled={!channels} options={channels} value={channel?.id} onSelect={(id) => onSetChannelId(id)}/>
</Form.Item>
{isBank &&
<Form.Item label={t('label.bank.org')}>
<Form.Item label={t('withdraw::account.bank.org')}>
<Input value={withdrawAccount.org} onChange={({ currentTarget }) => {
const { value } = currentTarget;
onUpdate('org', value);
}}/>
</Form.Item>}
<Form.Item label={isBank ? t('label.bank.name') : t('label.others.name')}>
<Form.Item label={isBank ? t('withdraw::account.bank.name') : t('withdraw::account.others.name')}>
<Input value={withdrawAccount.name} onChange={({ currentTarget }) => {
const { value } = currentTarget;
onUpdate('name', value);

View File

@ -1,3 +1,4 @@
/// <reference types="wechat-miniprogram" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "withdrawTransfer", true, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "withdrawTransfer", true, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
export default _default;

View File

@ -1,7 +1,8 @@
import { ToYuan, ThousandCont } from 'oak-domain/lib/utils/money';
import assert from 'assert';
export default OakComponent({
entity: 'withdrawTransfer',
isList: true,
actions: ['succeed', 'fail'],
projection: {
id: 1,
price: 1,
@ -31,6 +32,9 @@ export default OakComponent({
}
},
withdrawAccount: {
org: 1,
name: 1,
code: 1,
channel: {
entity: 1,
entityId: 1,
@ -46,23 +50,26 @@ export default OakComponent({
},
formData({ data }) {
return {
refunds: data?.map((ele) => {
const { creator, price, loss, operator, withdrawAccount, ...rest } = ele;
transfers: data?.map((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() {
@ -78,4 +85,31 @@ export default OakComponent({
}
}
],
methods: {
async setUpdateId(id, action) {
this.clean();
this.setState({
updateId: id,
});
if (id) {
this.updateItem({}, id, action);
const row = this.state.transfers.find(ele => ele.id === id);
// 每次都刷新这个系统账户当前余额吧
const { entity, entityId } = row.withdrawAccount.channel;
const { data: [sysAccount] } = await this.features.cache.refresh(entity, {
data: {
id: 1,
price: 1,
},
filter: {
id: entityId,
}
});
assert(sysAccount);
this.setState({
sysAccountAmount: sysAccount.price,
});
}
}
}
});

View File

@ -1,7 +1,10 @@
{
"label": {
"cn": "创建人",
"on": "操作者",
"channel": "提现渠道"
}
"channel": "提现渠道",
"transferActualPrice": "实际转账金额",
"sysAccountAmount": "系统账户余额",
"cn": "创建者",
"on": "操作者"
},
"saNotEnough": "系统中此账户的余额不足,请及时同步数据和实际账户一致"
}

View File

@ -2,11 +2,15 @@ import React from 'react';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
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;
}>): React.JSX.Element | null;

View File

@ -1,10 +1,15 @@
import React from 'react';
import React, { useState } from 'react';
import { Input, Form, Modal } from 'antd';
import Styles from './web.pc.module.less';
import ListPro from 'oak-frontend-base/es/components/listPro';
import { FilterPanel } from '../../../components/AbstractComponents';
import { ThousandCont, ToYuan } from 'oak-domain/lib/utils/money';
export default function Render(props) {
const { refunds, oakFullpath } = props.data;
const { t } = props.methods;
if (refunds) {
const { transfers, oakFullpath, oakExecutable, updateId, sysAccountAmount } = props.data;
const { t, updateItem, execute, clean, setMessage, setUpdateId } = props.methods;
const [updateAction, setUpdateAction] = useState('');
if (transfers) {
const updateRow = updateId && transfers.find(ele => ele.id === updateId);
return (<div>
<FilterPanel oakPath={oakFullpath} entity="withdrawTransfer" columns={[
{
@ -14,21 +19,27 @@ export default function Render(props) {
attr: 'externalId',
}
]}/>
<ListPro entity="withdrawTransfer" data={refunds} attributes={[
<ListPro entity="withdrawTransfer" 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$$',
@ -67,15 +78,73 @@ export default function Render(props) {
},
{
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>);
}
return null;

View File

@ -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;
}

View File

@ -570,10 +570,13 @@ const i18ns = [
position: "src/components/withdrawTransfer/list",
data: {
"label": {
"cn": "创建人",
"on": "操作者",
"channel": "提现渠道"
}
"channel": "提现渠道",
"transferActualPrice": "实际转账金额",
"sysAccountAmount": "系统账户余额",
"cn": "创建者",
"on": "操作者"
},
"saNotEnough": "系统中此账户的余额不足,请及时同步数据和实际账户一致"
}
},
{
@ -811,6 +814,15 @@ const i18ns = [
"others": "其它渠道"
},
"wpAccount": "微信支付(到零钱)"
},
"account": {
"bank": {
"org": "银行及所属支行",
"name": "户主姓名"
},
"others": {
"name": "收款人真实姓名"
}
}
}
}

View File

@ -16,7 +16,7 @@ export const entityDesc = {
loss: '损耗',
withdraw: '关联提现',
meta: 'metadata',
externalId: '外部退款ID',
externalId: '外部退款流水号',
iState: '状态',
creator: '创建者',
reason: '原因',

View File

@ -18,7 +18,7 @@ export const entityDesc = {
operator: '操作者',
creator: '创建者',
iState: '状态',
externalId: '外部Id',
externalId: '外部转账流水号',
meta: 'metadata',
reason: '原因',
sysOpers: '账户操作'
@ -31,8 +31,8 @@ export const entityDesc = {
},
},
action: {
succeed: '提现成功',
fail: '提现失败',
succeed: '转账成功',
fail: '转账失败',
},
},
},

View File

@ -22,5 +22,14 @@
"others": "其它渠道"
},
"wpAccount": "微信支付(到零钱)"
},
"account": {
"bank": {
"org": "银行及所属支行",
"name": "户主姓名"
},
"others": {
"name": "收款人真实姓名"
}
}
}

View File

@ -51,9 +51,7 @@ const triggers = [
}
}, {});
cnt++;
if (tax) {
// 如果转账有手续费由system的account来承受
assert(tax > 0);
if (tax || loss) {
const systemId = withdrawAccount.ofSystemId;
const [account] = await context.select('account', {
data: {
@ -64,20 +62,41 @@ const triggers = [
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++;
}
}
}
return cnt;

View File

@ -10,6 +10,7 @@ const wpProduct_1 = tslib_1.__importDefault(require("./wpProduct"));
const abstractChecker_1 = tslib_1.__importDefault(require("./abstractChecker"));
const withdrawAccount_1 = tslib_1.__importDefault(require("./withdrawAccount"));
const refund_1 = tslib_1.__importDefault(require("./refund"));
const withdrawTransfer_1 = tslib_1.__importDefault(require("./withdrawTransfer"));
const checkers = [
...refund_1.default,
...withdrawAccount_1.default,
@ -20,5 +21,6 @@ const checkers = [
...application_1.default,
...offlineAccount_1.default,
...wpProduct_1.default,
...withdrawTransfer_1.default,
];
exports.default = checkers;

View File

@ -8,9 +8,25 @@ const checkers = [
action: 'succeed',
checker(operation, context) {
const { data } = operation;
const { externalId } = data;
if (!externalId) {
throw new types_1.OakAttrNotNullException('pay', ['externalId']);
if (data) {
const { externalId } = data;
if (!externalId) {
throw new types_1.OakAttrNotNullException('pay', ['externalId']);
}
}
}
},
{
entity: 'withdrawTransfer',
type: 'data',
action: 'fail',
checker(operation, context) {
const { data } = operation;
if (data) {
const { reason } = data;
if (!reason) {
throw new types_1.OakAttrNotNullException('pay', ['reason']);
}
}
}
}

View File

@ -572,10 +572,13 @@ const i18ns = [
position: "src/components/withdrawTransfer/list",
data: {
"label": {
"cn": "创建人",
"on": "操作者",
"channel": "提现渠道"
}
"channel": "提现渠道",
"transferActualPrice": "实际转账金额",
"sysAccountAmount": "系统账户余额",
"cn": "创建者",
"on": "操作者"
},
"saNotEnough": "系统中此账户的余额不足,请及时同步数据和实际账户一致"
}
},
{
@ -813,6 +816,15 @@ const i18ns = [
"others": "其它渠道"
},
"wpAccount": "微信支付(到零钱)"
},
"account": {
"bank": {
"org": "银行及所属支行",
"name": "户主姓名"
},
"others": {
"name": "收款人真实姓名"
}
}
}
}

View File

@ -19,7 +19,7 @@ exports.entityDesc = {
loss: '损耗',
withdraw: '关联提现',
meta: 'metadata',
externalId: '外部退款ID',
externalId: '外部退款流水号',
iState: '状态',
creator: '创建者',
reason: '原因',

View File

@ -21,7 +21,7 @@ exports.entityDesc = {
operator: '操作者',
creator: '创建者',
iState: '状态',
externalId: '外部Id',
externalId: '外部转账流水号',
meta: 'metadata',
reason: '原因',
sysOpers: '账户操作'
@ -34,8 +34,8 @@ exports.entityDesc = {
},
},
action: {
succeed: '提现成功',
fail: '提现失败',
succeed: '转账成功',
fail: '转账失败',
},
},
},

View File

@ -22,5 +22,14 @@
"others": "其它渠道"
},
"wpAccount": "微信支付(到零钱)"
},
"account": {
"bank": {
"org": "银行及所属支行",
"name": "户主姓名"
},
"others": {
"name": "收款人真实姓名"
}
}
}

View File

@ -54,9 +54,7 @@ const triggers = [
}
}, {});
cnt++;
if (tax) {
// 如果转账有手续费由system的account来承受
(0, assert_1.default)(tax > 0);
if (tax || loss) {
const systemId = withdrawAccount.ofSystemId;
const [account] = await context.select('account', {
data: {
@ -67,20 +65,41 @@ const triggers = [
entityId: systemId,
}
}, { dontCollect: true });
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
if (loss) {
// loss也进system account的账户
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: account.id,
type: 'tax',
totalPlus: -tax,
availPlus: -tax,
entity: 'withdrawTransfer',
entityId: transfer.id,
},
}, {});
cnt++;
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: account.id,
type: 'earn',
totalPlus: loss,
availPlus: loss,
entity: 'withdrawTransfer',
entityId: transfer.id,
},
}, {});
cnt++;
}
if (tax) {
// 如果转账有手续费由system的account来承受
(0, assert_1.default)(tax > 0);
await context.operate('accountOper', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
accountId: account.id,
type: 'tax',
totalPlus: -tax,
availPlus: -tax,
entity: 'withdrawTransfer',
entityId: transfer.id,
},
}, {});
cnt++;
}
}
}
return cnt;