IMP-63: New payments table, fix metadata form Map (#228)

This commit is contained in:
Rinat Arsaev 2023-06-06 19:07:44 +04:00 committed by GitHub
parent 5993f71141
commit 817b58149a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 617 additions and 1301 deletions

163
package-lock.json generated
View File

@ -22,19 +22,15 @@
"@angular/platform-browser-dynamic": "15.0.3", "@angular/platform-browser-dynamic": "15.0.3",
"@angular/platform-server": "15.0.3", "@angular/platform-server": "15.0.3",
"@angular/router": "15.0.3", "@angular/router": "15.0.3",
"@ng-matero/extensions": "15.4.2",
"@ngneat/input-mask": "6.0.0", "@ngneat/input-mask": "6.0.0",
"@ngneat/until-destroy": "9.2.2", "@ngneat/until-destroy": "9.2.2",
"@s-libs/js-core": "15.0.0", "@s-libs/ng-core": "^15.0.0",
"@s-libs/micro-dash": "15.0.0",
"@s-libs/ng-core": "15.0.0",
"@s-libs/rxjs-core": "15.0.0",
"@vality/deanonimus-proto": "2.0.1-2a3d5ad.0", "@vality/deanonimus-proto": "2.0.1-2a3d5ad.0",
"@vality/domain-proto": "2.0.1-bfedcb9.0", "@vality/domain-proto": "2.0.1-bfedcb9.0",
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0", "@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
"@vality/fistful-proto": "2.0.1-4ff4ea3.0", "@vality/fistful-proto": "2.0.1-4ff4ea3.0",
"@vality/magista-proto": "2.0.1-cf0eff8.0", "@vality/magista-proto": "2.0.1-cf0eff8.0",
"@vality/ng-core": "0.7.1-pr-22-ac52e74.0", "@vality/ng-core": "0.7.1-pr-22-ff78425.0",
"@vality/payout-manager-proto": "2.0.1-b079679.0", "@vality/payout-manager-proto": "2.0.1-b079679.0",
"@vality/repairer-proto": "2.0.1-8f7973d.0", "@vality/repairer-proto": "2.0.1-8f7973d.0",
"@vality/thrift-ts": "2.4.1-8ad5123.0", "@vality/thrift-ts": "2.4.1-8ad5123.0",
@ -42,6 +38,7 @@
"angular2-prettyjson": "3.0.1", "angular2-prettyjson": "3.0.1",
"coerce-property": "15.0.1", "coerce-property": "15.0.1",
"css-element-queries": "1.2.3", "css-element-queries": "1.2.3",
"date-fns": "2.30.0",
"element-resize-detector": "1.2.4", "element-resize-detector": "1.2.4",
"humanize-duration": "3.21.0", "humanize-duration": "3.21.0",
"inputmask": "5.0.7", "inputmask": "5.0.7",
@ -51,7 +48,7 @@
"moment": "2.29.4", "moment": "2.29.4",
"monaco-editor": "0.21.2", "monaco-editor": "0.21.2",
"ngx-mat-select-search": "7.0.1", "ngx-mat-select-search": "7.0.1",
"rxjs": "7.5.4", "rxjs": "7.8.1",
"short-uuid": "4.1.0", "short-uuid": "4.1.0",
"tslib": "2.3.1", "tslib": "2.3.1",
"utility-types": "3.10.0", "utility-types": "3.10.0",
@ -4664,6 +4661,7 @@
"node_modules/@s-libs/js-core": { "node_modules/@s-libs/js-core": {
"version": "15.0.0", "version": "15.0.0",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@ -4674,6 +4672,7 @@
"node_modules/@s-libs/micro-dash": { "node_modules/@s-libs/micro-dash": {
"version": "15.0.0", "version": "15.0.0",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0", "tslib": "^2.3.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
@ -4697,6 +4696,7 @@
"node_modules/@s-libs/rxjs-core": { "node_modules/@s-libs/rxjs-core": {
"version": "15.0.0", "version": "15.0.0",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
@ -5639,29 +5639,30 @@
"integrity": "sha512-59ncaJpt7tXFLOq9KrDu4OgrDQr9vTQ3j30T0hjN+ZIsPBsE+lld/pGKASWLLQfwvTtvp9laAuKgQGX9GuvIiQ==" "integrity": "sha512-59ncaJpt7tXFLOq9KrDu4OgrDQr9vTQ3j30T0hjN+ZIsPBsE+lld/pGKASWLLQfwvTtvp9laAuKgQGX9GuvIiQ=="
}, },
"node_modules/@vality/ng-core": { "node_modules/@vality/ng-core": {
"version": "0.7.1-pr-22-ac52e74.0", "version": "0.7.1-pr-22-ff78425.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-0.7.1-pr-22-ac52e74.0.tgz", "resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-0.7.1-pr-22-ff78425.0.tgz",
"integrity": "sha512-9BstLrgeI9Q3IIGHF75o+b4MkcaZrw9B3odUbGKNr8FSWLCONcCApPFANeKBjg2/ytoe+iX4UfHukY+xt7/o2g==", "integrity": "sha512-NzGQLu2tAZFim0apE4gItGrE9YbbzKYFDs3wBQovmUhFom7zglcG8O8ohtyHGmFZTGfPoHGKTMkjzjtpiZah5g==",
"dependencies": { "dependencies": {
"@ng-matero/extensions": "^15.0.0",
"@s-libs/js-core": "^15.2.0", "@s-libs/js-core": "^15.2.0",
"@s-libs/micro-dash": "^15.2.0", "@s-libs/micro-dash": "^15.2.0",
"@s-libs/ng-core": "^15.2.0", "@s-libs/ng-core": "^15.2.0",
"@s-libs/rxjs-core": "^15.2.0", "@s-libs/rxjs-core": "^15.2.0",
"dinero.js": "^2.0.0-alpha.14", "dinero.js": "^2.0.0-alpha.14",
"ng-let": "^15.0.2",
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/cdk": ">=15.0.0", "@angular/cdk": "^15.0.0",
"@angular/common": ">=15.0.0", "@angular/common": "^15.0.0",
"@angular/core": ">=15.0.0", "@angular/core": "^15.0.0",
"@angular/material": ">=15.0.0", "@angular/material": "^15.0.0",
"@ng-matero/extensions": ">=15.0.0", "@ngneat/until-destroy": "^9.0.0",
"@ngneat/until-destroy": ">=9.0.0",
"@types/lodash-es": "^4.0.0", "@types/lodash-es": "^4.0.0",
"coerce-property": ">=15.0.0", "coerce-property": "^15.0.0",
"lodash-es": "^4.0.0", "lodash-es": "^4.0.0",
"rxjs": ">=7.0.0", "rxjs": "^7.0.0",
"utility-types": ">=3.0.0" "utility-types": "^3.0.0"
} }
}, },
"node_modules/@vality/ng-core/node_modules/@s-libs/js-core": { "node_modules/@vality/ng-core/node_modules/@s-libs/js-core": {
@ -9851,6 +9852,37 @@
"version": "1.0.1", "version": "1.0.1",
"license": "MIT" "license": "MIT"
}, },
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/date-fns/node_modules/@babel/runtime": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz",
"integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/date-fns/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/date-format": { "node_modules/date-format": {
"version": "4.0.11", "version": "4.0.11",
"dev": true, "dev": true,
@ -13087,14 +13119,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/inquirer/node_modules/rxjs": {
"version": "7.5.5",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/inquirer/node_modules/string-width": { "node_modules/inquirer/node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"dev": true, "dev": true,
@ -15287,6 +15311,18 @@
"version": "2.6.2", "version": "2.6.2",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ng-let": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/ng-let/-/ng-let-15.0.2.tgz",
"integrity": "sha512-bOt8GOMCRtMPbJEgNrK2YLr8uEkhfQdw41UJziOQp2fCLxdmEP42ppUzr17Wcv0emsOo2bJ01Y7xfos7uJWczA==",
"dependencies": {
"tslib": "^2.3.1"
},
"peerDependencies": {
"@angular/common": "^15.0.0",
"@angular/core": "^15.0.0"
}
},
"node_modules/ng-packagr": { "node_modules/ng-packagr": {
"version": "15.0.3", "version": "15.0.3",
"dev": true, "dev": true,
@ -15460,14 +15496,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/ng-packagr/node_modules/rxjs": {
"version": "7.6.0",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/ngx-build-plus": { "node_modules/ngx-build-plus": {
"version": "15.0.0", "version": "15.0.0",
"dev": true, "dev": true,
@ -17828,8 +17856,9 @@
} }
}, },
"node_modules/rxjs": { "node_modules/rxjs": {
"version": "7.5.4", "version": "7.8.1",
"license": "Apache-2.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dependencies": { "dependencies": {
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
@ -23916,12 +23945,14 @@
}, },
"@s-libs/js-core": { "@s-libs/js-core": {
"version": "15.0.0", "version": "15.0.0",
"peer": true,
"requires": { "requires": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"@s-libs/micro-dash": { "@s-libs/micro-dash": {
"version": "15.0.0", "version": "15.0.0",
"peer": true,
"requires": { "requires": {
"tslib": "^2.3.0", "tslib": "^2.3.0",
"utility-types": "^3.10.0" "utility-types": "^3.10.0"
@ -23937,6 +23968,7 @@
}, },
"@s-libs/rxjs-core": { "@s-libs/rxjs-core": {
"version": "15.0.0", "version": "15.0.0",
"peer": true,
"requires": { "requires": {
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
@ -24530,15 +24562,17 @@
"integrity": "sha512-59ncaJpt7tXFLOq9KrDu4OgrDQr9vTQ3j30T0hjN+ZIsPBsE+lld/pGKASWLLQfwvTtvp9laAuKgQGX9GuvIiQ==" "integrity": "sha512-59ncaJpt7tXFLOq9KrDu4OgrDQr9vTQ3j30T0hjN+ZIsPBsE+lld/pGKASWLLQfwvTtvp9laAuKgQGX9GuvIiQ=="
}, },
"@vality/ng-core": { "@vality/ng-core": {
"version": "0.7.1-pr-22-ac52e74.0", "version": "0.7.1-pr-22-ff78425.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-0.7.1-pr-22-ac52e74.0.tgz", "resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-0.7.1-pr-22-ff78425.0.tgz",
"integrity": "sha512-9BstLrgeI9Q3IIGHF75o+b4MkcaZrw9B3odUbGKNr8FSWLCONcCApPFANeKBjg2/ytoe+iX4UfHukY+xt7/o2g==", "integrity": "sha512-NzGQLu2tAZFim0apE4gItGrE9YbbzKYFDs3wBQovmUhFom7zglcG8O8ohtyHGmFZTGfPoHGKTMkjzjtpiZah5g==",
"requires": { "requires": {
"@ng-matero/extensions": "^15.0.0",
"@s-libs/js-core": "^15.2.0", "@s-libs/js-core": "^15.2.0",
"@s-libs/micro-dash": "^15.2.0", "@s-libs/micro-dash": "^15.2.0",
"@s-libs/ng-core": "^15.2.0", "@s-libs/ng-core": "^15.2.0",
"@s-libs/rxjs-core": "^15.2.0", "@s-libs/rxjs-core": "^15.2.0",
"dinero.js": "^2.0.0-alpha.14", "dinero.js": "^2.0.0-alpha.14",
"ng-let": "^15.0.2",
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"dependencies": { "dependencies": {
@ -27508,6 +27542,29 @@
"cyclist": { "cyclist": {
"version": "1.0.1" "version": "1.0.1"
}, },
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"requires": {
"@babel/runtime": "^7.21.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz",
"integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
}
}
},
"date-format": { "date-format": {
"version": "4.0.11", "version": "4.0.11",
"dev": true "dev": true
@ -29614,13 +29671,6 @@
"version": "3.0.0", "version": "3.0.0",
"dev": true "dev": true
}, },
"rxjs": {
"version": "7.5.5",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
},
"string-width": { "string-width": {
"version": "4.2.3", "version": "4.2.3",
"dev": true, "dev": true,
@ -31022,6 +31072,14 @@
"neo-async": { "neo-async": {
"version": "2.6.2" "version": "2.6.2"
}, },
"ng-let": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/ng-let/-/ng-let-15.0.2.tgz",
"integrity": "sha512-bOt8GOMCRtMPbJEgNrK2YLr8uEkhfQdw41UJziOQp2fCLxdmEP42ppUzr17Wcv0emsOo2bJ01Y7xfos7uJWczA==",
"requires": {
"tslib": "^2.3.1"
}
},
"ng-packagr": { "ng-packagr": {
"version": "15.0.3", "version": "15.0.3",
"dev": true, "dev": true,
@ -31135,13 +31193,6 @@
"requires": { "requires": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
} }
},
"rxjs": {
"version": "7.6.0",
"dev": true,
"requires": {
"tslib": "^2.1.0"
}
} }
} }
}, },
@ -32642,7 +32693,9 @@
} }
}, },
"rxjs": { "rxjs": {
"version": "7.5.4", "version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"requires": { "requires": {
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }

View File

@ -31,19 +31,15 @@
"@angular/platform-browser-dynamic": "15.0.3", "@angular/platform-browser-dynamic": "15.0.3",
"@angular/platform-server": "15.0.3", "@angular/platform-server": "15.0.3",
"@angular/router": "15.0.3", "@angular/router": "15.0.3",
"@ng-matero/extensions": "15.4.2",
"@ngneat/input-mask": "6.0.0", "@ngneat/input-mask": "6.0.0",
"@ngneat/until-destroy": "9.2.2", "@ngneat/until-destroy": "9.2.2",
"@s-libs/js-core": "15.0.0", "@s-libs/ng-core": "^15.0.0",
"@s-libs/micro-dash": "15.0.0",
"@s-libs/ng-core": "15.0.0",
"@s-libs/rxjs-core": "15.0.0",
"@vality/deanonimus-proto": "2.0.1-2a3d5ad.0", "@vality/deanonimus-proto": "2.0.1-2a3d5ad.0",
"@vality/domain-proto": "2.0.1-bfedcb9.0", "@vality/domain-proto": "2.0.1-bfedcb9.0",
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0", "@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
"@vality/fistful-proto": "2.0.1-4ff4ea3.0", "@vality/fistful-proto": "2.0.1-4ff4ea3.0",
"@vality/magista-proto": "2.0.1-cf0eff8.0", "@vality/magista-proto": "2.0.1-cf0eff8.0",
"@vality/ng-core": "0.7.1-pr-22-ac52e74.0", "@vality/ng-core": "0.7.1-pr-22-ff78425.0",
"@vality/payout-manager-proto": "2.0.1-b079679.0", "@vality/payout-manager-proto": "2.0.1-b079679.0",
"@vality/repairer-proto": "2.0.1-8f7973d.0", "@vality/repairer-proto": "2.0.1-8f7973d.0",
"@vality/thrift-ts": "2.4.1-8ad5123.0", "@vality/thrift-ts": "2.4.1-8ad5123.0",
@ -51,6 +47,7 @@
"angular2-prettyjson": "3.0.1", "angular2-prettyjson": "3.0.1",
"coerce-property": "15.0.1", "coerce-property": "15.0.1",
"css-element-queries": "1.2.3", "css-element-queries": "1.2.3",
"date-fns": "2.30.0",
"element-resize-detector": "1.2.4", "element-resize-detector": "1.2.4",
"humanize-duration": "3.21.0", "humanize-duration": "3.21.0",
"inputmask": "5.0.7", "inputmask": "5.0.7",
@ -60,7 +57,7 @@
"moment": "2.29.4", "moment": "2.29.4",
"monaco-editor": "0.21.2", "monaco-editor": "0.21.2",
"ngx-mat-select-search": "7.0.1", "ngx-mat-select-search": "7.0.1",
"rxjs": "7.5.4", "rxjs": "7.8.1",
"short-uuid": "4.1.0", "short-uuid": "4.1.0",
"tslib": "2.3.1", "tslib": "2.3.1",
"utility-types": "3.10.0", "utility-types": "3.10.0",

View File

@ -11,7 +11,7 @@ import { PartyManagementService } from '@cc/app/api/payment-processing';
export class PartiesStoreService { export class PartiesStoreService {
constructor(private partyManagementService: PartyManagementService) {} constructor(private partyManagementService: PartyManagementService) {}
@MemoizeExpiring(30_000) @MemoizeExpiring(5 * 60_000)
get(partyId: PartyID) { get(partyId: PartyID) {
return this.partyManagementService return this.partyManagementService
.Get(partyId) .Get(partyId)

View File

@ -5,11 +5,11 @@ import { AppAuthGuardService } from '@cc/app/shared/services';
import { ROUTING_CONFIG as DEPOSITS_ROUTING_CONFIG } from './sections/deposits/routing-config'; import { ROUTING_CONFIG as DEPOSITS_ROUTING_CONFIG } from './sections/deposits/routing-config';
import { ROUTING_CONFIG as DOMAIN_ROUTING_CONFIG } from './sections/domain/routing-config'; import { ROUTING_CONFIG as DOMAIN_ROUTING_CONFIG } from './sections/domain/routing-config';
import { ROUTING_CONFIG as PAYMENTS_ROUTING_CONFIG } from './sections/payments/routing-config';
import { ROUTING_CONFIG as PAYOUTS_ROUTING_CONFIG } from './sections/payouts/payouts/routing-config'; import { ROUTING_CONFIG as PAYOUTS_ROUTING_CONFIG } from './sections/payouts/payouts/routing-config';
import { ROUTING_CONFIG as REPAIRING_ROUTING_CONFIG } from './sections/repairing/routing-config'; import { ROUTING_CONFIG as REPAIRING_ROUTING_CONFIG } from './sections/repairing/routing-config';
import { ROUTING_CONFIG as CLAIMS_ROUTING_CONFIG } from './sections/search-claims/routing-config'; import { ROUTING_CONFIG as CLAIMS_ROUTING_CONFIG } from './sections/search-claims/routing-config';
import { ROUTING_CONFIG as PARTIES_ROUTING_CONFIG } from './sections/search-parties/routing-config'; import { ROUTING_CONFIG as PARTIES_ROUTING_CONFIG } from './sections/search-parties/routing-config';
import { ROUTING_CONFIG as PAYMENTS_ROUTING_CONFIG } from './sections/search-payments/routing-config';
import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sections/sources/routing-config'; import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sections/sources/routing-config';
import { ROUTING_CONFIG as WALLETS_ROUTING_CONFIG } from './sections/wallets/routing-config'; import { ROUTING_CONFIG as WALLETS_ROUTING_CONFIG } from './sections/wallets/routing-config';
import { ROUTING_CONFIG as WITHDRAWALS_ROUTING_CONFIG } from './sections/withdrawals/routing-config'; import { ROUTING_CONFIG as WITHDRAWALS_ROUTING_CONFIG } from './sections/withdrawals/routing-config';

View File

@ -1,3 +1,5 @@
import { registerLocaleData } from '@angular/common';
import localeRu from '@angular/common/locales/ru';
import { LOCALE_ID, NgModule, Injector } from '@angular/core'; import { LOCALE_ID, NgModule, Injector } from '@angular/core';
import { MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter'; import { MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter';
import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER } from '@angular/material/autocomplete'; import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER } from '@angular/material/autocomplete';
@ -39,6 +41,8 @@ import {
SMALL_SEARCH_LIMIT, SMALL_SEARCH_LIMIT,
} from './tokens'; } from './tokens';
registerLocaleData(localeRu);
/** /**
* For use in specific locations (for example, questionary PDF document) * For use in specific locations (for example, questionary PDF document)
*/ */
@ -77,8 +81,8 @@ export let AppInjector: Injector;
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }, { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } },
{ provide: MAT_DATE_FORMATS, useValue: DEFAULT_MAT_DATE_FORMATS }, { provide: MAT_DATE_FORMATS, useValue: DEFAULT_MAT_DATE_FORMATS },
{ provide: DateAdapter, useClass: MomentUtcDateAdapter, deps: [MAT_DATE_LOCALE] }, { provide: DateAdapter, useClass: MomentUtcDateAdapter, deps: [MAT_DATE_LOCALE] },
{ provide: MAT_DATE_LOCALE, useValue: 'ru' }, { provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
{ provide: LOCALE_ID, useValue: 'en' }, { provide: LOCALE_ID, useValue: 'ru' },
{ provide: SEARCH_LIMIT, useValue: DEFAULT_SEARCH_LIMIT }, { provide: SEARCH_LIMIT, useValue: DEFAULT_SEARCH_LIMIT },
{ provide: SMALL_SEARCH_LIMIT, useValue: DEFAULT_SMALL_SEARCH_LIMIT }, { provide: SMALL_SEARCH_LIMIT, useValue: DEFAULT_SMALL_SEARCH_LIMIT },
{ provide: QUERY_PARAMS_SERIALIZERS, useValue: DEFAULT_QUERY_PARAMS_SERIALIZERS }, { provide: QUERY_PARAMS_SERIALIZERS, useValue: DEFAULT_QUERY_PARAMS_SERIALIZERS },

View File

@ -3,7 +3,7 @@ import { FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { InvoicePaymentAdjustmentParams } from '@vality/domain-proto/payment_processing'; import { InvoicePaymentAdjustmentParams } from '@vality/domain-proto/payment_processing';
import { StatPayment } from '@vality/magista-proto/magista'; import { StatPayment } from '@vality/magista-proto/magista';
import { DialogSuperclass } from '@vality/ng-core'; import { DialogSuperclass, NotifyLogService } from '@vality/ng-core';
import chunk from 'lodash-es/chunk'; import chunk from 'lodash-es/chunk';
import { BehaviorSubject, from, concatMap, of, forkJoin } from 'rxjs'; import { BehaviorSubject, from, concatMap, of, forkJoin } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators'; import { catchError, finalize } from 'rxjs/operators';
@ -11,8 +11,6 @@ import { catchError, finalize } from 'rxjs/operators';
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services'; import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
import { InvoicingService } from '../../../../api/payment-processing'; import { InvoicingService } from '../../../../api/payment-processing';
import { NotificationService } from '../../../services/notification';
import { NotificationErrorService } from '../../../services/notification-error';
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
@ -33,9 +31,8 @@ export class CreatePaymentAdjustmentComponent extends DialogSuperclass<
constructor( constructor(
injector: Injector, injector: Injector,
private invoicingService: InvoicingService, private invoicingService: InvoicingService,
private notificationService: NotificationService, private log: NotifyLogService,
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService, private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService
private notificationErrorService: NotificationErrorService
) { ) {
super(injector); super(injector);
} }
@ -71,7 +68,7 @@ export class CreatePaymentAdjustmentComponent extends DialogSuperclass<
.subscribe({ .subscribe({
complete: () => { complete: () => {
if (!this.withError.length) { if (!this.withError.length) {
this.notificationService.success(`${payments.length} created successfully`); this.log.success(`${payments.length} created successfully`);
this.closeWithSuccess(); this.closeWithSuccess();
} else { } else {
const errors = this.withError const errors = this.withError
@ -82,7 +79,7 @@ export class CreatePaymentAdjustmentComponent extends DialogSuperclass<
}) })
.filter(Boolean) .filter(Boolean)
.join(', '); .join(', ');
this.notificationErrorService.error( this.log.error(
new Error( new Error(
`${this.withError.length} out of ${payments.length} failed. Errors: ${errors}` `${this.withError.length} out of ${payments.length} failed. Errors: ${errors}`
) )

View File

@ -0,0 +1,15 @@
<v-table
[columns]="columns"
[data]="data"
[hasMore]="hasMore"
[progress]="isLoading"
[rowSelected]="selected"
rowSelectable
(more)="more.emit()"
(rowSelectionChange)="selectedChange.emit($event)"
(update)="update.emit($event)"
>
<v-table-actions>
<ng-content></ng-content>
</v-table-actions>
</v-table>

View File

@ -0,0 +1,90 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { InvoicePaymentStatus } from '@vality/domain-proto/domain';
import { StatPayment } from '@vality/magista-proto/magista';
import { Column, TagColumn, LoadOptions } from '@vality/ng-core';
import startCase from 'lodash-es/startCase';
import { map } from 'rxjs/operators';
import { PartiesStoreService } from '@cc/app/api/payment-processing';
import { AmountCurrencyService } from '@cc/app/shared/services';
import { getUnionKey } from '@cc/utils';
@Component({
selector: 'cc-payments-table',
templateUrl: './payments-table.component.html',
})
export class PaymentsTableComponent {
@Input() data!: StatPayment[];
@Input() isLoading?: boolean | null;
@Input() hasMore?: boolean | null;
@Input() selected?: StatPayment[];
@Output() selectedChange = new EventEmitter<StatPayment[]>();
@Output() update = new EventEmitter<LoadOptions>();
@Output() more = new EventEmitter<void>();
columns: Column<StatPayment>[] = [
{ field: 'id', hide: true },
'invoice_id',
{
field: 'amount',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(data.amount, data.currency_symbolic_code),
typeParameters: {
currencyCode: 'currency_symbolic_code',
},
},
{
field: 'fee',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(data.fee, data.currency_symbolic_code),
typeParameters: {
currencyCode: 'currency_symbolic_code',
},
},
{
field: 'status',
type: 'tag',
formatter: (data) => startCase(getUnionKey(data.status)),
typeParameters: {
value: (data) => getUnionKey(data.status),
tags: {
pending: { color: 'pending' },
processed: { color: 'pending' },
captured: { color: 'success' },
cancelled: {},
refunded: { color: 'success' },
failed: { color: 'warn' },
charged_back: { color: 'success' },
},
},
} as TagColumn<StatPayment, keyof InvoicePaymentStatus>,
{ field: 'created_at', type: 'datetime' },
{
field: 'owner_id',
header: 'Party',
formatter: (data) =>
this.partiesStoreService.get(data.owner_id).pipe(map((p) => p.contact_info.email)),
description: 'owner_id',
},
{
field: 'shop',
formatter: (data) =>
this.partiesStoreService
.get(data.owner_id)
.pipe(map((p) => p.shops.get(data.shop_id).details.name)),
description: 'shop_id',
header: 'Shop',
},
'domain_revision',
{ field: 'terminal_id.id', header: 'Terminal' },
{ field: 'provider_id.id', header: 'Provider' },
];
constructor(
private amountCurrencyService: AmountCurrencyService,
private partiesStoreService: PartiesStoreService
) {}
}

View File

@ -0,0 +1 @@
export * from './payments.module';

View File

@ -3,15 +3,15 @@ import { RouterModule } from '@angular/router';
import { AppAuthGuardService } from '@cc/app/shared/services'; import { AppAuthGuardService } from '@cc/app/shared/services';
import { PaymentsComponent } from './payments.component';
import { ROUTING_CONFIG } from './routing-config'; import { ROUTING_CONFIG } from './routing-config';
import { SearchPaymentsComponent } from './search-payments.component';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([
{ {
path: '', path: '',
component: SearchPaymentsComponent, component: PaymentsComponent,
canActivate: [AppAuthGuardService], canActivate: [AppAuthGuardService],
data: ROUTING_CONFIG, data: ROUTING_CONFIG,
}, },
@ -19,4 +19,4 @@ import { SearchPaymentsComponent } from './search-payments.component';
], ],
exports: [RouterModule], exports: [RouterModule],
}) })
export class SearchPaymentsRoutingModule {} export class PaymentsRoutingModule {}

View File

@ -0,0 +1,53 @@
<cc-page-layout title="Payments">
<cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions>
<v-filters
#filters
[active]="active"
merge
(clear)="filtersForm.reset(); otherFiltersControl.reset()"
>
<ng-template [formGroup]="filtersForm" vMainFilters>
<v-date-range-field formControlName="dateRange"></v-date-range-field>
<v-list-field formControlName="invoice_ids" label="Invoice Ids"></v-list-field>
<cc-merchant-field formControlName="party_id"></cc-merchant-field>
<cc-shop-field
[partyId]="filtersForm.value.party_id"
formControlName="shop_ids"
multiple
></cc-shop-field>
<v-input-field formControlName="payment_first6" label="Card BIN"></v-input-field>
<v-input-field formControlName="payment_last4" label="Card PAN"></v-input-field>
<v-input-field formControlName="payment_rrn" label="Payment RRN"></v-input-field>
<v-input-field formControlName="payment_email" label="Payer email"></v-input-field>
</ng-template>
<ng-template vOtherFilters>
<cc-metadata-form
[extensions]="extensions"
[formControl]="otherFiltersControl"
[metadata]="metadata$ | async"
namespace="magista"
type="PaymentSearchQuery"
></cc-metadata-form>
</ng-template>
</v-filters>
<cc-payments-table
[data]="(payments$ | async) || []"
[hasMore]="hasMore$ | async"
[isLoading]="isLoading$ | async"
[selected]="selected$ | async"
(more)="more()"
(selectedChange)="selected$.next($event)"
(update)="load($event ?? {})"
>
<button
[disabled]="!(selected$ | async)?.length"
color="primary"
mat-button
(click)="createPaymentAdjustment()"
>
Create payment adjustment
</button>
</cc-payments-table>
</cc-page-layout>

View File

@ -0,0 +1,149 @@
import { Component, OnInit } from '@angular/core';
import { NonNullableFormBuilder } from '@angular/forms';
import { Router } from '@angular/router';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { ThriftAstMetadata } from '@vality/fistful-proto';
import { PaymentSearchQuery, StatPayment } from '@vality/magista-proto/magista';
import {
DialogService,
DialogResponseStatus,
LoadOptions,
getNoTimeZoneIsoString,
clean,
DateRange,
} from '@vality/ng-core';
import { endOfDay, startOfDay, subDays } from 'date-fns';
import lodashMerge from 'lodash-es/merge';
import { BehaviorSubject, debounceTime, from, of, merge } from 'rxjs';
import { startWith } from 'rxjs/operators';
import { MetadataFormExtension, isTypeWithAliases } from '../../shared/components/metadata-form';
import { QueryParamsService } from '../../shared/services';
import { CreatePaymentAdjustmentComponent } from './components/create-payment-adjustment/create-payment-adjustment.component';
import { FetchPaymentsService } from './services/fetch-payments.service';
@UntilDestroy()
@Component({
templateUrl: 'payments.component.html',
})
export class PaymentsComponent implements OnInit {
isLoading$ = this.fetchPaymentsService.isLoading$;
payments$ = this.fetchPaymentsService.result$;
hasMore$ = this.fetchPaymentsService.hasMore$;
selected$ = new BehaviorSubject<StatPayment[]>([]);
filtersForm = this.fb.group({
dateRange: {
start: subDays(startOfDay(new Date()), 1),
end: endOfDay(new Date()),
} as DateRange,
invoice_ids: [undefined as string[]],
party_id: undefined as string,
shop_ids: [undefined as string[]],
payment_first6: undefined as string,
payment_last4: undefined as string,
payment_rrn: undefined as string,
payment_email: undefined as string,
});
otherFiltersControl = this.fb.control({
common_search_query_params: {},
payment_params: {},
});
metadata$ = from(
import('@vality/magista-proto/metadata.json').then((m) => m.default as ThriftAstMetadata[])
);
extensions: MetadataFormExtension[] = [
{
determinant: (data) =>
of(
isTypeWithAliases(data, 'CommonSearchQueryParams', 'magista') ||
[
'invoice_ids',
'payment_email',
'payment_first6',
'payment_last4',
'payment_rrn',
].includes(data?.field?.name)
),
extension: () => of({ hidden: true }),
},
];
active = 0;
constructor(
private router: Router,
private qp: QueryParamsService<{
filters: object;
otherFilters: object;
dateRange: DateRange;
}>,
private fetchPaymentsService: FetchPaymentsService,
private dialogService: DialogService,
private fb: NonNullableFormBuilder
) {}
ngOnInit() {
this.filtersForm.patchValue(
lodashMerge({}, this.qp.params.filters, clean({ dateRange: this.qp.params.dateRange }))
);
const otherFilters = this.otherFiltersControl.value;
const otherFiltersParams: Partial<PaymentSearchQuery> = this.qp.params.otherFilters || {};
this.otherFiltersControl.patchValue(lodashMerge({}, otherFilters, otherFiltersParams));
merge(this.filtersForm.valueChanges, this.otherFiltersControl.valueChanges)
.pipe(startWith(null), debounceTime(500), untilDestroyed(this))
.subscribe(() => {
this.load();
});
}
more() {
this.fetchPaymentsService.more();
}
load(options?: LoadOptions) {
const { dateRange, ...filters } = clean(this.filtersForm.value);
const otherFilters = clean(this.otherFiltersControl.value);
void this.qp.set({ filters, otherFilters, dateRange });
this.fetchPaymentsService.load(
clean({
...otherFilters,
common_search_query_params: {
...(otherFilters.common_search_query_params || {}),
party_id: filters.party_id,
shop_ids: filters.shop_ids,
from_time: getNoTimeZoneIsoString(dateRange.start),
to_time: getNoTimeZoneIsoString(dateRange.end),
},
payment_params: {
...(otherFilters.payment_params || {}),
payment_email: filters.payment_email,
payment_first6: filters.payment_first6,
payment_last4: filters.payment_last4,
payment_rrn: filters.payment_rrn,
},
invoice_ids: filters.invoice_ids,
}),
options
);
this.active =
Object.keys(dateRange).length +
Object.keys(filters).length +
Object.keys(otherFilters.payment_params || {}).length +
Object.keys(otherFilters.common_search_query_params || {}).length;
}
createPaymentAdjustment() {
this.dialogService
.open(CreatePaymentAdjustmentComponent, {
payments: this.selected$.value,
})
.afterClosed()
.subscribe((res) => {
if (res.status === DialogResponseStatus.Success) {
this.load();
this.selected$.next([]);
} else if (res.data?.withError?.length) {
this.selected$.next(res.data.withError.map((w) => w.payment));
}
});
}
}

View File

@ -0,0 +1,43 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import {
FiltersModule,
DateRangeFieldModule,
ListFieldModule,
TableModule,
DialogModule,
InputFieldModule,
} from '@vality/ng-core';
import { PageLayoutModule, ShopFieldModule } from '@cc/app/shared';
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
import { CreatePaymentAdjustmentComponent } from './components/create-payment-adjustment/create-payment-adjustment.component';
import { PaymentsTableComponent } from './components/payments-table/payments-table.component';
import { PaymentsRoutingModule } from './payments-routing.module';
import { PaymentsComponent } from './payments.component';
@NgModule({
imports: [
CommonModule,
PaymentsRoutingModule,
PageLayoutModule,
FiltersModule,
DateRangeFieldModule,
ListFieldModule,
MerchantFieldModule,
ReactiveFormsModule,
TableModule,
DialogModule,
MetadataFormModule,
MatButtonModule,
ShopFieldModule,
InputFieldModule,
FormsModule,
],
declarations: [PaymentsComponent, CreatePaymentAdjustmentComponent, PaymentsTableComponent],
})
export class PaymentsModule {}

View File

@ -0,0 +1,44 @@
import { Injectable } from '@angular/core';
import { StatPayment, PaymentSearchQuery } from '@vality/magista-proto/magista';
import { FetchSuperclass, FetchOptions, FetchResult, NotifyLogService } from '@vality/ng-core';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { MerchantStatisticsService } from '@cc/app/api/magista';
@Injectable({
providedIn: 'root',
})
export class FetchPaymentsService extends FetchSuperclass<StatPayment, PaymentSearchQuery> {
constructor(
private merchantStatisticsService: MerchantStatisticsService,
private log: NotifyLogService
) {
super();
}
protected fetch(
params: PaymentSearchQuery,
{ size, continuationToken }: FetchOptions
): Observable<FetchResult<StatPayment>> {
return this.merchantStatisticsService
.SearchPayments({
payment_params: {},
...params,
common_search_query_params: Object.assign({}, params.common_search_query_params, {
continuation_token: continuationToken,
limit: size,
}),
})
.pipe(
map(({ payments, continuation_token }) => ({
result: payments,
continuationToken: continuation_token,
})),
catchError((err) => {
this.log.errorOperation(err, 'receive', 'payments');
return of({ result: [] });
})
);
}
}

View File

@ -43,10 +43,8 @@
[rowSelected]="selected$ | async" [rowSelected]="selected$ | async"
[trackBy]="trackById" [trackBy]="trackById"
rowSelectable rowSelectable
sizes
(more)="fetchMore()" (more)="fetchMore()"
(rowSelectionChange)="selected$.next($event)" (rowSelectionChange)="selected$.next($event)"
(sizeChange)="update($event)"
(update)="update($event.size)" (update)="update($event.size)"
> >
<v-table-actions> <v-table-actions>

View File

@ -1 +0,0 @@
export * from './search-payments.module';

View File

@ -1,7 +0,0 @@
<cc-page-layout title="Payments">
<cc-payments-searcher
[initSearchParams]="qp.params || {}"
(paymentEventFired)="paymentEventFired($event)"
(searchParamsChanged)="searchParamsUpdated($event)"
></cc-payments-searcher>
</cc-page-layout>

View File

@ -1,32 +0,0 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { clean } from '@vality/ng-core';
import {
PaymentActions,
PaymentMenuItemEvent,
SearchFiltersParams,
} from '@cc/app/shared/components';
import { QueryParamsService } from '../../shared/services';
@Component({
templateUrl: 'search-payments.component.html',
})
export class SearchPaymentsComponent {
constructor(private router: Router, public qp: QueryParamsService<SearchFiltersParams>) {}
searchParamsUpdated(params: SearchFiltersParams) {
void this.qp.set(clean(params));
}
paymentEventFired($event: PaymentMenuItemEvent) {
const { partyID, invoiceID, paymentID } = $event;
switch ($event.action) {
case PaymentActions.NavigateToPayment:
void this.router.navigate([
`/party/${partyID}/invoice/${invoiceID}/payment/${paymentID}`,
]);
}
}
}

View File

@ -1,29 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
import { PageLayoutModule } from '../../shared';
import { PaymentsSearcherModule } from '../../shared/components/payments-searcher';
import { SearchPaymentsRoutingModule } from './search-payments-routing.module';
import { SearchPaymentsComponent } from './search-payments.component';
@NgModule({
imports: [
SearchPaymentsRoutingModule,
MatCardModule,
FlexModule,
MatProgressBarModule,
CommonModule,
EmptySearchResultModule,
MatButtonModule,
PaymentsSearcherModule,
PageLayoutModule,
],
declarations: [SearchPaymentsComponent],
})
export class SearchPaymentsModule {}

View File

@ -38,8 +38,7 @@ const ROUTES: Routes = [
}, },
{ {
path: 'payments', path: 'payments',
loadChildren: () => loadChildren: () => import('./payments/payments.module').then((m) => m.PaymentsModule),
import('./search-payments/search-payments.module').then((m) => m.SearchPaymentsModule),
}, },
{ {
path: 'deposits', path: 'deposits',

View File

@ -24,9 +24,7 @@
[data]="wallets$ | async" [data]="wallets$ | async"
[hasMore]="hasMore$ | async" [hasMore]="hasMore$ | async"
[progress]="inProgress$ | async" [progress]="inProgress$ | async"
sizes
(more)="fetchMore()" (more)="fetchMore()"
(sizeChange)="search($event)"
(update)="search($event.size)" (update)="search($event.size)"
> >
<ng-template #balanceTpl let-col="colDef" let-index="index" let-row> <ng-template #balanceTpl let-col="colDef" let-index="index" let-row>

View File

@ -1,8 +1,6 @@
export * from './claim-search-form'; export * from './claim-search-form';
export * from './party-modification-creator'; export * from './party-modification-creator';
export * from './party-modification-forms'; export * from './party-modification-forms';
export * from './payments-search-filters';
export * from './payments-table';
export * from './status'; export * from './status';
export * from './shop-field'; export * from './shop-field';
export * from './shop-details'; export * from './shop-details';

View File

@ -9,6 +9,7 @@ import {
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { FormComponentSuperclass } from '@s-libs/ng-core'; import { FormComponentSuperclass } from '@s-libs/ng-core';
import { MapType, SetType, ListType } from '@vality/thrift-ts'; import { MapType, SetType, ListType } from '@vality/thrift-ts';
import { merge } from 'rxjs';
import { MetadataFormExtension } from '@cc/app/shared/components/metadata-form'; import { MetadataFormExtension } from '@cc/app/shared/components/metadata-form';
import { createControlProviders, getErrorsTree } from '@cc/utils'; import { createControlProviders, getErrorsTree } from '@cc/utils';
@ -23,6 +24,8 @@ function updateFormArray<V>(formArray: FormArray<AbstractControl<V>>, values: V[
formArray.patchValue(values); formArray.patchValue(values);
} }
type ComplexType<T, K = never> = T[] | Map<K, T> | Set<T>;
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
selector: 'cc-complex-form', selector: 'cc-complex-form',
@ -30,15 +33,15 @@ function updateFormArray<V>(formArray: FormArray<AbstractControl<V>>, values: V[
styleUrls: ['complex-form.component.scss'], styleUrls: ['complex-form.component.scss'],
providers: createControlProviders(() => ComplexFormComponent), providers: createControlProviders(() => ComplexFormComponent),
}) })
export class ComplexFormComponent<T extends unknown[] | Map<unknown, unknown> | Set<unknown>> export class ComplexFormComponent<V, K = never>
extends FormComponentSuperclass<T> extends FormComponentSuperclass<ComplexType<V, K>>
implements OnInit, Validator implements OnInit, Validator
{ {
@Input() data: MetadataFormData<SetType | MapType | ListType>; @Input() data: MetadataFormData<SetType | MapType | ListType>;
@Input() extensions: MetadataFormExtension[]; @Input() extensions: MetadataFormExtension[];
valueControls = new FormArray([]); valueControls = new FormArray<AbstractControl<V>>([]);
keyControls = new FormArray([]); keyControls = new FormArray<AbstractControl<K>>([]);
get hasLabel() { get hasLabel() {
return !!this.data.trueParent; return !!this.data.trueParent;
@ -53,31 +56,34 @@ export class ComplexFormComponent<T extends unknown[] | Map<unknown, unknown> |
} }
ngOnInit() { ngOnInit() {
this.valueControls.valueChanges.pipe(untilDestroyed(this)).subscribe((value) => { merge(this.valueControls.valueChanges, this.keyControls.valueChanges)
switch (this.data.type.name) { .pipe(untilDestroyed(this))
case 'list': .subscribe(() => {
this.emitOutgoingValue(value as never); const values = this.valueControls.value;
break; switch (this.data.type.name) {
case 'map': case 'list':
this.emitOutgoingValue( this.emitOutgoingValue(values);
new Map(value.map((v, idx) => [this.keyControls.value[idx], v])) as never break;
); case 'map': {
break; const keys = this.keyControls.value;
case 'set': this.emitOutgoingValue(new Map(values.map((v, idx) => [keys[idx], v])));
this.emitOutgoingValue(new Set(value) as never); break;
break; }
} case 'set':
}); this.emitOutgoingValue(new Set(values));
break;
}
});
} }
handleIncomingValue(value: T) { handleIncomingValue(value: ComplexType<V, K>) {
if (this.isKeyValue) { if (this.isKeyValue) {
const keys = Array.from(value?.keys() || []); const keys = Array.from((value as Map<K, V>)?.keys() || []);
updateFormArray(this.keyControls, keys); updateFormArray(this.keyControls, keys);
} }
const values = this.isKeyValue const values = this.isKeyValue
? Array.from(value?.values() || []) ? Array.from(value?.values() || [])
: Array.from(value || []); : Array.from((value as V[]) || []);
updateFormArray(this.valueControls, values); updateFormArray(this.valueControls, values);
} }

View File

@ -1,4 +1,4 @@
<div *ngIf="data" [ngSwitch]="data?.typeGroup"> <div *ngIf="!(extensionResult$ | async)?.hidden" [ngSwitch]="data?.typeGroup">
<cc-extension-field <cc-extension-field
*ngIf="(extensionResult$ | async)?.type; else defaultFields" *ngIf="(extensionResult$ | async)?.type; else defaultFields"
[data]="data" [data]="data"

View File

@ -21,6 +21,7 @@ export interface MetadataFormExtensionResult {
label?: string; label?: string;
type?: 'datetime' | 'cash'; type?: 'datetime' | 'cash';
converter?: Converter; converter?: Converter;
hidden?: boolean;
} }
export interface MetadataFormExtensionOption { export interface MetadataFormExtensionOption {

View File

@ -1,3 +0,0 @@
export * from './payments-main-search-filters';
export * from './payments-other-search-filters';
export * from './search-filters-params';

View File

@ -1,11 +0,0 @@
import { Moment } from 'moment';
export interface FormValue {
fromTime: Moment;
toTime: Moment;
invoiceIDs?: string;
shopIDs?: string[];
bin?: string;
pan?: string;
rrn?: string;
}

View File

@ -1,2 +0,0 @@
export * from './payments-main-search-filters.module';
export * from './payments-main-search-filters.component';

View File

@ -1,71 +0,0 @@
<div
[formGroup]="form"
gdColumns="1fr 1fr 1fr 1fr"
gdColumns.lt-lg="1fr 1fr 1fr"
gdColumns.lt-md="1fr 1fr"
gdColumns.lt-sm="1fr"
gdGap="16px"
>
<mat-form-field>
<mat-label>Date Range</mat-label>
<mat-date-range-input [rangePicker]="picker">
<input formControlName="fromTime" matInput matStartDate placeholder="Start Date" />
<input formControlName="toTime" matEndDate matInput placeholder="End Date" />
</mat-date-range-input>
<mat-datepicker-toggle [for]="picker" matSuffix>
<mat-icon matDatepickerToggleIcon>keyboard_arrow_down</mat-icon>
</mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="invoiceIDs"
matInput
placeholder="Invoice IDs"
type="string"
/>
<mat-hint>id0,id1</mat-hint>
</mat-form-field>
<cc-merchant-field formControlName="partyID"></cc-merchant-field>
<mat-form-field>
<mat-label>Shops</mat-label>
<mat-select
[disabled]="!form.value['partyID']"
class="select"
formControlName="shopIDs"
multiple
>
<mat-option *ngFor="let shop of shops$ | async" [value]="shop.id">
{{ shop.details.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="bin"
matInput
placeholder="Card Bin"
type="string"
/>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="pan"
matInput
placeholder="Card Pan"
type="string"
/>
</mat-form-field>
<mat-form-field>
<input
autocomplete="false"
formControlName="rrn"
matInput
placeholder="Payment RRN"
type="string"
/>
</mat-form-field>
</div>

View File

@ -1,42 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { filter } from 'rxjs/operators';
import { SearchFiltersParams } from '../search-filters-params';
import { PaymentsMainSearchFiltersService } from './payments-main-search-filters.service';
@UntilDestroy()
@Component({
selector: 'cc-payments-main-search-filters',
templateUrl: 'payments-main-search-filters.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [PaymentsMainSearchFiltersService],
})
export class PaymentsMainSearchFiltersComponent implements OnInit {
@Input() initParams: SearchFiltersParams;
@Output() valueChanges = new EventEmitter<SearchFiltersParams>();
shops$ = this.paymentsMainSearchFiltersService.shops$;
form = this.paymentsMainSearchFiltersService.form;
constructor(private paymentsMainSearchFiltersService: PaymentsMainSearchFiltersService) {}
ngOnInit() {
this.paymentsMainSearchFiltersService.searchParamsChanges$
.pipe(untilDestroyed(this))
.subscribe((params) => this.valueChanges.emit(params));
this.paymentsMainSearchFiltersService.init(this.initParams);
this.form.controls.partyID.valueChanges
.pipe(filter(Boolean), untilDestroyed(this))
.subscribe((partyID: string) => {
this.paymentsMainSearchFiltersService.getShops(partyID);
});
}
}

View File

@ -1,37 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MerchantFieldModule } from '../../merchant-field';
import { PaymentsMainSearchFiltersComponent } from './payments-main-search-filters.component';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
MatFormFieldModule,
MatDatepickerModule,
MatIconModule,
MatInputModule,
MatButtonModule,
MatBadgeModule,
MatDialogModule,
MatDividerModule,
FlexLayoutModule,
MatSelectModule,
MerchantFieldModule,
],
declarations: [PaymentsMainSearchFiltersComponent],
exports: [PaymentsMainSearchFiltersComponent],
})
export class PaymentsMainSearchFiltersModule {}

View File

@ -1,52 +0,0 @@
import { Injectable } from '@angular/core';
import { UntypedFormBuilder, Validators } from '@angular/forms';
import { PartyID } from '@vality/domain-proto/payment_processing';
import * as moment from 'moment';
import { ReplaySubject, defer } from 'rxjs';
import { debounceTime, filter, map, shareReplay, switchMap } from 'rxjs/operators';
import { PartyManagementService } from '@cc/app/api/payment-processing';
import { SearchFiltersParams } from '../search-filters-params';
import { searchParamsToFormParams } from './search-params-to-form-params';
@Injectable()
export class PaymentsMainSearchFiltersService {
form = this.fb.group({
fromTime: [moment().subtract(1, 'year').startOf('d'), Validators.required],
toTime: [moment().endOf('d'), Validators.required],
invoiceIDs: '',
partyID: '',
shopIDs: [],
bin: ['', [Validators.pattern(/\d{6}$/), Validators.maxLength(6)]],
pan: ['', [Validators.pattern(/\d{4}$/), Validators.maxLength(4)]],
rrn: '',
});
searchParamsChanges$ = this.form.valueChanges.pipe(
debounceTime(600),
filter(() => this.form.valid),
shareReplay(1)
);
shops$ = defer(() => this.getShops$).pipe(
switchMap((partyID) => this.partyManagementService.Get(partyID)),
map(({ shops }) => Array.from(shops.values())),
shareReplay(1)
);
private getShops$ = new ReplaySubject<string>();
constructor(
private partyManagementService: PartyManagementService,
private fb: UntypedFormBuilder
) {}
getShops(partyID: PartyID) {
this.getShops$.next(partyID);
}
init(params: SearchFiltersParams) {
this.form.patchValue(searchParamsToFormParams(params));
}
}

View File

@ -1,11 +0,0 @@
import moment from 'moment';
import { SearchFiltersParams } from '../search-filters-params';
import { FormValue } from './form-value';
export const searchParamsToFormParams = (value: SearchFiltersParams): FormValue =>
({
...value,
...(value.fromTime ? { fromTime: moment(value.fromTime) } : {}),
...(value.toTime ? { toTime: moment(value.toTime) } : {}),
} as any);

View File

@ -1,6 +0,0 @@
import { SearchFiltersParams } from '../search-filters-params';
export const countActiveFilters = (filters: SearchFiltersParams, formFields: string[]): number => {
const paramsFields = Object.keys(filters);
return paramsFields.reduce((acc, curr) => (formFields.includes(curr) ? ++acc : acc), 0) || null;
};

View File

@ -1,9 +0,0 @@
import { toMinor } from '@cc/utils/to-minor';
import { SearchFiltersParams } from '../search-filters-params';
export const formParamsToSearchParams = (params: SearchFiltersParams): SearchFiltersParams => ({
...params,
...(params.paymentAmountFrom ? { paymentAmountFrom: toMinor(params.paymentAmountFrom) } : {}),
...(params.paymentAmountTo ? { paymentAmountTo: toMinor(params.paymentAmountTo) } : {}),
});

View File

@ -1,2 +0,0 @@
export * from './payments-other-search-filters.module';
export * from './payments-other-search-filters.component';

View File

@ -1,2 +0,0 @@
export * from './other-filters-dialog.module';
export * from './other-filters-dialog.component';

View File

@ -1,78 +0,0 @@
<v-dialog title="Other filters">
<div [formGroup]="form" fxLayout="column" fxLayoutGap="24px">
<div fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<input formControlName="payerEmail" matInput placeholder="Payer email" />
</mat-form-field>
<mat-form-field fxFlex>
<input formControlName="terminalID" matInput placeholder="Terminal ID" />
</mat-form-field>
</div>
<mat-form-field fxFlex>
<input formControlName="providerID" matInput placeholder="Provider ID" />
</mat-form-field>
<mat-form-field>
<mat-select formControlName="paymentStatus" placeholder="Payment Status">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let s of paymentStatuses" [value]="s.value">
{{ s.key }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="paymentMethod" placeholder="Payment Method">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let pm of paymentMethods" [value]="pm.value">
{{ pm.key }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="tokenProvider" placeholder="Token Provider">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let p of tokenProviders$ | async" [value]="p.ref.id">
{{ p.data.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select formControlName="paymentSystem" placeholder="Payment System">
<mat-option [value]="null">any</mat-option>
<mat-option *ngFor="let ps of paymentSystems$ | async" [value]="ps.ref.id">
{{ ps.data.name }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-divider></mat-divider>
<h2 class="mat-title">Domain Revision</h2>
<div fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<input formControlName="domainRevisionFrom" matInput placeholder="From" />
</mat-form-field>
<mat-form-field fxFlex>
<input formControlName="domainRevisionTo" matInput placeholder="To" />
<mat-hint *ngIf="currentDomainVersion$ | async as version"
>Latest: {{ version }}</mat-hint
>
</mat-form-field>
</div>
<mat-divider></mat-divider>
<h2 class="mat-title">Payment Amount</h2>
<div fxLayout fxLayoutGap="16px">
<mat-form-field fxFlex>
<input formControlName="paymentAmountFrom" matInput placeholder="From" />
</mat-form-field>
<mat-form-field fxFlex>
<input formControlName="paymentAmountTo" matInput placeholder="To" />
</mat-form-field>
</div>
</div>
<v-dialog-actions>
<button [disabled]="form.invalid" color="primary" mat-button (click)="save()">Save</button>
</v-dialog-actions>
</v-dialog>

View File

@ -1,43 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit, Injector } from '@angular/core';
import { magista } from '@vality/magista-proto';
import { DialogSuperclass } from '@vality/ng-core';
import { DomainStoreService } from '@cc/app/api/deprecated-damsel';
import { getEnumKeyValues } from '../../../../../../utils';
import { SearchFiltersParams } from '../../search-filters-params';
import { OtherFiltersDialogService } from './other-filters-dialog.service';
@Component({
templateUrl: 'other-filters-dialog.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [OtherFiltersDialogService],
})
export class OtherFiltersDialogComponent
extends DialogSuperclass<OtherFiltersDialogComponent, SearchFiltersParams, SearchFiltersParams>
implements OnInit
{
paymentStatuses = getEnumKeyValues(magista.InvoicePaymentStatus);
paymentMethods = getEnumKeyValues(magista.PaymentToolType);
tokenProviders$ = this.domainStoreService.getObjects('payment_token');
paymentSystems$ = this.domainStoreService.getObjects('payment_system');
currentDomainVersion$ = this.paymentsOtherSearchFiltersService.currentDomainVersion$;
form = this.paymentsOtherSearchFiltersService.form;
constructor(
injector: Injector,
private paymentsOtherSearchFiltersService: OtherFiltersDialogService,
private domainStoreService: DomainStoreService
) {
super(injector);
}
ngOnInit() {
this.form.patchValue(this.dialogData);
}
save() {
this.closeWithSuccess(this.form.value);
}
}

View File

@ -1,37 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { DialogModule } from '@vality/ng-core';
import { OtherFiltersDialogComponent } from './other-filters-dialog.component';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
MatFormFieldModule,
MatDatepickerModule,
MatIconModule,
MatInputModule,
MatButtonModule,
MatBadgeModule,
MatDialogModule,
MatDividerModule,
FlexLayoutModule,
MatSelectModule,
DialogModule,
],
declarations: [OtherFiltersDialogComponent],
exports: [OtherFiltersDialogComponent],
})
export class OtherFiltersDialogModule {}

View File

@ -1,25 +0,0 @@
import { Injectable } from '@angular/core';
import { UntypedFormBuilder, Validators } from '@angular/forms';
import { DomainStoreService } from '@cc/app/api/deprecated-damsel';
@Injectable()
export class OtherFiltersDialogService {
currentDomainVersion$ = this.domainStoreService.version$;
form = this.fb.group({
payerEmail: ['', [Validators.email]],
terminalID: '',
providerID: '',
paymentStatus: null,
domainRevisionFrom: '',
domainRevisionTo: '',
paymentAmountFrom: '',
paymentAmountTo: '',
paymentMethod: null,
tokenProvider: null,
paymentSystem: null,
});
constructor(private fb: UntypedFormBuilder, private domainStoreService: DomainStoreService) {}
}

View File

@ -1,10 +0,0 @@
<button
[matBadge]="count$ | async"
class="other-filters-button"
mat-button
matBadgeColor="accent"
matBadgePosition="after"
(click)="openOtherFiltersDialog()"
>
Other filters
</button>

View File

@ -1,38 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
OnInit,
} from '@angular/core';
import { SearchFiltersParams } from '../search-filters-params';
import { PaymentsOtherSearchFiltersService } from './payments-other-search-filters.service';
@Component({
selector: 'cc-payments-other-search-filters',
templateUrl: 'payments-other-search-filters.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [PaymentsOtherSearchFiltersService],
})
export class PaymentsOtherSearchFiltersComponent implements OnInit {
@Input() initParams: SearchFiltersParams;
@Output() valueChanges = new EventEmitter<SearchFiltersParams>();
count$ = this.paymentsOtherSearchFiltersService.filtersCount$;
private searchParamsChanges$ = this.paymentsOtherSearchFiltersService.searchParamsChanges$;
constructor(private paymentsOtherSearchFiltersService: PaymentsOtherSearchFiltersService) {
this.searchParamsChanges$.subscribe((params) => this.valueChanges.emit(params));
}
ngOnInit() {
this.paymentsOtherSearchFiltersService.init(this.initParams);
}
openOtherFiltersDialog() {
this.paymentsOtherSearchFiltersService.openOtherFiltersDialog();
}
}

View File

@ -1,35 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { OtherFiltersDialogModule } from './other-filters-dialog';
import { PaymentsOtherSearchFiltersComponent } from './payments-other-search-filters.component';
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule,
MatFormFieldModule,
MatDatepickerModule,
MatIconModule,
MatInputModule,
MatButtonModule,
MatBadgeModule,
MatDialogModule,
MatDividerModule,
FlexLayoutModule,
OtherFiltersDialogModule,
],
declarations: [PaymentsOtherSearchFiltersComponent],
exports: [PaymentsOtherSearchFiltersComponent],
})
export class PaymentsOtherSearchFiltersModule {}

View File

@ -1,62 +0,0 @@
import { Injectable } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { DialogService, DialogResponseStatus } from '@vality/ng-core';
import { ReplaySubject } from 'rxjs';
import { filter, map, shareReplay, switchMap, first } from 'rxjs/operators';
import { removeEmptyProperties } from '@cc/utils/remove-empty-properties';
import { SearchFiltersParams } from '../search-filters-params';
import { formParamsToSearchParams } from './form-params-to-search-params';
import { OtherFiltersDialogComponent } from './other-filters-dialog';
import { searchParamsToFormParams } from './search-params-to-form-params';
import { toFiltersCount } from './to-filters-count';
@UntilDestroy()
@Injectable()
export class PaymentsOtherSearchFiltersService {
private formParams = new ReplaySubject<SearchFiltersParams>(1);
private countableKeys = [
'payerEmail',
'terminalID',
'providerID',
'paymentStatus',
'domainRevisionFrom',
'domainRevisionTo',
'paymentAmountFrom',
'paymentAmountTo',
'paymentMethod',
'tokenProvider',
'paymentSystem',
];
// eslint-disable-next-line @typescript-eslint/member-ordering
searchParamsChanges$ = this.formParams.pipe(map(formParamsToSearchParams), shareReplay(1));
// eslint-disable-next-line @typescript-eslint/member-ordering
filtersCount$ = this.searchParamsChanges$.pipe(
map(removeEmptyProperties),
map(toFiltersCount(this.countableKeys)),
shareReplay(1)
);
constructor(private dialogService: DialogService) {}
init(params: SearchFiltersParams) {
this.formParams.next(searchParamsToFormParams(params));
}
openOtherFiltersDialog() {
this.formParams
.pipe(
first(),
switchMap((data) =>
this.dialogService.open(OtherFiltersDialogComponent, data).afterClosed()
),
filter(({ status }) => status === DialogResponseStatus.Success),
untilDestroyed(this)
)
.subscribe(({ data }) => this.formParams.next(data));
}
}

View File

@ -1,9 +0,0 @@
import { toMajor } from '@cc/utils/to-major';
import { SearchFiltersParams } from '../search-filters-params';
export const searchParamsToFormParams = (params: SearchFiltersParams): SearchFiltersParams => ({
...params,
...(params.paymentAmountFrom ? { paymentAmountFrom: toMajor(params.paymentAmountFrom) } : {}),
...(params.paymentAmountTo ? { paymentAmountTo: toMajor(params.paymentAmountTo) } : {}),
});

View File

@ -1,6 +0,0 @@
import { SearchFiltersParams } from '../search-filters-params';
export const toFiltersCount =
(keys: string[]) =>
(p: Partial<SearchFiltersParams>): number =>
Object.keys(p).filter((k) => keys.includes(k)).length || null;

View File

@ -1,23 +0,0 @@
import { InvoicePaymentStatus, PaymentToolType } from '@vality/magista-proto/magista';
export interface SearchFiltersParams {
partyID?: string;
fromTime?: string;
toTime?: string;
invoiceIDs?: string;
shopIDs?: string[];
payerEmail?: string;
terminalID?: string;
providerID?: string;
rrn?: string;
paymentMethod?: PaymentToolType;
paymentSystem?: string;
tokenProvider?: string;
bin?: string;
pan?: string;
domainRevisionFrom?: number;
domainRevisionTo?: number;
paymentAmountFrom?: number;
paymentAmountTo?: number;
paymentStatus?: InvoicePaymentStatus;
}

View File

@ -1,83 +0,0 @@
import { Injectable } from '@angular/core';
import { StatPayment } from '@vality/magista-proto/magista';
import { cleanPrimitiveProps, clean, splitBySeparators } from '@vality/ng-core';
import * as moment from 'moment';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { MerchantStatisticsService } from '@cc/app/api/magista';
import { FetchResult, PartialFetcher } from '@cc/app/shared/services';
import { SearchFiltersParams } from '../payments-search-filters';
const SEARCH_LIMIT = 1000;
@Injectable()
export class FetchPaymentsService extends PartialFetcher<StatPayment, SearchFiltersParams> {
constructor(private merchantStatisticsService: MerchantStatisticsService) {
super();
}
protected fetch(
params: SearchFiltersParams,
continuationToken: string
): Observable<FetchResult<StatPayment>> {
const {
partyID,
fromTime,
toTime,
invoiceIDs,
shopIDs,
payerEmail,
terminalID,
providerID,
rrn,
paymentMethod,
paymentSystem,
tokenProvider,
bin,
pan,
domainRevisionFrom,
domainRevisionTo,
paymentAmountFrom,
paymentAmountTo,
paymentStatus,
} = params;
return this.merchantStatisticsService
.SearchPayments(
cleanPrimitiveProps({
common_search_query_params: clean({
from_time: moment(fromTime).utc().format(),
to_time: moment(toTime).utc().format(),
limit: SEARCH_LIMIT,
continuation_token: continuationToken,
party_id: partyID,
shop_ids: shopIDs,
}),
invoice_ids: clean(splitBySeparators(invoiceIDs), true),
payment_params: clean({
payment_status: paymentStatus,
payment_tool: paymentMethod,
payment_email: payerEmail,
payment_first6: bin,
payment_system: { id: paymentSystem },
payment_last4: pan,
payment_provider_id: providerID,
payment_terminal_id: terminalID,
from_payment_domain_revision: domainRevisionFrom,
to_payment_domain_revision: domainRevisionTo,
payment_rrn: rrn,
payment_amount_from: paymentAmountFrom,
payment_amount_to: paymentAmountTo,
payment_token_provider: { id: tokenProvider },
}),
})
)
.pipe(
map(({ payments, continuation_token }) => ({
result: payments,
continuationToken: continuation_token,
}))
);
}
}

View File

@ -1 +0,0 @@
export * from './payments-searcher.module';

View File

@ -1,51 +0,0 @@
<div fxLayout="column" fxLayoutGap="24px">
<mat-card>
<mat-card-content>
<cc-payments-main-search-filters
[initParams]="initSearchParams"
(valueChanges)="searchParamsChanges($event)"
></cc-payments-main-search-filters>
</mat-card-content>
<mat-card-footer *ngIf="doAction$ | async">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</mat-card-footer>
</mat-card>
<v-actions>
<button mat-button (click)="searchParamsChanges()">Update</button>
<cc-payments-other-search-filters
[initParams]="initSearchParams"
(valueChanges)="searchParamsChanges($event)"
></cc-payments-other-search-filters>
<button
[disabled]="!selectedPayments?.length"
color="primary"
mat-button
(click)="createPaymentAdjustment()"
>
Create payment adjustment{{
selectedPayments?.length ? ' (' + selectedPayments?.length + ')' : ''
}}
</button>
</v-actions>
<cc-empty-search-result
*ngIf="!(doAction$ | async) && !(payments$ | async)?.length"
label="Payments not found"
></cc-empty-search-result>
<mat-card *ngIf="(payments$ | async)?.length > 0" fxLayout="column" fxLayoutGap="16px">
<cc-payments-table
[payments]="payments$ | async"
[selected]="selectedPayments"
(menuItemSelected$)="paymentMenuItemSelected($event)"
(selected$)="selectedPayments = $event.selected"
></cc-payments-table>
<button
*ngIf="hasMore$ | async"
[disabled]="doAction$ | async"
fxFlex="100"
mat-button
(click)="fetchMore()"
>
{{ (doAction$ | async) ? 'Loading...' : 'Show more' }}
</button>
</mat-card>
</div>

View File

@ -1,85 +0,0 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
import { StatPayment } from '@vality/magista-proto/magista';
import { DialogService, DialogResponseStatus } from '@vality/ng-core';
import { BehaviorSubject, skip } from 'rxjs';
import { SearchFiltersParams } from '../payments-search-filters';
import { PaymentActions, PaymentMenuItemEvent } from '../payments-table';
import { CreatePaymentAdjustmentComponent } from './create-payment-adjustment/create-payment-adjustment.component';
import { FetchPaymentsService } from './fetch-payments.service';
@UntilDestroy()
@Component({
selector: 'cc-payments-searcher',
templateUrl: 'payments-searcher.component.html',
providers: [FetchPaymentsService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PaymentsSearcherComponent implements OnInit {
@Input() initSearchParams: SearchFiltersParams;
@Output() searchParamsChanged: EventEmitter<SearchFiltersParams> = new EventEmitter();
@Output() paymentEventFired: EventEmitter<PaymentMenuItemEvent> = new EventEmitter();
doAction$ = this.fetchPaymentsService.doAction$;
payments$ = this.fetchPaymentsService.searchResult$;
hasMore$ = this.fetchPaymentsService.hasMore$;
searchParamsChange$ = new BehaviorSubject<SearchFiltersParams>({});
selectedPayments: StatPayment[];
constructor(
private fetchPaymentsService: FetchPaymentsService,
private snackBar: MatSnackBar,
private dialogService: DialogService
) {}
ngOnInit() {
this.searchParamsChange$.pipe(skip(1), untilDestroyed(this)).subscribe((params) => {
this.fetchPaymentsService.search(params);
this.searchParamsChanged.emit(params);
});
this.fetchPaymentsService.errors$.subscribe((e) =>
this.snackBar.open(`An error occurred while search payments (${String(e)})`, 'OK')
);
}
fetchMore() {
this.fetchPaymentsService.fetchMore();
}
searchParamsChanges(params: SearchFiltersParams = {}) {
this.searchParamsChange$.next({ ...this.searchParamsChange$.value, ...params });
}
paymentMenuItemSelected(paymentMenuItemEvent: PaymentMenuItemEvent) {
switch (paymentMenuItemEvent.action) {
case PaymentActions.NavigateToPayment:
this.paymentEventFired.emit(paymentMenuItemEvent);
break;
}
}
createPaymentAdjustment() {
this.dialogService
.open(CreatePaymentAdjustmentComponent, {
payments: this.selectedPayments,
})
.afterClosed()
.subscribe((res) => {
if (res.status === DialogResponseStatus.Success) {
this.searchParamsChanges();
this.selectedPayments = [];
} else if (res.data?.withError?.length) {
this.selectedPayments = res.data.withError.map((w) => w.payment);
}
});
}
}

View File

@ -1,54 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatTableModule } from '@angular/material/table';
import { ActionsModule, DialogModule } from '@vality/ng-core';
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
import { MetadataFormModule } from '../metadata-form';
import {
PaymentsMainSearchFiltersModule,
PaymentsOtherSearchFiltersModule,
} from '../payments-search-filters';
import { PaymentsTableModule } from '../payments-table';
import { StatusModule } from '../status';
import { CreatePaymentAdjustmentComponent } from './create-payment-adjustment/create-payment-adjustment.component';
import { PaymentsSearcherComponent } from './payments-searcher.component';
@NgModule({
imports: [
FlexModule,
MatCardModule,
MatProgressBarModule,
CommonModule,
MatButtonModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatMenuModule,
MatIconModule,
PaymentsMainSearchFiltersModule,
StatusModule,
PaymentsTableModule,
MatBadgeModule,
PaymentsOtherSearchFiltersModule,
EmptySearchResultModule,
ActionsModule,
DialogModule,
MetadataFormModule,
],
declarations: [PaymentsSearcherComponent, CreatePaymentAdjustmentComponent],
exports: [PaymentsSearcherComponent],
})
export class PaymentsSearcherModule {}

View File

@ -1,3 +0,0 @@
export * from './payments-table.module';
export * from './payment-actions';
export * from './payment-menu-item-event';

View File

@ -1,16 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core';
import { PaymentActions } from './payment-actions';
const PAYMENT_ACTION_NAMES: { [N in PaymentActions]: string } = {
[PaymentActions.NavigateToPayment]: 'Details',
};
@Pipe({
name: 'ccPaymentActions',
})
export class PaymentActionsPipe implements PipeTransform {
transform(action: string): string {
return PAYMENT_ACTION_NAMES[action] || action;
}
}

View File

@ -1,3 +0,0 @@
export enum PaymentActions {
NavigateToPayment = 'NavigateToPayment',
}

View File

@ -1,10 +0,0 @@
import { InvoiceID, InvoicePaymentID, PartyID } from '@vality/domain-proto/domain';
import { PaymentActions } from './payment-actions';
export interface PaymentMenuItemEvent {
action: PaymentActions;
paymentID: InvoicePaymentID;
invoiceID: InvoiceID;
partyID: PartyID;
}

View File

@ -1,90 +0,0 @@
<table *ngIf="cols" [dataSource]="payments" mat-table>
<cc-select-column
[dataSource]="payments"
sticky
(changed)="selection = $event; selected$.emit($event)"
></cc-select-column>
<ng-container matColumnDef="revision">
<th *matHeaderCellDef mat-header-cell>Revision</th>
<td *matCellDef="let payment" mat-cell>
{{ payment.domain_revision }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th *matHeaderCellDef mat-header-cell>Amount</th>
<td *matCellDef="let payment" mat-cell>
{{ payment.amount | ccFormatAmount }}
{{ payment.currency_symbolic_code | ccCurrency }}
</td>
</ng-container>
<ng-container matColumnDef="status">
<th *matHeaderCellDef mat-header-cell>Status</th>
<td *matCellDef="let payment" mat-cell>
<cc-status [color]="payment.status | toStatus | toPaymentColor">{{
payment.status | toStatus
}}</cc-status>
</td>
</ng-container>
<ng-container matColumnDef="createdAt">
<th *matHeaderCellDef mat-header-cell>Created At</th>
<td *matCellDef="let payment" mat-cell>
{{ payment.created_at | date : 'dd.MM.yyyy HH:mm:ss' }}
</td>
</ng-container>
<ng-container matColumnDef="shop">
<th *matHeaderCellDef mat-header-cell>Shop</th>
<td *matCellDef="let payment" mat-cell>
<div>{{ payment.shop_id | shopName : payment.owner_id }}</div>
<div class="mat-caption mat-secondary-text">{{ payment.shop_id }}</div>
</td>
</ng-container>
<ng-container matColumnDef="party">
<th *matHeaderCellDef mat-header-cell>Party</th>
<td *matCellDef="let payment" mat-cell>
{{ payment.owner_id }}
</td>
</ng-container>
<ng-container matColumnDef="invoice">
<th *matHeaderCellDef mat-header-cell>Invoice</th>
<td *matCellDef="let payment" mat-cell>
{{ payment.invoice_id }}
</td>
</ng-container>
<ng-container matColumnDef="terminal_id">
<th *matHeaderCellDef mat-header-cell>Terminal</th>
<td *matCellDef="let payment" mat-cell>
{{ payment.terminal_id.id }}
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th *matHeaderCellDef class="action-cell" mat-header-cell></th>
<td *matCellDef="let payment" class="action-cell" mat-cell>
<button [matMenuTriggerFor]="menu" mat-icon-button>
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button
*ngFor="let action of paymentActions"
mat-menu-item
(click)="
menuItemSelected(action, payment.id, payment.invoice_id, payment.owner_id)
"
>
{{ action | ccPaymentActions }}
</button>
</mat-menu>
</td>
</ng-container>
<tr *matHeaderRowDef="cols.list; sticky: true" mat-header-row></tr>
<tr *matRowDef="let row; columns: cols.list" mat-row></tr>
</table>

View File

@ -1,17 +0,0 @@
:host {
overflow: scroll;
}
table {
width: 100%;
td,
th {
white-space: nowrap;
padding: 0 4px;
}
}
.action-cell {
width: 8px;
}

View File

@ -1,54 +0,0 @@
import { SelectionModel } from '@angular/cdk/collections';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { InvoiceID, InvoicePaymentID, PartyID } from '@vality/domain-proto/domain';
import { StatPayment } from '@vality/magista-proto/magista';
import { Columns, SELECT_COLUMN_NAME } from '../../../../components/table';
import { PaymentActions } from './payment-actions';
import { PaymentMenuItemEvent } from './payment-menu-item-event';
@Component({
selector: 'cc-payments-table',
templateUrl: 'payments-table.component.html',
styleUrls: ['payments-table.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PaymentsTableComponent {
@Input() payments: StatPayment[];
@Input() set selected(selected: StatPayment[]) {
this.selection.clear();
if (selected?.length) this.selection.select(...selected);
}
@Output() menuItemSelected$ = new EventEmitter<PaymentMenuItemEvent>();
@Output() selected$ = new EventEmitter<SelectionModel<StatPayment>>();
paymentActions = Object.keys(PaymentActions);
cols = new Columns(
SELECT_COLUMN_NAME,
'amount',
'status',
'createdAt',
'shop',
'revision',
'invoice',
'party',
'terminal_id',
'actions'
);
selection = new SelectionModel<StatPayment>();
menuItemSelected(
action: string,
paymentID: InvoicePaymentID,
invoiceID: InvoiceID,
partyID: PartyID
) {
switch (action) {
case PaymentActions.NavigateToPayment:
this.menuItemSelected$.emit({ action, paymentID, invoiceID, partyID });
break;
default:
console.error('Wrong payment action type.');
}
}
}

View File

@ -1,31 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTableModule } from '@angular/material/table';
import { TableModule } from '../../../../components/table';
import { ApiModelPipesModule, CommonPipesModule } from '../../pipes';
import { StatusModule } from '../status';
import { PaymentActionsPipe } from './payment-actions.pipe';
import { PaymentsTableComponent } from './payments-table.component';
@NgModule({
imports: [
CommonModule,
MatTableModule,
FlexModule,
StatusModule,
MatButtonModule,
MatIconModule,
MatMenuModule,
CommonPipesModule,
ApiModelPipesModule,
TableModule,
],
declarations: [PaymentsTableComponent, PaymentActionsPipe],
exports: [PaymentsTableComponent],
})
export class PaymentsTableModule {}

View File

@ -1,11 +1,6 @@
<mat-form-field> <mat-form-field>
<mat-label>{{ multiple ? 'Shops' : 'Shop' }}</mat-label> <mat-label>{{ multiple ? 'Shops' : 'Shop' }}</mat-label>
<mat-select <mat-select [formControl]="control" [multiple]="multiple" [required]="required">
[disabled]="!partyId"
[formControl]="control"
[multiple]="multiple"
[required]="required"
>
<mat-option *ngFor="let shop of shops$ | async" [value]="shop.id"> <mat-option *ngFor="let shop of shops$ | async" [value]="shop.id">
{{ shop.details.name }} {{ shop.details.name }}
</mat-option> </mat-option>

View File

@ -1,3 +1,8 @@
:host {
overflow: hidden;
text-overflow: ellipsis;
}
mat-form-field { mat-form-field {
width: 100%; width: 100%;
} }

View File

@ -1,15 +1,19 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Shop } from '@vality/domain-proto/domain'; import { Shop } from '@vality/domain-proto/domain';
import { PartyID, ShopID } from '@vality/domain-proto/payment_processing'; import { PartyID, ShopID } from '@vality/domain-proto/payment_processing';
import {
createControlProviders,
FormControlSuperclass,
setDisabled,
isEmpty,
} from '@vality/ng-core';
import { coerceBoolean } from 'coerce-property'; import { coerceBoolean } from 'coerce-property';
import { BehaviorSubject, defer, of } from 'rxjs'; import { BehaviorSubject, defer, of } from 'rxjs';
import { filter, map, share, switchMap } from 'rxjs/operators'; import { filter, map, share, switchMap } from 'rxjs/operators';
import { PartyManagementService } from '@cc/app/api/payment-processing'; import { PartyManagementService } from '@cc/app/api/payment-processing';
import { ComponentChanges } from '@cc/app/shared/utils'; import { ComponentChanges } from '@cc/app/shared/utils';
import { createControlProviders, ValidatedControlSuperclass } from '@cc/utils/forms';
@UntilDestroy() @UntilDestroy()
@Component({ @Component({
@ -20,7 +24,7 @@ import { createControlProviders, ValidatedControlSuperclass } from '@cc/utils/fo
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ShopFieldComponent<M extends boolean = boolean> export class ShopFieldComponent<M extends boolean = boolean>
extends ValidatedControlSuperclass< extends FormControlSuperclass<
M extends true ? Shop[] : Shop, M extends true ? Shop[] : Shop,
M extends true ? ShopID[] : ShopID M extends true ? ShopID[] : ShopID
> >
@ -30,7 +34,6 @@ export class ShopFieldComponent<M extends boolean = boolean>
@Input() @coerceBoolean multiple: M; @Input() @coerceBoolean multiple: M;
@Input() @coerceBoolean required: boolean; @Input() @coerceBoolean required: boolean;
control = new FormControl() as FormControl<M extends true ? ShopID[] : ShopID>;
shops$ = defer(() => this.partyId$).pipe( shops$ = defer(() => this.partyId$).pipe(
switchMap((partyId) => switchMap((partyId) =>
partyId partyId
@ -48,10 +51,15 @@ export class ShopFieldComponent<M extends boolean = boolean>
super(); super();
} }
setDisabledState(isDisabled: boolean) {
super.setDisabledState(!this.partyId || isDisabled);
}
ngOnChanges(changes: ComponentChanges<ShopFieldComponent>): void { ngOnChanges(changes: ComponentChanges<ShopFieldComponent>): void {
super.ngOnChanges(changes); super.ngOnChanges(changes);
if (changes.partyId) { if (changes.partyId && this.partyId !== this.partyId$.value) {
this.partyId$.next(changes.partyId.currentValue); this.partyId$.next(changes.partyId.currentValue);
setDisabled(this.control, !this.partyId);
} }
} }
@ -59,7 +67,11 @@ export class ShopFieldComponent<M extends boolean = boolean>
this.shops$ this.shops$
.pipe( .pipe(
filter( filter(
(shops) => this.control.value && !shops.find((s) => s.id === this.control.value) (shops) =>
!isEmpty(this.control.value) &&
(Array.isArray(this.control.value)
? !this.control.value.every((v) => shops.some((s) => s.id === v))
: !shops.some((s) => s.id === this.control.value))
), ),
untilDestroyed(this) untilDestroyed(this)
) )

View File

@ -1,6 +1,9 @@
import { getCurrencySymbol } from '@angular/common'; import { getCurrencySymbol } from '@angular/common';
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
/**
* @deprecated
*/
@Pipe({ @Pipe({
name: 'ccCurrency', name: 'ccCurrency',
}) })

View File

@ -4,6 +4,9 @@ import round from 'lodash-es/round';
@Pipe({ @Pipe({
name: 'ccFormatAmount', name: 'ccFormatAmount',
}) })
/**
* @deprecated
*/
export class FormatAmountPipe implements PipeTransform { export class FormatAmountPipe implements PipeTransform {
public transform(input: number): number { public transform(input: number): number {
const value = round(input / 100, 2); const value = round(input / 100, 2);
@ -11,6 +14,9 @@ export class FormatAmountPipe implements PipeTransform {
} }
} }
/**
* @deprecated
*/
function format( function format(
value: any, value: any,
decimalLength: number, decimalLength: number,

View File

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { toMajorByExponent } from '@vality/ng-core';
import { map, first } from 'rxjs/operators';
import { DomainStoreService } from '@cc/app/api/deprecated-damsel';
@Injectable({
providedIn: 'root',
})
export class AmountCurrencyService {
constructor(private domainStoreService: DomainStoreService) {}
toMajor(amount: number, symbolicCode: string) {
return this.getCurrency(symbolicCode).pipe(
first(),
map((currency) => toMajorByExponent(amount, currency.data.exponent))
);
}
getCurrency(symbolicCode: string) {
return this.domainStoreService
.getObjects('currency')
.pipe(
map((currencies) => currencies.find((c) => c.ref.symbolic_code === symbolicCode))
);
}
}

View File

@ -8,3 +8,4 @@ export * from './query-params';
export * from './moment-utc-date-adapter'; export * from './moment-utc-date-adapter';
export * from './domain-metadata-form-extensions'; export * from './domain-metadata-form-extensions';
export * from './domain-secret-service'; export * from './domain-secret-service';
export * from './amount-currency.service';

View File

@ -1,6 +1,7 @@
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { MatDateFormats } from '@angular/material/core'; import { MatDateFormats } from '@angular/material/core';
import { DateRange } from '@angular/material/datepicker'; import { DateRange } from '@angular/material/datepicker';
import { DATE_QUERY_PARAMS_SERIALIZERS } from '@vality/ng-core';
import { Moment } from 'moment'; import { Moment } from 'moment';
import * as moment from 'moment'; import * as moment from 'moment';
@ -34,6 +35,7 @@ export const DEFAULT_QUERY_PARAMS_SERIALIZERS: Serializer[] = [
return (!start || moment.isMoment(start)) && (!end || moment.isMoment(end)); return (!start || moment.isMoment(start)) && (!end || moment.isMoment(end));
}, },
}, },
...DATE_QUERY_PARAMS_SERIALIZERS,
]; ];
export const DEFAULT_MAT_DATE_FORMATS: MatDateFormats = { export const DEFAULT_MAT_DATE_FORMATS: MatDateFormats = {