@mojaloop/central-ledger 19.14.0 → 20.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.grype.yaml +3 -0
  2. package/.license-scanner.json +16 -0
  3. package/.ncurc.yaml +19 -13
  4. package/.nvmrc +1 -1
  5. package/.nycrc.yml +14 -2
  6. package/CHANGELOG.md +51 -0
  7. package/Dockerfile +13 -10
  8. package/README.md +39 -89
  9. package/audit-ci.jsonc +1 -1
  10. package/config/default.json +60 -3
  11. package/config/deprecated.default-settlement.json +160 -0
  12. package/docker/ml-api-adapter/default.json +28 -0
  13. package/docker-compose.yml +6 -0
  14. package/nodemon.json +6 -0
  15. package/package.json +128 -64
  16. package/{sbom-v19.13.2.csv → sbom-v20.0.0.csv} +886 -736
  17. package/scripts/_wait4_all.js +5 -5
  18. package/scripts/transferSettlementTemp/interchangeFeeCalculation.js +51 -0
  19. package/src/api/index.ts +30 -0
  20. package/src/api/interface/swagger.json +2249 -2054
  21. package/src/api/participants/routes.js +1 -1
  22. package/src/api/root/handler.int.ts +55 -0
  23. package/src/api/routes.js +7 -6
  24. package/src/api/server.ts +27 -0
  25. package/src/api/server.unit.ts +16 -0
  26. package/src/api/settlementModels/routes.js +4 -4
  27. package/src/domain/participant/index.d.ts +47 -0
  28. package/src/domain/participant/index.int.ts +724 -0
  29. package/src/domain/participant/index.js +4 -5
  30. package/src/domain/position/fx-prepare.js +1 -1
  31. package/src/domain/position/fx-timeout-reserved.js +6 -0
  32. package/src/domain/position/prepare.js +1 -1
  33. package/src/handlers/admin/handler.js +1 -1
  34. package/src/handlers/index.js +30 -0
  35. package/src/handlers/positions/handler.js +0 -2
  36. package/src/handlers/positions/handlerBatch.js +5 -5
  37. package/src/handlers/register.js +44 -29
  38. package/src/handlers/timeouts/handler.js +1 -4
  39. package/src/handlers/transfers/fx-abort.int.ts +209 -0
  40. package/src/handlers/transfers/fx-handlers.int.ts +275 -0
  41. package/src/handlers/transfers/fx-timeout.int.ts +284 -0
  42. package/src/handlers/transfers/handler.js +1 -3
  43. package/src/handlers/transfers/handlers.int.ts +538 -0
  44. package/src/handlers/transfers/prepare.int.ts +1283 -0
  45. package/src/handlers/transfers/prepare.js +2 -2
  46. package/src/knexfile.js +19 -0
  47. package/src/lib/cache.js +1 -1
  48. package/src/lib/config/index.ts +60 -0
  49. package/src/lib/config/resolver.ts +231 -0
  50. package/src/lib/config/resolver.unit.ts +72 -0
  51. package/src/lib/config/types.ts +372 -0
  52. package/src/lib/config/util.ts +523 -0
  53. package/src/lib/config/util.unit.ts +825 -0
  54. package/src/lib/migrator.js +3 -3
  55. package/src/lib/proxyCache.js +3 -3
  56. package/{migrations → src/migrations}/100100_event.js +1 -1
  57. package/{migrations → src/migrations}/110100_contactType.js +1 -1
  58. package/{migrations → src/migrations}/110200_currency.js +1 -1
  59. package/{migrations → src/migrations}/110201_currency-scale.js +1 -1
  60. package/{migrations → src/migrations}/110300_endpointType.js +1 -1
  61. package/{migrations → src/migrations}/110400_ledgerEntryType.js +1 -1
  62. package/{migrations → src/migrations}/110450_ledgerAccountType.js +1 -1
  63. package/{migrations → src/migrations}/110500_participantLimitType.js +1 -1
  64. package/{migrations → src/migrations}/110600_transferParticipantRoleType.js +1 -1
  65. package/{migrations → src/migrations}/110700_transferState.js +1 -1
  66. package/{migrations → src/migrations}/110900_settlementState.js +1 -1
  67. package/{migrations → src/migrations}/112000_bulkTransferState.js +1 -1
  68. package/{migrations → src/migrations}/112100_bulkProcessingState.js +1 -1
  69. package/{migrations → src/migrations}/200100_participant.js +1 -1
  70. package/{migrations → src/migrations}/200200_participantContact.js +1 -1
  71. package/{migrations → src/migrations}/200300_participantEndpoint.js +1 -1
  72. package/{migrations → src/migrations}/200400_participantParty.js +1 -1
  73. package/{migrations → src/migrations}/200600_token.js +1 -1
  74. package/{migrations → src/migrations}/200601_token-indexes.js +1 -1
  75. package/{migrations → src/migrations}/300100_transferDuplicateCheck.js +2 -2
  76. package/{migrations → src/migrations}/300150_bulkTransferDuplicateCheck.js +2 -2
  77. package/{migrations → src/migrations}/300200_transfer.js +2 -2
  78. package/{migrations → src/migrations}/300202_transfer-decimal184.js +1 -1
  79. package/{migrations → src/migrations}/300250_bulkTransfer.js +2 -3
  80. package/{migrations → src/migrations}/300275_bulkTransferAssociation.js +2 -3
  81. package/{migrations → src/migrations}/300300_ilpPacket.js +1 -1
  82. package/{migrations → src/migrations}/300400_transferStateChange.js +1 -1
  83. package/{migrations → src/migrations}/300450_bulkTransferStateChange.js +1 -1
  84. package/{migrations → src/migrations}/300500_segment.js +1 -1
  85. package/{migrations → src/migrations}/310100_participantCurrency.js +1 -1
  86. package/{migrations → src/migrations}/310200_transferParticipant.js +1 -1
  87. package/{migrations → src/migrations}/310202_transferParticipant-decimal184.js +1 -1
  88. package/{migrations → src/migrations}/310204_transferParticipant-participantId.js +2 -2
  89. package/{migrations → src/migrations}/310300_participantPosition.js +1 -1
  90. package/{migrations → src/migrations}/310302_participantPosition-decimal184.js +1 -1
  91. package/{migrations → src/migrations}/310400_participantPositionChange.js +1 -1
  92. package/{migrations → src/migrations}/310402_participantPositionChange-decimal184.js +1 -1
  93. package/{migrations → src/migrations}/310403_participantPositionChange-participantCurrencyId.js +2 -2
  94. package/{migrations → src/migrations}/310404_participantPositionChange-change.js +2 -2
  95. package/{migrations → src/migrations}/310500_participantLimit.js +1 -1
  96. package/{migrations → src/migrations}/310502_participantLimit-decimal184.js +1 -1
  97. package/{migrations → src/migrations}/400100_settlementWindow.js +1 -1
  98. package/{migrations → src/migrations}/400200_settlement.js +1 -1
  99. package/{migrations → src/migrations}/400300_settlementSettlementWindow.js +1 -1
  100. package/{migrations → src/migrations}/400301_settlementSettlementWindow-indexes.js +0 -1
  101. package/{migrations → src/migrations}/400400_settlementWindowStateChange.js +1 -1
  102. package/{migrations → src/migrations}/400500_settlementTransferParticipant.js +1 -1
  103. package/{migrations → src/migrations}/400502_settlementTransferParticipant-decimal184.js +1 -1
  104. package/{migrations → src/migrations}/400600_settlementParticipantCurrency.js +1 -1
  105. package/{migrations → src/migrations}/400602_settlementParticipantCurrency-decimal184.js +1 -1
  106. package/{migrations → src/migrations}/400700_settlementParticipantCurrencyStateChange.js +1 -1
  107. package/{migrations → src/migrations}/400800_settlementStateChange.js +1 -1
  108. package/{migrations → src/migrations}/400900_settlementWindowContent.js +1 -1
  109. package/{migrations → src/migrations}/401000_settlementWindowContentStateChange.js +1 -1
  110. package/{migrations → src/migrations}/401001_settlementWindowContentStateChange-indexes.js +6 -6
  111. package/{migrations → src/migrations}/401200_settlementContentAggregation.js +1 -1
  112. package/{migrations → src/migrations}/401300_settlementModel.js +1 -1
  113. package/{migrations → src/migrations}/410100_transferFulfilmentDuplicateCheck.js +1 -1
  114. package/{migrations → src/migrations}/410102_transferFulfilmentDuplicateCheck-dropForeignTransferId.js +1 -1
  115. package/{migrations → src/migrations}/410103_transferFulfilmentDuplicateCheck-hashNullable.js +1 -1
  116. package/{migrations → src/migrations}/410200_transferFulfilment.js +1 -1
  117. package/{migrations → src/migrations}/410202_transferFulfilment-ilpFulfilment.js +1 -1
  118. package/{migrations → src/migrations}/410203_transferFulfilment-foreignTransferFulfilmentId.js +1 -1
  119. package/{migrations → src/migrations}/410240_bulkTransferFulfilmentDuplicateCheck.js +1 -1
  120. package/{migrations → src/migrations}/410250_bulkTransferFulfilment.js +1 -1
  121. package/{migrations → src/migrations}/410300_transferExtension.js +1 -1
  122. package/{migrations → src/migrations}/410302_transferExtension-transferErrorId.js +1 -1
  123. package/{migrations → src/migrations}/410350_bulkTransferExtension.js +1 -1
  124. package/{migrations → src/migrations}/410400_transferTimeout.js +1 -1
  125. package/{migrations → src/migrations}/410450_transferErrorDuplicateCheck.js +1 -1
  126. package/{migrations → src/migrations}/410500_transferError.js +1 -1
  127. package/{migrations → src/migrations}/410502_transferError-foreignTransferErrorDuplicateCheckId.js +1 -1
  128. package/{migrations → src/migrations}/410550_bulkTransferError.js +1 -1
  129. package/{migrations → src/migrations}/410600_expiringTransfer.js +1 -1
  130. package/{migrations → src/migrations}/500401_quote-hotfix-2719.js +8 -8
  131. package/{migrations → src/migrations}/500501_feature-fixSubIdRef.js +12 -12
  132. package/{migrations → src/migrations}/500601_party-2480.js +3 -3
  133. package/{migrations → src/migrations}/501001_quoteExtension-quote-service-174.js +1 -2
  134. package/{migrations → src/migrations}/501002_quoteExtension-2522.js +3 -3
  135. package/{migrations → src/migrations}/600010_fxTransferType.js +1 -1
  136. package/{migrations → src/migrations}/600012_fxParticipantCurrencyType.js +1 -1
  137. package/{migrations → src/migrations}/600100_fxTransferDuplicateCheck.js +2 -2
  138. package/{migrations → src/migrations}/600110_fxTransferErrorDuplicateCheck.js.js +1 -1
  139. package/{migrations → src/migrations}/600200_fxTransfer.js +2 -2
  140. package/{migrations → src/migrations}/600400_fxTransferStateChange.js +1 -1
  141. package/{migrations → src/migrations}/600501_fxWatchList.js +2 -2
  142. package/{migrations → src/migrations}/600600_fxTransferFulfilmentDuplicateCheck.js +1 -1
  143. package/{migrations → src/migrations}/600700_fxTransferFulfilment.js +1 -1
  144. package/{migrations → src/migrations}/600800_fxTransferExtension.js +1 -1
  145. package/{migrations → src/migrations}/601400_fxTransferTimeout.js +1 -1
  146. package/{migrations → src/migrations}/601500_fxTransferError.js +1 -1
  147. package/{migrations → src/migrations}/610200_fxTransferParticipant.js +1 -1
  148. package/{migrations → src/migrations}/610202_fxTransferParticipant-participantId.js +2 -2
  149. package/{migrations → src/migrations}/610403_participantPositionChange-fxTransfer.js +1 -1
  150. package/{migrations → src/migrations}/800100_quotePartyView.js +1 -1
  151. package/{migrations → src/migrations}/800101_feature-fixSubIdRef.js +1 -1
  152. package/{migrations → src/migrations}/800200_quoteView.js +1 -1
  153. package/{migrations → src/migrations}/800300_quoteResponseView.js +1 -1
  154. package/{migrations → src/migrations}/900100_foreign-keys.js +18 -18
  155. package/{migrations → src/migrations}/900101_foreign-keys.js +6 -6
  156. package/{migrations → src/migrations}/910100_feature904DDL.js +7 -7
  157. package/{migrations → src/migrations}/910101_feature904DataMigration.js +1 -1
  158. package/{migrations → src/migrations}/910102_feature949DataMigration.js +1 -1
  159. package/{migrations → src/migrations}/910103_feature1093SettlementByCurrency.js +3 -3
  160. package/{migrations → src/migrations}/940101_quotePartyIdInfoExtension-2522.js +25 -25
  161. package/{migrations → src/migrations}/950101_transferParticipantStateChange.js +2 -2
  162. package/{migrations → src/migrations}/950102_settlementModel-adjustPosition.js +2 -2
  163. package/{migrations → src/migrations}/950103_dropTransferParticipantStateChange.js +3 -3
  164. package/{migrations → src/migrations}/950104_settlementModel-settlementAccountTypeId.js +3 -3
  165. package/src/migrations/950107_settlementWindowContent_foreign_settlementModel.js +63 -0
  166. package/{migrations → src/migrations}/950108_participantProxy.js +1 -2
  167. package/{migrations → src/migrations}/950120_fxQuoteConversionTerms_sourceAmount_nullable.js +1 -1
  168. package/{migrations → src/migrations}/960100_create_externalParticipant.js +2 -2
  169. package/{migrations → src/migrations}/960110_alter_transferParticipant__addFiled_externalParticipantId.js +2 -2
  170. package/{migrations → src/migrations}/961001_transferForwarded.js +1 -1
  171. package/{migrations → src/migrations}/961002_fxTransferForwarded.js +1 -1
  172. package/src/models/participant/externalParticipant.int.ts +85 -0
  173. package/src/models/participant/facade.js +4 -2
  174. package/src/models/participant/participantCurrencyCached.js +1 -1
  175. package/src/models/position/facade.js +8 -2
  176. package/src/models/transfer/facade.int.ts +139 -0
  177. package/src/models/transfer/facade.js +8 -3
  178. package/src/models/transfer/transferDuplicateCheck.js +2 -2
  179. package/src/models/transfer/transferError.int.ts +112 -0
  180. package/src/models/transfer/transferExtension.int.ts +137 -0
  181. package/src/models/transfer/transferStateChange.int.ts +117 -0
  182. package/{seeds → src/seeds}/participant.js +1 -1
  183. package/{seeds → src/seeds}/settlementInterchange.js +1 -0
  184. package/src/settlement/api/handlerRoutes.js +71 -0
  185. package/src/settlement/api/handlers/health.js +10 -0
  186. package/src/settlement/api/handlers/index.js +57 -0
  187. package/src/settlement/api/handlers/settlementWindows/{id}.js +103 -0
  188. package/src/settlement/api/handlers/settlementWindows.js +77 -0
  189. package/src/settlement/api/handlers/settlements/{id}.js +133 -0
  190. package/src/settlement/api/handlers/settlements/{sid}/participants/{pid}/accounts/{aid}.js +127 -0
  191. package/src/settlement/api/handlers/settlements/{sid}/participants/{pid}.js +127 -0
  192. package/src/settlement/api/handlers/settlements.js +114 -0
  193. package/src/settlement/api/openapiRouting.js +87 -0
  194. package/src/settlement/api/routes.js +176 -0
  195. package/src/settlement/domain/rules/index.js +46 -0
  196. package/src/settlement/domain/settlement/index.js +405 -0
  197. package/src/settlement/domain/settlementWindow/index.js +117 -0
  198. package/src/settlement/domain/transactions/index.js +83 -0
  199. package/src/settlement/domain/transferSettlement/index.js +57 -0
  200. package/src/settlement/handlers/deferredSettlement/handler.js +180 -0
  201. package/src/settlement/handlers/grossSettlement/handler.js +180 -0
  202. package/src/settlement/handlers/index.js +101 -0
  203. package/src/settlement/handlers/register.js +92 -0
  204. package/src/settlement/handlers/rules/handler.js +164 -0
  205. package/src/settlement/interface/swagger-handler.json +34 -0
  206. package/src/settlement/interface/swagger.json +1560 -0
  207. package/src/settlement/lib/db.js +32 -0
  208. package/src/settlement/lib/kafka/index.js +41 -0
  209. package/src/settlement/lib/kafka/producer.js +142 -0
  210. package/src/settlement/lib/scriptEngine.js +99 -0
  211. package/src/settlement/lib/scriptsLoader.js +175 -0
  212. package/src/settlement/lib/utility.js +243 -0
  213. package/src/settlement/models/ilpPackets/ilpPacket.js +42 -0
  214. package/src/settlement/models/lib/enums.js +192 -0
  215. package/src/settlement/models/misc/migrationLock.js +53 -0
  216. package/src/settlement/models/settlement/facade.js +1652 -0
  217. package/src/settlement/models/settlement/index.js +55 -0
  218. package/src/settlement/models/settlement/participantCurrency.js +48 -0
  219. package/src/settlement/models/settlement/settlement.js +49 -0
  220. package/src/settlement/models/settlement/settlementModel.js +44 -0
  221. package/src/settlement/models/settlement/settlementParticipantCurrency.js +61 -0
  222. package/src/settlement/models/settlement/settlementStateChange.js +44 -0
  223. package/src/settlement/models/settlement/settlementTransferParticipant.js +46 -0
  224. package/src/settlement/models/settlementWindow/facade.js +345 -0
  225. package/src/settlement/models/settlementWindow/index.js +47 -0
  226. package/src/settlement/models/settlementWindow/settlementWindowStateChange.js +55 -0
  227. package/src/settlement/models/settlementWindowContent/facade.js +79 -0
  228. package/src/settlement/models/settlementWindowContent/index.js +40 -0
  229. package/src/settlement/models/settlementWindowContent/settlementWindowContentStateChange.js +53 -0
  230. package/src/settlement/models/transferSettlement/facade.js +363 -0
  231. package/src/settlement/models/transferSettlement/index.js +42 -0
  232. package/src/settlement/shared/logger/index.js +7 -0
  233. package/src/settlement/shared/plugins.js +88 -0
  234. package/src/settlement/shared/setup.js +224 -0
  235. package/src/settlement/utils/truthyProperty.js +37 -0
  236. package/src/shared/legacyUtilShim.js +20 -0
  237. package/src/shared/plugins.js +3 -3
  238. package/src/shared/results.ts +22 -0
  239. package/src/shared/results.unit.ts +38 -0
  240. package/src/shared/setup.js +14 -1
  241. package/src/testing/api-helpers.ts +1222 -0
  242. package/src/testing/exec-async.ts +67 -0
  243. package/src/testing/harness.snapshot.sql +3895 -0
  244. package/src/testing/harness.test.ts +15 -0
  245. package/src/testing/harness.ts +1160 -0
  246. package/src/testing/kafka.ts +221 -0
  247. package/src/testing/prng.ts +82 -0
  248. package/src/testing/rdkafka.test.ts +263 -0
  249. package/src/testing/run.ts +559 -0
  250. package/src/testing/snapshot.test.ts +92 -0
  251. package/src/testing/snapshot.ts +549 -0
  252. package/src/testing/tap-stream.ts +164 -0
  253. package/src/testing/types.ts +95 -0
  254. package/src/testing/util.ts +333 -0
  255. package/src/types/central-services-error-handling.d.ts +72 -0
  256. package/src/types/central-services-stream.d.ts +148 -0
  257. package/test-integration.Dockerfile +0 -2
  258. package/test.Dockerfile +0 -2
  259. package/tsconfig.json +40 -0
  260. package/config/knexfile.js +0 -19
  261. package/migrations/950107_settlementWindowContent_foreign_settlementModel.js +0 -63
  262. package/src/api/index.js +0 -16
  263. package/src/lib/config.js +0 -81
  264. /package/{migrations → src/migrations}/110101_contactType-indexes.js +0 -0
  265. /package/{migrations → src/migrations}/110301_endpointType-indexes.js +0 -0
  266. /package/{migrations → src/migrations}/110401_ledgerEntryType-indexes.js +0 -0
  267. /package/{migrations → src/migrations}/110451_ledgerAccountType-indexes.js +0 -0
  268. /package/{migrations → src/migrations}/110501_participantLimitType-indexes.js +0 -0
  269. /package/{migrations → src/migrations}/110601_transferParticipantRoleType-indexes.js +0 -0
  270. /package/{migrations → src/migrations}/110800_settlementWindowState.js +0 -0
  271. /package/{migrations → src/migrations}/111000_amountType.js +0 -0
  272. /package/{migrations → src/migrations}/111001_amountType-indexes.js +0 -0
  273. /package/{migrations → src/migrations}/111100_balanceOfPayments.js +0 -0
  274. /package/{migrations → src/migrations}/111101_balanceOfPayments-indexes.js +0 -0
  275. /package/{migrations → src/migrations}/111200_partyIdentifierType.js +0 -0
  276. /package/{migrations → src/migrations}/111201_partyIdentifierType-indexes.js +0 -0
  277. /package/{migrations → src/migrations}/111300_partyType.js +0 -0
  278. /package/{migrations → src/migrations}/111301_partyType-indexes.js +0 -0
  279. /package/{migrations → src/migrations}/111400_quoteDuplicateCheck.js +0 -0
  280. /package/{migrations → src/migrations}/111500_transactionInitiator.js +0 -0
  281. /package/{migrations → src/migrations}/111501_transactionInitiator-indexes.js +0 -0
  282. /package/{migrations → src/migrations}/111600_transactionInitiatorType.js +0 -0
  283. /package/{migrations → src/migrations}/111601_transactionInitiatorType-indexes.js +0 -0
  284. /package/{migrations → src/migrations}/111700_settlementDelay.js +0 -0
  285. /package/{migrations → src/migrations}/111701_settlementDelay-indexes.js +0 -0
  286. /package/{migrations → src/migrations}/111800_settlementInterchange.js +0 -0
  287. /package/{migrations → src/migrations}/111801_settlementInterchange-indexes.js +0 -0
  288. /package/{migrations → src/migrations}/111900_settlementGranularity.js +0 -0
  289. /package/{migrations → src/migrations}/111901_settlementGranularity-indexes.js +0 -0
  290. /package/{migrations → src/migrations}/112101_bulkProcessingState-indexes.js +0 -0
  291. /package/{migrations → src/migrations}/200101_participant-indexes.js +0 -0
  292. /package/{migrations → src/migrations}/200201_participantContact-indexes.js +0 -0
  293. /package/{migrations → src/migrations}/200301_participantEndpoint-indexes.js +0 -0
  294. /package/{migrations → src/migrations}/200401_participantParty-indexes.js +0 -0
  295. /package/{migrations → src/migrations}/300201_transfer-indexes.js +0 -0
  296. /package/{migrations → src/migrations}/300251_bulkTransfer-indexes.js +0 -0
  297. /package/{migrations → src/migrations}/300276_bulkTransferAssociation-indexes.js +0 -0
  298. /package/{migrations → src/migrations}/300401_transferStateChange-indexes.js +0 -0
  299. /package/{migrations → src/migrations}/300451_bulkTransferStateChange-indexes.js +0 -0
  300. /package/{migrations → src/migrations}/300501_segment-indexes.js +0 -0
  301. /package/{migrations → src/migrations}/310101_participantCurrency-indexes.js +0 -0
  302. /package/{migrations → src/migrations}/310201_transferParticipant-indexes.js +0 -0
  303. /package/{migrations → src/migrations}/310203_transferParticipant-indexes.js +0 -0
  304. /package/{migrations → src/migrations}/310301_participantPosition-indexes.js +0 -0
  305. /package/{migrations → src/migrations}/310401_participantPositionChange-indexes.js +0 -0
  306. /package/{migrations → src/migrations}/310405_participantPositionChange-positionChange.js +0 -0
  307. /package/{migrations → src/migrations}/310406_participantPositionChange-positionChange-revert.js +0 -0
  308. /package/{migrations → src/migrations}/310501_participantLimit-indexes.js +0 -0
  309. /package/{migrations → src/migrations}/310503_participantLimit-indexes-composite.js +0 -0
  310. /package/{migrations → src/migrations}/400401_settlmentWindowStateChange-indexes.js +0 -0
  311. /package/{migrations → src/migrations}/400501_settlementTransferParticipant-indexes.js +0 -0
  312. /package/{migrations → src/migrations}/400601_settlementParticipantCurrency-indexes.js +0 -0
  313. /package/{migrations → src/migrations}/400701_settlementParticipantCurrencyStateChange-indexes.js +0 -0
  314. /package/{migrations → src/migrations}/400801_settlementStateChange-indexes.js +0 -0
  315. /package/{migrations → src/migrations}/401002_settlementWindowContent-indexes.js +0 -0
  316. /package/{migrations → src/migrations}/401201_settlementContentAggregation-indexes.js +0 -0
  317. /package/{migrations → src/migrations}/401301_settlementModel-indexes.js +0 -0
  318. /package/{migrations → src/migrations}/410101_transferFulfilmentDuplicateCheck-indexes.js +0 -0
  319. /package/{migrations → src/migrations}/410201_transferFulfilment-indexes.js +0 -0
  320. /package/{migrations → src/migrations}/410301_transferExtension-indexes.js +0 -0
  321. /package/{migrations → src/migrations}/410351_bulkTransferExtension-indexes.js +0 -0
  322. /package/{migrations → src/migrations}/410401_transferTimeout-indexes.js +0 -0
  323. /package/{migrations → src/migrations}/410501_transferError-indexes.js +0 -0
  324. /package/{migrations → src/migrations}/410551_bulkTransferError-indexes.js +0 -0
  325. /package/{migrations → src/migrations}/410601_expiringTransfer-indexes.js +0 -0
  326. /package/{migrations → src/migrations}/500100_transactionReference.js +0 -0
  327. /package/{migrations → src/migrations}/500101_transactionReference-indexes.js +0 -0
  328. /package/{migrations → src/migrations}/500200_transactionScenario.js +0 -0
  329. /package/{migrations → src/migrations}/500201_transactionScenario-indexes.js +0 -0
  330. /package/{migrations → src/migrations}/500300_transactionSubScenario.js +0 -0
  331. /package/{migrations → src/migrations}/500400_quote.js +0 -0
  332. /package/{migrations → src/migrations}/500500_quoteParty.js +0 -0
  333. /package/{migrations → src/migrations}/500600_party.js +0 -0
  334. /package/{migrations → src/migrations}/500700_quoteResponse.js +0 -0
  335. /package/{migrations → src/migrations}/500800_quoteResponseIlpPacket.js +0 -0
  336. /package/{migrations → src/migrations}/500900_geoCode.js +0 -0
  337. /package/{migrations → src/migrations}/501000_quoteExtension.js +0 -0
  338. /package/{migrations → src/migrations}/501100_quoteResponseDuplicateCheck.js +0 -0
  339. /package/{migrations → src/migrations}/501200_quoteError.js +0 -0
  340. /package/{migrations → src/migrations}/501300_transferRules.js +0 -0
  341. /package/{migrations → src/migrations}/600011_fxTransferType-indexes.js +0 -0
  342. /package/{migrations → src/migrations}/600013_fxParticipantCurrencyType-indexes.js +0 -0
  343. /package/{migrations → src/migrations}/600201_fxTransfer-indexes.js +0 -0
  344. /package/{migrations → src/migrations}/600401_fxTransferStateChange-indexes.js +0 -0
  345. /package/{migrations → src/migrations}/600502_fxWatchList-indexes.js +0 -0
  346. /package/{migrations → src/migrations}/600601_fxTransferFulfilmentDuplicateCheck-indexes.js +0 -0
  347. /package/{migrations → src/migrations}/600701_fxTransferFulfilment-indexes.js +0 -0
  348. /package/{migrations → src/migrations}/601401_fxTransferTimeout-indexes.js +0 -0
  349. /package/{migrations → src/migrations}/601501_fxTransferError-indexes.js +0 -0
  350. /package/{migrations → src/migrations}/610201_fxTransferParticipant-indexes.js +0 -0
  351. /package/{migrations → src/migrations}/910102_hotfix954.js +0 -0
  352. /package/{migrations → src/migrations}/940100_quotePartyIdInfoExtension.js +0 -0
  353. /package/{migrations → src/migrations}/950100_settlementContentAggregation-decimal184.js +0 -0
  354. /package/{migrations → src/migrations}/950106_unique_settlement_model_ledger_account_currency.js +0 -0
  355. /package/{migrations → src/migrations}/950109_fxQuote.js +0 -0
  356. /package/{migrations → src/migrations}/950110_fxQuoteResponse.js +0 -0
  357. /package/{migrations → src/migrations}/950111_fxQuoteError.js +0 -0
  358. /package/{migrations → src/migrations}/950113_fxQuoteDuplicateCheck.js +0 -0
  359. /package/{migrations → src/migrations}/950114_fxQuoteResponseDuplicateCheck.js +0 -0
  360. /package/{migrations → src/migrations}/950115_fxQuoteConversionTerms.js +0 -0
  361. /package/{migrations → src/migrations}/950116_fxQuoteConversionTermsExtension.js +0 -0
  362. /package/{migrations → src/migrations}/950117_fxQuoteResponseConversionTerms.js +0 -0
  363. /package/{migrations → src/migrations}/950118_fxQuoteResponseConversionTermsExtension.js +0 -0
  364. /package/{migrations → src/migrations}/950119_fxCharge.js +0 -0
  365. /package/{migrations → src/migrations}/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js +0 -0
  366. /package/{migrations → src/migrations}/960112_fxTransfer-indexes2.js +0 -0
  367. /package/{migrations → src/migrations}/960113_fxTransferExtension-indexes.js +0 -0
  368. /package/{migrations → src/migrations}/960114_fxTransferTimeout-indexes2.js +0 -0
  369. /package/{migrations → src/migrations}/961000_participantEndpoint_unique.js +0 -0
  370. /package/{migrations → src/migrations}/migration.template +0 -0
  371. /package/{seeds → src/seeds}/amountType.js +0 -0
  372. /package/{seeds → src/seeds}/balanceOfPayments.js +0 -0
  373. /package/{seeds → src/seeds}/bulkProcessingState.js +0 -0
  374. /package/{seeds → src/seeds}/bulkTransferState.js +0 -0
  375. /package/{seeds → src/seeds}/currency.js +0 -0
  376. /package/{seeds → src/seeds}/endpointType.js +0 -0
  377. /package/{seeds → src/seeds}/fxParticipantCurrencyType.js +0 -0
  378. /package/{seeds → src/seeds}/fxTransferType.js +0 -0
  379. /package/{seeds → src/seeds}/ledgerAccountType.js +0 -0
  380. /package/{seeds → src/seeds}/ledgerEntryType.js +0 -0
  381. /package/{seeds → src/seeds}/participantLimitType.js +0 -0
  382. /package/{seeds → src/seeds}/partyIdentifierType.js +0 -0
  383. /package/{seeds → src/seeds}/partyType.js +0 -0
  384. /package/{seeds → src/seeds}/settlementDelay.js +0 -0
  385. /package/{seeds → src/seeds}/settlementGranularity.js +0 -0
  386. /package/{seeds → src/seeds}/settlementState.js +0 -0
  387. /package/{seeds → src/seeds}/settlementWindow1State.js +0 -0
  388. /package/{seeds → src/seeds}/settlementWindow2Open.js +0 -0
  389. /package/{seeds → src/seeds}/transactionInitiator.js +0 -0
  390. /package/{seeds → src/seeds}/transactionInitiatorType.js +0 -0
  391. /package/{seeds → src/seeds}/transactionScenario.js +0 -0
  392. /package/{seeds → src/seeds}/transferParticipantRoleType.js +0 -0
  393. /package/{seeds → src/seeds}/transferState.js +0 -0
  394. /package/{seeds → src/seeds}/z1000_settlementModel-deprecated.js- +0 -0
@@ -0,0 +1,1652 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2025 Mojaloop Foundation
5
+ The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
10
+
11
+ Contributors
12
+ --------------
13
+ This is the official list of the Mojaloop project contributors for this file.
14
+ Names of the original copyright holders (individuals or organizations)
15
+ should be listed with a '*' in the first column. People who have
16
+ contributed from an organization can be listed under the organization
17
+ that actually holds the copyright for their contributions (see the
18
+ Mojaloop Foundation for an example). Those individuals should have
19
+ their names indented and be marked with a '-'. Email address can be added
20
+ optionally within square brackets <email>.
21
+
22
+ * Mojaloop Foundation
23
+ - Name Surname <name.surname@mojaloop.io>
24
+
25
+ * ModusBox
26
+ - Deon Botha <deon.botha@modusbox.com>
27
+ - Georgi Georgiev <georgi.georgiev@modusbox.com>
28
+ - Valentin Genev <valentin.genev@modusbox.com>
29
+ --------------
30
+ ******/
31
+ 'use strict'
32
+
33
+ const arrayDiff = require('lodash').difference
34
+ const ErrorHandler = require('@mojaloop/central-services-error-handling')
35
+ const MLNumber = require('@mojaloop/ml-number')
36
+ const Db = require('../../lib/db')
37
+ const idGenerator = require('@mojaloop/central-services-shared').Util.id
38
+ const Crypto = require('crypto')
39
+ const Config = require('../../../lib/config')
40
+
41
+ const ParticipantFacade = require('../../../models/participant/facade')
42
+ const Utility = require('../../lib/utility')
43
+ const SettlementModelModel = require('./settlementModel')
44
+ const { logger } = require('../../shared/logger')
45
+ const generateULID = idGenerator({ type: 'ulid' })
46
+
47
+ const groupByWindowsWithContent = (records) => {
48
+ const settlementWindowsAssoc = {}
49
+ for (const record of records) {
50
+ const id = record.settlementWindowId
51
+ if (id in settlementWindowsAssoc) {
52
+ settlementWindowsAssoc[id].content.push({
53
+ id: record.settlementWindowContentId,
54
+ state: record.state,
55
+ ledgerAccountType: record.ledgerAccountType,
56
+ currencyId: record.currencyId,
57
+ createdDate: record.createdDate,
58
+ changedDate: record.changedDate
59
+ })
60
+ } else {
61
+ settlementWindowsAssoc[id] = {
62
+ id,
63
+ state: record.settlementWindowStateId,
64
+ reason: record.reason,
65
+ createdDate: record.createdDate1,
66
+ changedDate: record.changedDate1,
67
+ content: [{
68
+ id: record.settlementWindowContentId,
69
+ state: record.state,
70
+ ledgerAccountType: record.ledgerAccountType,
71
+ currencyId: record.currencyId,
72
+ createdDate: record.createdDate,
73
+ changedDate: record.changedDate
74
+ }]
75
+ }
76
+ }
77
+ }
78
+ const settlementWindows = []
79
+ for (const key of Object.keys(settlementWindowsAssoc)) {
80
+ settlementWindows.push(settlementWindowsAssoc[key])
81
+ }
82
+ return settlementWindows
83
+ }
84
+
85
+ const getNotificationMessage = function (action, destination, payload) {
86
+ return {
87
+ id: generateULID(),
88
+ from: Config.HUB_NAME,
89
+ to: destination,
90
+ type: 'application/json',
91
+ content: {
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ Date: new Date().toISOString(),
95
+ 'FSPIOP-Source': Config.HUB_NAME,
96
+ 'FSPIOP-Destination': destination
97
+ },
98
+ payload
99
+ },
100
+ metadata: {
101
+ event: {
102
+ action
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * @param enums.ledgerAccountTypes.HUB_MULTILATERAL_SETTLEMENT
110
+ * @param enums.ledgerEntryTypes
111
+ * @param enums.participantLimitTypes
112
+ * @param enums.settlementStates.PS_TRANSFERS_RECORDED
113
+ * @param enums.settlementStates.PS_TRANSFERS_RESERVED
114
+ * @param enums.settlementStates.PS_TRANSFERS_COMMITTED
115
+ * @param enums.transferParticipantRoleTypes
116
+ * @param enums.transferParticipantRoleTypes.DFSP_POSITION
117
+ * @param enums.transferParticipantRoleTypes.HUB
118
+ * @param enums.transferStates
119
+ */
120
+ const settlementTransfersPrepare = async function (settlementId, transactionTimestamp, enums, trx = null) {
121
+ const knex = await Db.getKnex()
122
+ let t // see (t of settlementTransferList) below
123
+
124
+ // Retrieve list of PS_TRANSFERS_RECORDED, but not RECEIVED_PREPARE
125
+ const settlementTransferList = await knex('settlementParticipantCurrency AS spc')
126
+ .join('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyId', 'spc.settlementParticipantCurrencyId')
127
+ .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'spc.participantCurrencyId')
128
+ .leftJoin('transferDuplicateCheck AS tdc', 'tdc.transferId', 'spc.settlementTransferId')
129
+ .select('spc.*', 'pc.currencyId', 'pc.participantId')
130
+ .where('spc.settlementId', settlementId)
131
+ .where('spcsc.settlementStateId', enums.settlementStates.PS_TRANSFERS_RECORDED)
132
+ .whereNotNull('spc.settlementTransferId')
133
+ .whereNull('tdc.transferId')
134
+ .transacting(trx)
135
+
136
+ const trxFunction = async (trx) => {
137
+ try {
138
+ const hashSha256 = Crypto.createHash('sha256')
139
+ let hash = hashSha256.update(String(t.settlementTransferId))
140
+ hash = hashSha256.digest(hash).toString('base64').slice(0, -1) // removing the trailing '=' as per the specification
141
+ // Insert transferDuplicateCheck
142
+ await knex('transferDuplicateCheck')
143
+ .insert({
144
+ transferId: t.settlementTransferId,
145
+ hash,
146
+ createdDate: transactionTimestamp
147
+ })
148
+ .transacting(trx)
149
+
150
+ // Insert transfer
151
+ await knex('transfer')
152
+ .insert({
153
+ transferId: t.settlementTransferId,
154
+ amount: Math.abs(t.netAmount),
155
+ currencyId: t.currencyId,
156
+ ilpCondition: 0,
157
+ expirationDate: new Date(+new Date() + 1000 * Number(Config.INTERNAL_TRANSFER_VALIDITY_SECONDS)).toISOString().replace(/[TZ]/g, ' ').trim(),
158
+ createdDate: transactionTimestamp
159
+ })
160
+ .transacting(trx)
161
+
162
+ // Retrieve Hub mlns account
163
+ const { mlnsAccountId } = await knex('participantCurrency AS pc1')
164
+ .join('participantCurrency AS pc2', function () {
165
+ this.on('pc2.participantId', Config.HUB_ID)
166
+ .andOn('pc2.currencyId', 'pc1.currencyId')
167
+ .andOn('pc2.ledgerAccountTypeId', enums.ledgerAccountTypes.HUB_MULTILATERAL_SETTLEMENT)
168
+ .andOn('pc2.isActive', 1)
169
+ })
170
+ .select('pc2.participantCurrencyId AS mlnsAccountId')
171
+ .where('pc1.participantCurrencyId', t.participantCurrencyId)
172
+ .first()
173
+ .transacting(trx)
174
+
175
+ let ledgerEntryTypeId
176
+ if (t.netAmount < 0) {
177
+ ledgerEntryTypeId = enums.ledgerEntryTypes.SETTLEMENT_NET_RECIPIENT
178
+ } else if (t.netAmount > 0) {
179
+ ledgerEntryTypeId = enums.ledgerEntryTypes.SETTLEMENT_NET_SENDER
180
+ } else { // t.netAmount === 0
181
+ ledgerEntryTypeId = enums.ledgerEntryTypes.SETTLEMENT_NET_ZERO
182
+ }
183
+
184
+ // Insert transferParticipant records
185
+ await knex('transferParticipant')
186
+ .insert({
187
+ transferId: t.settlementTransferId,
188
+ participantCurrencyId: mlnsAccountId,
189
+ transferParticipantRoleTypeId: enums.transferParticipantRoleTypes.HUB,
190
+ ledgerEntryTypeId,
191
+ amount: t.netAmount,
192
+ createdDate: transactionTimestamp,
193
+ participantId: Config.HUB_ID
194
+ })
195
+ .transacting(trx)
196
+ await knex('transferParticipant')
197
+ .insert({
198
+ transferId: t.settlementTransferId,
199
+ participantCurrencyId: t.participantCurrencyId,
200
+ transferParticipantRoleTypeId: enums.transferParticipantRoleTypes.DFSP_POSITION,
201
+ ledgerEntryTypeId,
202
+ amount: -t.netAmount,
203
+ createdDate: transactionTimestamp,
204
+ participantId: t.participantId
205
+ })
206
+ .transacting(trx)
207
+
208
+ // Insert transferStateChange
209
+ await knex('transferStateChange')
210
+ .insert({
211
+ transferId: t.settlementTransferId,
212
+ transferStateId: enums.transferStates.RECEIVED_PREPARE,
213
+ reason: 'Settlement transfer prepare',
214
+ createdDate: transactionTimestamp
215
+ })
216
+ .transacting(trx)
217
+ } catch (err) {
218
+ logger.error(err)
219
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
220
+ }
221
+ }
222
+
223
+ for (t of settlementTransferList) {
224
+ if (trx) {
225
+ await trxFunction(trx)
226
+ } else {
227
+ await knex.transaction(trxFunction)
228
+ }
229
+ }
230
+ return 0
231
+ }
232
+
233
+ /**
234
+ * @param enums.ledgerAccountTypes.HUB_MULTILATERAL_SETTLEMENT
235
+ * @param enums.ledgerEntryTypes
236
+ * @param enums.participantLimitTypes
237
+ * @param enums.settlementStates.PS_TRANSFERS_RECORDED
238
+ * @param enums.settlementStates.PS_TRANSFERS_RESERVED
239
+ * @param enums.settlementStates.PS_TRANSFERS_COMMITTED
240
+ * @param enums.transferParticipantRoleTypes
241
+ * @param enums.transferParticipantRoleTypes.DFSP_POSITION
242
+ * @param enums.transferParticipantRoleTypes.HUB
243
+ * @param enums.transferStates
244
+ */
245
+ const settlementTransfersReserve = async function (settlementId, transactionTimestamp, requireLiquidityCheck, enums, trx = null) {
246
+ const knex = await Db.getKnex()
247
+ let isLimitExceeded, transferStateChangeId
248
+ // Retrieve list of PS_TRANSFERS_RESERVED, but not RESERVED
249
+ const settlementTransferList = await knex('settlementParticipantCurrency AS spc')
250
+ .join('settlementParticipantCurrencyStateChange AS spcsc', function () {
251
+ this.on('spcsc.settlementParticipantCurrencyId', 'spc.settlementParticipantCurrencyId')
252
+ .andOn('spcsc.settlementStateId', knex.raw('?', [enums.settlementStates.PS_TRANSFERS_RESERVED]))
253
+ })
254
+ .join('transferStateChange AS tsc1', function () {
255
+ this.on('tsc1.transferId', 'spc.settlementTransferId')
256
+ .andOn('tsc1.transferStateId', knex.raw('?', [enums.transferStates.RECEIVED_PREPARE]))
257
+ })
258
+ .leftJoin('transferStateChange AS tsc2', function () {
259
+ this.on('tsc2.transferId', 'spc.settlementTransferId')
260
+ .andOn('tsc2.transferStateId', knex.raw('?', [enums.transferStates.RESERVED]))
261
+ })
262
+ .join('transferParticipant AS tp1', function () {
263
+ this.on('tp1.transferId', 'spc.settlementTransferId')
264
+ .andOn('tp1.transferParticipantRoleTypeId', knex.raw('?', [enums.transferParticipantRoleTypes.DFSP_POSITION]))
265
+ })
266
+ .join('participantCurrency AS pc1', 'pc1.participantCurrencyId', 'tp1.participantCurrencyId')
267
+ .join('participant AS p1', 'p1.participantId', 'pc1.participantId')
268
+ .join('transferParticipant AS tp2', function () {
269
+ this.on('tp2.transferId', 'spc.settlementTransferId')
270
+ .andOn('tp2.transferParticipantRoleTypeId', knex.raw('?', [enums.transferParticipantRoleTypes.HUB]))
271
+ })
272
+ .select('tp1.transferId', 'tp1.ledgerEntryTypeId', 'tp1.participantCurrencyId AS dfspAccountId', 'tp1.amount AS dfspAmount',
273
+ 'tp2.participantCurrencyId AS hubAccountId', 'tp2.amount AS hubAmount',
274
+ 'p1.name AS dfspName', 'pc1.currencyId')
275
+ .where('spc.settlementId', settlementId)
276
+ .whereNull('tsc2.transferId')
277
+ .transacting(trx)
278
+
279
+ const trxFunction = async (trx) => {
280
+ try {
281
+ for (const {
282
+ transferId, ledgerEntryTypeId, dfspAccountId, dfspAmount, hubAccountId, hubAmount,
283
+ dfspName, currencyId
284
+ } of settlementTransferList) {
285
+ // Persist transfer state change
286
+ transferStateChangeId = await knex('transferStateChange')
287
+ .insert({
288
+ transferId,
289
+ transferStateId: enums.transferStates.RESERVED,
290
+ reason: 'Settlement transfer reserve',
291
+ createdDate: transactionTimestamp
292
+ })
293
+ .transacting(trx)
294
+
295
+ if (ledgerEntryTypeId === enums.ledgerEntryTypes.SETTLEMENT_NET_RECIPIENT) {
296
+ // Select dfspPosition FOR UPDATE
297
+ const { dfspPositionId, dfspPositionValue, dfspReservedValue } = await knex('participantPosition')
298
+ .select('participantPositionId AS dfspPositionId', 'value AS dfspPositionValue', 'reservedValue AS dfspReservedValue')
299
+ .where('participantCurrencyId', dfspAccountId)
300
+ .first()
301
+ .transacting(trx)
302
+ .forUpdate()
303
+
304
+ if (requireLiquidityCheck) { // TODO: This is not a liquidity check, but rather a limit-check since we are only checking against the NED-DEBIT-CAP!
305
+ // Select dfsp NET_DEBIT_CAP limit
306
+ const { netDebitCap } = await knex('participantLimit')
307
+ .select('value AS netDebitCap')
308
+ .where('participantCurrencyId', dfspAccountId)
309
+ .andWhere('participantLimitTypeId', enums.participantLimitTypes.NET_DEBIT_CAP)
310
+ .first()
311
+ .transacting(trx)
312
+ .forUpdate()
313
+ isLimitExceeded = netDebitCap - dfspPositionValue - dfspReservedValue - dfspAmount < 0
314
+
315
+ if (isLimitExceeded) {
316
+ /* let { startAfterParticipantPositionChangeId } = */
317
+ await knex('participantPositionChange')
318
+ .select('participantPositionChangeId AS startAfterParticipantPositionChangeId')
319
+ .where('participantPositionId', dfspPositionId)
320
+ .orderBy('participantPositionChangeId', 'desc')
321
+ .first()
322
+ .transacting(trx)
323
+
324
+ // TODO:: notify dfsp for NDC change
325
+ // TODO:: insert new limit with correct value for startAfterParticipantPositionChangeId
326
+ await ParticipantFacade.adjustLimits(dfspAccountId, {
327
+ type: 'NET_DEBIT_CAP',
328
+ value: new MLNumber(netDebitCap).add(dfspAmount).toNumber()
329
+ }, trx)
330
+ }
331
+ }
332
+
333
+ // Persist dfsp latestPosition
334
+ await knex('participantPosition')
335
+ .update('value', new MLNumber(dfspPositionValue).add(dfspAmount).toNumber())
336
+ .where('participantPositionId', dfspPositionId)
337
+ .transacting(trx)
338
+
339
+ // Persist dfsp position change
340
+ await knex('participantPositionChange')
341
+ .insert({
342
+ participantPositionId: dfspPositionId,
343
+ participantCurrencyId: dfspAccountId,
344
+ transferStateChangeId,
345
+ value: new MLNumber(dfspPositionValue).add(dfspAmount).toNumber(),
346
+ change: new MLNumber(dfspAmount).toNumber(),
347
+ reservedValue: dfspReservedValue,
348
+ createdDate: transactionTimestamp
349
+ })
350
+ .transacting(trx)
351
+
352
+ // Send notification for position change
353
+ const action = 'settlement-transfer-position-change'
354
+ const destination = dfspName
355
+ const payload = {
356
+ currency: currencyId,
357
+ value: new MLNumber(dfspPositionValue).add(dfspAmount).toNumber(),
358
+ changedDate: new Date().toISOString()
359
+ }
360
+ const message = Facade.getNotificationMessage(action, destination, payload)
361
+ await Utility.produceGeneralMessage(Utility.ENUMS.NOTIFICATION, Utility.ENUMS.EVENT, message, Utility.ENUMS.STATE.SUCCESS)
362
+
363
+ // Select hubPosition FOR UPDATE
364
+ const { hubPositionId, hubPositionValue } = await knex('participantPosition')
365
+ .select('participantPositionId AS hubPositionId', 'value AS hubPositionValue')
366
+ .where('participantCurrencyId', hubAccountId)
367
+ .first()
368
+ .transacting(trx)
369
+ .forUpdate()
370
+
371
+ // Persist hub latestPosition
372
+ await knex('participantPosition')
373
+ .update('value', new MLNumber(hubPositionValue).add(hubAmount).toNumber())
374
+ .where('participantPositionId', hubPositionId)
375
+ .transacting(trx)
376
+
377
+ // Persist hub position change
378
+ await knex('participantPositionChange')
379
+ .insert({
380
+ participantPositionId: hubPositionId,
381
+ participantCurrencyId: hubAccountId,
382
+ transferStateChangeId,
383
+ value: new MLNumber(hubPositionValue).add(hubAmount).toNumber(),
384
+ change: new MLNumber(hubAmount).toNumber(),
385
+ reservedValue: 0,
386
+ createdDate: transactionTimestamp
387
+ })
388
+ .transacting(trx)
389
+ }
390
+ }
391
+ } catch (err) {
392
+ logger.error(err)
393
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
394
+ }
395
+ }
396
+
397
+ if (trx) {
398
+ await trxFunction(trx)
399
+ } else {
400
+ await knex.transaction(trxFunction)
401
+ }
402
+ return 0
403
+ }
404
+
405
+ /**
406
+ * @param enums.ledgerAccountTypes.HUB_MULTILATERAL_SETTLEMENT
407
+ * @param enums.ledgerEntryTypes
408
+ * @param enums.participantLimitTypes
409
+ * @param enums.settlementStates.PS_TRANSFERS_RECORDED
410
+ * @param enums.settlementStates.PS_TRANSFERS_RESERVED
411
+ * @param enums.settlementStates.PS_TRANSFERS_COMMITTED
412
+ * @param enums.transferParticipantRoleTypes
413
+ * @param enums.transferParticipantRoleTypes.DFSP_POSITION
414
+ * @param enums.transferParticipantRoleTypes.HUB
415
+ * @param enums.transferStates
416
+ */
417
+ const settlementTransfersAbort = async function (settlementId, transactionTimestamp, enums, trx = null) {
418
+ const knex = await Db.getKnex()
419
+ let transferStateChangeId
420
+
421
+ // Retrieve list of ABORTED, but not ABORTED
422
+ const settlementTransferList = await knex('settlementParticipantCurrency AS spc')
423
+ .join('settlementParticipantCurrencyStateChange AS spcsc', function () {
424
+ this.on('spcsc.settlementParticipantCurrencyId', 'spc.settlementParticipantCurrencyId')
425
+ .andOn('spcsc.settlementStateId', knex.raw('?', [enums.settlementStates.ABORTED]))
426
+ })
427
+ .leftJoin('transferStateChange AS tsc1', 'tsc1.transferId', 'spc.settlementTransferId')
428
+ .leftJoin('transferState AS ts1', function () {
429
+ this.on('ts1.transferStateId', 'tsc1.transferStateId')
430
+ .andOn('ts1.enumeration', knex.raw('?', [enums.transferStateEnums.RESERVED]))
431
+ })
432
+ .leftJoin('transferStateChange AS tsc2', 'tsc2.transferId', 'spc.settlementTransferId')
433
+ .leftJoin('transferState AS ts2', function () {
434
+ this.on('ts2.transferStateId', 'tsc2.transferStateId')
435
+ .andOn('ts2.enumeration', knex.raw('?', [enums.transferStateEnums.ABORTED]))
436
+ })
437
+ .join('transferParticipant AS tp1', function () {
438
+ this.on('tp1.transferId', 'spc.settlementTransferId')
439
+ .andOn('tp1.transferParticipantRoleTypeId', knex.raw('?', [enums.transferParticipantRoleTypes.DFSP_POSITION]))
440
+ })
441
+ .join('participantCurrency AS pc1', 'pc1.participantCurrencyId', 'tp1.participantCurrencyId')
442
+ .join('participant AS p1', 'p1.participantId', 'pc1.participantId')
443
+ .join('transferParticipant AS tp2', function () {
444
+ this.on('tp2.transferId', 'spc.settlementTransferId')
445
+ .andOn('tp2.transferParticipantRoleTypeId', knex.raw('?', [enums.transferParticipantRoleTypes.HUB]))
446
+ })
447
+ .select('tp1.transferId', 'tp1.ledgerEntryTypeId', 'tp1.participantCurrencyId AS dfspAccountId', 'tp1.amount AS dfspAmount',
448
+ 'tp2.participantCurrencyId AS hubAccountId', 'tp2.amount AS hubAmount', 'tsc1.transferId AS isReserved',
449
+ 'p1.name AS dfspName', 'pc1.currencyId')
450
+ .where('spc.settlementId', settlementId)
451
+ .whereNull('tsc2.transferId')
452
+ .transacting(trx)
453
+
454
+ const trxFunction = async (trx) => {
455
+ try {
456
+ for (const {
457
+ transferId, ledgerEntryTypeId, dfspAccountId, dfspAmount, hubAccountId, hubAmount, isReserved,
458
+ dfspName, currencyId
459
+ } of settlementTransferList) {
460
+ // Persist transfer state change
461
+ await knex('transferStateChange')
462
+ .insert({
463
+ transferId,
464
+ transferStateId: enums.transferStates.REJECTED,
465
+ reason: 'Settlement transfer reject',
466
+ createdDate: transactionTimestamp
467
+ })
468
+ .transacting(trx)
469
+ transferStateChangeId = await knex('transferStateChange')
470
+ .insert({
471
+ transferId,
472
+ transferStateId: enums.transferStates.ABORTED,
473
+ reason: 'Settlement transfer abort',
474
+ createdDate: transactionTimestamp
475
+ })
476
+ .transacting(trx)
477
+
478
+ if (isReserved !== null && ledgerEntryTypeId === enums.ledgerEntryTypes.SETTLEMENT_NET_RECIPIENT) {
479
+ // Select dfspPosition FOR UPDATE
480
+ const { dfspPositionId, dfspPositionValue, dfspReservedValue } = await knex('participantPosition')
481
+ .select('participantPositionId AS dfspPositionId', 'value AS dfspPositionValue', 'reservedValue AS dfspReservedValue')
482
+ .where('participantCurrencyId', dfspAccountId)
483
+ .first()
484
+ .transacting(trx)
485
+ .forUpdate()
486
+
487
+ // Persist dfsp latestPosition
488
+ await knex('participantPosition')
489
+ .update('value', dfspPositionValue - dfspAmount)
490
+ .where('participantPositionId', dfspPositionId)
491
+ .transacting(trx)
492
+
493
+ // Persist dfsp position change
494
+ await knex('participantPositionChange')
495
+ .insert({
496
+ participantPositionId: dfspPositionId,
497
+ participantCurrencyId: dfspAccountId,
498
+ transferStateChangeId,
499
+ value: dfspPositionValue - dfspAmount,
500
+ change: new MLNumber(dfspAmount).toNumber(),
501
+ reservedValue: dfspReservedValue,
502
+ createdDate: transactionTimestamp
503
+ })
504
+ .transacting(trx)
505
+
506
+ // Send notification for position change
507
+ const action = 'settlement-transfer-position-change'
508
+ const destination = dfspName
509
+ const payload = {
510
+ currency: currencyId,
511
+ value: dfspPositionValue - dfspAmount,
512
+ changedDate: new Date().toISOString()
513
+ }
514
+ const message = Facade.getNotificationMessage(action, destination, payload)
515
+ await Utility.produceGeneralMessage(Utility.ENUMS.NOTIFICATION, Utility.ENUMS.EVENT, message, Utility.ENUMS.STATE.SUCCESS)
516
+
517
+ // Select hubPosition FOR UPDATE
518
+ const { hubPositionId, hubPositionValue } = await knex('participantPosition')
519
+ .select('participantPositionId AS hubPositionId', 'value AS hubPositionValue')
520
+ .where('participantCurrencyId', hubAccountId)
521
+ .first()
522
+ .transacting(trx)
523
+ .forUpdate()
524
+
525
+ // Persist hub latestPosition
526
+ await knex('participantPosition')
527
+ .update('value', hubPositionValue - hubAmount)
528
+ .where('participantPositionId', hubPositionId)
529
+ .transacting(trx)
530
+
531
+ // Persist hub position change
532
+ await knex('participantPositionChange')
533
+ .insert({
534
+ participantPositionId: hubPositionId,
535
+ participantCurrencyId: hubAccountId,
536
+ transferStateChangeId,
537
+ value: hubPositionValue - hubAmount,
538
+ change: new MLNumber(hubAmount).toNumber(),
539
+ reservedValue: 0,
540
+ createdDate: transactionTimestamp
541
+ })
542
+ .transacting(trx)
543
+ }
544
+ }
545
+ } catch (err) {
546
+ logger.error(err)
547
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
548
+ }
549
+ }
550
+
551
+ if (trx) {
552
+ await trxFunction(trx)
553
+ } else {
554
+ await knex.transaction(trxFunction)
555
+ }
556
+ return 0
557
+ }
558
+
559
+ /**
560
+ * @param enums.ledgerAccountTypes.HUB_MULTILATERAL_SETTLEMENT
561
+ * @param enums.ledgerEntryTypes
562
+ * @param enums.participantLimitTypes
563
+ * @param enums.settlementStates.PS_TRANSFERS_RECORDED
564
+ * @param enums.settlementStates.PS_TRANSFERS_RESERVED
565
+ * @param enums.settlementStates.PS_TRANSFERS_COMMITTED
566
+ * @param enums.transferParticipantRoleTypes
567
+ * @param enums.transferParticipantRoleTypes.DFSP_POSITION
568
+ * @param enums.transferParticipantRoleTypes.HUB
569
+ * @param enums.transferStates
570
+ */
571
+ const settlementTransfersCommit = async function (settlementId, transactionTimestamp, enums, trx = null) {
572
+ const knex = await Db.getKnex()
573
+ let transferStateChangeId
574
+
575
+ // Retrieve list of PS_TRANSFERS_COMMITTED, but not COMMITTED
576
+ const settlementTransferList = await knex('settlementParticipantCurrency AS spc')
577
+ .join('settlementParticipantCurrencyStateChange AS spcsc', function () {
578
+ this.on('spcsc.settlementParticipantCurrencyId', 'spc.settlementParticipantCurrencyId')
579
+ .andOn('spcsc.settlementStateId', knex.raw('?', [enums.settlementStates.PS_TRANSFERS_COMMITTED]))
580
+ })
581
+ .join('transferStateChange AS tsc1', function () {
582
+ this.on('tsc1.transferId', 'spc.settlementTransferId')
583
+ .andOn('tsc1.transferStateId', knex.raw('?', [enums.transferStates.RESERVED]))
584
+ })
585
+ .leftJoin('transferStateChange AS tsc2', function () {
586
+ this.on('tsc2.transferId', 'spc.settlementTransferId')
587
+ .andOn('tsc2.transferStateId', knex.raw('?', [enums.transferStates.COMMITTED]))
588
+ })
589
+ .join('transferParticipant AS tp1', function () {
590
+ this.on('tp1.transferId', 'spc.settlementTransferId')
591
+ .andOn('tp1.transferParticipantRoleTypeId', knex.raw('?', [enums.transferParticipantRoleTypes.DFSP_POSITION]))
592
+ })
593
+ .join('participantCurrency AS pc1', 'pc1.participantCurrencyId', 'tp1.participantCurrencyId')
594
+ .join('participant AS p1', 'p1.participantId', 'pc1.participantId')
595
+ .join('transferParticipant AS tp2', function () {
596
+ this.on('tp2.transferId', 'spc.settlementTransferId')
597
+ .andOn('tp2.transferParticipantRoleTypeId', knex.raw('?', [enums.transferParticipantRoleTypes.HUB]))
598
+ })
599
+ .select('tp1.transferId', 'tp1.ledgerEntryTypeId', 'tp1.participantCurrencyId AS dfspAccountId', 'tp1.amount AS dfspAmount',
600
+ 'tp2.participantCurrencyId AS hubAccountId', 'tp2.amount AS hubAmount',
601
+ 'p1.name AS dfspName', 'pc1.currencyId')
602
+ .where('spc.settlementId', settlementId)
603
+ .whereNull('tsc2.transferId')
604
+ .transacting(trx)
605
+
606
+ const trxFunction = async (trx) => {
607
+ try {
608
+ for (const {
609
+ transferId, ledgerEntryTypeId, dfspAccountId, dfspAmount, hubAccountId, hubAmount,
610
+ dfspName, currencyId
611
+ } of settlementTransferList) {
612
+ // Persist transfer fulfilment and transfer state change
613
+ await knex('transferFulfilmentDuplicateCheck')
614
+ .insert({
615
+ transferId
616
+ })
617
+ .transacting(trx)
618
+
619
+ await knex('transferFulfilment')
620
+ .insert({
621
+ transferId,
622
+ ilpFulfilment: 0,
623
+ completedDate: transactionTimestamp,
624
+ isValid: 1,
625
+ settlementWindowId: null,
626
+ createdDate: transactionTimestamp
627
+ })
628
+ .transacting(trx)
629
+
630
+ await knex('transferStateChange')
631
+ .insert({
632
+ transferId,
633
+ transferStateId: enums.transferStates.RECEIVED_FULFIL,
634
+ reason: 'Settlement transfer commit initiated',
635
+ createdDate: transactionTimestamp
636
+ })
637
+ .transacting(trx)
638
+
639
+ transferStateChangeId = await knex('transferStateChange')
640
+ .insert({
641
+ transferId,
642
+ transferStateId: enums.transferStates.COMMITTED,
643
+ reason: 'Settlement transfer commit',
644
+ createdDate: transactionTimestamp
645
+ })
646
+ .transacting(trx)
647
+
648
+ if (ledgerEntryTypeId === enums.ledgerEntryTypes.SETTLEMENT_NET_SENDER) {
649
+ // Select dfspPosition FOR UPDATE
650
+ const { dfspPositionId, dfspPositionValue, dfspReservedValue } = await knex('participantPosition')
651
+ .select('participantPositionId AS dfspPositionId', 'value AS dfspPositionValue', 'reservedValue AS dfspReservedValue')
652
+ .where('participantCurrencyId', dfspAccountId)
653
+ .first()
654
+ .transacting(trx)
655
+ .forUpdate()
656
+
657
+ // Persist dfsp latestPosition
658
+ await knex('participantPosition')
659
+ .update('value', new MLNumber(dfspPositionValue).add(dfspAmount).toNumber())
660
+ .where('participantPositionId', dfspPositionId)
661
+ .transacting(trx)
662
+
663
+ // Persist dfsp position change
664
+ await knex('participantPositionChange')
665
+ .insert({
666
+ participantPositionId: dfspPositionId,
667
+ participantCurrencyId: dfspAccountId,
668
+ transferStateChangeId,
669
+ value: new MLNumber(dfspPositionValue).add(dfspAmount).toNumber(),
670
+ change: new MLNumber(dfspAmount).toNumber(),
671
+ reservedValue: dfspReservedValue,
672
+ createdDate: transactionTimestamp
673
+ })
674
+ .transacting(trx)
675
+
676
+ // Select hubPosition FOR UPDATE
677
+ const { hubPositionId, hubPositionValue } = await knex('participantPosition')
678
+ .select('participantPositionId AS hubPositionId', 'value AS hubPositionValue')
679
+ .where('participantCurrencyId', hubAccountId)
680
+ .first()
681
+ .transacting(trx)
682
+ .forUpdate()
683
+
684
+ // Persist hub latestPosition
685
+ await knex('participantPosition')
686
+ .update('value', new MLNumber(hubPositionValue).add(hubAmount).toNumber())
687
+ .where('participantPositionId', hubPositionId)
688
+ .transacting(trx)
689
+
690
+ // Persist hub position change
691
+ await knex('participantPositionChange')
692
+ .insert({
693
+ participantPositionId: hubPositionId,
694
+ participantCurrencyId: hubAccountId,
695
+ transferStateChangeId,
696
+ value: new MLNumber(hubPositionValue).add(hubAmount).toNumber(),
697
+ change: new MLNumber(hubAmount).toNumber(),
698
+ reservedValue: 0,
699
+ createdDate: transactionTimestamp
700
+ })
701
+ .transacting(trx)
702
+
703
+ // Send notification for position change
704
+ const action = 'settlement-transfer-position-change'
705
+ const destination = dfspName
706
+ const payload = {
707
+ currency: currencyId,
708
+ value: new MLNumber(dfspPositionValue).add(dfspAmount).toNumber(),
709
+ changedDate: new Date().toISOString()
710
+ }
711
+ const message = Facade.getNotificationMessage(action, destination, payload)
712
+ await Utility.produceGeneralMessage(Utility.ENUMS.NOTIFICATION, Utility.ENUMS.EVENT, message, Utility.ENUMS.STATE.SUCCESS)
713
+ }
714
+ }
715
+ } catch (err) {
716
+ logger.error(err)
717
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
718
+ }
719
+ }
720
+
721
+ if (trx) {
722
+ await trxFunction(trx)
723
+ } else {
724
+ await knex.transaction(trxFunction)
725
+ }
726
+ return 0
727
+ }
728
+
729
+ const abortByIdStateAborted = async (settlementId, payload, enums) => {
730
+ const knex = await Db.getKnex()
731
+ // seq-settlement-6.2.6, step 5
732
+ const settlementStateChangeId = await knex('settlementStateChange')
733
+ .insert({
734
+ settlementId,
735
+ settlementStateId: enums.settlementStates.ABORTED,
736
+ reason: payload.reason
737
+ })
738
+ // seq-settlement-6.2.6, step 5a
739
+ await knex('settlement')
740
+ .where('settlementId', settlementId)
741
+ .update({ currentStateChangeId: settlementStateChangeId[0] })
742
+
743
+ return {
744
+ id: settlementId,
745
+ state: payload.state,
746
+ reason: payload.reason
747
+ }
748
+ }
749
+
750
+ const getTransferCommitedAccount = async (settlementId, enums) => {
751
+ const knex = await Db.getKnex()
752
+ // seq-settlement-6.2.6, step 6
753
+ return await knex('settlementParticipantCurrency AS spc')
754
+ .join('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'spc.currentStateChangeId')
755
+ .where('spc.settlementId', settlementId)
756
+ .where('spcsc.settlementStateId', enums.settlementStates.PS_TRANSFERS_COMMITTED)
757
+ .first()
758
+ }
759
+
760
+ const Facade = {
761
+
762
+ abortByIdStateAborted,
763
+ getTransferCommitedAccount,
764
+ getNotificationMessage,
765
+ settlementTransfersPrepare,
766
+ settlementTransfersReserve,
767
+ settlementTransfersAbort,
768
+ settlementTransfersCommit,
769
+
770
+ /**
771
+ * @param enums.ledgerAccountTypes.HUB_MULTILATERAL_SETTLEMENT
772
+ * @param enums.ledgerEntryTypes
773
+ * @param enums.participantLimitTypes
774
+ * @param enums.settlementStates.PS_TRANSFERS_RECORDED
775
+ * @param enums.settlementStates.PS_TRANSFERS_RESERVED
776
+ * @param enums.settlementStates.PS_TRANSFERS_COMMITTED
777
+ * @param enums.settlementStates.SETTLING
778
+ * @param enums.settlementWindowStates
779
+ * @param enums.transferParticipantRoleTypes
780
+ * @param enums.transferParticipantRoleTypes.DFSP_POSITION
781
+ * @param enums.transferParticipantRoleTypes.HUB
782
+ * @param enums.transferStates
783
+ */
784
+ putById: async function (settlementId, payload, enums) {
785
+ const knex = await Db.getKnex()
786
+ return knex.transaction(async (trx) => {
787
+ try {
788
+ const transactionTimestamp = new Date().toISOString().replace(/[TZ]/g, ' ').trim()
789
+
790
+ // seq-settlement-6.2.5, step 3
791
+ const settlementData = await knex('settlement AS s')
792
+ .join('settlementStateChange AS ssc', 'ssc.settlementStateChangeId', 's.currentStateChangeId')
793
+ .join('settlementModel AS sm', 'sm.settlementModelId', 's.settlementModelId')
794
+ .select('s.settlementId', 'ssc.settlementStateId', 'ssc.reason', 'ssc.createdDate', 'sm.autoPositionReset', 'sm.requireLiquidityCheck')
795
+ .where('s.settlementId', settlementId)
796
+ .first()
797
+ .transacting(trx)
798
+ .forUpdate()
799
+
800
+ if (!settlementData) {
801
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.VALIDATION_ERROR, 'Settlement not found')
802
+ } else {
803
+ const autoPositionReset = settlementData.autoPositionReset
804
+ delete settlementData.autoPositionReset
805
+ const requireLiquidityCheck = settlementData.requireLiquidityCheck
806
+ delete settlementData.requireLiquidityCheck
807
+
808
+ // seq-settlement-6.2.5, step 5
809
+ const settlementAccountList = await knex('settlementParticipantCurrency AS spc')
810
+ .leftJoin('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'spc.currentStateChangeId')
811
+ .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'spc.participantCurrencyId')
812
+ .select('pc.participantId', 'spc.participantCurrencyId', 'spcsc.settlementStateId', 'spcsc.reason', 'spc.netAmount', 'pc.currencyId', 'spc.settlementParticipantCurrencyId AS key'
813
+ )
814
+ .where('spc.settlementId', settlementId)
815
+ .transacting(trx)
816
+ .forUpdate()
817
+
818
+ // seq-settlement-6.2.5, step 7
819
+ const settlementAccounts = {
820
+ pendingSettlementCount: 0,
821
+ psTransfersRecordedCount: 0,
822
+ psTransfersReservedCount: 0,
823
+ psTransfersCommittedCount: 0,
824
+ settledCount: 0,
825
+ abortedCount: 0,
826
+ unknownCount: 0,
827
+ settledIdList: [],
828
+ changedIdList: []
829
+ }
830
+ const allAccounts = new Map()
831
+
832
+ // seq-settlement-6.2.5, step 8
833
+ for (const account of settlementAccountList) {
834
+ const pid = account.participantId
835
+ const aid = account.participantCurrencyId
836
+ const state = account.settlementStateId
837
+ allAccounts[aid] = {
838
+ id: aid,
839
+ state,
840
+ reason: account.reason,
841
+ createDate: account.createdDate,
842
+ netSettlementAmount: {
843
+ amount: account.netAmount,
844
+ currency: account.currencyId
845
+ },
846
+ participantId: pid,
847
+ key: account.key
848
+ }
849
+
850
+ // seq-settlement-6.2.5, step 9
851
+ switch (state) {
852
+ case enums.settlementStates.PENDING_SETTLEMENT: {
853
+ settlementAccounts.pendingSettlementCount++
854
+ break
855
+ }
856
+ case enums.settlementStates.PS_TRANSFERS_RECORDED: {
857
+ settlementAccounts.psTransfersRecordedCount++
858
+ break
859
+ }
860
+ case enums.settlementStates.PS_TRANSFERS_RESERVED: {
861
+ settlementAccounts.psTransfersReservedCount++
862
+ break
863
+ }
864
+ case enums.settlementStates.PS_TRANSFERS_COMMITTED: {
865
+ settlementAccounts.psTransfersCommittedCount++
866
+ break
867
+ }
868
+ case enums.settlementStates.SETTLED: {
869
+ settlementAccounts.settledCount++
870
+ break
871
+ }
872
+ case enums.settlementStates.ABORTED: {
873
+ settlementAccounts.abortedCount++
874
+ break
875
+ }
876
+ default: {
877
+ settlementAccounts.unknownCount++
878
+ break
879
+ }
880
+ }
881
+ }
882
+ // seq-settlement-6.2.5, step 10
883
+ // let settlementAccountsInit = Object.assign({}, settlementAccounts)
884
+
885
+ // seq-settlement-6.2.5, step 10
886
+ const participants = []
887
+ const settlementParticipantCurrencyStateChange = []
888
+ const processedAccounts = []
889
+
890
+ // seq-settlement-6.2.5, step 11
891
+ for (let participant in payload.participants) {
892
+ const participantPayload = payload.participants[participant]
893
+ participants.push({ id: participantPayload.id, accounts: [] })
894
+ const pi = participants.length - 1
895
+ participant = participants[pi]
896
+ // seq-settlement-6.2.5, step 12
897
+ for (const account in participantPayload.accounts) {
898
+ const accountPayload = participantPayload.accounts[account]
899
+ // seq-settlement-6.2.5, step 13
900
+ if (allAccounts[accountPayload.id] === undefined) {
901
+ participant.accounts.push({
902
+ id: accountPayload.id,
903
+ errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Account not found').toApiErrorObject().errorInformation
904
+ })
905
+ // seq-settlement-6.2.5, step 14
906
+ } else if (participantPayload.id !== allAccounts[accountPayload.id].participantId) {
907
+ processedAccounts.push(accountPayload.id)
908
+ participant.accounts.push({
909
+ id: accountPayload.id,
910
+ errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Participant and account mismatch').toApiErrorObject().errorInformation
911
+ })
912
+ // seq-settlement-6.2.5, step 15
913
+ } else if (processedAccounts.indexOf(accountPayload.id) > -1) {
914
+ participant.accounts.push({
915
+ id: accountPayload.id,
916
+ state: allAccounts[accountPayload.id].state,
917
+ reason: allAccounts[accountPayload.id].reason,
918
+ createdDate: allAccounts[accountPayload.id].createdDate,
919
+ netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount,
920
+ errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'Account already processed once').toApiErrorObject().errorInformation
921
+ })
922
+ // seq-settlement-6.2.5, step 16
923
+ } else if (allAccounts[accountPayload.id].state === accountPayload.state) {
924
+ processedAccounts.push(accountPayload.id)
925
+ participant.accounts.push({
926
+ id: accountPayload.id,
927
+ state: accountPayload.state,
928
+ reason: accountPayload.reason,
929
+ externalReference: accountPayload.externalReference,
930
+ createdDate: transactionTimestamp,
931
+ netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount
932
+ })
933
+ settlementParticipantCurrencyStateChange.push({
934
+ settlementParticipantCurrencyId: allAccounts[accountPayload.id].key,
935
+ settlementStateId: accountPayload.state,
936
+ reason: accountPayload.reason,
937
+ externalReference: accountPayload.externalReference
938
+ })
939
+ allAccounts[accountPayload.id].reason = accountPayload.reason
940
+ allAccounts[accountPayload.id].createdDate = transactionTimestamp
941
+ // seq-settlement-6.2.5, step 17
942
+ } else if ((settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT && accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) ||
943
+ (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RECORDED && accountPayload.state === enums.settlementStates.PS_TRANSFERS_RESERVED) ||
944
+ (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RESERVED && accountPayload.state === enums.settlementStates.PS_TRANSFERS_COMMITTED) ||
945
+ ((settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_COMMITTED || settlementData.settlementStateId === enums.settlementStates.SETTLING) &&
946
+ accountPayload.state === enums.settlementStates.SETTLED)) {
947
+ processedAccounts.push(accountPayload.id)
948
+ participant.accounts.push({
949
+ id: accountPayload.id,
950
+ state: accountPayload.state,
951
+ reason: accountPayload.reason,
952
+ externalReference: accountPayload.externalReference,
953
+ createdDate: transactionTimestamp,
954
+ netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount
955
+ })
956
+ const spcsc = {
957
+ settlementParticipantCurrencyId: allAccounts[accountPayload.id].key,
958
+ settlementStateId: accountPayload.state,
959
+ reason: accountPayload.reason,
960
+ externalReference: accountPayload.externalReference,
961
+ createdDate: transactionTimestamp
962
+ }
963
+ if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) {
964
+ spcsc.settlementTransferId = generateULID()
965
+ }
966
+ settlementParticipantCurrencyStateChange.push(spcsc)
967
+
968
+ if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RECORDED) {
969
+ settlementAccounts.pendingSettlementCount--
970
+ settlementAccounts.psTransfersRecordedCount++
971
+ } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_RESERVED) {
972
+ settlementAccounts.psTransfersRecordedCount--
973
+ settlementAccounts.psTransfersReservedCount++
974
+ } else if (accountPayload.state === enums.settlementStates.PS_TRANSFERS_COMMITTED) {
975
+ settlementAccounts.psTransfersReservedCount--
976
+ settlementAccounts.psTransfersCommittedCount++
977
+ } else /* if (accountPayload.state === enums.settlementStates.SETTLED) */ { // disabled as else path is never taken
978
+ settlementAccounts.psTransfersCommittedCount--
979
+ settlementAccounts.settledCount++
980
+ settlementAccounts.settledIdList.push(accountPayload.id)
981
+ }
982
+ settlementAccounts.changedIdList.push(accountPayload.id)
983
+ allAccounts[accountPayload.id].state = accountPayload.state
984
+ allAccounts[accountPayload.id].reason = accountPayload.reason
985
+ allAccounts[accountPayload.id].externalReference = accountPayload.externalReference
986
+ allAccounts[accountPayload.id].createdDate = transactionTimestamp
987
+ // seq-settlement-6.2.5, step 18
988
+ } else {
989
+ participant.accounts.push({
990
+ id: accountPayload.id,
991
+ state: allAccounts[accountPayload.id].state,
992
+ reason: allAccounts[accountPayload.id].reason,
993
+ createdDate: allAccounts[accountPayload.id].createdDate,
994
+ netSettlementAmount: allAccounts[accountPayload.id].netSettlementAmount,
995
+ errorInformation: ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.CLIENT_ERROR, 'State change not allowed').toApiErrorObject().errorInformation
996
+ })
997
+ }
998
+ }
999
+ }
1000
+ let insertPromises = []
1001
+ let updatePromises = []
1002
+ // seq-settlement-6.2.5, step 19
1003
+ for (const spcsc of settlementParticipantCurrencyStateChange) {
1004
+ // Switched to insert from batchInsert because only LAST_INSERT_ID is returned
1005
+ // TODO:: PoC - batchInsert + select inserted ids vs multiple inserts without select
1006
+ const spcscCopy = Object.assign({}, spcsc)
1007
+ delete spcscCopy.settlementTransferId
1008
+ insertPromises.push(
1009
+ knex('settlementParticipantCurrencyStateChange')
1010
+ .insert(spcscCopy)
1011
+ .transacting(trx)
1012
+ )
1013
+ }
1014
+ const settlementParticipantCurrencyStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1015
+ // seq-settlement-6.2.5, step 21
1016
+ for (const i in settlementParticipantCurrencyStateChangeIdList) {
1017
+ const updatedColumns = { currentStateChangeId: settlementParticipantCurrencyStateChangeIdList[i] }
1018
+ if (settlementParticipantCurrencyStateChange[i].settlementTransferId) {
1019
+ updatedColumns.settlementTransferId = settlementParticipantCurrencyStateChange[i].settlementTransferId
1020
+ }
1021
+ updatePromises.push(
1022
+ knex('settlementParticipantCurrency')
1023
+ .where('settlementParticipantCurrencyId', settlementParticipantCurrencyStateChange[i].settlementParticipantCurrencyId)
1024
+ .update(updatedColumns)
1025
+ .transacting(trx)
1026
+ )
1027
+ }
1028
+ await Promise.all(updatePromises)
1029
+
1030
+ if (autoPositionReset) {
1031
+ if (settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT) {
1032
+ await Facade.settlementTransfersPrepare(settlementId, transactionTimestamp, enums, trx)
1033
+ } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RECORDED) {
1034
+ await Facade.settlementTransfersReserve(settlementId, transactionTimestamp, requireLiquidityCheck, enums, trx)
1035
+ } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RESERVED) {
1036
+ await Facade.settlementTransfersCommit(settlementId, transactionTimestamp, enums, trx)
1037
+ }
1038
+ }
1039
+
1040
+ // seq-settlement-6.2.5, step 23
1041
+ if (settlementAccounts.settledIdList.length > 0) {
1042
+ await knex('settlementContentAggregation').transacting(trx)
1043
+ .where('settlementId', settlementId)
1044
+ .whereIn('participantCurrencyId', settlementAccounts.settledIdList)
1045
+ .update('currentStateId', enums.settlementWindowStates.SETTLED)
1046
+
1047
+ // check for settled content
1048
+ const scaContentToCheck = await knex('settlementContentAggregation').transacting(trx)
1049
+ .where('settlementId', settlementId)
1050
+ .whereIn('participantCurrencyId', settlementAccounts.settledIdList)
1051
+ .distinct('settlementWindowContentId')
1052
+ const contentIdCheckList = scaContentToCheck.map(v => v.settlementWindowContentId)
1053
+ const unsettledContent = await knex('settlementContentAggregation').transacting(trx)
1054
+ .whereIn('settlementWindowContentId', contentIdCheckList)
1055
+ .whereNot('currentStateId', enums.settlementWindowStates.SETTLED)
1056
+ .distinct('settlementWindowContentId')
1057
+ const unsettledContentIdList = unsettledContent.map(v => v.settlementWindowContentId)
1058
+ const settledContentIdList = arrayDiff(contentIdCheckList, unsettledContentIdList)
1059
+
1060
+ // persist settled content
1061
+ insertPromises = []
1062
+ for (const settlementWindowContentId of settledContentIdList) {
1063
+ const swcsc = {
1064
+ settlementWindowContentId,
1065
+ settlementWindowStateId: enums.settlementWindowStates.SETTLED,
1066
+ reason: 'All content aggregation records are SETTLED'
1067
+ }
1068
+ insertPromises.push(
1069
+ knex('settlementWindowContentStateChange').transacting(trx)
1070
+ .insert(swcsc)
1071
+ )
1072
+ }
1073
+ const settlementWindowContentStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1074
+ updatePromises = []
1075
+ for (const i in settlementWindowContentStateChangeIdList) {
1076
+ const updatedColumns = { currentStateChangeId: settlementWindowContentStateChangeIdList[i] }
1077
+ updatePromises.push(
1078
+ knex('settlementWindowContent').transacting(trx)
1079
+ .where('settlementWindowContentId', settledContentIdList[i])
1080
+ .update(updatedColumns)
1081
+ )
1082
+ }
1083
+ await Promise.all(updatePromises)
1084
+
1085
+ // check for settled windows
1086
+ const windowsToCheck = await knex('settlementWindowContent').transacting(trx)
1087
+ .whereIn('settlementWindowContentId', settledContentIdList)
1088
+ .distinct('settlementWindowId')
1089
+ const windowIdCheckList = windowsToCheck.map(v => v.settlementWindowId)
1090
+ const unsettledWindows = await knex('settlementWindowContent AS swc').transacting(trx)
1091
+ .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId')
1092
+ .whereIn('swc.settlementWindowId', windowIdCheckList)
1093
+ .whereNot('swcsc.settlementWindowStateId', enums.settlementWindowStates.SETTLED)
1094
+ .distinct('swc.settlementWindowId')
1095
+ const unsettledWindowIdList = unsettledWindows.map(v => v.settlementWindowId)
1096
+ const settledWindowIdList = arrayDiff(windowIdCheckList, unsettledWindowIdList)
1097
+
1098
+ // persist settled windows
1099
+ insertPromises = []
1100
+ for (const settlementWindowId of settledWindowIdList) {
1101
+ const swsc = {
1102
+ settlementWindowId,
1103
+ settlementWindowStateId: enums.settlementWindowStates.SETTLED,
1104
+ reason: 'All settlement window content is SETTLED'
1105
+ }
1106
+ insertPromises.push(
1107
+ knex('settlementWindowStateChange').transacting(trx)
1108
+ .insert(swsc)
1109
+ )
1110
+ }
1111
+ const settlementWindowStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1112
+ updatePromises = []
1113
+ for (const i in settlementWindowStateChangeIdList) {
1114
+ const updatedColumns = { currentStateChangeId: settlementWindowStateChangeIdList[i] }
1115
+ updatePromises.push(
1116
+ knex('settlementWindow').transacting(trx)
1117
+ .where('settlementWindowId', settledWindowIdList[i])
1118
+ .update(updatedColumns)
1119
+ )
1120
+ }
1121
+ await Promise.all(updatePromises)
1122
+ }
1123
+
1124
+ // seq-settlement-6.2.5, step 24
1125
+ const processedContent = await knex('settlementContentAggregation AS sca').transacting(trx)
1126
+ .join('settlementWindowContent AS swc', 'swc.settlementWindowContentId', 'sca.settlementWindowContentId')
1127
+ .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId')
1128
+ .join('ledgerAccountType AS lat', 'lat.ledgerAccountTypeId', 'swc.ledgerAccountTypeId')
1129
+ .join('settlementWindow AS sw', 'sw.settlementWindowId', 'swc.settlementWindowId')
1130
+ .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId')
1131
+ .whereIn('sca.participantCurrencyId', settlementAccounts.changedIdList)
1132
+ .where('sca.settlementId', settlementId)
1133
+ .distinct(
1134
+ 'sw.settlementWindowId',
1135
+ 'swsc.settlementWindowStateId',
1136
+ 'swsc.reason',
1137
+ 'sw.createdDate AS createdDate1',
1138
+ 'swsc.createdDate AS changedDate1',
1139
+ 'swc.settlementWindowContentId',
1140
+ 'swcsc.settlementWindowStateId AS state',
1141
+ 'lat.name AS ledgerAccountType',
1142
+ 'swc.currencyId',
1143
+ 'swc.createdDate',
1144
+ 'swcsc.createdDate AS changedDate'
1145
+ )
1146
+ .orderBy(['sw.settlementWindowId', 'swc.settlementWindowContentId'])
1147
+ const settlementWindows = groupByWindowsWithContent(processedContent)
1148
+
1149
+ // seq-settlement-6.2.5, step post-26
1150
+ let settlementStateChanged = true
1151
+ if (settlementData.settlementStateId === enums.settlementStates.PENDING_SETTLEMENT &&
1152
+ settlementAccounts.pendingSettlementCount === 0) {
1153
+ settlementData.settlementStateId = enums.settlementStates.PS_TRANSFERS_RECORDED
1154
+ settlementData.reason = 'All settlement accounts are PS_TRANSFERS_RECORDED'
1155
+ } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RECORDED &&
1156
+ settlementAccounts.psTransfersRecordedCount === 0) {
1157
+ settlementData.settlementStateId = enums.settlementStates.PS_TRANSFERS_RESERVED
1158
+ settlementData.reason = 'All settlement accounts are PS_TRANSFERS_RESERVED'
1159
+ } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_RESERVED &&
1160
+ settlementAccounts.psTransfersReservedCount === 0) {
1161
+ settlementData.settlementStateId = enums.settlementStates.PS_TRANSFERS_COMMITTED
1162
+ settlementData.reason = 'All settlement accounts are PS_TRANSFERS_COMMITTED'
1163
+ } else if (settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_COMMITTED &&
1164
+ settlementAccounts.psTransfersCommittedCount > 0 &&
1165
+ settlementAccounts.settledCount > 0) {
1166
+ settlementData.settlementStateId = enums.settlementStates.SETTLING
1167
+ settlementData.reason = 'Some settlement accounts are SETTLED'
1168
+ } else if ((settlementData.settlementStateId === enums.settlementStates.PS_TRANSFERS_COMMITTED ||
1169
+ settlementData.settlementStateId === enums.settlementStates.SETTLING) &&
1170
+ settlementAccounts.psTransfersCommittedCount === 0) {
1171
+ settlementData.settlementStateId = enums.settlementStates.SETTLED
1172
+ settlementData.reason = 'All settlement accounts are SETTLED'
1173
+ } else {
1174
+ settlementStateChanged = false
1175
+ }
1176
+
1177
+ // seq-settlement-6.2.5, step pre-27
1178
+ if (settlementStateChanged) {
1179
+ settlementData.createdDate = transactionTimestamp
1180
+
1181
+ // seq-settlement-6.2.5, step 27
1182
+ const settlementStateChangeId = await knex('settlementStateChange')
1183
+ .insert(settlementData)
1184
+ .transacting(trx)
1185
+ // seq-settlement-6.2.5, step 29
1186
+ await knex('settlement')
1187
+ .where('settlementId', settlementData.settlementId)
1188
+ .update({ currentStateChangeId: settlementStateChangeId })
1189
+ .transacting(trx)
1190
+ }
1191
+ return {
1192
+ id: settlementId,
1193
+ state: settlementData.settlementStateId,
1194
+ createdDate: settlementData.createdDate,
1195
+ settlementWindows,
1196
+ participants
1197
+ }
1198
+ }
1199
+ } catch (err) {
1200
+ logger.error(err)
1201
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
1202
+ }
1203
+ })
1204
+ },
1205
+
1206
+ abortById: async function (settlementId, payload, enums) {
1207
+ const knex = await Db.getKnex()
1208
+ return knex.transaction(async (trx) => {
1209
+ try {
1210
+ const transactionTimestamp = new Date().toISOString().replace(/[TZ]/g, ' ').trim()
1211
+
1212
+ // seq-settlement-6.2.6, step 8
1213
+ const settlementAccountList = await knex('settlementParticipantCurrency AS spc')
1214
+ .leftJoin('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'spc.currentStateChangeId')
1215
+ .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'spc.participantCurrencyId')
1216
+ .select('pc.participantId', 'spc.participantCurrencyId', 'spcsc.settlementStateId', 'spcsc.reason', 'spc.netAmount', 'pc.currencyId', 'spc.settlementParticipantCurrencyId AS key'
1217
+ )
1218
+ .where('spc.settlementId', settlementId)
1219
+ .transacting(trx)
1220
+ .forUpdate()
1221
+
1222
+ // seq-settlement-6.2.6, step 10
1223
+ const windowsList = await knex('settlementSettlementWindow AS ssw')
1224
+ .join('settlementWindow AS sw', 'sw.settlementWindowId', 'ssw.settlementWindowId')
1225
+ .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId')
1226
+ .select('sw.settlementWindowId', 'swsc.settlementWindowStateId', 'swsc.reason', 'sw.createdDate')
1227
+ .where('ssw.settlementId', settlementId)
1228
+ .transacting(trx)
1229
+ .forUpdate()
1230
+
1231
+ let insertPromises = []
1232
+ let updatePromises = []
1233
+ // seq-settlement-6.2.6, step 12
1234
+ for (const sal of settlementAccountList) {
1235
+ // Switched to insert from batchInsert because only LAST_INSERT_ID is returned
1236
+ // TODO:: PoC - batchInsert + select inserted ids vs multiple inserts without select
1237
+ const spcsc = {
1238
+ settlementParticipantCurrencyId: sal.key,
1239
+ settlementStateId: enums.settlementStates.ABORTED,
1240
+ reason: payload.reason,
1241
+ externalReference: payload.externalReference
1242
+ }
1243
+ insertPromises.push(
1244
+ knex('settlementParticipantCurrencyStateChange')
1245
+ .insert(spcsc)
1246
+ .transacting(trx)
1247
+ )
1248
+ }
1249
+ const settlementParticipantCurrencyStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1250
+ // seq-settlement-6.2.6, step 15
1251
+ for (const i in settlementParticipantCurrencyStateChangeIdList) {
1252
+ const updatedColumns = { currentStateChangeId: settlementParticipantCurrencyStateChangeIdList[i] }
1253
+ updatePromises.push(
1254
+ knex('settlementParticipantCurrency')
1255
+ .where('settlementParticipantCurrencyId', settlementAccountList[i].key)
1256
+ .update(updatedColumns)
1257
+ .transacting(trx)
1258
+ )
1259
+ }
1260
+ await Promise.all(updatePromises)
1261
+
1262
+ await Facade.settlementTransfersAbort(settlementId, transactionTimestamp, enums, trx)
1263
+
1264
+ // seq-settlement-6.2.6, step 16
1265
+ insertPromises = []
1266
+ for (const rec of windowsList) {
1267
+ const swsc = {
1268
+ settlementWindowId: rec.settlementWindowId,
1269
+ settlementWindowStateId: enums.settlementWindowStates.ABORTED,
1270
+ reason: payload.reason
1271
+ }
1272
+ insertPromises.push(
1273
+ knex('settlementWindowStateChange')
1274
+ .insert(swsc)
1275
+ .transacting(trx)
1276
+ )
1277
+ }
1278
+ const settlementWindowStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1279
+ // seq-settlement-6.2.6, step 19
1280
+ updatePromises = []
1281
+ for (const i in settlementWindowStateChangeIdList) {
1282
+ updatePromises.push(
1283
+ knex('settlementWindow')
1284
+ .where('settlementWindowId', windowsList[i].settlementWindowId)
1285
+ .update({ currentStateChangeId: settlementWindowStateChangeIdList[i] })
1286
+ .transacting(trx)
1287
+ )
1288
+ }
1289
+ await Promise.all(updatePromises)
1290
+
1291
+ // seq-settlement-6.2.6, step 20
1292
+ const settlementStateChangeId = await knex('settlementStateChange')
1293
+ .insert({
1294
+ settlementId,
1295
+ settlementStateId: enums.settlementStates.ABORTED,
1296
+ reason: payload.reason
1297
+ })
1298
+ .transacting(trx)
1299
+ // seq-settlement-6.2.6, step 22
1300
+ await knex('settlement')
1301
+ .where('settlementId', settlementId)
1302
+ .update({ currentStateChangeId: settlementStateChangeId })
1303
+ .transacting(trx)
1304
+
1305
+ return {
1306
+ id: settlementId,
1307
+ state: payload.state,
1308
+ reason: payload.reason
1309
+ }
1310
+ } catch (err) {
1311
+ logger.error(err)
1312
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
1313
+ }
1314
+ })
1315
+ },
1316
+
1317
+ getById: async function ({ settlementId }) {
1318
+ return Db.from('settlement').query(builder => {
1319
+ return builder
1320
+ .join('settlementStateChange AS ssc', 'ssc.settlementStateChangeId', 'settlement.currentStateChangeId')
1321
+ .select('settlement.settlementId',
1322
+ 'settlement.settlementModelId',
1323
+ 'ssc.settlementStateId AS state',
1324
+ 'ssc.reason',
1325
+ 'settlement.createdDate',
1326
+ 'ssc.createdDate AS changedDate')
1327
+ .where('settlement.settlementId', settlementId)
1328
+ .first()
1329
+ })
1330
+ },
1331
+
1332
+ getByParams: async function ({ state, fromDateTime, toDateTime, currency, settlementWindowId, fromSettlementWindowDateTime, toSettlementWindowDateTime, participantId, accountId }) {
1333
+ return Db.from('settlement').query(builder => {
1334
+ const b = builder
1335
+ .innerJoin('settlementStateChange AS ssc', 'ssc.settlementStateChangeId', 'settlement.currentStateChangeId')
1336
+ .innerJoin('settlementSettlementWindow AS ssw', 'ssw.settlementId', 'settlement.settlementId')
1337
+ .innerJoin('settlementWindow AS sw', 'sw.settlementWindowId', 'ssw.settlementWindowId')
1338
+ .innerJoin('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId')
1339
+ .innerJoin('settlementContentAggregation AS sca', 'sca.settlementId', 'settlement.settlementId')
1340
+ .innerJoin('settlementParticipantCurrency AS spc', 'spc.settlementId', 'sca.settlementId')
1341
+ .innerJoin('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'spc.currentStateChangeId')
1342
+ .innerJoin('participantCurrency AS pc', 'pc.participantCurrencyId', 'spc.participantCurrencyId')
1343
+ .distinct('settlement.settlementId', 'ssc.settlementStateId', 'ssw.settlementWindowId',
1344
+ 'swsc.settlementWindowStateId', 'swsc.reason AS settlementWindowReason', 'sw.createdDate',
1345
+ 'swsc.createdDate AS changedDate', 'pc.participantId', 'spc.participantCurrencyId',
1346
+ 'spcsc.reason AS accountReason', 'spcsc.settlementStateId AS accountState',
1347
+ 'spc.netAmount AS accountAmount', 'pc.currencyId AS accountCurrency')
1348
+ .select()
1349
+ if (state) { b.where('ssc.settlementStateId', state) }
1350
+ if (fromDateTime) { b.where('settlement.createdDate', '>=', fromDateTime) }
1351
+ if (toDateTime) { b.where('settlement.createdDate', '<=', toDateTime) }
1352
+ if (currency) { b.where('pc.currencyId', currency) }
1353
+ if (settlementWindowId) { b.where('ssw.settlementWindowId', settlementWindowId) }
1354
+ if (fromSettlementWindowDateTime) { b.where('sw.createdDate', '>=', fromSettlementWindowDateTime) }
1355
+ if (toSettlementWindowDateTime) { b.where('sw.createdDate', '<=', toSettlementWindowDateTime) }
1356
+ if (participantId) { b.where('pc.participantId', participantId) }
1357
+ if (accountId) { b.where('spc.participantCurrencyId', accountId) }
1358
+ return b
1359
+ })
1360
+ },
1361
+
1362
+ triggerSettlementEvent: async function ({ idList, reason }, settlementModel, enums = {}) {
1363
+ const knex = await Db.getKnex()
1364
+ // begin transaction
1365
+ return knex.transaction(async (trx) => {
1366
+ try {
1367
+ // insert new settlement
1368
+ const transactionTimestamp = new Date().toISOString().replace(/[TZ]/g, ' ').trim()
1369
+ let settlementId = await knex('settlement').transacting(trx)
1370
+ .insert({
1371
+ reason,
1372
+ createdDate: transactionTimestamp,
1373
+ settlementModelId: settlementModel.settlementModelId
1374
+ })
1375
+ settlementId = settlementId[0]
1376
+ const settlementSettlementWindowList = idList.map(settlementWindowId => {
1377
+ return {
1378
+ settlementId,
1379
+ settlementWindowId,
1380
+ createdDate: transactionTimestamp
1381
+ }
1382
+ })
1383
+
1384
+ // associate settlement windows with the settlement
1385
+ await knex.batchInsert('settlementSettlementWindow', settlementSettlementWindowList).transacting(trx)
1386
+
1387
+ // retrieve affected settlementWindowContent
1388
+ let swcList = await knex('settlementWindow AS sw').transacting(trx)
1389
+ .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId')
1390
+ .join('settlementWindowContent AS swc', 'swc.settlementWindowId', 'sw.settlementWindowId')
1391
+ .join('settlementWindowContentStateChange AS swcsc', 'swcsc.settlementWindowContentStateChangeId', 'swc.currentStateChangeId')
1392
+ .whereRaw(`sw.settlementWindowId IN (${idList})`)
1393
+ .where('swc.ledgerAccountTypeId', settlementModel.ledgerAccountTypeId)
1394
+ .where('swc.currencyId', knex.raw('COALESCE(?, swc.currencyId)', settlementModel.currencyId))
1395
+ .whereIn('swsc.settlementWindowStateId', [enums.settlementWindowStates.CLOSED, enums.settlementWindowStates.ABORTED, enums.settlementWindowStates.PENDING_SETTLEMENT])
1396
+ .whereIn('swcsc.settlementWindowStateId', [enums.settlementWindowStates.CLOSED, enums.settlementWindowStates.ABORTED])
1397
+
1398
+ if (settlementModel.currencyId === null) { // Default settlement model
1399
+ const allSettlementModels = await SettlementModelModel.getAll()
1400
+ const settlementModelCurrenciesList = allSettlementModels.filter(record => record.currencyId !== null).map(record => record.currencyId)
1401
+ swcList = swcList.filter(swc => !settlementModelCurrenciesList.includes(swc.currencyId))
1402
+ }
1403
+
1404
+ const swcIdArray = swcList.map(record => record.settlementWindowContentId)
1405
+
1406
+ // bind requested settlementWindowContent and settlementContentAggregation records
1407
+ await knex('settlementWindowContent').transacting(trx)
1408
+ .whereIn('settlementWindowContentId', swcIdArray)
1409
+ .update({ settlementId })
1410
+ await knex('settlementContentAggregation').transacting(trx)
1411
+ .whereIn('settlementWindowContentId', swcIdArray)
1412
+ .update({ settlementId, currentStateId: enums.settlementWindowStates.PENDING_SETTLEMENT })
1413
+
1414
+ // change settlementWindowContent records state
1415
+ const settlementWindowContentStateChangeList = swcIdArray.map(value => {
1416
+ return {
1417
+ settlementWindowContentId: value,
1418
+ settlementWindowStateId: enums.settlementStates.PENDING_SETTLEMENT,
1419
+ reason,
1420
+ createdDate: transactionTimestamp
1421
+ }
1422
+ })
1423
+ let insertPromises = []
1424
+ for (const swcsc of settlementWindowContentStateChangeList) {
1425
+ insertPromises.push(
1426
+ knex('settlementWindowContentStateChange').transacting(trx)
1427
+ .insert(swcsc)
1428
+ )
1429
+ }
1430
+ const settlementWindowContentStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1431
+ let updatePromises = []
1432
+ for (let index = 0; index < swcIdArray.length; index++) {
1433
+ updatePromises.push(await knex('settlementWindowContent').transacting(trx)
1434
+ .where('settlementWindowContentId', swcIdArray[index])
1435
+ .update({ currentStateChangeId: settlementWindowContentStateChangeIdList[index] }))
1436
+ }
1437
+ await Promise.all(updatePromises)
1438
+
1439
+ // aggregate and insert settlement net amounts
1440
+ const builder = knex
1441
+ .from(knex.raw('settlementParticipantCurrency (settlementId, participantCurrencyId, createdDate, netAmount)'))
1442
+ .insert(function () {
1443
+ this.from('settlementContentAggregation AS sca')
1444
+ .whereRaw('sca.settlementId = ?', settlementId)
1445
+ .groupBy('sca.settlementId', 'sca.participantCurrencyId')
1446
+ .select('sca.settlementId', 'sca.participantCurrencyId', knex.raw('? AS createdDate', transactionTimestamp))
1447
+ .sum('sca.amount AS netAmount')
1448
+ })
1449
+ .transacting(trx)
1450
+ await builder
1451
+
1452
+ // change settlementParticipantCurrency records state
1453
+ const settlementParticipantCurrencyList = await knex('settlementParticipantCurrency').select('settlementParticipantCurrencyId').where('settlementId', settlementId).transacting(trx)
1454
+ const settlementParticipantCurrencyIdList = []
1455
+ const settlementParticipantCurrencyStateChangeList = settlementParticipantCurrencyList.map(value => {
1456
+ settlementParticipantCurrencyIdList.push(value.settlementParticipantCurrencyId)
1457
+ return {
1458
+ settlementParticipantCurrencyId: value.settlementParticipantCurrencyId,
1459
+ settlementStateId: enums.settlementStates.PENDING_SETTLEMENT,
1460
+ reason,
1461
+ createdDate: transactionTimestamp
1462
+ }
1463
+ })
1464
+ insertPromises = []
1465
+ for (const spcsc of settlementParticipantCurrencyStateChangeList) {
1466
+ insertPromises.push(
1467
+ knex('settlementParticipantCurrencyStateChange').transacting(trx)
1468
+ .insert(spcsc)
1469
+ )
1470
+ }
1471
+ const settlementParticipantCurrencyStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1472
+ updatePromises = []
1473
+ for (const index in settlementParticipantCurrencyIdList) {
1474
+ updatePromises.push(knex('settlementParticipantCurrency').transacting(trx)
1475
+ .where('settlementParticipantCurrencyId', settlementParticipantCurrencyIdList[index])
1476
+ .update({ currentStateChangeId: settlementParticipantCurrencyStateChangeIdList[index] }))
1477
+ }
1478
+ await Promise.all(updatePromises)
1479
+
1480
+ // set state of CLOSED and ABORTED windows to PENDING_SETTLEMENT, skip already in PENDING_SETTLEMENT state
1481
+ const windowsStateToBeUpdatedIdList = await knex('settlementWindow AS sw').transacting(trx)
1482
+ .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'sw.currentStateChangeId')
1483
+ .whereIn('sw.settlementWindowId', idList)
1484
+ .whereIn('swsc.settlementWindowStateId', [enums.settlementWindowStates.CLOSED, enums.settlementWindowStates.ABORTED])
1485
+ .select('sw.settlementWindowId')
1486
+ const settlementWindowStateChangeList = windowsStateToBeUpdatedIdList.map(record => {
1487
+ return {
1488
+ settlementWindowId: record.settlementWindowId,
1489
+ settlementWindowStateId: enums.settlementStates.PENDING_SETTLEMENT,
1490
+ reason,
1491
+ createdDate: transactionTimestamp
1492
+ }
1493
+ })
1494
+ insertPromises = []
1495
+ for (const swsc of settlementWindowStateChangeList) {
1496
+ insertPromises.push(
1497
+ knex('settlementWindowStateChange').transacting(trx)
1498
+ .insert(swsc)
1499
+ )
1500
+ }
1501
+ const settlementWindowStateChangeIdList = (await Promise.all(insertPromises)).map(v => v[0])
1502
+ updatePromises = []
1503
+ for (let index = 0; index < settlementWindowStateChangeList.length; index++) {
1504
+ updatePromises.push(await knex('settlementWindow').transacting(trx)
1505
+ .where('settlementWindowId', settlementWindowStateChangeList[index].settlementWindowId)
1506
+ .update({ currentStateChangeId: settlementWindowStateChangeIdList[index] }))
1507
+ }
1508
+ await Promise.all(updatePromises)
1509
+
1510
+ // initiate settlement state to PENDING_SETTLEMENT
1511
+ const settlementStateChangeId = await knex('settlementStateChange').transacting(trx)
1512
+ .insert({
1513
+ settlementId,
1514
+ settlementStateId: enums.settlementStates.PENDING_SETTLEMENT,
1515
+ reason,
1516
+ createdDate: transactionTimestamp
1517
+ })
1518
+ await knex('settlement').transacting(trx)
1519
+ .where('settlementId', settlementId)
1520
+ .update({ currentStateChangeId: settlementStateChangeId })
1521
+ return settlementId
1522
+ } catch (err) {
1523
+ logger.error(err)
1524
+ throw ErrorHandler.Factory.reformatFSPIOPError(err)
1525
+ }
1526
+ })
1527
+ },
1528
+
1529
+ settlementParticipantCurrency: {
1530
+ getByListOfIds: async function (listOfIds) {
1531
+ return Db.from('settlementParticipantCurrency').query(builder => {
1532
+ return builder
1533
+ .leftJoin('participantCurrency AS pc', 'pc.participantCurrencyId', 'settlementParticipantCurrency.participantCurrencyId')
1534
+ .leftJoin('participant as p', 'p.participantCurrencyId', 'pc.participantCurrencyId')
1535
+ .select(
1536
+ 'settlementParticipantCurrency.netAmount as amount',
1537
+ 'pc.currencyId as currency',
1538
+ 'p.participantId as participant'
1539
+ )
1540
+ .whereIn('settlementWindow.settlementWindowId', listOfIds)
1541
+ })
1542
+ },
1543
+
1544
+ getAccountsInSettlementByIds: async function ({ settlementId, participantId }) {
1545
+ return Db.from('settlementParticipantCurrency').query(builder => {
1546
+ return builder
1547
+ .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'settlementParticipantCurrency.participantCurrencyId')
1548
+ .select('settlementParticipantCurrencyId')
1549
+ .where({ settlementId })
1550
+ .andWhere('pc.participantId', participantId)
1551
+ })
1552
+ },
1553
+
1554
+ getParticipantCurrencyBySettlementId: async function ({ settlementId }) {
1555
+ return Db.from('settlementParticipantCurrency').query(builder => {
1556
+ return builder
1557
+ .leftJoin('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'settlementParticipantCurrency.currentStateChangeId')
1558
+ .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'settlementParticipantCurrency.participantCurrencyId')
1559
+ .select(
1560
+ 'pc.participantId AS id',
1561
+ 'settlementParticipantCurrency.participantCurrencyId AS participantCurrencyId',
1562
+ 'spcsc.settlementStateId AS state',
1563
+ 'spcsc.reason AS reason',
1564
+ 'settlementParticipantCurrency.netAmount AS netAmount',
1565
+ 'pc.currencyId AS currency',
1566
+ 'settlementParticipantCurrency.settlementParticipantCurrencyId AS key'
1567
+ )
1568
+ .where({ settlementId })
1569
+ })
1570
+ },
1571
+
1572
+ getSettlementAccountById: async function (settlementParticipantCurrencyId) {
1573
+ return Db.from('settlementParticipantCurrency').query(builder => {
1574
+ return builder
1575
+ .join('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'settlementParticipantCurrency.currentStateChangeId')
1576
+ .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'settlementParticipantCurrency.participantCurrencyId')
1577
+ .select(
1578
+ 'pc.participantId AS id',
1579
+ 'settlementParticipantCurrency.participantCurrencyId',
1580
+ 'spcsc.settlementStateId AS state',
1581
+ 'spcsc.reason AS reason',
1582
+ 'settlementParticipantCurrency.netAmount as netAmount',
1583
+ 'pc.currencyId AS currency'
1584
+ )
1585
+ .where('settlementParticipantCurrency.settlementParticipantCurrencyId', settlementParticipantCurrencyId)
1586
+ })
1587
+ },
1588
+
1589
+ getSettlementAccountsByListOfIds: async function (settlementParticipantCurrencyIdList) {
1590
+ return Db.from('settlementParticipantCurrency').query(builder => {
1591
+ return builder
1592
+ .join('settlementParticipantCurrencyStateChange AS spcsc', 'spcsc.settlementParticipantCurrencyStateChangeId', 'settlementParticipantCurrency.currentStateChangeId')
1593
+ .join('participantCurrency AS pc', 'pc.participantCurrencyId', 'settlementParticipantCurrency.participantCurrencyId')
1594
+ .select(
1595
+ 'pc.participantId AS id',
1596
+ 'settlementParticipantCurrency.participantCurrencyId',
1597
+ 'spcsc.settlementStateId AS state',
1598
+ 'spcsc.reason AS reason',
1599
+ 'settlementParticipantCurrency.netAmount as netAmount',
1600
+ 'pc.currencyId AS currency'
1601
+ )
1602
+ .whereIn('settlementParticipantCurrency.settlementParticipantCurrencyId', settlementParticipantCurrencyIdList)
1603
+ })
1604
+ }
1605
+ },
1606
+ settlementSettlementWindow: {
1607
+ getWindowsBySettlementIdAndAccountId: async function ({ settlementId, accountId }) {
1608
+ return Db.from('settlementSettlementWindow').query(builder => {
1609
+ return builder
1610
+ .join('settlementWindow', 'settlementWindow.settlementWindowId', 'settlementSettlementWindow.settlementWindowId')
1611
+ .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId')
1612
+ .join('settlementTransferParticipant AS stp', function () {
1613
+ this.on('stp.settlementWindowId', 'settlementWindow.settlementWindowId')
1614
+ .on('stp.participantCurrencyId', accountId)
1615
+ })
1616
+ .distinct(
1617
+ 'settlementWindow.settlementWindowId as id',
1618
+ 'swsc.settlementWindowStateId as state',
1619
+ 'swsc.reason as reason',
1620
+ 'settlementWindow.createdDate as createdDate',
1621
+ 'swsc.createdDate as changedDate'
1622
+ )
1623
+ .select()
1624
+ .where('settlementSettlementWindow.settlementId', settlementId)
1625
+ })
1626
+ },
1627
+
1628
+ getWindowsBySettlementIdAndParticipantId: async function ({ settlementId, participantId }, enums) {
1629
+ const participantAccountList = (await Db.from('participantCurrency').find({ participantId, ledgerAccountTypeId: enums.ledgerAccountTypes.POSITION })).map(record => record.participantCurrencyId)
1630
+ return Db.from('settlementSettlementWindow').query(builder => {
1631
+ return builder
1632
+ .join('settlementWindow', 'settlementWindow.settlementWindowId', 'settlementSettlementWindow.settlementWindowId')
1633
+ .join('settlementWindowStateChange AS swsc', 'swsc.settlementWindowStateChangeId', 'settlementWindow.currentStateChangeId')
1634
+ .join('settlementTransferParticipant AS stp', async function () {
1635
+ this.on('stp.settlementWindowId', 'settlementWindow.settlementWindowId')
1636
+ .onIn('stp.participantCurrencyId', participantAccountList)
1637
+ })
1638
+ .distinct(
1639
+ 'settlementWindow.settlementWindowId as id',
1640
+ 'swsc.settlementWindowStateId as state',
1641
+ 'swsc.reason as reason',
1642
+ 'settlementWindow.createdDate as createdDate',
1643
+ 'swsc.createdDate as changedDate'
1644
+ )
1645
+ .select()
1646
+ .where('settlementSettlementWindow.settlementId', settlementId)
1647
+ })
1648
+ }
1649
+ }
1650
+ }
1651
+
1652
+ module.exports = Facade