feat: show liability account credit balances as deposits

This commit is contained in:
陈煜 committed 2026-10-03 12:06:36 +08:00
1 parent e77650f0c0
commit 20643b366f
19 files changed
+385 -68

No files matched your search

+2 -5
View File
@@ -161,11 +161,8 @@ export function validateBackup(raw: unknown) {
if (!b.currencies.includes(p.currency)) throw new BadRequestException('币种清单不完整');
}
for (const p of b.positions)
if (
(p.kind !== 'account' || p.side !== 'asset') &&
p.revisions.some((r) => r.amount.startsWith('-'))
)
throw new BadRequestException('仅资产账户支持负余额');
if (p.kind !== 'account' && p.revisions.some((r) => r.amount.startsWith('-')))
throw new BadRequestException('仅账户支持负余额');
const planIds = new Set<string>();
for (const plan of b.schedules || []) {
const source = ids.get(plan.sourceId),
+1
View File
@@ -75,6 +75,7 @@ export function totals(positions: Holding[], rates: Rate[], base: string, date:
const converted = fx ? amount.mul(fx.value) : null;
if (converted) {
if (p.side === 'asset') assets = assets.plus(converted);
else if (p.kind === 'account' && converted.isNegative()) assets = assets.minus(converted);
else liabilities = liabilities.plus(converted);
}
return {
+6
View File
@@ -60,6 +60,12 @@ export function setupOpenApi(app: INestApplication) {
const operation = entry[method];
if (!operation) continue;
operation.summary = `${method.toUpperCase()} ${path}`;
if (
(path === '/api/positions' && method === 'post') ||
(path.startsWith('/api/positions/{id}/revisions') && ['post', 'put'].includes(method))
)
operation.description =
'amount 为数据库原币余额:资产账户正数为存款、负数为透支;负债账户正数为欠款、负数为溢缴存款。网页负债账户输入和显示使用相反符号(负数欠款、正数存款)。独立资产和借入借出债务本金须非负。';
if (
method === 'get' &&
['/api/history', '/api/positions/{id}/history', '/api/transfers'].includes(path)
+4 -4
View File
@@ -159,8 +159,8 @@ export class PortfolioController {
});
if (!p) throw new NotFoundException('项目不存在');
if (p.archived) throw new ConflictException('请先恢复归档项目');
if (v.amount.startsWith('-') && (p.kind !== 'account' || p.side !== 'asset'))
throw new BadRequestException('仅资产账户支持负余额');
if (v.amount.startsWith('-') && p.kind !== 'account')
throw new BadRequestException('仅账户支持负余额');
const lastTransfer = await tx.revision.findFirst({
where: { positionId: p.id, reason: { in: pairedReasons } },
orderBy: [{ effectiveDate: 'desc' }, { sequence: 'desc' }],
@@ -203,8 +203,8 @@ export class PortfolioController {
});
if (!p) throw new NotFoundException('项目不存在');
if (p.archived) throw new ConflictException('请先恢复归档项目');
if (v.amount.startsWith('-') && (p.kind !== 'account' || p.side !== 'asset'))
throw new BadRequestException('仅资产账户支持负余额');
if (v.amount.startsWith('-') && p.kind !== 'account')
throw new BadRequestException('仅账户支持负余额');
const original = await tx.revision.findFirst({ where: { id: revisionId, positionId: p.id } });
if (!original) throw new NotFoundException('历史记录不存在');
if (pairedReasons.includes(original.reason))
+1 -1
View File
@@ -55,7 +55,7 @@ export async function captureReplay(tx: Prisma.TransactionClient, userId: string
const amount = delta
? (balances.get(row.positionId) || new Decimal(0)).plus(delta)
: new Decimal(row.amount.toString());
if (amount.isNegative() && (p.kind !== 'account' || p.side !== 'asset'))
if (amount.isNegative() && p.kind !== 'account')
throw new BadRequestException('修改后债务或资产金额不能为负数');
if (amount.abs().gte('10000000000000000'))
throw new BadRequestException('重算后的金额超出支持范围');
+2 -2
View File
@@ -98,8 +98,8 @@ export const positionInput = positionMeta
})
.strict()
.superRefine((p, c) => {
if (p.amount.startsWith('-') && (p.kind !== 'account' || p.side !== 'asset'))
c.addIssue({ code: 'custom', message: '仅资产账户支持负余额' });
if (p.amount.startsWith('-') && p.kind !== 'account')
c.addIssue({ code: 'custom', message: '仅账户支持负余额' });
if (
(p.kind === 'asset' && p.side !== 'asset') ||
(p.kind === 'account' &&