forked from Yara724/api
Fixed Lock for Field expert + user view of files
This commit is contained in:
191
package-lock.json
generated
191
package-lock.json
generated
@@ -179,24 +179,6 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular-devkit/schematics-cli/node_modules/chokidar": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"readdirp": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.16.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@angular-devkit/schematics-cli/node_modules/json-schema-traverse": {
|
"node_modules/@angular-devkit/schematics-cli/node_modules/json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
@@ -204,22 +186,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@angular-devkit/schematics-cli/node_modules/readdirp": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.18.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@angular-devkit/schematics-cli/node_modules/rxjs": {
|
"node_modules/@angular-devkit/schematics-cli/node_modules/rxjs": {
|
||||||
"version": "7.8.1",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||||
@@ -275,24 +241,6 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular-devkit/schematics/node_modules/chokidar": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"readdirp": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.16.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": {
|
"node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
@@ -300,22 +248,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@angular-devkit/schematics/node_modules/readdirp": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.18.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@angular-devkit/schematics/node_modules/rxjs": {
|
"node_modules/@angular-devkit/schematics/node_modules/rxjs": {
|
||||||
"version": "7.8.1",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||||
@@ -1075,9 +1007,9 @@
|
|||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/@hapi/tlds": {
|
"node_modules/@hapi/tlds": {
|
||||||
"version": "1.1.6",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.7.tgz",
|
||||||
"integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==",
|
"integrity": "sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -2175,6 +2107,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2192,6 +2127,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2209,6 +2147,9 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2226,6 +2167,9 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2243,6 +2187,9 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2905,24 +2852,6 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/schematics/node_modules/chokidar": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"readdirp": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.16.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@nestjs/schematics/node_modules/json-schema-traverse": {
|
"node_modules/@nestjs/schematics/node_modules/json-schema-traverse": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
@@ -2930,22 +2859,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/schematics/node_modules/readdirp": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.18.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@nestjs/schematics/node_modules/rxjs": {
|
"node_modules/@nestjs/schematics/node_modules/rxjs": {
|
||||||
"version": "7.8.1",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||||
@@ -3340,6 +3253,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3357,6 +3273,9 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3374,6 +3293,9 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3391,6 +3313,9 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "Apache-2.0 AND MIT",
|
"license": "Apache-2.0 AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -5794,24 +5719,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"readdirp": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 20.19.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chrome-trace-event": {
|
"node_modules/chrome-trace-event": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
||||||
@@ -7254,16 +7161,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.2",
|
"hasown": "^2.0.4",
|
||||||
"mime-types": "^2.1.12"
|
"mime-types": "^2.1.35"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -7711,9 +7618,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -8810,9 +8717,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/joi": {
|
"node_modules/joi": {
|
||||||
"version": "18.2.1",
|
"version": "18.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/joi/-/joi-18.2.2.tgz",
|
||||||
"integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==",
|
"integrity": "sha512-STrV933NPLPME2nfPo+lFIfgCff9T/vhObpoRtFb/vNlLQynrUJPBCS3OePJ4Lunu/Egw9lqtNms72xbPbxrxw==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/address": "^5.1.1",
|
"@hapi/address": "^5.1.1",
|
||||||
@@ -10396,22 +10303,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/readdirp": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 20.19.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/reflect-metadata": {
|
"node_modules/reflect-metadata": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import {
|
|||||||
} from "./dto/my-claims-v2.dto";
|
} from "./dto/my-claims-v2.dto";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||||
|
import { partyPersonMatchesUser } from "src/helpers/iran-mobile";
|
||||||
import { ClaimDetailsV2ResponseDto } from "./dto/claim-details-v2.dto";
|
import { ClaimDetailsV2ResponseDto } from "./dto/claim-details-v2.dto";
|
||||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||||
import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-management/required-document-type.enum";
|
import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-management/required-document-type.enum";
|
||||||
@@ -4786,6 +4787,26 @@ export class ClaimRequestManagementService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(!actor?.role || actor.role === RoleEnum.USER) &&
|
||||||
|
claim.blameRequestId
|
||||||
|
) {
|
||||||
|
const blame = await this.blameRequestDbService.findById(
|
||||||
|
claim.blameRequestId.toString(),
|
||||||
|
);
|
||||||
|
const isParty =
|
||||||
|
Array.isArray(blame?.parties) &&
|
||||||
|
blame.parties.some((p: any) =>
|
||||||
|
partyPersonMatchesUser(p?.person, {
|
||||||
|
sub: currentUserId,
|
||||||
|
username: (actor as { username?: string })?.username,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (isParty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (actor?.role === RoleEnum.FIELD_EXPERT) {
|
if (actor?.role === RoleEnum.FIELD_EXPERT) {
|
||||||
if (claim.initiatedByFieldExpertId?.toString() === currentUserId) {
|
if (claim.initiatedByFieldExpertId?.toString() === currentUserId) {
|
||||||
return;
|
return;
|
||||||
@@ -7223,8 +7244,25 @@ export class ClaimRequestManagementService {
|
|||||||
{ lean: true },
|
{ lean: true },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const partyBlameIds = await this.blameRequestDbService
|
||||||
|
.find(
|
||||||
|
{
|
||||||
|
$or: [
|
||||||
|
{ "parties.person.userId": new Types.ObjectId(currentUserId) },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ select: "_id", lean: true },
|
||||||
|
)
|
||||||
|
.then((docs) => docs.map((d) => (d as any)._id));
|
||||||
claims = await this.claimCaseDbService.find(
|
claims = await this.claimCaseDbService.find(
|
||||||
|
{
|
||||||
|
$or: [
|
||||||
{ "owner.userId": new Types.ObjectId(currentUserId) },
|
{ "owner.userId": new Types.ObjectId(currentUserId) },
|
||||||
|
...(partyBlameIds.length
|
||||||
|
? [{ blameRequestId: { $in: partyBlameIds } }]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
},
|
||||||
{ lean: true },
|
{ lean: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
assertBlameCaseForExpertTenant,
|
assertBlameCaseForExpertTenant,
|
||||||
blameCaseAccessibleToExpert,
|
blameCaseAccessibleToExpert,
|
||||||
|
blameCaseInitiatedByFieldExpert,
|
||||||
requireActorClientKey,
|
requireActorClientKey,
|
||||||
} from "src/helpers/tenant-scope";
|
} from "src/helpers/tenant-scope";
|
||||||
import {
|
import {
|
||||||
@@ -436,8 +437,9 @@ export class ExpertBlameService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blame review inbox for FIELD_EXPERT — expert-initiated DISAGREEMENT files only.
|
* Blame review inbox for FIELD_EXPERT — expert-initiated DISAGREEMENT files.
|
||||||
* IN_PERSON flows are AGREED and handled outside this panel; completed blames appear in expert-claim.
|
* Covers LINK flows (parties complete via link → WAITING_FOR_EXPERT) and in-progress
|
||||||
|
* disputes while parties are still filling. IN_PERSON AGREED blames use expert-claim instead.
|
||||||
*/
|
*/
|
||||||
private async getFieldExpertBlameListV2(
|
private async getFieldExpertBlameListV2(
|
||||||
actor: { sub: string },
|
actor: { sub: string },
|
||||||
@@ -448,9 +450,25 @@ export class ExpertBlameService {
|
|||||||
|
|
||||||
const visibleCases = (await this.blameRequestDbService.find(
|
const visibleCases = (await this.blameRequestDbService.find(
|
||||||
{
|
{
|
||||||
blameStatus: BlameStatus.DISAGREEMENT,
|
|
||||||
expertInitiated: true,
|
expertInitiated: true,
|
||||||
initiatedByFieldExpertId: expertOid,
|
initiatedByFieldExpertId: expertOid,
|
||||||
|
blameStatus: BlameStatus.DISAGREEMENT,
|
||||||
|
type: { $ne: BlameRequestType.CAR_BODY },
|
||||||
|
$or: [
|
||||||
|
{ status: CaseStatus.WAITING_FOR_EXPERT },
|
||||||
|
{
|
||||||
|
status: {
|
||||||
|
$in: [
|
||||||
|
CaseStatus.OPEN,
|
||||||
|
CaseStatus.WAITING_FOR_SECOND_PARTY,
|
||||||
|
CaseStatus.WAITING_FOR_DOCUMENT_RESEND,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ "workflow.lockedBy.actorId": expertOid },
|
||||||
|
{ "workflow.assignedForReviewBy.actorId": expertOid },
|
||||||
|
{ "expert.decision.decidedByExpertId": expertOid },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{ lean: true },
|
{ lean: true },
|
||||||
)) as Record<string, unknown>[];
|
)) as Record<string, unknown>[];
|
||||||
@@ -966,6 +984,8 @@ export class ExpertBlameService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFieldExpertOwner = blameCaseInitiatedByFieldExpert(doc, actor);
|
||||||
|
|
||||||
const decision = (doc.expert as any)?.decision;
|
const decision = (doc.expert as any)?.decision;
|
||||||
const decidedByExpertId = decision?.decidedByExpertId
|
const decidedByExpertId = decision?.decidedByExpertId
|
||||||
? String(decision.decidedByExpertId)
|
? String(decision.decidedByExpertId)
|
||||||
@@ -978,6 +998,7 @@ export class ExpertBlameService {
|
|||||||
(doc.workflow as any)?.assignedForReviewBy?.actorId ?? "",
|
(doc.workflow as any)?.assignedForReviewBy?.actorId ?? "",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!isFieldExpertOwner) {
|
||||||
// Access gates — must satisfy at least one bucket
|
// Access gates — must satisfy at least one bucket
|
||||||
const isAvailable =
|
const isAvailable =
|
||||||
doc.status === CaseStatus.WAITING_FOR_EXPERT && !decidedByExpertId;
|
doc.status === CaseStatus.WAITING_FOR_EXPERT && !decidedByExpertId;
|
||||||
@@ -1002,6 +1023,7 @@ export class ExpertBlameService {
|
|||||||
"You do not have permission to view this request.",
|
"You do not have permission to view this request.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build evidence URLs
|
// Build evidence URLs
|
||||||
const parties = Array.isArray(doc.parties) ? doc.parties : [];
|
const parties = Array.isArray(doc.parties) ? doc.parties : [];
|
||||||
@@ -1158,7 +1180,12 @@ export class ExpertBlameService {
|
|||||||
*/
|
*/
|
||||||
async assignBlameCaseForReviewV2(
|
async assignBlameCaseForReviewV2(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
actor: { sub: string; fullName?: string; clientKey?: string },
|
actor: {
|
||||||
|
sub: string;
|
||||||
|
fullName?: string;
|
||||||
|
clientKey?: string;
|
||||||
|
role?: string;
|
||||||
|
},
|
||||||
): Promise<ExpertFileAssignResultDto> {
|
): Promise<ExpertFileAssignResultDto> {
|
||||||
await this.expireBlameCaseWorkflowLockV2IfStale(requestId);
|
await this.expireBlameCaseWorkflowLockV2IfStale(requestId);
|
||||||
|
|
||||||
@@ -1169,6 +1196,8 @@ export class ExpertBlameService {
|
|||||||
|
|
||||||
assertBlameCaseForExpertTenant(request, actor);
|
assertBlameCaseForExpertTenant(request, actor);
|
||||||
|
|
||||||
|
const isFieldExpertOwner = blameCaseInitiatedByFieldExpert(request, actor);
|
||||||
|
|
||||||
if (request.type === BlameRequestType.CAR_BODY) {
|
if (request.type === BlameRequestType.CAR_BODY) {
|
||||||
throw new BadRequestException({
|
throw new BadRequestException({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -1177,9 +1206,17 @@ export class ExpertBlameService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.blameStatus !== BlameStatus.DISAGREEMENT) {
|
||||||
|
throw new BadRequestException({
|
||||||
|
success: false,
|
||||||
|
status: "unavailable" satisfies ExpertFileAssignStatus,
|
||||||
|
message: "Request is not available for expert review",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
request.status !== CaseStatus.WAITING_FOR_EXPERT ||
|
!isFieldExpertOwner &&
|
||||||
request.blameStatus !== BlameStatus.DISAGREEMENT
|
request.status !== CaseStatus.WAITING_FOR_EXPERT
|
||||||
) {
|
) {
|
||||||
throw new BadRequestException({
|
throw new BadRequestException({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -1188,6 +1225,26 @@ export class ExpertBlameService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFieldExpertOwner && request.expert?.decision) {
|
||||||
|
throw new BadRequestException({
|
||||||
|
success: false,
|
||||||
|
status: "unavailable" satisfies ExpertFileAssignStatus,
|
||||||
|
message: "This request already has an expert decision",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFieldExpertOwner &&
|
||||||
|
request.status !== CaseStatus.WAITING_FOR_EXPERT
|
||||||
|
) {
|
||||||
|
throw new BadRequestException({
|
||||||
|
success: false,
|
||||||
|
status: "unavailable" satisfies ExpertFileAssignStatus,
|
||||||
|
message:
|
||||||
|
"Parties are still completing this file. Lock and review become available when status is WAITING_FOR_EXPERT (e.g. after both parties finish via link).",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
request.expertInitiated &&
|
request.expertInitiated &&
|
||||||
request.initiatedByFieldExpertId &&
|
request.initiatedByFieldExpertId &&
|
||||||
@@ -1272,7 +1329,6 @@ export class ExpertBlameService {
|
|||||||
|
|
||||||
const assignFilter: Record<string, unknown> = {
|
const assignFilter: Record<string, unknown> = {
|
||||||
_id: new Types.ObjectId(requestId),
|
_id: new Types.ObjectId(requestId),
|
||||||
status: CaseStatus.WAITING_FOR_EXPERT,
|
|
||||||
blameStatus: BlameStatus.DISAGREEMENT,
|
blameStatus: BlameStatus.DISAGREEMENT,
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
@@ -1293,8 +1349,11 @@ export class ExpertBlameService {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.expertInitiated) {
|
if (isFieldExpertOwner) {
|
||||||
assignFilter.initiatedByFieldExpertId = expertOid;
|
assignFilter.initiatedByFieldExpertId = expertOid;
|
||||||
|
assignFilter.status = CaseStatus.WAITING_FOR_EXPERT;
|
||||||
|
} else {
|
||||||
|
assignFilter.status = CaseStatus.WAITING_FOR_EXPERT;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await this.blameRequestDbService.findOneAndUpdate(
|
const updated = await this.blameRequestDbService.findOneAndUpdate(
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class ExpertBlameV2Controller {
|
|||||||
summary: "List blame cases for expert review (V2)",
|
summary: "List blame cases for expert review (V2)",
|
||||||
description:
|
description:
|
||||||
"Damage experts (`expert`): tenant-scoped **DISAGREEMENT** queue (available, locked, or decided by you). " +
|
"Damage experts (`expert`): tenant-scoped **DISAGREEMENT** queue (available, locked, or decided by you). " +
|
||||||
"Field experts (`field_expert`): only **DISAGREEMENT** files they initiated that need expert review (e.g. LINK disputes). " +
|
"Field experts (`field_expert`): expert-initiated **DISAGREEMENT** files — including **LINK** flows where parties complete via link and the case moves to `WAITING_FOR_EXPERT` for your review. " +
|
||||||
"IN_PERSON expert-initiated blames are usually `AGREED` and are managed via expert-initiated / request-management APIs; after completion, use expert-claim. " +
|
"IN_PERSON expert-initiated blames are usually `AGREED` and are managed via expert-initiated / request-management APIs; after completion, use expert-claim. " +
|
||||||
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`.",
|
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`.",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2437,12 +2437,35 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
assertClaimCaseForExpertActor(claim, actor);
|
assertClaimCaseForExpertActor(claim, actor);
|
||||||
|
|
||||||
|
const isFieldExpertOwner =
|
||||||
|
(actor as any).role === RoleEnum.FIELD_EXPERT &&
|
||||||
|
claimCaseInitiatedByFieldExpert(claim, actor);
|
||||||
const isFactorValidationLock =
|
const isFactorValidationLock =
|
||||||
claimIsAwaitingExpertFactorValidationV2(claim);
|
claimIsAwaitingExpertFactorValidationV2(claim);
|
||||||
const isDamageReviewLock =
|
const isDamageReviewLock =
|
||||||
claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
|
const isExpertReviewingReentry =
|
||||||
|
claim.status === ClaimCaseStatus.EXPERT_REVIEWING;
|
||||||
|
const isResendPending =
|
||||||
|
claim.status === ClaimCaseStatus.WAITING_FOR_USER_RESEND;
|
||||||
|
const isTerminalClaim =
|
||||||
|
claim.status === ClaimCaseStatus.COMPLETED ||
|
||||||
|
claim.status === ClaimCaseStatus.CANCELLED;
|
||||||
|
const isFieldExpertPortfolioLock =
|
||||||
|
isFieldExpertOwner &&
|
||||||
|
!isTerminalClaim &&
|
||||||
|
!isDamageReviewLock &&
|
||||||
|
!isFactorValidationLock &&
|
||||||
|
!isExpertReviewingReentry &&
|
||||||
|
!isResendPending;
|
||||||
|
|
||||||
if (!isDamageReviewLock && !isFactorValidationLock) {
|
if (
|
||||||
|
!isDamageReviewLock &&
|
||||||
|
!isFactorValidationLock &&
|
||||||
|
!isExpertReviewingReentry &&
|
||||||
|
!isResendPending &&
|
||||||
|
!isFieldExpertPortfolioLock
|
||||||
|
) {
|
||||||
throw new BadRequestException({
|
throw new BadRequestException({
|
||||||
success: false,
|
success: false,
|
||||||
status: "unavailable" satisfies ExpertFileAssignStatus,
|
status: "unavailable" satisfies ExpertFileAssignStatus,
|
||||||
@@ -2490,11 +2513,15 @@ export class ExpertClaimService {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
const lockSnapshot = await this.snapshotDamageExpert(actor.sub);
|
const lockSnapshot = await this.snapshotDamageExpert(actor.sub);
|
||||||
const expiredAt = new Date(now.getTime() + this.claimV2WorkflowLockTtlMs);
|
const expiredAt = new Date(now.getTime() + this.claimV2WorkflowLockTtlMs);
|
||||||
|
const actorType = isFieldExpertOwner ? "field_expert" : "damage_expert";
|
||||||
|
const actorRoleLabel = isFieldExpertOwner
|
||||||
|
? ("field_expert" as const)
|
||||||
|
: ("damage_expert" as const);
|
||||||
|
|
||||||
const lockedByPayload = {
|
const lockedByPayload = {
|
||||||
actorId: expertOid,
|
actorId: expertOid,
|
||||||
actorName: actor.fullName,
|
actorName: actor.fullName,
|
||||||
actorRole: "damage_expert" as const,
|
actorRole: actorRoleLabel,
|
||||||
...(lockSnapshot && { expertProfileSnapshot: lockSnapshot }),
|
...(lockSnapshot && { expertProfileSnapshot: lockSnapshot }),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2503,6 +2530,8 @@ export class ExpertClaimService {
|
|||||||
"workflow.lockedAt": now,
|
"workflow.lockedAt": now,
|
||||||
"workflow.expiredAt": expiredAt,
|
"workflow.expiredAt": expiredAt,
|
||||||
"workflow.lockedBy": lockedByPayload,
|
"workflow.lockedBy": lockedByPayload,
|
||||||
|
...(isDamageReviewLock || isExpertReviewingReentry || isResendPending
|
||||||
|
? {
|
||||||
"workflow.preLockQueueSnapshot": {
|
"workflow.preLockQueueSnapshot": {
|
||||||
claimStatus: claim.claimStatus,
|
claimStatus: claim.claimStatus,
|
||||||
currentStep: this.normalizeClaimWorkflowStepForSnapshot(
|
currentStep: this.normalizeClaimWorkflowStepForSnapshot(
|
||||||
@@ -2518,18 +2547,22 @@ export class ExpertClaimService {
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
$push: {
|
$push: {
|
||||||
history: {
|
history: {
|
||||||
type: "CLAIM_ASSIGNED",
|
type: "CLAIM_ASSIGNED",
|
||||||
actor: {
|
actor: {
|
||||||
actorId: expertOid,
|
actorId: expertOid,
|
||||||
actorName: actor.fullName,
|
actorName: actor.fullName,
|
||||||
actorType: "damage_expert",
|
actorType,
|
||||||
},
|
},
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
metadata: {
|
metadata: {
|
||||||
note: isFactorValidationLock
|
note: isFactorValidationLock
|
||||||
? "Expert assigned for factor validation review"
|
? "Expert assigned for factor validation review"
|
||||||
|
: isFieldExpertPortfolioLock
|
||||||
|
? "Field expert portfolio lock (no status change)"
|
||||||
: "Expert assigned via review assign endpoint",
|
: "Expert assigned via review assign endpoint",
|
||||||
...(isFactorValidationLock ? { phase: "factorValidation" } : {}),
|
...(isFactorValidationLock ? { phase: "factorValidation" } : {}),
|
||||||
},
|
},
|
||||||
@@ -2541,7 +2574,11 @@ export class ExpertClaimService {
|
|||||||
baseLockUpdate["workflow.assignedForReviewBy"] = lockedByPayload;
|
baseLockUpdate["workflow.assignedForReviewBy"] = lockedByPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lockUpdate: Record<string, unknown> = isFactorValidationLock
|
const lockUpdate: Record<string, unknown> =
|
||||||
|
isFactorValidationLock ||
|
||||||
|
isExpertReviewingReentry ||
|
||||||
|
isResendPending ||
|
||||||
|
isFieldExpertPortfolioLock
|
||||||
? baseLockUpdate
|
? baseLockUpdate
|
||||||
: {
|
: {
|
||||||
...baseLockUpdate,
|
...baseLockUpdate,
|
||||||
@@ -2581,6 +2618,13 @@ export class ExpertClaimService {
|
|||||||
ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL,
|
ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
} else if (isExpertReviewingReentry) {
|
||||||
|
assignFilter.status = ClaimCaseStatus.EXPERT_REVIEWING;
|
||||||
|
} else if (isResendPending) {
|
||||||
|
assignFilter.status = ClaimCaseStatus.WAITING_FOR_USER_RESEND;
|
||||||
|
} else if (isFieldExpertPortfolioLock) {
|
||||||
|
assignFilter.initiatedByFieldExpertId = expertOid;
|
||||||
|
assignFilter.status = claim.status;
|
||||||
} else {
|
} else {
|
||||||
assignFilter.status = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
assignFilter.status = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
}
|
}
|
||||||
@@ -2634,7 +2678,13 @@ export class ExpertClaimService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const ownerPhone = await this.resolveClaimOwnerPhone(updated);
|
const ownerPhone = await this.resolveClaimOwnerPhone(updated);
|
||||||
if (ownerPhone) {
|
if (
|
||||||
|
ownerPhone &&
|
||||||
|
isDamageReviewLock &&
|
||||||
|
!isFieldExpertPortfolioLock &&
|
||||||
|
!isExpertReviewingReentry &&
|
||||||
|
!isResendPending
|
||||||
|
) {
|
||||||
const expertLastName =
|
const expertLastName =
|
||||||
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||||
await this.smsOrchestrationService.sendThirdPartyExpertStartedReviewNotice(
|
await this.smsOrchestrationService.sendThirdPartyExpertStartedReviewNotice(
|
||||||
|
|||||||
@@ -44,3 +44,28 @@ export function buildUserLookupByPhone(
|
|||||||
$or: variants.flatMap((v) => [{ username: v }, { mobile: v }]),
|
$or: variants.flatMap((v) => [{ username: v }, { mobile: v }]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** True when a blame party person row belongs to the authenticated user. */
|
||||||
|
export function partyPersonMatchesUser(
|
||||||
|
person:
|
||||||
|
| { userId?: unknown; phoneNumber?: string }
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
user: { sub?: string; username?: string } | null | undefined,
|
||||||
|
): boolean {
|
||||||
|
if (!person || !user) return false;
|
||||||
|
if (
|
||||||
|
person.userId != null &&
|
||||||
|
user.sub &&
|
||||||
|
String(person.userId) === String(user.sub)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!person.phoneNumber || !user.username) return false;
|
||||||
|
const stored =
|
||||||
|
normalizeIranMobile(person.phoneNumber) ?? person.phoneNumber;
|
||||||
|
const variants = iranMobileLookupVariants(user.username);
|
||||||
|
return variants.some(
|
||||||
|
(v) => v === stored || v === person.phoneNumber,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BadRequestException, ForbiddenException } from "@nestjs/common";
|
import { BadRequestException, ForbiddenException } from "@nestjs/common";
|
||||||
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||||
|
|
||||||
/** Insurance company tenant id on the actor JWT (Mongo ObjectId string). */
|
/** Insurance company tenant id on the actor JWT (Mongo ObjectId string). */
|
||||||
export function requireActorClientKey(actor: { clientKey?: string }): string {
|
export function requireActorClientKey(actor: { clientKey?: string }): string {
|
||||||
@@ -64,6 +65,19 @@ export function blameCaseAccessibleToExpert(
|
|||||||
return blameCaseTouchesClient(doc, ck);
|
return blameCaseTouchesClient(doc, ck);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** True when the acting field expert created this expert-initiated blame file. */
|
||||||
|
export function blameCaseInitiatedByFieldExpert(
|
||||||
|
doc: any,
|
||||||
|
actor: { sub: string; role?: string },
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
actor.role === RoleEnum.FIELD_EXPERT &&
|
||||||
|
!!doc?.expertInitiated &&
|
||||||
|
!!doc?.initiatedByFieldExpertId &&
|
||||||
|
String(doc.initiatedByFieldExpertId) === String(actor.sub)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function assertBlameCaseForExpertTenant(
|
export function assertBlameCaseForExpertTenant(
|
||||||
doc: any,
|
doc: any,
|
||||||
actor: { sub: string; clientKey?: string },
|
actor: { sub: string; clientKey?: string },
|
||||||
|
|||||||
@@ -91,6 +91,12 @@ import {
|
|||||||
MUTUAL_AGREEMENT_EXPERT_DECISION_FIELDS,
|
MUTUAL_AGREEMENT_EXPERT_DECISION_FIELDS,
|
||||||
} from "src/helpers/blame-party-agreement-decision";
|
} from "src/helpers/blame-party-agreement-decision";
|
||||||
import { buildFileLink } from "src/helpers/urlCreator";
|
import { buildFileLink } from "src/helpers/urlCreator";
|
||||||
|
import {
|
||||||
|
buildUserLookupByPhone,
|
||||||
|
iranMobileLookupVariants,
|
||||||
|
normalizeIranMobile,
|
||||||
|
partyPersonMatchesUser,
|
||||||
|
} from "src/helpers/iran-mobile";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RequestManagementService {
|
export class RequestManagementService {
|
||||||
@@ -209,14 +215,8 @@ export class RequestManagementService {
|
|||||||
private assertPartyOwner(party: any, user: any, errMsg: string, req?: any) {
|
private assertPartyOwner(party: any, user: any, errMsg: string, req?: any) {
|
||||||
// The initiating field-expert/registrar fills steps on behalf of parties.
|
// The initiating field-expert/registrar fills steps on behalf of parties.
|
||||||
if (req && this.isBlameOnBehalfActor(req, user)) return;
|
if (req && this.isBlameOnBehalfActor(req, user)) return;
|
||||||
const partyUserId = party?.person?.userId
|
if (partyPersonMatchesUser(party?.person, user)) return;
|
||||||
? String(party.person.userId)
|
throw new ForbiddenException(errMsg);
|
||||||
: null;
|
|
||||||
const ok =
|
|
||||||
(partyUserId && partyUserId === String(user?.sub)) ||
|
|
||||||
(party?.person?.phoneNumber &&
|
|
||||||
party.person.phoneNumber === user?.username);
|
|
||||||
if (!ok) throw new ForbiddenException(errMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -4434,7 +4434,8 @@ export class RequestManagementService {
|
|||||||
throw new BadRequestException("First party not found on request");
|
throw new BadRequestException("First party not found on request");
|
||||||
const userId = await this.getOrCreateUserByPhoneNumber(phone);
|
const userId = await this.getOrCreateUserByPhoneNumber(phone);
|
||||||
if (!req.parties[firstIdx].person) req.parties[firstIdx].person = {} as any;
|
if (!req.parties[firstIdx].person) req.parties[firstIdx].person = {} as any;
|
||||||
req.parties[firstIdx].person.phoneNumber = phone;
|
req.parties[firstIdx].person.phoneNumber =
|
||||||
|
normalizeIranMobile(phone) ?? phone;
|
||||||
req.parties[firstIdx].person.userId = userId;
|
req.parties[firstIdx].person.userId = userId;
|
||||||
|
|
||||||
const expertName = `${expert?.lastName || ""}`.trim() || "کارشناس";
|
const expertName = `${expert?.lastName || ""}`.trim() || "کارشناس";
|
||||||
@@ -4591,7 +4592,9 @@ export class RequestManagementService {
|
|||||||
throw new BadRequestException("First party not found on request");
|
throw new BadRequestException("First party not found on request");
|
||||||
if (!req.parties[firstIdx].person) req.parties[firstIdx].person = {} as any;
|
if (!req.parties[firstIdx].person) req.parties[firstIdx].person = {} as any;
|
||||||
req.parties[firstIdx].person.userId = firstUserId;
|
req.parties[firstIdx].person.userId = firstUserId;
|
||||||
req.parties[firstIdx].person.phoneNumber = dto.firstPartyPhoneNumber;
|
req.parties[firstIdx].person.phoneNumber =
|
||||||
|
normalizeIranMobile(dto.firstPartyPhoneNumber) ??
|
||||||
|
dto.firstPartyPhoneNumber;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
req.type === BlameRequestType.THIRD_PARTY &&
|
req.type === BlameRequestType.THIRD_PARTY &&
|
||||||
@@ -4617,7 +4620,9 @@ export class RequestManagementService {
|
|||||||
if (!req.parties[secondIdx].person)
|
if (!req.parties[secondIdx].person)
|
||||||
req.parties[secondIdx].person = {} as any;
|
req.parties[secondIdx].person = {} as any;
|
||||||
req.parties[secondIdx].person.userId = secondUserId;
|
req.parties[secondIdx].person.userId = secondUserId;
|
||||||
req.parties[secondIdx].person.phoneNumber = dto.secondPartyPhoneNumber;
|
req.parties[secondIdx].person.phoneNumber =
|
||||||
|
normalizeIranMobile(dto.secondPartyPhoneNumber) ??
|
||||||
|
dto.secondPartyPhoneNumber;
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
req.type === BlameRequestType.THIRD_PARTY &&
|
req.type === BlameRequestType.THIRD_PARTY &&
|
||||||
@@ -4794,6 +4799,7 @@ export class RequestManagementService {
|
|||||||
const valid = await this.hashService.compare(otp, u.otp);
|
const valid = await this.hashService.compare(otp, u.otp);
|
||||||
if (!valid) throw new BadRequestException(`Invalid OTP for ${phone}`);
|
if (!valid) throw new BadRequestException(`Invalid OTP for ${phone}`);
|
||||||
const userId = u._id as Types.ObjectId;
|
const userId = u._id as Types.ObjectId;
|
||||||
|
const canonicalPhone = normalizeIranMobile(phone) ?? phone;
|
||||||
|
|
||||||
if (role === PartyRole.FIRST) {
|
if (role === PartyRole.FIRST) {
|
||||||
if (firstIdx === -1)
|
if (firstIdx === -1)
|
||||||
@@ -4801,13 +4807,21 @@ export class RequestManagementService {
|
|||||||
if (!req.parties[firstIdx].person)
|
if (!req.parties[firstIdx].person)
|
||||||
req.parties[firstIdx].person = {} as any;
|
req.parties[firstIdx].person = {} as any;
|
||||||
req.parties[firstIdx].person.userId = userId;
|
req.parties[firstIdx].person.userId = userId;
|
||||||
req.parties[firstIdx].person.phoneNumber = phone;
|
req.parties[firstIdx].person.phoneNumber = canonicalPhone;
|
||||||
} else {
|
} else {
|
||||||
const firstPhone =
|
const firstPhone =
|
||||||
firstIdx !== -1
|
firstIdx !== -1
|
||||||
? (req.parties[firstIdx]?.person as any)?.phoneNumber
|
? (req.parties[firstIdx]?.person as any)?.phoneNumber
|
||||||
: undefined;
|
: undefined;
|
||||||
if (firstPhone && firstPhone === phone) {
|
const firstVariants = firstPhone
|
||||||
|
? iranMobileLookupVariants(firstPhone)
|
||||||
|
: [];
|
||||||
|
if (
|
||||||
|
firstPhone &&
|
||||||
|
(firstPhone === phone ||
|
||||||
|
firstVariants.includes(phone) ||
|
||||||
|
firstVariants.includes(canonicalPhone))
|
||||||
|
) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Second party phone number cannot be the same as first party.",
|
"Second party phone number cannot be the same as first party.",
|
||||||
);
|
);
|
||||||
@@ -4816,13 +4830,13 @@ export class RequestManagementService {
|
|||||||
if (secondIdx === -1) {
|
if (secondIdx === -1) {
|
||||||
req.parties.push({
|
req.parties.push({
|
||||||
role: PartyRole.SECOND,
|
role: PartyRole.SECOND,
|
||||||
person: { userId, phoneNumber: phone },
|
person: { userId, phoneNumber: canonicalPhone },
|
||||||
} as any);
|
} as any);
|
||||||
} else {
|
} else {
|
||||||
if (!req.parties[secondIdx].person)
|
if (!req.parties[secondIdx].person)
|
||||||
req.parties[secondIdx].person = {} as any;
|
req.parties[secondIdx].person = {} as any;
|
||||||
req.parties[secondIdx].person.userId = userId;
|
req.parties[secondIdx].person.userId = userId;
|
||||||
req.parties[secondIdx].person.phoneNumber = phone;
|
req.parties[secondIdx].person.phoneNumber = canonicalPhone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5018,8 +5032,15 @@ export class RequestManagementService {
|
|||||||
user?.sub && Types.ObjectId.isValid(user.sub)
|
user?.sub && Types.ObjectId.isValid(user.sub)
|
||||||
? { "parties.person.userId": new Types.ObjectId(user.sub) }
|
? { "parties.person.userId": new Types.ObjectId(user.sub) }
|
||||||
: null;
|
: null;
|
||||||
const phoneFilter = user?.username
|
const phoneVariants =
|
||||||
? { "parties.person.phoneNumber": user.username }
|
user?.role === RoleEnum.USER && user?.username
|
||||||
|
? iranMobileLookupVariants(user.username)
|
||||||
|
: user?.username
|
||||||
|
? [user.username]
|
||||||
|
: [];
|
||||||
|
const phoneFilter =
|
||||||
|
phoneVariants.length > 0
|
||||||
|
? { "parties.person.phoneNumber": { $in: phoneVariants } }
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const orConditions: Record<string, unknown>[] = [];
|
const orConditions: Record<string, unknown>[] = [];
|
||||||
@@ -5061,11 +5082,8 @@ export class RequestManagementService {
|
|||||||
req.registrarInitiated &&
|
req.registrarInitiated &&
|
||||||
String(req.initiatedByRegistrarId) === String(user.sub));
|
String(req.initiatedByRegistrarId) === String(user.sub));
|
||||||
|
|
||||||
const party = req.parties?.find(
|
const party = req.parties?.find((p: any) =>
|
||||||
(p: any) =>
|
partyPersonMatchesUser(p?.person, user),
|
||||||
(p?.person?.userId &&
|
|
||||||
String(p.person.userId) === String(user.sub)) ||
|
|
||||||
(p?.person?.phoneNumber && p.person.phoneNumber === user?.username),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const obj = req.toObject();
|
const obj = req.toObject();
|
||||||
@@ -5340,14 +5358,7 @@ export class RequestManagementService {
|
|||||||
String(req.initiatedByRegistrarId) === String(user?.sub);
|
String(req.initiatedByRegistrarId) === String(user?.sub);
|
||||||
const isParty =
|
const isParty =
|
||||||
Array.isArray(req.parties) &&
|
Array.isArray(req.parties) &&
|
||||||
req.parties.some((p: any) => {
|
req.parties.some((p: any) => partyPersonMatchesUser(p?.person, user));
|
||||||
const pid = p?.person?.userId ? String(p.person.userId) : null;
|
|
||||||
const phone = p?.person?.phoneNumber;
|
|
||||||
return (
|
|
||||||
(pid && pid === String(user?.sub)) ||
|
|
||||||
(phone && phone === user?.username)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if (!isFieldExpertOwner && !isRegistrarOwner && !isParty) {
|
if (!isFieldExpertOwner && !isRegistrarOwner && !isParty) {
|
||||||
throw new ForbiddenException("You do not have access to this request");
|
throw new ForbiddenException("You do not have access to this request");
|
||||||
}
|
}
|
||||||
@@ -5359,10 +5370,22 @@ export class RequestManagementService {
|
|||||||
Types.ObjectId.isValid(user.sub)
|
Types.ObjectId.isValid(user.sub)
|
||||||
) {
|
) {
|
||||||
let updated = false;
|
let updated = false;
|
||||||
|
const phoneVariants = iranMobileLookupVariants(user?.username);
|
||||||
for (const p of req.parties || []) {
|
for (const p of req.parties || []) {
|
||||||
if (p?.person?.phoneNumber === user?.username && !p.person?.userId) {
|
const person = p?.person;
|
||||||
|
if (!person) continue;
|
||||||
|
const phoneMatch =
|
||||||
|
person.phoneNumber &&
|
||||||
|
phoneVariants.some(
|
||||||
|
(v) => v === person.phoneNumber || v === normalizeIranMobile(person.phoneNumber),
|
||||||
|
);
|
||||||
|
if (phoneMatch && !person?.userId) {
|
||||||
p.person = p.person || {};
|
p.person = p.person || {};
|
||||||
(p.person as any).userId = new Types.ObjectId(user.sub);
|
(p.person as any).userId = new Types.ObjectId(user.sub);
|
||||||
|
if (user?.username) {
|
||||||
|
(p.person as any).phoneNumber =
|
||||||
|
normalizeIranMobile(user.username) ?? user.username;
|
||||||
|
}
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5384,14 +5407,9 @@ export class RequestManagementService {
|
|||||||
const visibleParties =
|
const visibleParties =
|
||||||
isFieldExpertOwner || isRegistrarOwner
|
isFieldExpertOwner || isRegistrarOwner
|
||||||
? allParties
|
? allParties
|
||||||
: allParties.filter((p: any) => {
|
: allParties.filter((p: any) =>
|
||||||
const pid = p?.person?.userId ? String(p.person.userId) : null;
|
partyPersonMatchesUser(p?.person, user),
|
||||||
const phone = p?.person?.phoneNumber;
|
|
||||||
return (
|
|
||||||
(pid && pid === String(user?.sub)) ||
|
|
||||||
(phone && phone === user?.username)
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
const parties = await Promise.all(
|
const parties = await Promise.all(
|
||||||
visibleParties.map((p: any) => this.sanitizePartyForBlameUserView(p)),
|
visibleParties.map((p: any) => this.sanitizePartyForBlameUserView(p)),
|
||||||
);
|
);
|
||||||
@@ -7288,19 +7306,18 @@ export class RequestManagementService {
|
|||||||
private async getOrCreateUserByPhoneNumber(
|
private async getOrCreateUserByPhoneNumber(
|
||||||
phoneNumber: string,
|
phoneNumber: string,
|
||||||
): Promise<Types.ObjectId> {
|
): Promise<Types.ObjectId> {
|
||||||
// Try to find existing user by phone number (username or mobile)
|
const canonical = normalizeIranMobile(phoneNumber) ?? phoneNumber.trim();
|
||||||
let user = await this.userDbService.findOne({
|
let user = await this.userDbService.findOne(
|
||||||
$or: [{ username: phoneNumber }, { mobile: phoneNumber }],
|
buildUserLookupByPhone(canonical),
|
||||||
});
|
);
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
return new Types.ObjectId((user as any)._id);
|
return new Types.ObjectId((user as any)._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// User doesn't exist, create a new one
|
|
||||||
const newUser = await this.userDbService.createUser({
|
const newUser = await this.userDbService.createUser({
|
||||||
mobile: phoneNumber,
|
mobile: canonical,
|
||||||
username: phoneNumber,
|
username: canonical,
|
||||||
otp: "",
|
otp: "",
|
||||||
tokens: {
|
tokens: {
|
||||||
token: "",
|
token: "",
|
||||||
|
|||||||
Reference in New Issue
Block a user