From ec3cb39494ef5d40dc40286e2d72d55344c82756 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:15:22 +0200 Subject: [PATCH 1/3] fix(custody): order the account history by the timestamp it shows The list displays completedAt for a completed order and created for every other, but was ordered by created alone. An order recorded later and settled faster therefore sat below an older row carrying a younger date. Sorting by the displayed timestamp also fixes which 100 rows survive the cap - previously the cut was made against a different key than the one the reader sees. --- .../custody/services/custody-order.service.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/subdomains/core/custody/services/custody-order.service.ts b/src/subdomains/core/custody/services/custody-order.service.ts index 4d61a1a926..7ff6b72453 100644 --- a/src/subdomains/core/custody/services/custody-order.service.ts +++ b/src/subdomains/core/custody/services/custody-order.service.ts @@ -227,12 +227,20 @@ export class CustodyOrderService { } async getOrdersByUserData(userDataId: number): Promise { - const orders = await this.custodyOrderRepo.find({ - where: { user: { userData: { id: userDataId } }, status: Not(CustodyOrderStatus.CREATED) }, - relations: { inputAsset: true, outputAsset: true, transactionRequest: true }, - order: { created: 'DESC' }, - take: 100, - }); + const orders = await this.custodyOrderRepo + .createQueryBuilder('custodyOrder') + .leftJoinAndSelect('custodyOrder.inputAsset', 'inputAsset') + .leftJoinAndSelect('custodyOrder.outputAsset', 'outputAsset') + .leftJoinAndSelect('custodyOrder.transactionRequest', 'transactionRequest') + .innerJoin('custodyOrder.user', 'user') + .innerJoin('user.userData', 'userData') + .where('userData.id = :userDataId', { userDataId }) + .andWhere('custodyOrder.status != :createdStatus', { createdStatus: CustodyOrderStatus.CREATED }) + // The list shows completedAt where the order is completed, created otherwise. Sorting by + // created alone would put rows out of order against the dates the reader can see. + .orderBy('COALESCE("custodyOrder"."completedAt", "custodyOrder"."created")', 'DESC') + .take(100) + .getMany(); return CustodyOrderHistoryDtoMapper.mapList(orders); } From 8b232def5b14c5513162140a4741228d9667298a Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:50:46 +0200 Subject: [PATCH 2/3] fix(custody): limit the history rows instead of taking entities take() splits the query into an id pass and an entity pass, and parses the raw orderBy string at every dot on the way - the COALESCE expression turns into a lookup for an alias that does not exist, and the call throws before any SQL reaches the database. Every relation joined here is to-one, so no row can be duplicated and limiting rows equals limiting entities. --- .../core/custody/services/custody-order.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/subdomains/core/custody/services/custody-order.service.ts b/src/subdomains/core/custody/services/custody-order.service.ts index 7ff6b72453..248b93d820 100644 --- a/src/subdomains/core/custody/services/custody-order.service.ts +++ b/src/subdomains/core/custody/services/custody-order.service.ts @@ -239,7 +239,11 @@ export class CustodyOrderService { // The list shows completedAt where the order is completed, created otherwise. Sorting by // created alone would put rows out of order against the dates the reader can see. .orderBy('COALESCE("custodyOrder"."completedAt", "custodyOrder"."created")', 'DESC') - .take(100) + // limit, not take: take() splits the query in two and parses the raw orderBy at every dot, + // which turns the expression above into a lookup for an alias named COALESCE("custodyOrder" + // and throws on every call. Every relation joined here is to-one, so no row can be + // duplicated and limiting rows is the same as limiting entities. + .limit(100) .getMany(); return CustodyOrderHistoryDtoMapper.mapList(orders); From af2ceb774c9347db5bdd341be2c40a6951f05bbb Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:15:29 +0200 Subject: [PATCH 3/3] fix(custody): keep the history order deterministic on equal timestamps Two orders can carry the same valuta. Without a tiebreaker their relative order is whatever the database happens to return, so rows could swap places between calls, or cross the cap and disappear from the list entirely. --- src/subdomains/core/custody/services/custody-order.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/subdomains/core/custody/services/custody-order.service.ts b/src/subdomains/core/custody/services/custody-order.service.ts index 248b93d820..d27cb43aae 100644 --- a/src/subdomains/core/custody/services/custody-order.service.ts +++ b/src/subdomains/core/custody/services/custody-order.service.ts @@ -239,6 +239,9 @@ export class CustodyOrderService { // The list shows completedAt where the order is completed, created otherwise. Sorting by // created alone would put rows out of order against the dates the reader can see. .orderBy('COALESCE("custodyOrder"."completedAt", "custodyOrder"."created")', 'DESC') + // Two orders can share a timestamp, and an undefined order among them would let rows swap + // places between calls - or cross the cap below and vanish. The id keeps it deterministic. + .addOrderBy('custodyOrder.id', 'DESC') // limit, not take: take() splits the query in two and parses the raw orderBy at every dot, // which turns the expression above into a lookup for an alias named COALESCE("custodyOrder" // and throws on every call. Every relation joined here is to-one, so no row can be