Author SHA1 Message Date
codex-bot 809936e62d refactor!: support RUB only (WealthLedger/main#55) (#204)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m32s
Backend verify / verify (push) Failing after 9m25s
BREAKING CHANGE: per-currency API fields replaced with single RUB Money.
2026-11-11 10:07:11 +03:00
codex-bot 85d65f8997 fix(sync): start hourly operations after portfolio works finish (#201) (#202)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m23s
Backend verify / verify (push) Failing after 8m11s
2026-11-11 09:29:08 +03:00
2 changed files with 31 additions and 1 deletions
@@ -244,16 +244,23 @@ export class TbankHourlyScheduler implements OnModuleDestroy, OnModuleInit {
dispatchProblems: HourlySection[],
walkStartedAt: Date,
) {
const portfolios: string[] = [];
for (const account of accounts) {
await this.dispatchPortfolio(
userId,
revision,
account.accountId,
dispatch,
async (child) => {
portfolios.push(child.id);
await dispatch(child);
},
dispatchProblems,
walkStartedAt,
);
}
// Operations coverage must end after the Portfolio valuation it explains:
// performance requires coverage up to the snapshot fetchedAt.
await this.waitWorks(userId, portfolios);
for (const account of accounts) {
await this.dispatchOperations(
userId,
+23
View File
@@ -167,6 +167,29 @@ describe('hourly attempt lifecycle (SQLite)', () => {
expect(read?.state).toBe('succeeded');
});
it('starts operations only after every portfolio work finished', async () => {
const portfolios: string[] = [];
portfolio.request.mockImplementation(
async (_userId: string, _revision: string, accountId: string) => {
const row = await child('running', null, `portfolio:${accountId}`);
portfolios.push(row.id);
return { id: row.id, done: Promise.resolve() };
},
);
const scheduler = createScheduler();
await scheduler.tick();
await pump();
expect(portfolios).toHaveLength(2);
expect(operations.request).not.toHaveBeenCalled();
await prisma.tbankSyncWork.updateMany({
where: { id: { in: portfolios } },
data: { state: 'complete' },
});
const row = await runWalk(scheduler);
expect(operations.request).toHaveBeenCalledTimes(2);
expect(row.state).toBe('complete');
});
it('settles partial and keeps successful steps when one account fails', async () => {
portfolio.request.mockImplementation(
async (_userId: string, _revision: string, accountId: string) => {