@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.
- package/.grype.yaml +3 -0
- package/.license-scanner.json +16 -0
- package/.ncurc.yaml +19 -13
- package/.nvmrc +1 -1
- package/.nycrc.yml +14 -2
- package/CHANGELOG.md +51 -0
- package/Dockerfile +13 -10
- package/README.md +39 -89
- package/audit-ci.jsonc +1 -1
- package/config/default.json +60 -3
- package/config/deprecated.default-settlement.json +160 -0
- package/docker/ml-api-adapter/default.json +28 -0
- package/docker-compose.yml +6 -0
- package/nodemon.json +6 -0
- package/package.json +128 -64
- package/{sbom-v19.13.2.csv → sbom-v20.0.0.csv} +886 -736
- package/scripts/_wait4_all.js +5 -5
- package/scripts/transferSettlementTemp/interchangeFeeCalculation.js +51 -0
- package/src/api/index.ts +30 -0
- package/src/api/interface/swagger.json +2249 -2054
- package/src/api/participants/routes.js +1 -1
- package/src/api/root/handler.int.ts +55 -0
- package/src/api/routes.js +7 -6
- package/src/api/server.ts +27 -0
- package/src/api/server.unit.ts +16 -0
- package/src/api/settlementModels/routes.js +4 -4
- package/src/domain/participant/index.d.ts +47 -0
- package/src/domain/participant/index.int.ts +724 -0
- package/src/domain/participant/index.js +4 -5
- package/src/domain/position/fx-prepare.js +1 -1
- package/src/domain/position/fx-timeout-reserved.js +6 -0
- package/src/domain/position/prepare.js +1 -1
- package/src/handlers/admin/handler.js +1 -1
- package/src/handlers/index.js +30 -0
- package/src/handlers/positions/handler.js +0 -2
- package/src/handlers/positions/handlerBatch.js +5 -5
- package/src/handlers/register.js +44 -29
- package/src/handlers/timeouts/handler.js +1 -4
- package/src/handlers/transfers/fx-abort.int.ts +209 -0
- package/src/handlers/transfers/fx-handlers.int.ts +275 -0
- package/src/handlers/transfers/fx-timeout.int.ts +284 -0
- package/src/handlers/transfers/handler.js +1 -3
- package/src/handlers/transfers/handlers.int.ts +538 -0
- package/src/handlers/transfers/prepare.int.ts +1283 -0
- package/src/handlers/transfers/prepare.js +2 -2
- package/src/knexfile.js +19 -0
- package/src/lib/cache.js +1 -1
- package/src/lib/config/index.ts +60 -0
- package/src/lib/config/resolver.ts +231 -0
- package/src/lib/config/resolver.unit.ts +72 -0
- package/src/lib/config/types.ts +372 -0
- package/src/lib/config/util.ts +523 -0
- package/src/lib/config/util.unit.ts +825 -0
- package/src/lib/migrator.js +3 -3
- package/src/lib/proxyCache.js +3 -3
- package/{migrations → src/migrations}/100100_event.js +1 -1
- package/{migrations → src/migrations}/110100_contactType.js +1 -1
- package/{migrations → src/migrations}/110200_currency.js +1 -1
- package/{migrations → src/migrations}/110201_currency-scale.js +1 -1
- package/{migrations → src/migrations}/110300_endpointType.js +1 -1
- package/{migrations → src/migrations}/110400_ledgerEntryType.js +1 -1
- package/{migrations → src/migrations}/110450_ledgerAccountType.js +1 -1
- package/{migrations → src/migrations}/110500_participantLimitType.js +1 -1
- package/{migrations → src/migrations}/110600_transferParticipantRoleType.js +1 -1
- package/{migrations → src/migrations}/110700_transferState.js +1 -1
- package/{migrations → src/migrations}/110900_settlementState.js +1 -1
- package/{migrations → src/migrations}/112000_bulkTransferState.js +1 -1
- package/{migrations → src/migrations}/112100_bulkProcessingState.js +1 -1
- package/{migrations → src/migrations}/200100_participant.js +1 -1
- package/{migrations → src/migrations}/200200_participantContact.js +1 -1
- package/{migrations → src/migrations}/200300_participantEndpoint.js +1 -1
- package/{migrations → src/migrations}/200400_participantParty.js +1 -1
- package/{migrations → src/migrations}/200600_token.js +1 -1
- package/{migrations → src/migrations}/200601_token-indexes.js +1 -1
- package/{migrations → src/migrations}/300100_transferDuplicateCheck.js +2 -2
- package/{migrations → src/migrations}/300150_bulkTransferDuplicateCheck.js +2 -2
- package/{migrations → src/migrations}/300200_transfer.js +2 -2
- package/{migrations → src/migrations}/300202_transfer-decimal184.js +1 -1
- package/{migrations → src/migrations}/300250_bulkTransfer.js +2 -3
- package/{migrations → src/migrations}/300275_bulkTransferAssociation.js +2 -3
- package/{migrations → src/migrations}/300300_ilpPacket.js +1 -1
- package/{migrations → src/migrations}/300400_transferStateChange.js +1 -1
- package/{migrations → src/migrations}/300450_bulkTransferStateChange.js +1 -1
- package/{migrations → src/migrations}/300500_segment.js +1 -1
- package/{migrations → src/migrations}/310100_participantCurrency.js +1 -1
- package/{migrations → src/migrations}/310200_transferParticipant.js +1 -1
- package/{migrations → src/migrations}/310202_transferParticipant-decimal184.js +1 -1
- package/{migrations → src/migrations}/310204_transferParticipant-participantId.js +2 -2
- package/{migrations → src/migrations}/310300_participantPosition.js +1 -1
- package/{migrations → src/migrations}/310302_participantPosition-decimal184.js +1 -1
- package/{migrations → src/migrations}/310400_participantPositionChange.js +1 -1
- package/{migrations → src/migrations}/310402_participantPositionChange-decimal184.js +1 -1
- package/{migrations → src/migrations}/310403_participantPositionChange-participantCurrencyId.js +2 -2
- package/{migrations → src/migrations}/310404_participantPositionChange-change.js +2 -2
- package/{migrations → src/migrations}/310500_participantLimit.js +1 -1
- package/{migrations → src/migrations}/310502_participantLimit-decimal184.js +1 -1
- package/{migrations → src/migrations}/400100_settlementWindow.js +1 -1
- package/{migrations → src/migrations}/400200_settlement.js +1 -1
- package/{migrations → src/migrations}/400300_settlementSettlementWindow.js +1 -1
- package/{migrations → src/migrations}/400301_settlementSettlementWindow-indexes.js +0 -1
- package/{migrations → src/migrations}/400400_settlementWindowStateChange.js +1 -1
- package/{migrations → src/migrations}/400500_settlementTransferParticipant.js +1 -1
- package/{migrations → src/migrations}/400502_settlementTransferParticipant-decimal184.js +1 -1
- package/{migrations → src/migrations}/400600_settlementParticipantCurrency.js +1 -1
- package/{migrations → src/migrations}/400602_settlementParticipantCurrency-decimal184.js +1 -1
- package/{migrations → src/migrations}/400700_settlementParticipantCurrencyStateChange.js +1 -1
- package/{migrations → src/migrations}/400800_settlementStateChange.js +1 -1
- package/{migrations → src/migrations}/400900_settlementWindowContent.js +1 -1
- package/{migrations → src/migrations}/401000_settlementWindowContentStateChange.js +1 -1
- package/{migrations → src/migrations}/401001_settlementWindowContentStateChange-indexes.js +6 -6
- package/{migrations → src/migrations}/401200_settlementContentAggregation.js +1 -1
- package/{migrations → src/migrations}/401300_settlementModel.js +1 -1
- package/{migrations → src/migrations}/410100_transferFulfilmentDuplicateCheck.js +1 -1
- package/{migrations → src/migrations}/410102_transferFulfilmentDuplicateCheck-dropForeignTransferId.js +1 -1
- package/{migrations → src/migrations}/410103_transferFulfilmentDuplicateCheck-hashNullable.js +1 -1
- package/{migrations → src/migrations}/410200_transferFulfilment.js +1 -1
- package/{migrations → src/migrations}/410202_transferFulfilment-ilpFulfilment.js +1 -1
- package/{migrations → src/migrations}/410203_transferFulfilment-foreignTransferFulfilmentId.js +1 -1
- package/{migrations → src/migrations}/410240_bulkTransferFulfilmentDuplicateCheck.js +1 -1
- package/{migrations → src/migrations}/410250_bulkTransferFulfilment.js +1 -1
- package/{migrations → src/migrations}/410300_transferExtension.js +1 -1
- package/{migrations → src/migrations}/410302_transferExtension-transferErrorId.js +1 -1
- package/{migrations → src/migrations}/410350_bulkTransferExtension.js +1 -1
- package/{migrations → src/migrations}/410400_transferTimeout.js +1 -1
- package/{migrations → src/migrations}/410450_transferErrorDuplicateCheck.js +1 -1
- package/{migrations → src/migrations}/410500_transferError.js +1 -1
- package/{migrations → src/migrations}/410502_transferError-foreignTransferErrorDuplicateCheckId.js +1 -1
- package/{migrations → src/migrations}/410550_bulkTransferError.js +1 -1
- package/{migrations → src/migrations}/410600_expiringTransfer.js +1 -1
- package/{migrations → src/migrations}/500401_quote-hotfix-2719.js +8 -8
- package/{migrations → src/migrations}/500501_feature-fixSubIdRef.js +12 -12
- package/{migrations → src/migrations}/500601_party-2480.js +3 -3
- package/{migrations → src/migrations}/501001_quoteExtension-quote-service-174.js +1 -2
- package/{migrations → src/migrations}/501002_quoteExtension-2522.js +3 -3
- package/{migrations → src/migrations}/600010_fxTransferType.js +1 -1
- package/{migrations → src/migrations}/600012_fxParticipantCurrencyType.js +1 -1
- package/{migrations → src/migrations}/600100_fxTransferDuplicateCheck.js +2 -2
- package/{migrations → src/migrations}/600110_fxTransferErrorDuplicateCheck.js.js +1 -1
- package/{migrations → src/migrations}/600200_fxTransfer.js +2 -2
- package/{migrations → src/migrations}/600400_fxTransferStateChange.js +1 -1
- package/{migrations → src/migrations}/600501_fxWatchList.js +2 -2
- package/{migrations → src/migrations}/600600_fxTransferFulfilmentDuplicateCheck.js +1 -1
- package/{migrations → src/migrations}/600700_fxTransferFulfilment.js +1 -1
- package/{migrations → src/migrations}/600800_fxTransferExtension.js +1 -1
- package/{migrations → src/migrations}/601400_fxTransferTimeout.js +1 -1
- package/{migrations → src/migrations}/601500_fxTransferError.js +1 -1
- package/{migrations → src/migrations}/610200_fxTransferParticipant.js +1 -1
- package/{migrations → src/migrations}/610202_fxTransferParticipant-participantId.js +2 -2
- package/{migrations → src/migrations}/610403_participantPositionChange-fxTransfer.js +1 -1
- package/{migrations → src/migrations}/800100_quotePartyView.js +1 -1
- package/{migrations → src/migrations}/800101_feature-fixSubIdRef.js +1 -1
- package/{migrations → src/migrations}/800200_quoteView.js +1 -1
- package/{migrations → src/migrations}/800300_quoteResponseView.js +1 -1
- package/{migrations → src/migrations}/900100_foreign-keys.js +18 -18
- package/{migrations → src/migrations}/900101_foreign-keys.js +6 -6
- package/{migrations → src/migrations}/910100_feature904DDL.js +7 -7
- package/{migrations → src/migrations}/910101_feature904DataMigration.js +1 -1
- package/{migrations → src/migrations}/910102_feature949DataMigration.js +1 -1
- package/{migrations → src/migrations}/910103_feature1093SettlementByCurrency.js +3 -3
- package/{migrations → src/migrations}/940101_quotePartyIdInfoExtension-2522.js +25 -25
- package/{migrations → src/migrations}/950101_transferParticipantStateChange.js +2 -2
- package/{migrations → src/migrations}/950102_settlementModel-adjustPosition.js +2 -2
- package/{migrations → src/migrations}/950103_dropTransferParticipantStateChange.js +3 -3
- package/{migrations → src/migrations}/950104_settlementModel-settlementAccountTypeId.js +3 -3
- package/src/migrations/950107_settlementWindowContent_foreign_settlementModel.js +63 -0
- package/{migrations → src/migrations}/950108_participantProxy.js +1 -2
- package/{migrations → src/migrations}/950120_fxQuoteConversionTerms_sourceAmount_nullable.js +1 -1
- package/{migrations → src/migrations}/960100_create_externalParticipant.js +2 -2
- package/{migrations → src/migrations}/960110_alter_transferParticipant__addFiled_externalParticipantId.js +2 -2
- package/{migrations → src/migrations}/961001_transferForwarded.js +1 -1
- package/{migrations → src/migrations}/961002_fxTransferForwarded.js +1 -1
- package/src/models/participant/externalParticipant.int.ts +85 -0
- package/src/models/participant/facade.js +4 -2
- package/src/models/participant/participantCurrencyCached.js +1 -1
- package/src/models/position/facade.js +8 -2
- package/src/models/transfer/facade.int.ts +139 -0
- package/src/models/transfer/facade.js +8 -3
- package/src/models/transfer/transferDuplicateCheck.js +2 -2
- package/src/models/transfer/transferError.int.ts +112 -0
- package/src/models/transfer/transferExtension.int.ts +137 -0
- package/src/models/transfer/transferStateChange.int.ts +117 -0
- package/{seeds → src/seeds}/participant.js +1 -1
- package/{seeds → src/seeds}/settlementInterchange.js +1 -0
- package/src/settlement/api/handlerRoutes.js +71 -0
- package/src/settlement/api/handlers/health.js +10 -0
- package/src/settlement/api/handlers/index.js +57 -0
- package/src/settlement/api/handlers/settlementWindows/{id}.js +103 -0
- package/src/settlement/api/handlers/settlementWindows.js +77 -0
- package/src/settlement/api/handlers/settlements/{id}.js +133 -0
- package/src/settlement/api/handlers/settlements/{sid}/participants/{pid}/accounts/{aid}.js +127 -0
- package/src/settlement/api/handlers/settlements/{sid}/participants/{pid}.js +127 -0
- package/src/settlement/api/handlers/settlements.js +114 -0
- package/src/settlement/api/openapiRouting.js +87 -0
- package/src/settlement/api/routes.js +176 -0
- package/src/settlement/domain/rules/index.js +46 -0
- package/src/settlement/domain/settlement/index.js +405 -0
- package/src/settlement/domain/settlementWindow/index.js +117 -0
- package/src/settlement/domain/transactions/index.js +83 -0
- package/src/settlement/domain/transferSettlement/index.js +57 -0
- package/src/settlement/handlers/deferredSettlement/handler.js +180 -0
- package/src/settlement/handlers/grossSettlement/handler.js +180 -0
- package/src/settlement/handlers/index.js +101 -0
- package/src/settlement/handlers/register.js +92 -0
- package/src/settlement/handlers/rules/handler.js +164 -0
- package/src/settlement/interface/swagger-handler.json +34 -0
- package/src/settlement/interface/swagger.json +1560 -0
- package/src/settlement/lib/db.js +32 -0
- package/src/settlement/lib/kafka/index.js +41 -0
- package/src/settlement/lib/kafka/producer.js +142 -0
- package/src/settlement/lib/scriptEngine.js +99 -0
- package/src/settlement/lib/scriptsLoader.js +175 -0
- package/src/settlement/lib/utility.js +243 -0
- package/src/settlement/models/ilpPackets/ilpPacket.js +42 -0
- package/src/settlement/models/lib/enums.js +192 -0
- package/src/settlement/models/misc/migrationLock.js +53 -0
- package/src/settlement/models/settlement/facade.js +1652 -0
- package/src/settlement/models/settlement/index.js +55 -0
- package/src/settlement/models/settlement/participantCurrency.js +48 -0
- package/src/settlement/models/settlement/settlement.js +49 -0
- package/src/settlement/models/settlement/settlementModel.js +44 -0
- package/src/settlement/models/settlement/settlementParticipantCurrency.js +61 -0
- package/src/settlement/models/settlement/settlementStateChange.js +44 -0
- package/src/settlement/models/settlement/settlementTransferParticipant.js +46 -0
- package/src/settlement/models/settlementWindow/facade.js +345 -0
- package/src/settlement/models/settlementWindow/index.js +47 -0
- package/src/settlement/models/settlementWindow/settlementWindowStateChange.js +55 -0
- package/src/settlement/models/settlementWindowContent/facade.js +79 -0
- package/src/settlement/models/settlementWindowContent/index.js +40 -0
- package/src/settlement/models/settlementWindowContent/settlementWindowContentStateChange.js +53 -0
- package/src/settlement/models/transferSettlement/facade.js +363 -0
- package/src/settlement/models/transferSettlement/index.js +42 -0
- package/src/settlement/shared/logger/index.js +7 -0
- package/src/settlement/shared/plugins.js +88 -0
- package/src/settlement/shared/setup.js +224 -0
- package/src/settlement/utils/truthyProperty.js +37 -0
- package/src/shared/legacyUtilShim.js +20 -0
- package/src/shared/plugins.js +3 -3
- package/src/shared/results.ts +22 -0
- package/src/shared/results.unit.ts +38 -0
- package/src/shared/setup.js +14 -1
- package/src/testing/api-helpers.ts +1222 -0
- package/src/testing/exec-async.ts +67 -0
- package/src/testing/harness.snapshot.sql +3895 -0
- package/src/testing/harness.test.ts +15 -0
- package/src/testing/harness.ts +1160 -0
- package/src/testing/kafka.ts +221 -0
- package/src/testing/prng.ts +82 -0
- package/src/testing/rdkafka.test.ts +263 -0
- package/src/testing/run.ts +559 -0
- package/src/testing/snapshot.test.ts +92 -0
- package/src/testing/snapshot.ts +549 -0
- package/src/testing/tap-stream.ts +164 -0
- package/src/testing/types.ts +95 -0
- package/src/testing/util.ts +333 -0
- package/src/types/central-services-error-handling.d.ts +72 -0
- package/src/types/central-services-stream.d.ts +148 -0
- package/test-integration.Dockerfile +0 -2
- package/test.Dockerfile +0 -2
- package/tsconfig.json +40 -0
- package/config/knexfile.js +0 -19
- package/migrations/950107_settlementWindowContent_foreign_settlementModel.js +0 -63
- package/src/api/index.js +0 -16
- package/src/lib/config.js +0 -81
- /package/{migrations → src/migrations}/110101_contactType-indexes.js +0 -0
- /package/{migrations → src/migrations}/110301_endpointType-indexes.js +0 -0
- /package/{migrations → src/migrations}/110401_ledgerEntryType-indexes.js +0 -0
- /package/{migrations → src/migrations}/110451_ledgerAccountType-indexes.js +0 -0
- /package/{migrations → src/migrations}/110501_participantLimitType-indexes.js +0 -0
- /package/{migrations → src/migrations}/110601_transferParticipantRoleType-indexes.js +0 -0
- /package/{migrations → src/migrations}/110800_settlementWindowState.js +0 -0
- /package/{migrations → src/migrations}/111000_amountType.js +0 -0
- /package/{migrations → src/migrations}/111001_amountType-indexes.js +0 -0
- /package/{migrations → src/migrations}/111100_balanceOfPayments.js +0 -0
- /package/{migrations → src/migrations}/111101_balanceOfPayments-indexes.js +0 -0
- /package/{migrations → src/migrations}/111200_partyIdentifierType.js +0 -0
- /package/{migrations → src/migrations}/111201_partyIdentifierType-indexes.js +0 -0
- /package/{migrations → src/migrations}/111300_partyType.js +0 -0
- /package/{migrations → src/migrations}/111301_partyType-indexes.js +0 -0
- /package/{migrations → src/migrations}/111400_quoteDuplicateCheck.js +0 -0
- /package/{migrations → src/migrations}/111500_transactionInitiator.js +0 -0
- /package/{migrations → src/migrations}/111501_transactionInitiator-indexes.js +0 -0
- /package/{migrations → src/migrations}/111600_transactionInitiatorType.js +0 -0
- /package/{migrations → src/migrations}/111601_transactionInitiatorType-indexes.js +0 -0
- /package/{migrations → src/migrations}/111700_settlementDelay.js +0 -0
- /package/{migrations → src/migrations}/111701_settlementDelay-indexes.js +0 -0
- /package/{migrations → src/migrations}/111800_settlementInterchange.js +0 -0
- /package/{migrations → src/migrations}/111801_settlementInterchange-indexes.js +0 -0
- /package/{migrations → src/migrations}/111900_settlementGranularity.js +0 -0
- /package/{migrations → src/migrations}/111901_settlementGranularity-indexes.js +0 -0
- /package/{migrations → src/migrations}/112101_bulkProcessingState-indexes.js +0 -0
- /package/{migrations → src/migrations}/200101_participant-indexes.js +0 -0
- /package/{migrations → src/migrations}/200201_participantContact-indexes.js +0 -0
- /package/{migrations → src/migrations}/200301_participantEndpoint-indexes.js +0 -0
- /package/{migrations → src/migrations}/200401_participantParty-indexes.js +0 -0
- /package/{migrations → src/migrations}/300201_transfer-indexes.js +0 -0
- /package/{migrations → src/migrations}/300251_bulkTransfer-indexes.js +0 -0
- /package/{migrations → src/migrations}/300276_bulkTransferAssociation-indexes.js +0 -0
- /package/{migrations → src/migrations}/300401_transferStateChange-indexes.js +0 -0
- /package/{migrations → src/migrations}/300451_bulkTransferStateChange-indexes.js +0 -0
- /package/{migrations → src/migrations}/300501_segment-indexes.js +0 -0
- /package/{migrations → src/migrations}/310101_participantCurrency-indexes.js +0 -0
- /package/{migrations → src/migrations}/310201_transferParticipant-indexes.js +0 -0
- /package/{migrations → src/migrations}/310203_transferParticipant-indexes.js +0 -0
- /package/{migrations → src/migrations}/310301_participantPosition-indexes.js +0 -0
- /package/{migrations → src/migrations}/310401_participantPositionChange-indexes.js +0 -0
- /package/{migrations → src/migrations}/310405_participantPositionChange-positionChange.js +0 -0
- /package/{migrations → src/migrations}/310406_participantPositionChange-positionChange-revert.js +0 -0
- /package/{migrations → src/migrations}/310501_participantLimit-indexes.js +0 -0
- /package/{migrations → src/migrations}/310503_participantLimit-indexes-composite.js +0 -0
- /package/{migrations → src/migrations}/400401_settlmentWindowStateChange-indexes.js +0 -0
- /package/{migrations → src/migrations}/400501_settlementTransferParticipant-indexes.js +0 -0
- /package/{migrations → src/migrations}/400601_settlementParticipantCurrency-indexes.js +0 -0
- /package/{migrations → src/migrations}/400701_settlementParticipantCurrencyStateChange-indexes.js +0 -0
- /package/{migrations → src/migrations}/400801_settlementStateChange-indexes.js +0 -0
- /package/{migrations → src/migrations}/401002_settlementWindowContent-indexes.js +0 -0
- /package/{migrations → src/migrations}/401201_settlementContentAggregation-indexes.js +0 -0
- /package/{migrations → src/migrations}/401301_settlementModel-indexes.js +0 -0
- /package/{migrations → src/migrations}/410101_transferFulfilmentDuplicateCheck-indexes.js +0 -0
- /package/{migrations → src/migrations}/410201_transferFulfilment-indexes.js +0 -0
- /package/{migrations → src/migrations}/410301_transferExtension-indexes.js +0 -0
- /package/{migrations → src/migrations}/410351_bulkTransferExtension-indexes.js +0 -0
- /package/{migrations → src/migrations}/410401_transferTimeout-indexes.js +0 -0
- /package/{migrations → src/migrations}/410501_transferError-indexes.js +0 -0
- /package/{migrations → src/migrations}/410551_bulkTransferError-indexes.js +0 -0
- /package/{migrations → src/migrations}/410601_expiringTransfer-indexes.js +0 -0
- /package/{migrations → src/migrations}/500100_transactionReference.js +0 -0
- /package/{migrations → src/migrations}/500101_transactionReference-indexes.js +0 -0
- /package/{migrations → src/migrations}/500200_transactionScenario.js +0 -0
- /package/{migrations → src/migrations}/500201_transactionScenario-indexes.js +0 -0
- /package/{migrations → src/migrations}/500300_transactionSubScenario.js +0 -0
- /package/{migrations → src/migrations}/500400_quote.js +0 -0
- /package/{migrations → src/migrations}/500500_quoteParty.js +0 -0
- /package/{migrations → src/migrations}/500600_party.js +0 -0
- /package/{migrations → src/migrations}/500700_quoteResponse.js +0 -0
- /package/{migrations → src/migrations}/500800_quoteResponseIlpPacket.js +0 -0
- /package/{migrations → src/migrations}/500900_geoCode.js +0 -0
- /package/{migrations → src/migrations}/501000_quoteExtension.js +0 -0
- /package/{migrations → src/migrations}/501100_quoteResponseDuplicateCheck.js +0 -0
- /package/{migrations → src/migrations}/501200_quoteError.js +0 -0
- /package/{migrations → src/migrations}/501300_transferRules.js +0 -0
- /package/{migrations → src/migrations}/600011_fxTransferType-indexes.js +0 -0
- /package/{migrations → src/migrations}/600013_fxParticipantCurrencyType-indexes.js +0 -0
- /package/{migrations → src/migrations}/600201_fxTransfer-indexes.js +0 -0
- /package/{migrations → src/migrations}/600401_fxTransferStateChange-indexes.js +0 -0
- /package/{migrations → src/migrations}/600502_fxWatchList-indexes.js +0 -0
- /package/{migrations → src/migrations}/600601_fxTransferFulfilmentDuplicateCheck-indexes.js +0 -0
- /package/{migrations → src/migrations}/600701_fxTransferFulfilment-indexes.js +0 -0
- /package/{migrations → src/migrations}/601401_fxTransferTimeout-indexes.js +0 -0
- /package/{migrations → src/migrations}/601501_fxTransferError-indexes.js +0 -0
- /package/{migrations → src/migrations}/610201_fxTransferParticipant-indexes.js +0 -0
- /package/{migrations → src/migrations}/910102_hotfix954.js +0 -0
- /package/{migrations → src/migrations}/940100_quotePartyIdInfoExtension.js +0 -0
- /package/{migrations → src/migrations}/950100_settlementContentAggregation-decimal184.js +0 -0
- /package/{migrations → src/migrations}/950106_unique_settlement_model_ledger_account_currency.js +0 -0
- /package/{migrations → src/migrations}/950109_fxQuote.js +0 -0
- /package/{migrations → src/migrations}/950110_fxQuoteResponse.js +0 -0
- /package/{migrations → src/migrations}/950111_fxQuoteError.js +0 -0
- /package/{migrations → src/migrations}/950113_fxQuoteDuplicateCheck.js +0 -0
- /package/{migrations → src/migrations}/950114_fxQuoteResponseDuplicateCheck.js +0 -0
- /package/{migrations → src/migrations}/950115_fxQuoteConversionTerms.js +0 -0
- /package/{migrations → src/migrations}/950116_fxQuoteConversionTermsExtension.js +0 -0
- /package/{migrations → src/migrations}/950117_fxQuoteResponseConversionTerms.js +0 -0
- /package/{migrations → src/migrations}/950118_fxQuoteResponseConversionTermsExtension.js +0 -0
- /package/{migrations → src/migrations}/950119_fxCharge.js +0 -0
- /package/{migrations → src/migrations}/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js +0 -0
- /package/{migrations → src/migrations}/960112_fxTransfer-indexes2.js +0 -0
- /package/{migrations → src/migrations}/960113_fxTransferExtension-indexes.js +0 -0
- /package/{migrations → src/migrations}/960114_fxTransferTimeout-indexes2.js +0 -0
- /package/{migrations → src/migrations}/961000_participantEndpoint_unique.js +0 -0
- /package/{migrations → src/migrations}/migration.template +0 -0
- /package/{seeds → src/seeds}/amountType.js +0 -0
- /package/{seeds → src/seeds}/balanceOfPayments.js +0 -0
- /package/{seeds → src/seeds}/bulkProcessingState.js +0 -0
- /package/{seeds → src/seeds}/bulkTransferState.js +0 -0
- /package/{seeds → src/seeds}/currency.js +0 -0
- /package/{seeds → src/seeds}/endpointType.js +0 -0
- /package/{seeds → src/seeds}/fxParticipantCurrencyType.js +0 -0
- /package/{seeds → src/seeds}/fxTransferType.js +0 -0
- /package/{seeds → src/seeds}/ledgerAccountType.js +0 -0
- /package/{seeds → src/seeds}/ledgerEntryType.js +0 -0
- /package/{seeds → src/seeds}/participantLimitType.js +0 -0
- /package/{seeds → src/seeds}/partyIdentifierType.js +0 -0
- /package/{seeds → src/seeds}/partyType.js +0 -0
- /package/{seeds → src/seeds}/settlementDelay.js +0 -0
- /package/{seeds → src/seeds}/settlementGranularity.js +0 -0
- /package/{seeds → src/seeds}/settlementState.js +0 -0
- /package/{seeds → src/seeds}/settlementWindow1State.js +0 -0
- /package/{seeds → src/seeds}/settlementWindow2Open.js +0 -0
- /package/{seeds → src/seeds}/transactionInitiator.js +0 -0
- /package/{seeds → src/seeds}/transactionInitiatorType.js +0 -0
- /package/{seeds → src/seeds}/transactionScenario.js +0 -0
- /package/{seeds → src/seeds}/transferParticipantRoleType.js +0 -0
- /package/{seeds → src/seeds}/transferState.js +0 -0
- /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
|