From 50ca111f2811e44685761da3fde141e11588a245 Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Wed, 22 Nov 2023 11:28:32 +0530 Subject: [PATCH 01/15] fix: Fix package.json (#2585) --- package-lock.json | 264 +++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 119 insertions(+), 147 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9c980b317..f95498111b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@capacitor/android": "^4.6.1", "@capacitor/app": "^4.1.1", "@capacitor/browser": "^4.1.0", - "@capacitor/camera": "^4.1.5", + "@capacitor/camera": "^4.1.4", "@capacitor/clipboard": "^4.1.0", "@capacitor/core": "^4.6.1", "@capacitor/device": "^4.1.0", @@ -47,14 +47,13 @@ "@googlemaps/js-api-loader": "^1.16.1", "@ionic/angular": "^6.1.5", "@ionic/core": "^6.1.5", - "@ionic/pwa-elements": "^3.2.2", + "@ionic/pwa-elements": "^3.1.1", "@sentry/angular": "^7.1.1", "@sentry/cli": "^2.1.0", "@sentry/tracing": "^7.1.1", "@types/google.maps": "^3.49.2", "@types/hammerjs": "^2.0.41", "capacitor-native-settings": "^4.0.3", - "capacitor-plugin-dynamsoft-document-normalizer": "^0.1.7", "capacitor-secure-storage-plugin": "^0.8.1", "cordova-plugin-googleplus": "^8.5.2", "cordova-plugin-inappbrowser": "^5.0.0", @@ -100,7 +99,7 @@ "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", "eslint-plugin-import": "2.25.2", - "eslint-plugin-jsdoc": "latest", + "eslint-plugin-jsdoc": "^46.9.0", "eslint-plugin-max-params-no-constructor": "^0.0.4", "eslint-plugin-prefer-arrow": "latest", "husky": "^7.0.2", @@ -2997,17 +2996,17 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", - "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", + "integrity": "sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==", "dev": true, "dependencies": { - "comment-parser": "1.3.1", - "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~3.1.0" + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" + "node": ">=16" } }, "node_modules/@eslint/eslintrc": { @@ -5236,6 +5235,15 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", @@ -5894,6 +5902,18 @@ "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", "dev": true }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/builtins": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", @@ -5993,17 +6013,6 @@ "@capacitor/core": "^4.0.1" } }, - "node_modules/capacitor-plugin-dynamsoft-document-normalizer": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/capacitor-plugin-dynamsoft-document-normalizer/-/capacitor-plugin-dynamsoft-document-normalizer-0.1.7.tgz", - "integrity": "sha512-SdrX+Two3erejycF990NWyxrhgS4fnMK7x7vevsFAwZnQZAVc84Xhcp8J8J9j0z7Br/KIPjIihq9Wv6hDG6LCQ==", - "dependencies": { - "dynamsoft-document-normalizer": "1.0.12" - }, - "peerDependencies": { - "@capacitor/core": "^4.0.0" - } - }, "node_modules/capacitor-secure-storage-plugin": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/capacitor-secure-storage-plugin/-/capacitor-secure-storage-plugin-0.8.1.tgz", @@ -6298,9 +6307,9 @@ } }, "node_modules/comment-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", - "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true, "engines": { "node": ">= 12.0.0" @@ -7250,19 +7259,6 @@ "node": ">=8" } }, - "node_modules/dm-fabric": { - "version": "5.1.17", - "resolved": "https://registry.npmjs.org/dm-fabric/-/dm-fabric-5.1.17.tgz", - "integrity": "sha512-YouI4F+svfqTi3FM9fKG8ZqtdygzLbAzaA53QfekreLvJRqaHhCplk+2EF+cgMmuO5kY62m2Pm0pBqqV/sc5Vg==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/dm-howler": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/dm-howler/-/dm-howler-2.2.4.tgz", - "integrity": "sha512-h+iDEP/cyALeqWNtGdZZRwm3buSwaG26wzF6fCUGhvkF/yxYoWb8F/v7qN+urqdZw3wotZGO/01rDlzOxJUUGw==" - }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -7400,35 +7396,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "node_modules/dynamsoft-camera-enhancer": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/dynamsoft-camera-enhancer/-/dynamsoft-camera-enhancer-3.2.0.tgz", - "integrity": "sha512-9vYydGJRaRH8FaJtKbmskOdjlx1nRd+A04oH5o6pESMlD7E1PaL1bQrdifG3QfJ6LhiUlq2mPgq5odaSDnt1Lg==", - "dependencies": { - "dm-fabric": "^5.1.17" - } - }, - "node_modules/dynamsoft-document-normalizer": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dynamsoft-document-normalizer/-/dynamsoft-document-normalizer-1.0.12.tgz", - "integrity": "sha512-kgrLVVJJySInq0+WynIJNcXqJUaMg0rxHni349OI5KbGMVpr2/z0v0aYJGkTCNLQV+VTQJaaVpXlmMOiqQ1Vpw==", - "dependencies": { - "dm-howler": "^2.2.4", - "dynamsoft-camera-enhancer": "3.2.0" - }, - "peerDependencies": { - "node-fetch": "^2.6.5", - "node-localstorage": "^2.2.1" - }, - "peerDependenciesMeta": { - "node-fetch": { - "optional": true - }, - "node-localstorage": { - "optional": true - } - } - }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -8256,21 +8223,23 @@ "dev": true }, "node_modules/eslint-plugin-jsdoc": { - "version": "39.6.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.4.tgz", - "integrity": "sha512-fskvdLCfwmPjHb6e+xNGDtGgbF8X7cDwMtVLAP2WwSf9Htrx68OAx31BESBM1FAwsN2HTQyYQq7m4aW4Q4Nlag==", + "version": "46.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz", + "integrity": "sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.36.1", - "comment-parser": "1.3.1", + "@es-joy/jsdoccomment": "~0.41.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", - "esquery": "^1.4.0", - "semver": "^7.3.8", + "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", + "semver": "^7.5.4", "spdx-expression-parse": "^3.0.1" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" + "node": ">=16" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" @@ -8306,9 +8275,9 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8674,9 +8643,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -10292,6 +10261,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -10988,9 +10972,9 @@ "dev": true }, "node_modules/jsdoc-type-pratt-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", - "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true, "engines": { "node": ">=12.0.0" @@ -19833,14 +19817,14 @@ "dev": true }, "@es-joy/jsdoccomment": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", - "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", + "integrity": "sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==", "dev": true, "requires": { - "comment-parser": "1.3.1", - "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~3.1.0" + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" } }, "@eslint/eslintrc": { @@ -21588,6 +21572,12 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true }, + "are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true + }, "are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", @@ -22083,6 +22073,12 @@ "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", "dev": true }, + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, "builtins": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", @@ -22155,14 +22151,6 @@ "integrity": "sha512-wxWFwdSySJ1dX8eLFogTYluLYu8fc8biMTL9x0ioI559mHiPDDoCBc//W0nhByUG7a1UxrY4vM4MGDTc/Mf7Bw==", "requires": {} }, - "capacitor-plugin-dynamsoft-document-normalizer": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/capacitor-plugin-dynamsoft-document-normalizer/-/capacitor-plugin-dynamsoft-document-normalizer-0.1.7.tgz", - "integrity": "sha512-SdrX+Two3erejycF990NWyxrhgS4fnMK7x7vevsFAwZnQZAVc84Xhcp8J8J9j0z7Br/KIPjIihq9Wv6hDG6LCQ==", - "requires": { - "dynamsoft-document-normalizer": "1.0.12" - } - }, "capacitor-secure-storage-plugin": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/capacitor-secure-storage-plugin/-/capacitor-secure-storage-plugin-0.8.1.tgz", @@ -22378,9 +22366,9 @@ "dev": true }, "comment-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", - "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true }, "commondir": { @@ -22633,7 +22621,7 @@ }, "cordova-plugin-telerik-imagepicker": { "version": "git+ssh://git@github.com/VerianMobile/ImagePicker.git#abee9f94e747766bac0c58a2ceccaae2f8d4ff82", - "from": "cordova-plugin-telerik-imagepicker@https://github.com/VerianMobile/ImagePicker.git" + "from": "cordova-plugin-telerik-imagepicker@github:VerianMobile/ImagePicker" }, "core-js": { "version": "3.20.3", @@ -23064,16 +23052,6 @@ "path-type": "^4.0.0" } }, - "dm-fabric": { - "version": "5.1.17", - "resolved": "https://registry.npmjs.org/dm-fabric/-/dm-fabric-5.1.17.tgz", - "integrity": "sha512-YouI4F+svfqTi3FM9fKG8ZqtdygzLbAzaA53QfekreLvJRqaHhCplk+2EF+cgMmuO5kY62m2Pm0pBqqV/sc5Vg==" - }, - "dm-howler": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/dm-howler/-/dm-howler-2.2.4.tgz", - "integrity": "sha512-h+iDEP/cyALeqWNtGdZZRwm3buSwaG26wzF6fCUGhvkF/yxYoWb8F/v7qN+urqdZw3wotZGO/01rDlzOxJUUGw==" - }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -23189,23 +23167,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "dynamsoft-camera-enhancer": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/dynamsoft-camera-enhancer/-/dynamsoft-camera-enhancer-3.2.0.tgz", - "integrity": "sha512-9vYydGJRaRH8FaJtKbmskOdjlx1nRd+A04oH5o6pESMlD7E1PaL1bQrdifG3QfJ6LhiUlq2mPgq5odaSDnt1Lg==", - "requires": { - "dm-fabric": "^5.1.17" - } - }, - "dynamsoft-document-normalizer": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dynamsoft-document-normalizer/-/dynamsoft-document-normalizer-1.0.12.tgz", - "integrity": "sha512-kgrLVVJJySInq0+WynIJNcXqJUaMg0rxHni349OI5KbGMVpr2/z0v0aYJGkTCNLQV+VTQJaaVpXlmMOiqQ1Vpw==", - "requires": { - "dm-howler": "^2.2.4", - "dynamsoft-camera-enhancer": "3.2.0" - } - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -23950,17 +23911,19 @@ } }, "eslint-plugin-jsdoc": { - "version": "39.6.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.4.tgz", - "integrity": "sha512-fskvdLCfwmPjHb6e+xNGDtGgbF8X7cDwMtVLAP2WwSf9Htrx68OAx31BESBM1FAwsN2HTQyYQq7m4aW4Q4Nlag==", + "version": "46.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz", + "integrity": "sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "~0.36.1", - "comment-parser": "1.3.1", + "@es-joy/jsdoccomment": "~0.41.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", - "esquery": "^1.4.0", - "semver": "^7.3.8", + "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", + "semver": "^7.5.4", "spdx-expression-parse": "^3.0.1" }, "dependencies": { @@ -23980,9 +23943,9 @@ "dev": true }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -24054,9 +24017,9 @@ "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -25308,6 +25271,15 @@ "has-tostringtag": "^1.0.0" } }, + "is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + } + }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -25808,9 +25780,9 @@ "dev": true }, "jsdoc-type-pratt-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", - "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true }, "jsesc": { @@ -30848,4 +30820,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index dc6bdfad6f..3e24ab1a35 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", "eslint-plugin-import": "2.25.2", - "eslint-plugin-jsdoc": "latest", + "eslint-plugin-jsdoc": "^46.9.0", "eslint-plugin-max-params-no-constructor": "^0.0.4", "eslint-plugin-prefer-arrow": "latest", "husky": "^7.0.2", From cc937c482923987e5e55914113602cfdbb7370f2 Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:34:16 +0530 Subject: [PATCH 02/15] test: Added parallel tests support (#2592) --- karma.conf.js | 3 +- package-lock.json | 544 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 547 insertions(+), 1 deletion(-) diff --git a/karma.conf.js b/karma.conf.js index 73d412ac72..b9640fa8bd 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,8 +1,9 @@ module.exports = function (config) { config.set({ basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], + frameworks: ['parallel', 'jasmine', '@angular-devkit/build-angular'], plugins: [ + require('karma-parallel'), require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), diff --git a/package-lock.json b/package-lock.json index f95498111b..5300207713 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,6 +111,7 @@ "karma-coverage-istanbul-reporter": "~3.0.2", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", + "karma-parallel": "^0.3.1", "lint-staged": "^11.1.2", "prettier": "^2.3.2", "protractor": "~7.0.0", @@ -5159,6 +5160,16 @@ "ajv": "^8.8.2" } }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.2" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -8067,6 +8078,114 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", + "dev": true, + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/eslint": { "version": "8.29.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", @@ -9519,6 +9638,36 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -10651,6 +10800,32 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "node_modules/istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha512-nMtdn4hvK0HjUlzr1DrKSUY8ychprt8dzHOgY2KXsIhHu5PuQQEOTM27gV9Xblyon7aUH/TSFIjRHEODF/FRPg==", + "deprecated": "This module is no longer maintained, try this instead:\n npm i nyc\nVisit https://istanbul.js.org/integrations for other alternatives.", + "dev": true, + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -10756,6 +10931,110 @@ "node": ">=8" } }, + "node_modules/istanbul/node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true + }, + "node_modules/istanbul/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true + }, + "node_modules/istanbul/node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/istanbul/node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/istanbul/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "dev": true + }, + "node_modules/istanbul/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/istanbul/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/jake": { "version": "10.8.5", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", @@ -11324,6 +11603,22 @@ "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==", "dev": true }, + "node_modules/karma-parallel": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/karma-parallel/-/karma-parallel-0.3.1.tgz", + "integrity": "sha512-64jxNYamYi/9Y67h4+FfViSYhwDgod3rLuq+ZdZ0c3XeZFp/3q3v3HVkd8b5Czp3hCB+LLF8DIv4zlR4xFqbRw==", + "dev": true, + "dependencies": { + "istanbul": "^0.4.5", + "lodash": "^4.17.11" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "karma": ">= 1.0.0" + } + }, "node_modules/karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", @@ -16777,6 +17072,19 @@ "node": "*" } }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -17560,6 +17868,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -21520,6 +21834,13 @@ "fast-deep-equal": "^3.1.3" } }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "dev": true, + "optional": true + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -23599,6 +23920,82 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, "eslint": { "version": "8.29.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", @@ -24703,6 +25100,27 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -25531,6 +25949,109 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha512-nMtdn4hvK0HjUlzr1DrKSUY8ychprt8dzHOgY2KXsIhHu5PuQQEOTM27gV9Xblyon7aUH/TSFIjRHEODF/FRPg==", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -26122,6 +26643,16 @@ "dev": true, "requires": {} }, + "karma-parallel": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/karma-parallel/-/karma-parallel-0.3.1.tgz", + "integrity": "sha512-64jxNYamYi/9Y67h4+FfViSYhwDgod3rLuq+ZdZ0c3XeZFp/3q3v3HVkd8b5Czp3hCB+LLF8DIv4zlR4xFqbRw==", + "dev": true, + "requires": { + "istanbul": "^0.4.5", + "lodash": "^4.17.11" + } + }, "karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", @@ -30091,6 +30622,13 @@ "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", "dev": true }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -30668,6 +31206,12 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 3e24ab1a35..10eafbbd4a 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "karma-coverage-istanbul-reporter": "~3.0.2", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", + "karma-parallel": "^0.3.1", "lint-staged": "^11.1.2", "prettier": "^2.3.2", "protractor": "~7.0.0", From 174274435e2ab9c1bcac5a0bebe716d2bef7b651 Mon Sep 17 00:00:00 2001 From: Dimple K H <31147415+Dimple16@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:51:29 +0530 Subject: [PATCH 03/15] fix: Mileage rate icon fix in view mileage page (#2595) Co-authored-by: Dimple --- src/app/fyle/view-mileage/view-mileage.page.spec.ts | 4 ++-- src/app/fyle/view-mileage/view-mileage.page.ts | 2 +- src/app/shared/icon/icon.module.ts | 1 + src/assets/svg/scooter.svg | 5 +++++ 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 src/assets/svg/scooter.svg diff --git a/src/app/fyle/view-mileage/view-mileage.page.spec.ts b/src/app/fyle/view-mileage/view-mileage.page.spec.ts index f004104927..400f04075d 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.spec.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.spec.ts @@ -637,7 +637,7 @@ describe('ViewMileagePage', () => { }); }); - it('should set the vehicle type to bike if the mileage_vehicle type has neither of htese words - car or four', (done) => { + it('should set the vehicle type to scooter if the mileage_vehicle type has neither of htese words - car or four', (done) => { const mockExtMileageData = { ...etxncData.data[0], tx_mileage_vehicle_type: 'Two Wheeler - Type 1 (₹11.00/km)', @@ -647,7 +647,7 @@ describe('ViewMileagePage', () => { component.ionViewWillEnter(); component.extendedMileage$.subscribe((data) => { expect(data).toEqual(mockExtMileageData); - expect(component.vehicleType).toEqual('bike'); + expect(component.vehicleType).toEqual('scooter'); done(); }); }); diff --git a/src/app/fyle/view-mileage/view-mileage.page.ts b/src/app/fyle/view-mileage/view-mileage.page.ts index dd81429fa4..a20cc7f53f 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.ts @@ -348,7 +348,7 @@ export class ViewMileagePage { ) { this.vehicleType = 'car'; } else { - this.vehicleType = 'bike'; + this.vehicleType = 'scooter'; } this.etxnCurrencySymbol = getCurrencySymbol(extendedMileage.tx_currency, 'wide'); diff --git a/src/app/shared/icon/icon.module.ts b/src/app/shared/icon/icon.module.ts index 1e4bd9162e..91fa1cb203 100644 --- a/src/app/shared/icon/icon.module.ts +++ b/src/app/shared/icon/icon.module.ts @@ -149,6 +149,7 @@ export class IconModule { 'user-two.svg', 'vertical-dots-menu.svg', 'wallet.svg', + 'scooter.svg', ]; constructor(private domSanitizer: DomSanitizer, private matIconRegistry: MatIconRegistry) { diff --git a/src/assets/svg/scooter.svg b/src/assets/svg/scooter.svg new file mode 100644 index 0000000000..1eba26e55e --- /dev/null +++ b/src/assets/svg/scooter.svg @@ -0,0 +1,5 @@ + + + + + From 3eaaa79e883934831968b1723ce722947ed4a0b5 Mon Sep 17 00:00:00 2001 From: Dimple K H <31147415+Dimple16@users.noreply.github.com> Date: Thu, 23 Nov 2023 16:33:58 +0530 Subject: [PATCH 04/15] feat: Standardize icons - Milestone-4 (#2583) Co-authored-by: Dimple --- src/app/shared/icon/icon.module.ts | 2 -- src/assets/svg/building.svg | 4 ++-- src/assets/svg/bulk-mode.svg | 4 ---- src/assets/svg/bulk.svg | 3 --- 4 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 src/assets/svg/bulk-mode.svg delete mode 100644 src/assets/svg/bulk.svg diff --git a/src/app/shared/icon/icon.module.ts b/src/app/shared/icon/icon.module.ts index 91fa1cb203..c5af145b28 100644 --- a/src/app/shared/icon/icon.module.ts +++ b/src/app/shared/icon/icon.module.ts @@ -16,9 +16,7 @@ export class IconModule { 'arrow-prev.svg', 'arrow-next.svg', 'attachment.svg', - 'bulk.svg', 'building.svg', - 'bulk-mode.svg', 'car.svg', 'chevron-right.svg', 'create-expense.svg', diff --git a/src/assets/svg/building.svg b/src/assets/svg/building.svg index db344c922e..574658e407 100644 --- a/src/assets/svg/building.svg +++ b/src/assets/svg/building.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/svg/bulk-mode.svg b/src/assets/svg/bulk-mode.svg deleted file mode 100644 index 2aa334cfc3..0000000000 --- a/src/assets/svg/bulk-mode.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/assets/svg/bulk.svg b/src/assets/svg/bulk.svg deleted file mode 100644 index 23afb138b2..0000000000 --- a/src/assets/svg/bulk.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - From b1e446255a6ebe799361dffde299dd63407f40a2 Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Sun, 26 Nov 2023 20:38:53 +0530 Subject: [PATCH 05/15] feat: Move view expense to platform combined changes (#2561) * feat: Migrated GET of single expense call via /v2/expenses to platform (#2544) * feat: Moved GET of /v2/expense to platform in View Mileage (#2513) * feat: Moved GET of /v2/expenses to platform in View Per Diem (#2514) * test: Added unit tests for /v2/expenses API migration in View Mileage (#2515) * test: Added unit tests for /v2/expenses migration in View Per Diem (#2516) * feat: Moved /etxns/:id in View Expense to Platform (#2533) * test: Added tests for /etxns/:id to platform migration in view expense page (#2548) * feat: Cleaned up remaining public calls in view expense, per diem and mileage pages (#2549) * test: Added tests for cleanup changes (#2550) * fix: Renamed etxn, tx, txn terminology to expense in view expense, mileage and per diem pages (#2553) * feat: Moved /etxns/:id call in navigation footer component to platform (#2554) * Minor * feat: Use mileage rates API in view mileage page (#2591) * Merged master --- .../mock-data/platform/v1/expense.data.ts | 686 ++++++++++++++++-- .../core/models/platform/v1/expense.model.ts | 2 +- src/app/core/services/file.service.spec.ts | 4 +- src/app/core/services/file.service.ts | 6 +- .../services/merge-expenses.service.spec.ts | 2 +- .../core/services/merge-expenses.service.ts | 2 +- .../services/mileage-rates.service.spec.ts | 38 + .../core/services/mileage-rates.service.ts | 36 +- .../v1/shared/expense.service.spec.ts | 7 +- .../core/services/transaction.service.spec.ts | 52 +- src/app/core/services/transaction.service.ts | 10 - .../fyle/view-expense/view-expense.page.html | 174 ++--- .../view-expense/view-expense.page.spec.ts | 556 ++++++-------- .../fyle/view-expense/view-expense.page.ts | 256 +++---- .../fyle/view-mileage/view-mileage.page.html | 124 ++-- .../view-mileage/view-mileage.page.spec.ts | 407 ++++++----- .../fyle/view-mileage/view-mileage.page.ts | 213 +++--- .../view-per-diem/view-per-diem.page.html | 88 +-- .../view-per-diem/view-per-diem.page.spec.ts | 184 ++--- .../fyle/view-per-diem/view-per-diem.page.ts | 186 ++--- .../view-team-advance-request.page.spec.ts | 2 +- .../view-team-advance-request.page.ts | 2 +- .../expenses-card.component.spec.ts | 32 +- .../fy-nav-footer.component.html | 8 +- .../fy-nav-footer.component.spec.ts | 10 +- .../fy-nav-footer/fy-nav-footer.component.ts | 16 +- .../navigation-footer.component.html | 4 +- .../navigation-footer.component.spec.ts | 121 +-- .../navigation-footer.component.ts | 68 +- 29 files changed, 1965 insertions(+), 1331 deletions(-) diff --git a/src/app/core/mock-data/platform/v1/expense.data.ts b/src/app/core/mock-data/platform/v1/expense.data.ts index aa7d2d0a00..43a9c0b79c 100644 --- a/src/app/core/mock-data/platform/v1/expense.data.ts +++ b/src/app/core/mock-data/platform/v1/expense.data.ts @@ -1,15 +1,243 @@ import { ExpenseState } from 'src/app/core/models/expense-state.enum'; import { PlatformCategory } from 'src/app/core/models/platform/platform-category.model'; import { MileageUnitEnum } from 'src/app/core/models/platform/platform-mileage-rates.model'; +import { ReportState } from 'src/app/core/models/platform/platform-report.model'; import { ApprovalState } from 'src/app/core/models/platform/report-approvals.model'; import { AccountType } from 'src/app/core/models/platform/v1/account.model'; import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { FileType } from 'src/app/core/models/platform/v1/file.model'; export const expenseData: Expense = { accounting_export_summary: {}, added_to_report_at: null, admin_amount: null, - amount: 103500.39, + amount: 10, + approvals: [ + { + approver_user: { + email: 'satyam.j@fyle.in', + full_name: 'Satyam', + id: 'usFjaCwwhEjD', + }, + approver_user_id: 'usFjaCwwhEjD', + state: ApprovalState.APPROVAL_DONE, + }, + ], + approver_comments: [], + category: { + code: null, + display_name: 'Food', + id: 264262, + name: 'Food', + sub_category: null, + system_category: 'Food', + }, + category_id: 264262, + claim_amount: 10, + code: null, + cost_center: { + code: null, + id: 19785, + name: 'Admin', + }, + cost_center_id: 19785, + created_at: new Date('2023-10-10T14:23:46.801464+00:00'), + creator_user_id: 'usvKA4X8Ugcr', + currency: 'USD', + custom_fields: [ + { + name: 'test - mult', + value: ['b', 'c'], + }, + ], + custom_fields_flattened: { + test21: '', + test___mult: ['b', 'c'], + test_field: '', + }, + distance: null, + distance_unit: null, + employee: { + business_unit: null, + code: null, + custom_fields: [], + department: { + code: null, + display_name: 'abcd', + id: 'deptXHRr3QFG76', + name: 'abcd', + sub_department: null, + }, + department_id: 'deptXHRr3QFG76', + flattened_custom_field: {}, + has_accepted_invite: true, + id: 'ougYBQgHDWFC', + is_enabled: true, + level: null, + location: null, + org_id: 'orDsbzdz7I7k', + org_name: 'Reimbursement test org', + title: null, + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + employee_id: 'ougYBQgHDWFC', + ended_at: null, + expense_rule_data: null, + expense_rule_id: null, + extracted_data: null, + file_ids: ['fi1w2IE6JeqS'], + files: [ + { + id: 'fi1w2IE6JeqS', + name: '000.jpeg', + content_type: 'image/jpeg', + type: FileType.RECEIPT, + }, + ], + foreign_amount: null, + foreign_currency: null, + hotel_is_breakfast_provided: false, + id: 'txcSFe6efB6R', + is_billable: null, + is_corporate_card_transaction_auto_matched: false, + is_exported: null, + is_manually_flagged: null, + is_physical_bill_submitted: null, + is_policy_flagged: null, + is_receipt_mandatory: null, + is_reimbursable: false, + is_split: false, + is_verified: true, + is_weekend_spend: false, + last_exported_at: null, + last_settled_at: null, + last_verified_at: new Date('2023-10-31T18:15:25.494224+00:00'), + locations: [], + matched_corporate_card_transaction_ids: ['btxn7DbV1VYnmT'], + matched_corporate_card_transactions: [ + { + amount: 10, + bank_name: 'VISA_BANK', + category: null, + corporate_card_id: 'baccLW9aBiAWPv', + corporate_card_number: 't8To', + corporate_card_user_full_name: 'Abhishek Jain', + currency: 'USD', + description: null, + foreign_amount: null, + foreign_currency: null, + id: 'btxn7DbV1VYnmT', + masked_corporate_card_number: 't8To', + matched_by: null, + merchant: 'Merchant1', + posted_at: null, + spent_at: new Date('2023-10-15T00:00:00+00:00'), + status: 'PENDING', + }, + ], + merchant: null, + mileage_calculated_amount: null, + mileage_calculated_distance: null, + mileage_is_round_trip: null, + mileage_rate: null, + mileage_rate_id: null, + missing_mandatory_fields: { + amount: false, + currency: false, + expense_field_ids: [], + receipt: false, + }, + org_id: 'orDsbzdz7I7k', + per_diem_num_days: null, + per_diem_rate: null, + per_diem_rate_id: null, + physical_bill_submitted_at: null, + policy_amount: null, + policy_checks: { + are_approvers_added: false, + is_amount_limit_applied: false, + is_flagged_ever: false, + violations: null, + }, + project: { + code: '10', + display_name: 'Geeta Kalapatapu', + id: 323459, + name: 'Geeta Kalapatapu', + sub_project: null, + }, + project_id: 323459, + purpose: + 'scelerisque purus semper eget duis at tellus at urna condimentum mattis pellentesque id nibh tortor id aliquet lectus proin nibh nisl condimentum id venenatis a condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas sed tempus urna et pharetra pharetra massa massa ultricies', + report: { + amount: 11556.4, + approvals: [ + { + approver_user: { + email: 'satyam.j@fyle.in', + full_name: 'Satyam', + id: 'usFjaCwwhEjD', + }, + approver_user_id: 'usFjaCwwhEjD', + state: ApprovalState.APPROVAL_DONE, + }, + ], + id: 'rp96APY6Efph', + last_approved_at: new Date('2023-11-02T10:44:14.29+00:00'), + last_paid_at: null, + last_submitted_at: new Date('2023-10-22T00:00:04.955+00:00'), + last_verified_at: new Date('2023-10-31T18:15:25.494224+00:00'), + reimbursement_id: 'reim9PJnm8PAUs', + reimbursement_seq_num: 'P/2023/11/T/P/2023/11/R/1', + seq_num: 'C/2023/10/R/8', + settlement_id: 'setLBTMkqCfL9', + state: ReportState.APPROVED, + title: '#7: October 2023', + }, + report_id: 'rp96APY6Efph', + report_last_approved_at: new Date('2023-11-02T10:44:14.290000+00:00'), + report_last_paid_at: null, + report_settlement_id: 'setLBTMkqCfL9', + seq_num: 'E/2023/10/T/7', + source: 'WEBAPP', + source_account: { + id: 'accWP1rUtQdQK', + type: AccountType.PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT, + }, + source_account_id: 'accWP1rUtQdQK', + spent_at: new Date('2023-10-10T17:00:00+00:00'), + split_group_amount: 10, + split_group_id: 'txcSFe6efB6R', + started_at: null, + state: ExpenseState.APPROVED, + state_display_name: 'Approved', + tax_amount: null, + tax_group: { + name: null, + percentage: null, + }, + tax_group_id: null, + travel_classes: [], + updated_at: new Date('2023-11-02T10:44:14.107720+00:00'), + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + verifier_comments: [], +}; + +export const mileageExpense: Expense = { + accounting_export_summary: {}, + added_to_report_at: new Date('2023-11-01T00:10:01.286157+00:00'), + admin_amount: null, + amount: 459, approvals: [ { approver_user: { @@ -24,14 +252,14 @@ export const expenseData: Expense = { approver_comments: [], category: { code: null, - display_name: 'Entertainment / Others', - id: 267841, - name: 'Entertainment', - sub_category: 'Others', - system_category: 'Entertainment', - }, - category_id: 267841, - claim_amount: 103500.39, + display_name: 'Mileage / Odd Hours', + id: 247012, + name: 'Mileage', + sub_category: 'Odd Hours', + system_category: 'Mileage', + }, + category_id: 247012, + claim_amount: 459, code: null, cost_center: { code: null, @@ -39,20 +267,13 @@ export const expenseData: Expense = { name: '80001_UKoffice', }, cost_center_id: 2885, - created_at: new Date('2023-10-30T06:30:23.577020+00:00'), + created_at: new Date('2023-02-01T05:41:41.004325+00:00'), creator_user_id: 'us29O6z3jnd3', - currency: 'INR', - custom_fields: [ - { - name: 'Custom Date.', - value: '2022-09-14T06:30:00.000Z', - }, - ], - custom_fields_flattened: { - custom_date_: '2022-09-14T06:30:00.000Z', - }, - distance: null, - distance_unit: null, + currency: 'USD', + custom_fields: [], + custom_fields_flattened: {}, + distance: 9, + distance_unit: MileageUnitEnum.MILES, employee: { business_unit: null, code: 'lol', @@ -273,15 +494,15 @@ export const expenseData: Expense = { file_ids: [], files: [], foreign_amount: null, - foreign_currency: 'OMR', - hotel_is_breakfast_provided: false, - id: 'txe0bYaJlRJf', - is_billable: null, + foreign_currency: null, + hotel_is_breakfast_provided: null, + id: 'txzPPNvxs98T', + is_billable: true, is_corporate_card_transaction_auto_matched: false, is_exported: null, is_manually_flagged: null, is_physical_bill_submitted: null, - is_policy_flagged: null, + is_policy_flagged: true, is_receipt_mandatory: null, is_reimbursable: true, is_split: false, @@ -297,12 +518,16 @@ export const expenseData: Expense = { mileage_calculated_amount: null, mileage_calculated_distance: null, mileage_is_round_trip: null, - mileage_rate: null, - mileage_rate_id: null, + mileage_rate: { + code: null, + id: 494, + vehicle_type: 'four_wheeler', + }, + mileage_rate_id: 494, missing_mandatory_fields: { amount: false, currency: false, - expense_field_ids: [1174726, 1181553], + expense_field_ids: [], receipt: false, }, org_id: 'orKaeO5xojOD', @@ -314,30 +539,65 @@ export const expenseData: Expense = { policy_checks: { are_approvers_added: false, is_amount_limit_applied: false, - is_flagged_ever: false, - violations: null, + is_flagged_ever: true, + violations: [ + { + policy_rule_description: ' when expense amount exceeds: USD 10. ', + policy_rule_id: 'tprOf6n9QHtZL', + }, + ], + }, + project: { + code: null, + display_name: 'as123', + id: 36609, + name: 'as123', + sub_project: null, }, - project: null, - project_id: null, + project_id: 36609, purpose: null, - report: null, - report_id: null, + report: { + amount: 36164626.4678, + approvals: [ + { + approver_user: { + email: 'aniruddha.s@fyle.in', + full_name: 'Aniruddha', + id: 'usPRnlrMNauG', + }, + approver_user_id: 'usPRnlrMNauG', + state: ApprovalState.APPROVAL_PENDING, + }, + ], + id: 'rpynbzxa3psU', + last_approved_at: null, + last_paid_at: null, + last_submitted_at: new Date('2023-11-01T00:10:01.286157+00:00'), + last_verified_at: null, + reimbursement_id: null, + reimbursement_seq_num: null, + seq_num: 'C/2023/11/R/2', + settlement_id: null, + state: ReportState.APPROVER_PENDING, + title: '#1: October 2023', + }, + report_id: 'rpynbzxa3psU', report_last_approved_at: null, report_last_paid_at: null, report_settlement_id: null, - seq_num: 'E/2023/10/T/504', - source: 'RECURRENCE_WEBAPP', + seq_num: 'E/2023/02/T/4', + source: 'WEBAPP_BULK', source_account: { id: 'accNL82BMedrB', type: AccountType.PERSONAL_CASH_ACCOUNT, }, source_account_id: 'accNL82BMedrB', - spent_at: new Date('2023-10-30T06:30:23.432000+00:00'), - split_group_amount: 103500.39, - split_group_id: 'txe0bYaJlRJf', + spent_at: new Date('2023-02-01T06:30:00+00:00'), + split_group_amount: 459, + split_group_id: 'txzPPNvxs98T', started_at: null, - state: ExpenseState.DRAFT, - state_display_name: 'Incomplete', + state: ExpenseState.APPROVER_PENDING, + state_display_name: 'Submitted', tax_amount: null, tax_group: { name: null, @@ -345,7 +605,7 @@ export const expenseData: Expense = { }, tax_group_id: null, travel_classes: [], - updated_at: new Date('2023-10-30T06:30:28.730426+00:00'), + updated_at: new Date('2023-11-01T00:11:02.572303+00:00'), user: { email: 'omkar.j@fyle.in', full_name: 'Omkar', @@ -355,6 +615,342 @@ export const expenseData: Expense = { verifier_comments: [], }; +export const perDiemExpense: Expense = { + accounting_export_summary: {}, + added_to_report_at: new Date('2023-11-02T07:56:41.065000+00:00'), + admin_amount: null, + amount: 440, + approvals: [ + { + approver_user: { + email: 'adityabaddur+test_accounts@gmail.com', + full_name: 'ab', + id: 'us6z24IHcdEa', + }, + approver_user_id: 'us6z24IHcdEa', + state: ApprovalState.APPROVAL_PENDING, + }, + ], + approver_comments: [], + category: { + code: null, + display_name: 'Per Diem / sfrf', + id: 116005, + name: 'Per Diem', + sub_category: 'sfrf', + system_category: 'Per Diem', + }, + category_id: 116005, + claim_amount: 440, + code: null, + cost_center: null, + cost_center_id: null, + created_at: new Date('2023-11-02T07:46:42.549015+00:00'), + creator_user_id: 'usvKA4X8Ugcr', + currency: 'INR', + custom_fields: [ + { + name: 'Multi Type', + value: '', + }, + { + name: 'name', + value: null, + }, + { + name: 'Cost Center Dep Field Level 1', + value: '', + }, + { + name: 'Cost Center Dep Field Level 123', + value: '', + }, + { + name: 'test', + value: '', + }, + { + name: 'test10', + value: '', + }, + { + name: 'test11', + value: '', + }, + { + name: 'test12', + value: '', + }, + { + name: 'test19', + value: '', + }, + { + name: 'test20', + value: '', + }, + ], + custom_fields_flattened: { + cost_center_dep_field_level_1: '', + cost_center_dep_field_level_123: '', + multi_type: '', + name: null, + test: '', + test10: '', + test11: '', + test12: '', + test19: '', + test20: '', + }, + distance: null, + distance_unit: null, + employee: { + business_unit: 'tst', + code: 'A', + custom_fields: [ + { + name: 'Previous work Experience', + value: 5, + }, + { + name: 'Nationality', + value: 'Choice 1', + }, + { + name: 'multi', + value: ['Choice 2', 'Choice 3'], + }, + { + name: 'bnbnbnb', + value: 'test', + }, + { + name: 'mnm', + value: true, + }, + { + name: 'nbnbnb', + value: { + city: 'Rome', + country: 'Italy', + display: 'Testaccio, Rome, Metropolitan City of Rome Capital, Italy', + formatted_address: 'Monte Testaccio, 00153 Rome, Metropolitan City of Rome Capital, Italy', + latitude: 41.87595200000001, + longitude: 12.475694, + state: 'Lazio', + }, + }, + { + name: 'nmn', + value: { + city: 'Chemmarathur', + country: 'India', + display: 'Test Ground Vadakara, Chemmarathur, Kerala, India', + formatted_address: 'JJ4R+FMP, Chemmarathur, Kerala 673104, India', + latitude: 11.6062154, + longitude: 75.6417055, + state: 'Kerala', + }, + }, + { + name: 'mnmm', + value: { + city: 'Chemmarathur', + country: 'India', + display: 'Test Ground Vadakara, Chemmarathur, Kerala, India', + formatted_address: 'JJ4R+FMP, Chemmarathur, Kerala 673104, India', + latitude: 11.6062154, + longitude: 75.6417055, + state: 'Kerala', + }, + }, + { + name: 'test number', + value: 4, + }, + ], + department: { + code: null, + display_name: '001', + id: 'deptlvDtgtBn8I', + name: '001', + sub_department: null, + }, + department_id: 'deptlvDtgtBn8I', + flattened_custom_field: { + bnbnbnb: 'test', + mnm: true, + mnmm: { + city: 'Chemmarathur', + country: 'India', + display: 'Test Ground Vadakara, Chemmarathur, Kerala, India', + formatted_address: 'JJ4R+FMP, Chemmarathur, Kerala 673104, India', + latitude: 11.6062154, + longitude: 75.6417055, + state: 'Kerala', + }, + multi: ['Choice 2', 'Choice 3'], + nationality: 'Choice 1', + nbnbnb: { + city: 'Rome', + country: 'Italy', + display: 'Testaccio, Rome, Metropolitan City of Rome Capital, Italy', + formatted_address: 'Monte Testaccio, 00153 Rome, Metropolitan City of Rome Capital, Italy', + latitude: 41.87595200000001, + longitude: 12.475694, + state: 'Lazio', + }, + nmn: { + city: 'Chemmarathur', + country: 'India', + display: 'Test Ground Vadakara, Chemmarathur, Kerala, India', + formatted_address: 'JJ4R+FMP, Chemmarathur, Kerala 673104, India', + latitude: 11.6062154, + longitude: 75.6417055, + state: 'Kerala', + }, + previous_work_experience: 5, + test_number: 4, + }, + has_accepted_invite: true, + id: 'ouCI4UQ2G0K1', + is_enabled: true, + level: null, + location: 'test', + org_id: 'orrjqbDbeP9p', + org_name: 'Fyle Staging', + title: 'test', + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + }, + employee_id: 'ouCI4UQ2G0K1', + ended_at: new Date('2023-11-22T06:30:00+00:00'), + expense_rule_data: null, + expense_rule_id: null, + extracted_data: null, + file_ids: [], + files: [], + foreign_amount: null, + foreign_currency: null, + hotel_is_breakfast_provided: null, + id: 'txhlIisPqUxc', + is_billable: null, + is_corporate_card_transaction_auto_matched: false, + is_exported: null, + is_manually_flagged: null, + is_physical_bill_submitted: null, + is_policy_flagged: null, + is_receipt_mandatory: null, + is_reimbursable: true, + is_split: false, + is_verified: false, + is_weekend_spend: false, + last_exported_at: null, + last_settled_at: null, + last_verified_at: null, + locations: [], + matched_corporate_card_transaction_ids: [], + matched_corporate_card_transactions: [], + merchant: null, + mileage_calculated_amount: null, + mileage_calculated_distance: null, + mileage_is_round_trip: null, + mileage_rate: null, + mileage_rate_id: null, + missing_mandatory_fields: { + amount: false, + currency: false, + expense_field_ids: [193988], + receipt: false, + }, + org_id: 'orrjqbDbeP9p', + per_diem_num_days: 22, + per_diem_rate: { + code: null, + id: 5463, + name: 'aaaaa', + }, + per_diem_rate_id: 5463, + physical_bill_submitted_at: null, + policy_amount: null, + policy_checks: { + are_approvers_added: false, + is_amount_limit_applied: false, + is_flagged_ever: false, + violations: null, + }, + project: { + code: null, + display_name: '#$', + id: 201184, + name: '#$', + sub_project: null, + }, + project_id: 201184, + purpose: '90', + report: { + amount: 855.76, + approvals: [ + { + approver_user: { + email: 'adityabaddur+test_accounts@gmail.com', + full_name: 'ab', + id: 'us6z24IHcdEa', + }, + approver_user_id: 'us6z24IHcdEa', + state: ApprovalState.APPROVAL_PENDING, + }, + ], + id: 'rpFvmTgyeBjN', + last_approved_at: null, + last_paid_at: null, + last_submitted_at: new Date('2023-10-31T00:00:01.237+00:00'), + last_verified_at: null, + reimbursement_id: null, + reimbursement_seq_num: null, + seq_num: 'C/2023/10/R/13', + settlement_id: null, + state: ReportState.APPROVER_PENDING, + title: '#5: October 2023', + }, + report_id: 'rpFvmTgyeBjN', + report_last_approved_at: null, + report_last_paid_at: null, + report_settlement_id: null, + seq_num: 'E/2023/11/T/2', + source: 'WEBAPP', + source_account: { + id: 'accfziaxbGFVW', + type: AccountType.PERSONAL_CASH_ACCOUNT, + }, + source_account_id: 'accfziaxbGFVW', + spent_at: new Date('2023-11-01T06:30:00+00:00'), + split_group_amount: 440, + split_group_id: 'txhlIisPqUxc', + started_at: new Date('2023-11-01T06:30:00+00:00'), + state: ExpenseState.APPROVER_PENDING, + state_display_name: 'Submitted', + tax_amount: null, + tax_group: { + name: null, + percentage: null, + }, + tax_group_id: null, + travel_classes: [], + updated_at: new Date('2023-11-02T07:56:41.156199+00:00'), + user: { + email: 'ajain@fyle.in', + full_name: 'Abhishek Jain', + id: 'usvKA4X8Ugcr', + }, + user_id: 'usvKA4X8Ugcr', + verifier_comments: [], +}; + export const expenseResponseData = [expenseData]; export const criticalPolicyViolatedExpense: Expense = { @@ -373,7 +969,7 @@ const mileageCategory: Category = { system_category: 'Mileage', }; -const perDiemCategory: Category = { +const perDiemCategory = { code: null, display_name: 'Per Diem display', id: 267841, diff --git a/src/app/core/models/platform/v1/expense.model.ts b/src/app/core/models/platform/v1/expense.model.ts index dcb921cccb..f71f7e0406 100644 --- a/src/app/core/models/platform/v1/expense.model.ts +++ b/src/app/core/models/platform/v1/expense.model.ts @@ -134,8 +134,8 @@ export interface MatchedCorporateCardTransaction { corporate_card_id: string; corporate_card_number: string; masked_corporate_card_number: string; + corporate_card_user_full_name: string; bank_name: string; - cardholder_name: string; amount: number; currency: string; spent_at: Date; diff --git a/src/app/core/services/file.service.spec.ts b/src/app/core/services/file.service.spec.ts index 2c2416a210..551911ecf4 100644 --- a/src/app/core/services/file.service.spec.ts +++ b/src/app/core/services/file.service.spec.ts @@ -189,7 +189,7 @@ describe('FileService', () => { describe('getReceiptsDetails():', () => { it('should return the receipt details', () => { spyOn(fileService, 'getReceiptExtension').and.returnValue('jpeg'); - expect(fileService.getReceiptsDetails(fileObjectAdv[0])).toEqual({ + expect(fileService.getReceiptsDetails(fileObjectAdv[0].name, fileObjectAdv[0].url)).toEqual({ thumbnail: fileObjectAdv[0].thumbnail, type: fileObjectAdv[0].type, }); @@ -198,7 +198,7 @@ describe('FileService', () => { it('should return the pdf receipt details', () => { spyOn(fileService, 'getReceiptExtension').and.returnValue('pdf'); - expect(fileService.getReceiptsDetails(fileObjectAdv1)).toEqual({ + expect(fileService.getReceiptsDetails(fileObjectAdv1.name, fileObjectAdv1.url)).toEqual({ thumbnail: 'img/fy-pdf.svg', type: fileObjectAdv1.type, }); diff --git a/src/app/core/services/file.service.ts b/src/app/core/services/file.service.ts index 6842ef1dc5..bd3fb4ffde 100644 --- a/src/app/core/services/file.service.ts +++ b/src/app/core/services/file.service.ts @@ -172,8 +172,8 @@ export class FileService { return dataUrl.split(';')[0].split(':')[1]; } - getReceiptsDetails(file: FileObject): ReceiptInfo { - const receiptExtn = this.getReceiptExtension(file.name); + getReceiptsDetails(fileName: string, downloadUrl: string): ReceiptInfo { + const receiptExtn = this.getReceiptExtension(fileName); const receiptInfo = { type: 'unknown', thumbnail: 'img/fy-receipt.svg', @@ -184,7 +184,7 @@ export class FileService { receiptInfo.thumbnail = 'img/fy-pdf.svg'; } else if (receiptExtn && ['png', 'jpg', 'jpeg', 'gif'].indexOf(receiptExtn) > -1) { receiptInfo.type = 'image'; - receiptInfo.thumbnail = file.url; + receiptInfo.thumbnail = downloadUrl; } return receiptInfo; } diff --git a/src/app/core/services/merge-expenses.service.spec.ts b/src/app/core/services/merge-expenses.service.spec.ts index cef21423b8..41335819c9 100644 --- a/src/app/core/services/merge-expenses.service.spec.ts +++ b/src/app/core/services/merge-expenses.service.spec.ts @@ -383,7 +383,7 @@ describe('MergeExpensesService', () => { expect(res).toEqual(fileObject5); expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(transactionId); expect(fileService.downloadUrl).toHaveBeenCalledOnceWith(fileObject5[0].id); - expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith(fileObject5[0]); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith(fileObject5[0].name, 'mock-url'); done(); }); }); diff --git a/src/app/core/services/merge-expenses.service.ts b/src/app/core/services/merge-expenses.service.ts index 2e9fcb143d..7fd1b34962 100644 --- a/src/app/core/services/merge-expenses.service.ts +++ b/src/app/core/services/merge-expenses.service.ts @@ -119,7 +119,7 @@ export class MergeExpensesService { this.fileService.downloadUrl(fileObj.id).pipe( map((downloadUrl) => { fileObj.url = downloadUrl; - const details = this.fileService.getReceiptsDetails(fileObj); + const details = this.fileService.getReceiptsDetails(fileObj.name, fileObj.url); fileObj.type = details.type; fileObj.thumbnail = details.thumbnail; return fileObj; diff --git a/src/app/core/services/mileage-rates.service.spec.ts b/src/app/core/services/mileage-rates.service.spec.ts index 6060454798..3492f5a0ba 100644 --- a/src/app/core/services/mileage-rates.service.spec.ts +++ b/src/app/core/services/mileage-rates.service.spec.ts @@ -15,14 +15,17 @@ import { platformMileageRates, platformMileageRatesSingleData } from '../mock-da import { of } from 'rxjs'; import { PAGINATION_SIZE } from 'src/app/constants'; import { cloneDeep } from 'lodash'; +import { ApproverPlatformApiService } from './approver-platform-api.service'; describe('MileageRatesService', () => { let mileageRatesService: MileageRatesService; let spenderPlatformV1ApiService: jasmine.SpyObj; + let approverPlatformApiService: jasmine.SpyObj; let currencyPipe: jasmine.SpyObj; beforeEach(() => { const spenderPlatformV1ApiServiceSpy = jasmine.createSpyObj('SpenderPlatformV1ApiService', ['get']); + const approverPlatformApiServiceSpy = jasmine.createSpyObj('ApproverPlatformApiService', ['get']); const currencyPipeSpy = jasmine.createSpyObj('CurrencyPipe', ['transform']); TestBed.configureTestingModule({ @@ -32,6 +35,10 @@ describe('MileageRatesService', () => { provide: SpenderPlatformV1ApiService, useValue: spenderPlatformV1ApiServiceSpy, }, + { + provide: ApproverPlatformApiService, + useValue: approverPlatformApiServiceSpy, + }, { provide: CurrencyPipe, useValue: currencyPipeSpy, @@ -46,6 +53,9 @@ describe('MileageRatesService', () => { spenderPlatformV1ApiService = TestBed.inject( SpenderPlatformV1ApiService ) as jasmine.SpyObj; + approverPlatformApiService = TestBed.inject( + ApproverPlatformApiService + ) as jasmine.SpyObj; currencyPipe = TestBed.inject(CurrencyPipe) as jasmine.SpyObj; }); @@ -161,4 +171,32 @@ describe('MileageRatesService', () => { done(); }); }); + + it('getSpenderMileageRateById(): should get spender mileage rate by id', (done) => { + spenderPlatformV1ApiService.get.and.returnValue(of(platformMileageRatesSingleData)); + const id = 1234; + mileageRatesService.getSpenderMileageRateById(id).subscribe((response) => { + expect(response).toEqual(platformMileageRatesSingleData.data[0]); + expect(spenderPlatformV1ApiService.get).toHaveBeenCalledOnceWith('/mileage_rates', { + params: { + id: `eq.${id}`, + }, + }); + done(); + }); + }); + + it('getApproverMileageRateById(): should get approver mileage rate by id', (done) => { + approverPlatformApiService.get.and.returnValue(of(platformMileageRatesSingleData)); + const id = 1234; + mileageRatesService.getApproverMileageRateById(id).subscribe((response) => { + expect(response).toEqual(platformMileageRatesSingleData.data[0]); + expect(approverPlatformApiService.get).toHaveBeenCalledOnceWith('/mileage_rates', { + params: { + id: `eq.${id}`, + }, + }); + done(); + }); + }); }); diff --git a/src/app/core/services/mileage-rates.service.ts b/src/app/core/services/mileage-rates.service.ts index d90ad2898c..e9e700dff2 100644 --- a/src/app/core/services/mileage-rates.service.ts +++ b/src/app/core/services/mileage-rates.service.ts @@ -3,6 +3,7 @@ import { Cacheable } from 'ts-cacheable'; import { Observable, range, Subject } from 'rxjs'; import { PlatformMileageRates } from '../models/platform/platform-mileage-rates.model'; import { SpenderPlatformV1ApiService } from './spender-platform-v1-api.service'; +import { ApproverPlatformApiService } from './approver-platform-api.service'; import { PlatformApiResponse } from '../models/platform/platform-api-response.model'; import { CurrencyPipe } from '@angular/common'; import { switchMap, concatMap, map, reduce } from 'rxjs/operators'; @@ -17,7 +18,8 @@ export class MileageRatesService { constructor( @Inject(PAGINATION_SIZE) private paginationSize: number, private spenderPlatformV1ApiService: SpenderPlatformV1ApiService, - private currencyPipe: CurrencyPipe, + private approverPlatformV1ApiService: ApproverPlatformApiService, + private currencyPipe: CurrencyPipe ) {} @Cacheable({ @@ -30,10 +32,40 @@ export class MileageRatesService { return range(0, count); }), concatMap((page) => this.getMileageRates({ offset: this.paginationSize * page, limit: this.paginationSize })), - reduce((acc, curr) => acc.concat(curr), [] as PlatformMileageRates[]), + reduce((acc, curr) => acc.concat(curr), [] as PlatformMileageRates[]) ); } + @Cacheable({ + cacheBusterObserver: mileageRateCacheBuster$, + }) + getSpenderMileageRateById(id: number): Observable { + const data = { + params: { + id: `eq.${id}`, + }, + }; + + return this.spenderPlatformV1ApiService + .get>('/mileage_rates', data) + .pipe(map((response) => response.data[0])); + } + + @Cacheable({ + cacheBusterObserver: mileageRateCacheBuster$, + }) + getApproverMileageRateById(id: number): Observable { + const data = { + params: { + id: `eq.${id}`, + }, + }; + + return this.approverPlatformV1ApiService + .get>('/mileage_rates', data) + .pipe(map((response) => response.data[0])); + } + getAllMileageRatesCount(): Observable { const data = { params: { diff --git a/src/app/core/services/platform/v1/shared/expense.service.spec.ts b/src/app/core/services/platform/v1/shared/expense.service.spec.ts index 468b4e2704..56d741f0ac 100644 --- a/src/app/core/services/platform/v1/shared/expense.service.spec.ts +++ b/src/app/core/services/platform/v1/shared/expense.service.spec.ts @@ -35,7 +35,12 @@ describe('ExpenseService', () => { describe('getIsDraft():', () => { it('should return true if transaction is draft', () => { - expect(service.getIsDraft(expenseData)).toBeTrue(); + const expense: Expense = { + ...expenseData, + state: ExpenseState.DRAFT, + }; + + expect(service.getIsDraft(expense)).toBeTrue(); }); it('should return false if transaction is not draft', () => { diff --git a/src/app/core/services/transaction.service.spec.ts b/src/app/core/services/transaction.service.spec.ts index da2a2aa995..f307ee1d64 100644 --- a/src/app/core/services/transaction.service.spec.ts +++ b/src/app/core/services/transaction.service.spec.ts @@ -190,7 +190,7 @@ describe('TransactionService', () => { timezoneService = TestBed.inject(TimezoneService) as jasmine.SpyObj; utilityService = TestBed.inject(UtilityService) as jasmine.SpyObj; spenderPlatformV1ApiService = TestBed.inject( - SpenderPlatformV1ApiService, + SpenderPlatformV1ApiService ) as jasmine.SpyObj; fileService = TestBed.inject(FileService) as jasmine.SpyObj; userEventService = TestBed.inject(UserEventService) as jasmine.SpyObj; @@ -265,22 +265,6 @@ describe('TransactionService', () => { }); }); - it('getExpenseV2(): should get expense from ID', (done) => { - apiV2Service.get.and.returnValue(of(etxncData)); - - const transactionID = 'tx5fBcPBAxLv'; - - transactionService.getExpenseV2(transactionID).subscribe((res) => { - expect(res).toEqual(etxncData.data[0]); - expect(apiV2Service.get).toHaveBeenCalledOnceWith('/expenses', { - params: { - tx_id: `eq.${transactionID}`, - }, - }); - done(); - }); - }); - it('getDefaultVehicleType(): should get default vehicle type', (done) => { const defaultVehicleType = 'two_wheeler'; storageService.get.and.returnValue(Promise.resolve(defaultVehicleType)); @@ -492,7 +476,7 @@ describe('TransactionService', () => { expect(transactionService.isEtxnInPaymentMode).toHaveBeenCalledOnceWith( expenseData1.tx_skip_reimbursement, expenseData1.source_account_type, - etxnPaymentMode.key, + etxnPaymentMode.key ); }); @@ -584,14 +568,14 @@ describe('TransactionService', () => { it('should return custom date params with start date only', () => { // @ts-ignore expect(transactionService.generateCustomDateParams(newQueryParams, filtersWithStartDateOnly)).toEqual( - customDateParamsWithStartDateOnly, + customDateParamsWithStartDateOnly ); }); it('should return custom date params with end date only', () => { // @ts-ignore expect(transactionService.generateCustomDateParams(newQueryParams, filtersWithEndDateOnly)).toEqual( - customDateParamsWithEndDateOnly, + customDateParamsWithEndDateOnly ); }); @@ -972,13 +956,13 @@ describe('TransactionService', () => { describe('getExpenseDeletionMessage():', () => { it('should return expense deletion message for single', () => { expect(transactionService.getExpenseDeletionMessage(apiExpenseRes)).toEqual( - 'You are about to permanently delete 1 selected expense.', + 'You are about to permanently delete 1 selected expense.' ); }); it('getExpenseDeletionMessage(): should return expense deletion message for multiple expenses', () => { expect(transactionService.getExpenseDeletionMessage(expenseList2)).toEqual( - 'You are about to permanently delete 2 selected expenses.', + 'You are about to permanently delete 2 selected expenses.' ); }); }); @@ -986,19 +970,19 @@ describe('TransactionService', () => { describe('getCCCExpenseMessage():', () => { it('should return ccc expense message for single ccc expense', () => { expect(transactionService.getCCCExpenseMessage(apiExpenseRes, 1)).toEqual( - "There is 1 corporate card expense from the selection which can't be deleted. However you can delete the other expenses from the selection.", + "There is 1 corporate card expense from the selection which can't be deleted. However you can delete the other expenses from the selection." ); }); it('should return ccc expense message for multiple ccc expenses', () => { expect(transactionService.getCCCExpenseMessage(expenseList2, 2)).toEqual( - "There are 2 corporate card expenses from the selection which can't be deleted. However you can delete the other expenses from the selection.", + "There are 2 corporate card expenses from the selection which can't be deleted. However you can delete the other expenses from the selection." ); }); it('should return ccc expense message for with only ccc expenses selected', () => { expect(transactionService.getCCCExpenseMessage(null, 3)).toEqual( - "There are 3 corporate card expenses from the selection which can't be deleted. ", + "There are 3 corporate card expenses from the selection which can't be deleted. " ); }); }); @@ -1009,13 +993,13 @@ describe('TransactionService', () => { "There is 1 corporate card expense from the selection which can't be deleted. However you can delete the other expenses from the selection."; const deletableExpensesMessage = 'You are about to permanently delete 1 selected expense.'; expect( - transactionService.getDeleteDialogBody(apiExpenseRes, 1, deletableExpensesMessage, cccExpensesMessage), + transactionService.getDeleteDialogBody(apiExpenseRes, 1, deletableExpensesMessage, cccExpensesMessage) ).toEqual( ` -

Are you sure to permanently delete the selected expenses?

`, +

Are you sure to permanently delete the selected expenses?

` ); }); @@ -1026,7 +1010,7 @@ describe('TransactionService', () => {
  • ${deletableExpensesMessage}
  • Once deleted, the action can't be reversed.
  • -

    Are you sure to permanently delete the selected expenses?

    `, +

    Are you sure to permanently delete the selected expenses?

    ` ); }); @@ -1036,7 +1020,7 @@ describe('TransactionService', () => { expect(transactionService.getDeleteDialogBody([], 1, null, cccExpensesMessage)).toEqual( ``, + ` ); }); }); @@ -1157,7 +1141,7 @@ describe('TransactionService', () => { const txnPaymentMode = 'reimbursable'; const txnSourceAccountType = AccountType.PERSONAL; expect( - transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode), + transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode) ).toBeTrue(); }); @@ -1166,7 +1150,7 @@ describe('TransactionService', () => { const txnPaymentMode = 'nonReimbursable'; const txnSourceAccountType = AccountType.PERSONAL; expect( - transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode), + transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode) ).toBeTrue(); }); @@ -1175,7 +1159,7 @@ describe('TransactionService', () => { const txnPaymentMode = 'advance'; const txnSourceAccountType = AccountType.ADVANCE; expect( - transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode), + transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode) ).toBeTrue(); }); @@ -1184,7 +1168,7 @@ describe('TransactionService', () => { const txnPaymentMode = 'ccc'; const txnSourceAccountType = AccountType.CCC; expect( - transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode), + transactionService.isEtxnInPaymentMode(txnSkipReimbursement, txnSourceAccountType, txnPaymentMode) ).toBeTrue(); }); }); @@ -1298,7 +1282,7 @@ describe('TransactionService', () => { expect(paymentModesService.getDefaultAccount).toHaveBeenCalledOnceWith( orgSettingsData, accountsData, - orgUserSettingsData, + orgUserSettingsData ); expect(orgSettingsService.get).toHaveBeenCalledTimes(1); expect(accountsService.getEMyAccounts).toHaveBeenCalledTimes(1); diff --git a/src/app/core/services/transaction.service.ts b/src/app/core/services/transaction.service.ts index 04b1303195..2ca70b479d 100644 --- a/src/app/core/services/transaction.service.ts +++ b/src/app/core/services/transaction.service.ts @@ -374,16 +374,6 @@ export class TransactionService { }).pipe(map((res) => res.count)); } - getExpenseV2(id: string): Observable { - return this.apiV2Service - .get('/expenses', { - params: { - tx_id: `eq.${id}`, - }, - }) - .pipe(map((res) => this.fixDates(res.data[0]))); - } - checkMandatoryFields(platformPolicyExpense: PlatformPolicyExpense): Observable { const payload = { data: platformPolicyExpense, diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index 68d70c1a7e..a27c74fc38 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -8,10 +8,10 @@ View Expense -

    - Showing {{ activeEtxnIndex + 1 }} of {{ numEtxnsInReport }} expenses +

    + Showing {{ activeExpenseIndex + 1 }} of {{ reportExpenseCount }} expenses

    @@ -20,11 +20,11 @@ - + @@ -42,8 +42,8 @@ -
    - +
    + - Claimed amount {{ etxn.tx_user_amount | currency:etxn.tx_currency: 'symbol-narrow' }} was capped to {{ - etxn.tx_amount | currency:etxn.tx_currency: 'symbol-narrow'}} due to policy. + Claimed amount {{ expense.claim_amount | currency:expense.currency: 'symbol-narrow' }} was capped to {{ + expense.amount | currency:expense.currency: 'symbol-narrow'}} due to policy.
    @@ -65,27 +65,29 @@
    -
    {{ etxn.tx_categoryDisplayName || 'Unspecified' }}
    +
    + {{ expense.category?.display_name || 'Unspecified' }} +
    - {{ etxn.tx_vendor || merchantFieldName + ' Unspecified'}} + {{ expense.merchant || merchantFieldName + ' Unspecified'}}
    - {{ projectFieldName | titlecase }}: {{ etxn.tx_project_name ? etxn.tx_project_name : 'Unspecified' }} + {{ projectFieldName | titlecase }}: {{ expense.project?.name || 'Unspecified' }}
    Card Number: {{ cardNumber | maskNumber }}
    - {{ etxnCurrencySymbol }} + {{ expenseCurrencySymbol }} - {{ etxn.tx_amount | humanizeCurrency: etxn.tx_currency:true }} + {{ expense.amount | humanizeCurrency: expense.currency:true }}
    - {{ etxn.tx_amount > 0 ? 'DR' : 'CR' }} + {{ expense.amount > 0 ? 'DR' : 'CR' }}
    -
    - {{ etxn.tx_orig_amount | humanizeCurrency: etxn.tx_orig_currency }} at {{ etxn.tx_amount / - etxn.tx_orig_amount | currency: etxn.tx_currency: 'symbol-narrow' }} +
    + {{ expense.foreign_amount | humanizeCurrency: expense.foreign_currency }} at {{ expense.amount / + expense.foreign_amount | currency: expense.currency: 'symbol-narrow' }}
    -
    - {{ etxn.tx_state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }} +
    + {{ expense.state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }}
    @@ -147,7 +149,7 @@
    Report Name
    -
    {{ etxn.rp_purpose }}
    +
    {{ expense.report?.title }}
    @@ -161,7 +163,7 @@
    Employee
    -
    {{ etxn.us_full_name }}
    +
    {{ expense.user.full_name }}
    @@ -175,7 +177,7 @@
    Expense ID
    -
    {{ etxn.tx_expense_number }}
    +
    {{ expense.seq_num }}
    @@ -197,12 +199,12 @@
    Created Date
    -
    {{ etxn.tx_created_at | date: 'MMM dd, YYYY' }}
    +
    {{ expense.created_at | date: 'MMM dd, YYYY' }}
    - + @@ -215,7 +217,7 @@
    Spend Date
    -
    {{ etxn.tx_txn_dt | date: 'MMM dd, YYYY' }}
    +
    {{ expense.spent_at | date: 'MMM dd, YYYY' }}
    @@ -253,8 +255,10 @@
    Card Transaction
    - - {{ (matchingCCCTransaction.description || matchingCCCTransaction.vendor) | ellipsis:20}} + + {{ (matchedTransaction.description || matchedTransaction.merchant) | ellipsis:20}} No matching card transaction
    @@ -262,7 +266,7 @@ - +
    @@ -271,12 +275,12 @@
    Purpose
    -
    {{ etxn.tx_purpose }}
    +
    {{ expense.purpose }}
    - +
    @@ -288,7 +292,7 @@
    Tax Group
    -
    {{ etxn.tg_name }}
    +
    {{ expense.tax_group?.name }}
    @@ -309,7 +313,7 @@
    {{ orgSettings?.tax_settings?.name }}
    - {{ (etxn.tx_tax | currency:etxn.tx_currency: 'symbol-narrow') || 'Not Added' }} + {{ (expense.tax_amount | currency:expense.currency: 'symbol-narrow') || 'Not Added' }}
    @@ -319,7 +323,7 @@ *ngIf="{ costCenterDependentCustomProperties: costCenterDependentCustomProperties$ | async} as data" > - + @@ -334,12 +338,12 @@
    Cost Center
    -
    {{ etxn.tx_cost_center_name }}
    +
    {{ expense.cost_center?.name }}
    - + @@ -352,7 +356,7 @@
    Cost Center Code
    -
    {{ etxn.tx_cost_center_code }}
    +
    {{ expense.cost_center?.code }}
    @@ -362,10 +366,10 @@
    - +
    - - + +
    @@ -374,12 +378,12 @@
    Distance
    -
    {{ etxn.tx_distance }}
    +
    {{ expense.distance }}
    - +
    @@ -388,17 +392,14 @@
    Unit
    -
    {{ etxn.tx_distance_unit | titlecase }}
    +
    {{ expense.distance_unit | titlecase }}
    - - + + @@ -413,12 +414,12 @@
    From
    -
    {{ etxn.tx_locations[0].display }}
    +
    {{ expense.locations[0].display }}
    - + @@ -431,7 +432,7 @@
    To
    -
    {{ etxn.tx_locations[1].display }}
    +
    {{ expense.locations[1].display }}
    @@ -439,7 +440,7 @@
    - + @@ -454,12 +455,12 @@
    Onward Date
    -
    {{ etxn.tx_from_dt | date: 'MMM dd, YYYY' }}
    +
    {{ expense.started_at | date: 'MMM dd, YYYY' }}
    - + @@ -472,7 +473,7 @@
    Return Date
    -
    {{ etxn.tx_to_dt | date: 'MMM dd, YYYY' }}
    +
    {{ expense.ended_at | date: 'MMM dd, YYYY' }}
    @@ -481,27 +482,27 @@
    - - + +
    Travel Class
    -
    {{ etxn.tx_train_travel_class || etxn.tx_bus_travel_class }}
    +
    {{ expense.travel_classes[0] }}
    @@ -518,12 +519,12 @@
    Onward Class
    -
    {{ etxn.tx_flight_journey_travel_class }}
    +
    {{ expense.travel_classes[0] }}
    - + @@ -536,7 +537,7 @@
    Return Class
    -
    {{ etxn.tx_flight_return_travel_class }}
    +
    {{ expense.travel_classes[1] }}
    @@ -546,9 +547,9 @@
    - +
    @@ -560,12 +561,12 @@
    City
    -
    {{ etxn.tx_locations[0]?.display }}
    +
    {{ expense.locations[0].display }}
    - + @@ -580,12 +581,12 @@
    Check In
    -
    {{ etxn.tx_from_dt | date: 'MMM dd, YYYY' }}
    +
    {{ expense.started_at | date: 'MMM dd, YYYY' }}
    - + @@ -598,7 +599,7 @@
    Check Out
    -
    {{ etxn.tx_to_dt | date: 'MMM dd, YYYY' }}
    +
    {{ expense.ended_at | date: 'MMM dd, YYYY' }}
    @@ -606,7 +607,7 @@
    - +
    @@ -615,7 +616,7 @@
    Breakfast Provided
    -
    {{ etxn.tx_hotel_is_breakfast_provided ? 'Yes' : 'No' }}
    +
    {{ expense.hotel_is_breakfast_provided ? 'Yes' : 'No' }}
    @@ -624,12 +625,12 @@ - - + + @@ -638,13 +639,13 @@ - - + + @@ -676,8 +677,9 @@ + diff --git a/src/app/fyle/view-expense/view-expense.page.spec.ts b/src/app/fyle/view-expense/view-expense.page.spec.ts index 45b1aeb276..7fdbc8e124 100644 --- a/src/app/fyle/view-expense/view-expense.page.spec.ts +++ b/src/app/fyle/view-expense/view-expense.page.spec.ts @@ -10,7 +10,6 @@ import { NetworkService } from '../../core/services/network.service'; import { PolicyService } from 'src/app/core/services/policy.service'; import { ModalPropertiesService } from 'src/app/core/services/modal-properties.service'; import { TrackingService } from '../../core/services/tracking.service'; -import { CorporateCreditCardExpenseService } from 'src/app/core/services/corporate-credit-card-expense.service'; import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { CategoriesService } from 'src/app/core/services/categories.service'; @@ -23,30 +22,30 @@ import { MatIconModule } from '@angular/material/icon'; import { MatIconTestingModule } from '@angular/material/icon/testing'; import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; import { of } from 'rxjs'; -import { etxncListData, etxnData, expenseData1, expenseData2 } from 'src/app/core/mock-data/expense.data'; +import { expenseData1, expenseData2 } from 'src/app/core/mock-data/expense.data'; import { ViewCommentComponent } from 'src/app/shared/components/comments-history/view-comment/view-comment.component'; import { ExpenseView } from 'src/app/core/models/expense-view.enum'; -import { getApiResponse, getEstatusApiResponse } from 'src/app/core/test-data/status.service.spec.data'; +import { getEstatusApiResponse } from 'src/app/core/test-data/status.service.spec.data'; import { ApproverExpensePolicyStatesData, expensePolicyStatesData, } from 'src/app/core/mock-data/platform-policy-expense.data'; -import { fileObjectAdv, fileObjectAdv1, fileObjectData } from 'src/app/core/mock-data/file-object.data'; -import { - individualExpPolicyStateData1, - individualExpPolicyStateData3, -} from 'src/app/core/mock-data/individual-expense-policy-state.data'; -import { IndividualExpensePolicyState } from 'src/app/core/models/platform/platform-individual-expense-policy-state.model'; -import { FyDeleteDialogComponent } from 'src/app/shared/components/fy-delete-dialog/fy-delete-dialog.component'; +import { fileObjectData } from 'src/app/core/mock-data/file-object.data'; +import { individualExpPolicyStateData3 } from 'src/app/core/mock-data/individual-expense-policy-state.data'; import { FyPopoverComponent } from 'src/app/shared/components/fy-popover/fy-popover.component'; import { FyViewAttachmentComponent } from 'src/app/shared/components/fy-view-attachment/fy-view-attachment.component'; import { expenseFieldsMapResponse, expenseFieldsMapResponse4 } from 'src/app/core/mock-data/expense-fields-map.data'; -import { apiTeamReportPaginated1, apiTeamRptSingleRes, expectedReports } from 'src/app/core/mock-data/api-reports.data'; -import { expectedECccResponse } from 'src/app/core/mock-data/corporate-card-expense-unflattened.data'; +import { apiTeamReportPaginated1, apiTeamRptSingleRes } from 'src/app/core/mock-data/api-reports.data'; import { filledCustomProperties } from 'src/app/core/test-data/custom-inputs.spec.data'; import { dependentFieldValues } from 'src/app/core/test-data/dependent-fields.service.spec.data'; import { orgSettingsGetData } from 'src/app/core/test-data/org-settings.service.spec.data'; import { txnStatusData } from 'src/app/core/mock-data/transaction-status.data'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { expenseData } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { AccountType } from 'src/app/core/models/platform/v1/account.model'; +import { ExpenseState } from 'src/app/core/models/expense-state.enum'; describe('ViewExpensePage', () => { let component: ViewExpensePage; @@ -64,23 +63,24 @@ describe('ViewExpensePage', () => { let policyService: jasmine.SpyObj; let modalProperties: jasmine.SpyObj; let trackingService: jasmine.SpyObj; - let corporateCreditCardExpenseService: jasmine.SpyObj; let expenseFieldsService: jasmine.SpyObj; let orgSettingsService: jasmine.SpyObj; let categoriesService: jasmine.SpyObj; let dependentFieldsService: jasmine.SpyObj; + let approverExpensesService: jasmine.SpyObj; + let spenderExpensesService: jasmine.SpyObj; let activateRouteMock: ActivatedRoute; beforeEach(waitForAsync(() => { const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['hideLoader', 'showLoader']); - const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['getEtxn', 'manualUnflag', 'manualFlag']); + const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['manualUnflag', 'manualFlag']); const reportServiceSpy = jasmine.createSpyObj('ReportService', ['getTeamReport', 'removeTransaction']); const customInputsServiceSpy = jasmine.createSpyObj('CustomInputsService', [ 'getCustomPropertyDisplayValue', 'fillCustomProperties', ]); const statusServiceSpy = jasmine.createSpyObj('StatusService', ['find', 'post']); - const fileServiceSpy = jasmine.createSpyObj('FileService', ['findByTransactionId', 'downloadUrl']); + const fileServiceSpy = jasmine.createSpyObj('FileService', ['getReceiptsDetails', 'downloadUrl']); const modalControllerSpy = jasmine.createSpyObj('ModalController', ['create']); const routerSpy = jasmine.createSpyObj('Router', ['navigate']); const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['create']); @@ -100,9 +100,6 @@ describe('ViewExpensePage', () => { 'viewComment', 'expenseFlagUnflagClicked', ]); - const corporateCreditCardExpenseServiceSpy = jasmine.createSpyObj('CorporateCreditCardExpenseService', [ - 'getEccceByGroupId', - ]); const expenseFieldsServiceSpy = jasmine.createSpyObj('ExpenseFieldsService', ['getAllMap']); const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']); const categoriesServiceSpy = jasmine.createSpyObj('CategoriesService', [ @@ -115,6 +112,8 @@ describe('ViewExpensePage', () => { const dependentFieldsServiceSpy = jasmine.createSpyObj('DependentFieldsService', [ 'getDependentFieldValuesForBaseField', ]); + const approverExpensesServiceSpy = jasmine.createSpyObj('ApproverExpensesService', ['getExpenseById']); + const spenderExpensesServiceSpy = jasmine.createSpyObj('SpenderExpensesService', ['getExpenseById']); TestBed.configureTestingModule({ declarations: [ViewExpensePage], @@ -172,10 +171,6 @@ describe('ViewExpensePage', () => { useValue: trackingServiceSpy, provide: TrackingService, }, - { - useValue: corporateCreditCardExpenseServiceSpy, - provide: CorporateCreditCardExpenseService, - }, { useValue: expenseFieldsServiceSpy, provide: ExpenseFieldsService, @@ -192,6 +187,14 @@ describe('ViewExpensePage', () => { useValue: dependentFieldsServiceSpy, provide: DependentFieldsService, }, + { + useValue: approverExpensesServiceSpy, + provide: ApproverExpensesService, + }, + { + useValue: spenderExpensesServiceSpy, + provide: SpenderExpensesService, + }, { provide: ActivatedRoute, useValue: { @@ -223,14 +226,13 @@ describe('ViewExpensePage', () => { policyService = TestBed.inject(PolicyService) as jasmine.SpyObj; modalProperties = TestBed.inject(ModalPropertiesService) as jasmine.SpyObj; trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj; - corporateCreditCardExpenseService = TestBed.inject( - CorporateCreditCardExpenseService - ) as jasmine.SpyObj; expenseFieldsService = TestBed.inject(ExpenseFieldsService) as jasmine.SpyObj; orgSettingsService = TestBed.inject(OrgSettingsService) as jasmine.SpyObj; categoriesService = TestBed.inject(CategoriesService) as jasmine.SpyObj; dependentFieldsService = TestBed.inject(DependentFieldsService) as jasmine.SpyObj; loaderService = TestBed.inject(LoaderService) as jasmine.SpyObj; + approverExpensesService = TestBed.inject(ApproverExpensesService) as jasmine.SpyObj; + spenderExpensesService = TestBed.inject(SpenderExpensesService) as jasmine.SpyObj; activateRouteMock = TestBed.inject(ActivatedRoute); fixture.detectChanges(); @@ -272,7 +274,6 @@ describe('ViewExpensePage', () => { describe('openCommentsModal', () => { it('on opening the comments modal it should add a comment if the data is updated', fakeAsync(() => { component.view = ExpenseView.individual; - transactionService.getEtxn.and.returnValue(of(expenseData1)); const modalSpy = jasmine.createSpyObj('HTMLIonModalElement', ['present', 'onDidDismiss']); modalController.create.and.resolveTo(modalSpy); modalSpy.onDidDismiss.and.resolveTo({ data: { updated: true } } as any); @@ -282,11 +283,10 @@ describe('ViewExpensePage', () => { component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: expenseData1.tx_id, + objectId: component.expenseId, }, ...modalProperties.getModalDefaultProperties(), }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); expect(modalSpy.present).toHaveBeenCalledTimes(1); expect(modalSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.addComment).toHaveBeenCalledOnceWith({ view: 'Individual' }); @@ -294,7 +294,6 @@ describe('ViewExpensePage', () => { it('on opening the comments modal it should show the comments if the data not updated', fakeAsync(() => { component.view = ExpenseView.individual; - transactionService.getEtxn.and.returnValue(of(expenseData1)); const modalSpy = jasmine.createSpyObj('HTMLIonModalElement', ['present', 'onDidDismiss']); modalController.create.and.resolveTo(modalSpy); modalSpy.onDidDismiss.and.resolveTo({ data: { updated: false } } as any); @@ -304,11 +303,10 @@ describe('ViewExpensePage', () => { component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: expenseData1.tx_id, + objectId: component.expenseId, }, ...modalProperties.getModalDefaultProperties(), }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); expect(modalSpy.present).toHaveBeenCalledTimes(1); expect(modalSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.viewComment).toHaveBeenCalledOnceWith({ view: 'Individual' }); @@ -351,30 +349,36 @@ describe('ViewExpensePage', () => { describe('setPaymentModeandIcon', () => { it('should set the payment mode and icon accordingly when the source account type is ADVANCE', () => { - const mockExchangeRateExpData = { - ...expenseData1, - source_account_type: 'PERSONAL_ADVANCE_ACCOUNT', + const mockExpense: Expense = { + ...expenseData, + source_account: { + ...expenseData.source_account, + type: AccountType.PERSONAL_ADVANCE_ACCOUNT, + }, }; - component.etxn$ = of(mockExchangeRateExpData); - component.setPaymentModeandIcon(mockExchangeRateExpData); - component.etxn$.subscribe((res) => { - expect(res.source_account_type).toEqual('PERSONAL_ADVANCE_ACCOUNT'); + + component.expense$ = of(mockExpense); + component.setPaymentModeandIcon(mockExpense); + component.expense$.subscribe((expense) => { + expect(expense.source_account.type).toEqual(AccountType.PERSONAL_ADVANCE_ACCOUNT); expect(component.paymentMode).toEqual('Advance'); expect(component.paymentModeIcon).toEqual('fy-non-reimbursable'); }); }); it('should set the payment mode and icon accordingly when the source account type is CCC', () => { - const mockExchangeRateExpData = { - ...expenseData1, - tx_skip_reimbursement: false, - source_account_type: 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT', + const mockExpense: Expense = { + ...expenseData, + source_account: { + ...expenseData.source_account, + type: AccountType.PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT, + }, }; - component.etxn$ = of(mockExchangeRateExpData); - component.setPaymentModeandIcon(mockExchangeRateExpData); - component.etxn$.subscribe((res) => { - expect(res.source_account_type).toEqual('PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT'); + component.expense$ = of(mockExpense); + component.setPaymentModeandIcon(mockExpense); + component.expense$.subscribe((expense) => { + expect(expense.source_account.type).toEqual(AccountType.PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT); expect(component.paymentMode).toEqual('Corporate Card'); expect(component.paymentModeIcon).toEqual('fy-unmatched'); expect(component.isCCCTransaction).toBeTrue(); @@ -382,29 +386,38 @@ describe('ViewExpensePage', () => { }); it('should set the payment mode and icon accordingly when the expense is non-reimbursable', () => { - const mockExchangeRateExpData = { - ...expenseData1, - tx_skip_reimbursement: true, + const mockExpense: Expense = { + ...expenseData, + is_reimbursable: false, + source_account: { + ...expenseData.source_account, + type: AccountType.PERSONAL_CASH_ACCOUNT, + }, }; - component.etxn$ = of(mockExchangeRateExpData); - component.setPaymentModeandIcon(mockExchangeRateExpData); - component.etxn$.subscribe((res) => { - expect(res.tx_skip_reimbursement).toBeTrue(); + + component.expense$ = of(mockExpense); + component.setPaymentModeandIcon(mockExpense); + component.expense$.subscribe((expense) => { + expect(expense.is_reimbursable).toBeFalse(); expect(component.paymentMode).toEqual('Paid by Company'); expect(component.paymentModeIcon).toEqual('fy-non-reimbursable'); }); }); it('the amount is reimbursable if non of the conditions match', () => { - const mockExchangeRateExpData = { - ...expenseData1, - source_account_type: 'PERSONAL_ACCOUNT', - tx_skip_reimbursement: false, + const mockExpense: Expense = { + ...expenseData, + source_account: { + ...expenseData.source_account, + type: AccountType.PERSONAL_CASH_ACCOUNT, + }, + is_reimbursable: true, }; - component.etxn$ = of(mockExchangeRateExpData); - component.setPaymentModeandIcon(mockExchangeRateExpData); - component.etxn$.subscribe((res) => { - expect(res.source_account_type).toEqual('PERSONAL_ACCOUNT'); + + component.expense$ = of(mockExpense); + component.setPaymentModeandIcon(mockExpense); + component.expense$.subscribe((expense) => { + expect(expense.source_account.type).toEqual(AccountType.PERSONAL_CASH_ACCOUNT); expect(component.paymentMode).toEqual('Paid by Employee'); expect(component.paymentModeIcon).toEqual('fy-reimbursable'); }); @@ -413,7 +426,9 @@ describe('ViewExpensePage', () => { describe('ionViewWillEnter', () => { beforeEach(() => { - component.reportId = 'rpT7x1BFlLOi'; + component.expenseId = 'tx5fBcPBAxLv'; + component.reportId = 'rp96APY6Efph'; + spyOn(component, 'setupNetworkWatcher'); spyOn(component, 'getPolicyDetails'); spyOn(component, 'setPaymentModeandIcon'); @@ -430,12 +445,14 @@ describe('ViewExpensePage', () => { categoriesService.getTravelSystemCategories.and.returnValue(['Bus', 'Airlines', 'Train']); categoriesService.getFlightSystemCategories.and.returnValue(['Airlines']); - const mockWithoutCustPropData = { - ...expenseData1, - tx_custom_properties: null, + const mockWithoutCustPropData: Expense = { + ...expenseData, + custom_fields: null, }; - component.etxnWithoutCustomProperties$ = of(mockWithoutCustPropData); - transactionService.getEtxn.and.returnValue(of(expenseData1)); + + component.expenseWithoutCustomProperties$ = of(mockWithoutCustPropData); + spenderExpensesService.getExpenseById.and.returnValue(of(expenseData)); + approverExpensesService.getExpenseById.and.returnValue(of(expenseData)); customInputsService.fillCustomProperties.and.returnValue(of(filledCustomProperties)); loaderService.showLoader.and.resolveTo(); @@ -443,12 +460,11 @@ describe('ViewExpensePage', () => { expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse4)); - component.etxn$ = of(expenseData1); - component.txnFields$ = of(expenseFieldsMapResponse4); + component.expense$ = of(expenseData); + component.expenseFields$ = of(expenseFieldsMapResponse4); dependentFieldsService.getDependentFieldValuesForBaseField.and.returnValue(of(dependentFieldValues)); - corporateCreditCardExpenseService.getEccceByGroupId.and.returnValue(of(expectedECccResponse)); statusService.find.and.returnValue(of(getEstatusApiResponse)); orgSettingsService.get.and.returnValue(of(orgSettingsGetData)); @@ -456,12 +472,16 @@ describe('ViewExpensePage', () => { const mockDownloadUrl = { url: 'mock-url', }; - fileService.findByTransactionId.and.returnValue(of([fileObjectData])); + fileService.downloadUrl.and.returnValue(of(mockDownloadUrl.url)); + fileService.getReceiptsDetails.and.returnValue({ + type: 'image', + thumbnail: 'mock-thumbnail', + }); reportService.getTeamReport.and.returnValue(of(apiTeamRptSingleRes.data[0])); }); - it('should get all the system categories and get the correct value of report is by subscribing to etxnWithoutCustomProperties$', fakeAsync(() => { + it('should get all the system categories and get the correct value of report is by subscribing to expenseWithoutCustomProperties$', fakeAsync(() => { component.ionViewWillEnter(); tick(500); expect(component.setupNetworkWatcher).toHaveBeenCalledTimes(1); @@ -470,25 +490,25 @@ describe('ViewExpensePage', () => { expect(categoriesService.getBreakfastSystemCategories).toHaveBeenCalledTimes(1); expect(categoriesService.getTravelSystemCategories).toHaveBeenCalledTimes(1); expect(categoriesService.getFlightSystemCategories).toHaveBeenCalledTimes(1); - component.etxnWithoutCustomProperties$.subscribe((res) => { - expect(res).toEqual(expenseData1); - expect(component.reportId).toEqual(res.tx_report_id); + component.expenseWithoutCustomProperties$.subscribe((expense) => { + expect(expense).toEqual(expenseData); + expect(component.reportId).toEqual(expense.report_id); }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); + expect(spenderExpensesService.getExpenseById).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); tick(500); - component.txnFields$.subscribe((res) => { - expect(res).toEqual(expenseFieldsMapResponse4); + component.expenseFields$.subscribe((expenseFields) => { + expect(expenseFields).toEqual(expenseFieldsMapResponse4); expect(expenseFieldsService.getAllMap).toHaveBeenCalledTimes(2); }); })); it('should get the custom properties', (done) => { component.ionViewWillEnter(); - component.customProperties$.subscribe((res) => { - expect(res).toEqual(filledCustomProperties); + component.customProperties$.subscribe((customProperties) => { + expect(customProperties).toEqual(filledCustomProperties); expect(customInputsService.fillCustomProperties).toHaveBeenCalledOnceWith( - expenseData1.tx_org_category_id, - expenseData1.tx_custom_properties, + expenseData.category_id, + expenseData.custom_fields, true ); done(); @@ -496,12 +516,12 @@ describe('ViewExpensePage', () => { }); it('should get the project dependent custom properties', (done) => { - const customProps = expenseData1.tx_custom_properties; + const customProps = expenseData.custom_fields; const projectIdNumber = expenseFieldsMapResponse4.project_id[0].id; component.ionViewWillEnter(); - component.projectDependentCustomProperties$.subscribe((res) => { - expect(res).toEqual(dependentFieldValues); - expect(expenseData1.tx_custom_properties).toBeDefined(); + component.projectDependentCustomProperties$.subscribe((customProperties) => { + expect(customProperties).toEqual(dependentFieldValues); + expect(expenseData.custom_fields).toBeDefined(); expect(expenseFieldsMapResponse4.project_id.length).toBeGreaterThan(0); expect(dependentFieldsService.getDependentFieldValuesForBaseField).toHaveBeenCalledOnceWith( customProps, @@ -512,12 +532,12 @@ describe('ViewExpensePage', () => { }); it('should get the cost center dependent custom properties', (done) => { - const customProps = expenseData1.tx_custom_properties; + const customProps = expenseData.custom_fields; const costCenterId = expenseFieldsMapResponse4.cost_center_id[0].id; component.ionViewWillEnter(); - component.costCenterDependentCustomProperties$.subscribe((res) => { - expect(res).toEqual(dependentFieldValues); - expect(expenseData1.tx_custom_properties).toBeDefined(); + component.costCenterDependentCustomProperties$.subscribe((customProperties) => { + expect(customProperties).toEqual(dependentFieldValues); + expect(expenseData.custom_fields).toBeDefined(); expect(expenseFieldsMapResponse4.project_id.length).toBeGreaterThan(0); expect(dependentFieldsService.getDependentFieldValuesForBaseField).toHaveBeenCalledOnceWith( customProps, @@ -527,54 +547,45 @@ describe('ViewExpensePage', () => { }); }); - it('should set the correct value for split expenses and expense rate', (done) => { - const mockExchangeRateExpData = { - ...expenseData1, - ou_org_name: 'Test', - tx_split_group_id: 'tx5fBcNgRxJk', + it('should set the correct value for split expense', () => { + const mockExpense: Expense = { + ...expenseData, + is_split: true, }; - transactionService.getEtxn.and.returnValue(of(mockExchangeRateExpData)); - component.etxn$ = of(mockExchangeRateExpData); + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); + component.expense$ = of(mockExpense); component.ionViewWillEnter(); - component.etxn$.subscribe((res) => { - expect(res.tx_split_group_id).not.toEqual(res.tx_id); - done(); - }); + expect(component.isSplitExpense).toBeTrue(); }); it('should set the correct exchange rate', () => { component.exchangeRate = 0; - const mockExchangeRateExpData = { - ...expenseData1, - tx_split_group_id: 'tx5fBcNgRxJk', - tx_amount: 500, - tx_orig_amount: 1000, + const mockExpense: Expense = { + ...expenseData, + amount: 500, + foreign_amount: 1000, }; - transactionService.getEtxn.and.returnValue(of(mockExchangeRateExpData)); + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); expect(component.exchangeRate).toBe(0.5); }); - it('should set the matchingCCCTxnIds and the correct card number and set foreign and expense transaction currency symbol', (done) => { - const mockExchangeRateExpData = { - ...expenseData1, - tx_skip_reimbursement: false, - tx_corporate_credit_card_expense_group_id: 'cccet1B17R8gWZ', + it('should set the correct card number and set foreign and expense transaction currency symbol', () => { + const mockExpense: Expense = { + ...expenseData, }; + component.isCCCTransaction = true; - transactionService.getEtxn.and.returnValue(of(mockExchangeRateExpData)); - component.etxn$ = of(mockExchangeRateExpData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); + component.expense$ = of(mockExpense); component.ionViewWillEnter(); - component.matchingCCCTransaction$.subscribe((res) => { - expect(component.paymentModeIcon).toEqual('fy-matched'); - expect(component.cardNumber).toEqual(res.card_or_account_number); - expect(corporateCreditCardExpenseService.getEccceByGroupId).toHaveBeenCalledOnceWith( - mockExchangeRateExpData.tx_corporate_credit_card_expense_group_id - ); - done(); - }); - expect(component.foreignCurrencySymbol).toEqual(expenseData1.tx_orig_currency); - expect(component.etxnCurrencySymbol).toEqual('$'); + + expect(component.paymentModeIcon).toEqual('fy-matched'); + expect(component.cardNumber).toEqual(expenseData.matched_corporate_card_transactions[0].corporate_card_number); + expect(component.foreignCurrencySymbol).toEqual(expenseData.foreign_currency); + expect(component.expenseCurrencySymbol).toEqual('$'); }); it('should get the project details', () => { @@ -582,24 +593,25 @@ describe('ViewExpensePage', () => { ...expenseFieldsMapResponse, project_id: [], }; - transactionService.getEtxn.and.returnValue(of(expenseData1)); - component.etxn$ = of(expenseData1); + + spenderExpensesService.getExpenseById.and.returnValue(of(expenseData)); + component.expense$ = of(expenseData); expenseFieldsService.getAllMap.and.returnValue(of(mockExpFieldData)); - component.txnFields$ = of(mockExpFieldData); + component.expenseFields$ = of(mockExpFieldData); component.ionViewWillEnter(); expect(component.projectFieldName).toBeUndefined(); expect(component.isProjectShown).toBeTruthy(); }); - it('should get the project details when project name is not present', () => { - const mockExpData = { - ...expenseData1, - tx_project_name: null, + it('should get the project details when project is not present', () => { + const mockExpData: Expense = { + ...expenseData, + project: null, }; - transactionService.getEtxn.and.returnValue(of(mockExpData)); + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpData)); expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse4)); - component.etxn$ = of(mockExpData); + component.expense$ = of(mockExpData); component.ionViewWillEnter(); expect(component.projectFieldName).toEqual('Project ID'); @@ -610,7 +622,7 @@ describe('ViewExpensePage', () => { spyOn(component, 'isPolicyComment').and.returnValue(true); component.ionViewWillEnter(); component.policyViloations$.subscribe(() => { - expect(statusService.find).toHaveBeenCalledWith('transactions', expenseData1.tx_id); + expect(statusService.find).toHaveBeenCalledWith('transactions', expenseData.id); expect(statusService.find).toHaveBeenCalledTimes(2); expect(component.isPolicyComment).toHaveBeenCalledTimes(5); done(); @@ -625,116 +637,118 @@ describe('ViewExpensePage', () => { }; component.ionViewWillEnter(); component.comments$.subscribe(() => { - expect(statusService.find).toHaveBeenCalledOnceWith('transactions', expenseData1.tx_id); + expect(statusService.find).toHaveBeenCalledOnceWith('transactions', component.expenseId); done(); }); expect(component.view).toEqual(activateRouteMock.snapshot.params.view); }); it('should get the flag status', (done) => { - const mockWithoutCustPropData = { - ...etxnData, - tx_custom_properties: null, + const mockWithoutCustPropData: Expense = { + ...expenseData, + custom_fields: null, }; - transactionService.getEtxn.and.returnValue(of(mockWithoutCustPropData)); - component.etxnWithoutCustomProperties$ = of(mockWithoutCustPropData); + approverExpensesService.getExpenseById.and.returnValue(of(mockWithoutCustPropData)); + component.expenseWithoutCustomProperties$ = of(mockWithoutCustPropData); activateRouteMock.snapshot.params.view = ExpenseView.team; - component.etxn$ = of(etxnData); + component.expense$ = of(expenseData); component.ionViewWillEnter(); - component.canFlagOrUnflag$.subscribe((res) => { - expect(etxnData.tx_state).toEqual('APPROVED'); - expect(res).toBeTrue(); + component.canFlagOrUnflag$.subscribe((canFlagOrUnflag) => { + expect(mockWithoutCustPropData.state).toEqual(ExpenseState.APPROVED); + expect(canFlagOrUnflag).toBeTrue(); done(); }); }); it('should return false if there is only one transaction in the report and the state is PAID', () => { - const mockWithoutCustPropData = { - ...etxnData, - tx_state: 'PAID', - tx_report_id: 'rphNNUiCISkD', - tx_custom_properties: null, + const mockWithoutCustPropData: Expense = { + ...expenseData, + state: ExpenseState.PAID, + report_id: 'rphNNUiCISkD', + custom_fields: null, }; + reportService.getTeamReport.and.returnValue(of(apiTeamRptSingleRes.data[0])); - transactionService.getEtxn.and.returnValue(of(mockWithoutCustPropData)); - component.etxnWithoutCustomProperties$ = of(mockWithoutCustPropData); + approverExpensesService.getExpenseById.and.returnValue(of(mockWithoutCustPropData)); + component.expenseWithoutCustomProperties$ = of(mockWithoutCustPropData); activateRouteMock.snapshot.params.view = ExpenseView.team; component.ionViewWillEnter(); - component.canDelete$.subscribe((res) => { - expect(mockWithoutCustPropData.tx_state).toEqual('PAID'); - expect(res).toBeFalse(); + component.canDelete$.subscribe((canDelete) => { + expect(mockWithoutCustPropData.state).toEqual(ExpenseState.PAID); + expect(canDelete).toBeFalse(); }); }); it('should return true if the transaction state is DRAFT and there are more than one transactions in the report', () => { - const mockWithoutCustPropData = { - ...expenseData1, - tx_report_id: 'rphNNUiCISkD', - tx_custom_properties: null, + const mockWithoutCustPropData: Expense = { + ...expenseData, + report_id: 'rphNNUiCISkD', + state: ExpenseState.DRAFT, + custom_fields: null, }; reportService.getTeamReport.and.returnValue(of(apiTeamReportPaginated1.data[3])); - transactionService.getEtxn.and.returnValue(of(mockWithoutCustPropData)); - component.etxnWithoutCustomProperties$ = of(mockWithoutCustPropData); + approverExpensesService.getExpenseById.and.returnValue(of(mockWithoutCustPropData)); + component.expenseWithoutCustomProperties$ = of(mockWithoutCustPropData); activateRouteMock.snapshot.params.view = ExpenseView.team; component.ionViewWillEnter(); - component.canDelete$.subscribe((res) => { - expect(mockWithoutCustPropData.tx_state).toEqual('DRAFT'); - expect(res).toBeTrue(); + component.canDelete$.subscribe((canDelete) => { + expect(mockWithoutCustPropData.state).toEqual(ExpenseState.DRAFT); + expect(canDelete).toBeTrue(); }); }); it('should return true if the policy amount value is of type number should check if the amount is capped', (done) => { spyOn(component, 'isNumber').and.returnValue(true); - const mockExpenseData = { - ...expenseData1, - tx_policy_amount: 1000, - tx_admin_amount: null, + const mockExpenseData: Expense = { + ...expenseData, + policy_amount: 1000, + admin_amount: null, }; - transactionService.getEtxn.and.returnValue(of(mockExpenseData)); - component.etxn$ = of(mockExpenseData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpenseData)); + component.expense$ = of(mockExpenseData); component.ionViewWillEnter(); - component.isAmountCapped$.subscribe((res) => { - expect(res).toBeTrue(); + component.isAmountCapped$.subscribe((isAmountCapped) => { + expect(isAmountCapped).toBeTrue(); done(); }); }); it('should return true if the admin amount value is of type number should check if the amount is capped', (done) => { spyOn(component, 'isNumber').and.returnValue(true); - const mockExpenseData = { - ...expenseData1, - tx_admin_amount: 1000, - tx_policy_amount: null, + const mockExpenseData: Expense = { + ...expenseData, + admin_amount: 1000, + policy_amount: null, }; - transactionService.getEtxn.and.returnValue(of(mockExpenseData)); - component.etxn$ = of(mockExpenseData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpenseData)); + component.expense$ = of(mockExpenseData); component.ionViewWillEnter(); - component.isAmountCapped$.subscribe((res) => { - expect(res).toBeTrue(); - expect(component.isNumber).toHaveBeenCalledOnceWith(mockExpenseData.tx_admin_amount); + component.isAmountCapped$.subscribe((isAmountCapped) => { + expect(isAmountCapped).toBeTrue(); + expect(component.isNumber).toHaveBeenCalledOnceWith(mockExpenseData.admin_amount); done(); }); }); it('should return false if the value is not of type number and check if the expense is capped', (done) => { spyOn(component, 'isNumber').and.returnValue(false); - const mockExpenseData = { - ...expenseData1, - tx_admin_amount: null, - tx_policy_amount: null, + const mockExpenseData: Expense = { + ...expenseData, + admin_amount: null, + policy_amount: null, }; - transactionService.getEtxn.and.returnValue(of(mockExpenseData)); - component.etxn$ = of(mockExpenseData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpenseData)); + component.expense$ = of(mockExpenseData); component.ionViewWillEnter(); - component.isAmountCapped$.subscribe((res) => { - expect(res).toBeFalse(); - expect(component.isNumber).toHaveBeenCalledWith(mockExpenseData.tx_policy_amount); + component.isAmountCapped$.subscribe((isAmountCapped) => { + expect(isAmountCapped).toBeFalse(); + expect(component.isNumber).toHaveBeenCalledWith(mockExpenseData.policy_amount); expect(component.isNumber).toHaveBeenCalledTimes(2); done(); }); @@ -792,43 +806,41 @@ describe('ViewExpensePage', () => { it('should be true if expense policy is violated', () => { spyOn(component, 'isNumber').and.returnValue(true); - const mockExpenseData = { - ...expenseData1, - tx_policy_amount: -1, + const mockExpenseData: Expense = { + ...expenseData, + policy_amount: -1, }; - transactionService.getEtxn.and.returnValue(of(mockExpenseData)); - component.etxn$ = of(mockExpenseData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpenseData)); + component.expense$ = of(mockExpenseData); component.ionViewWillEnter(); - component.isCriticalPolicyViolated$.subscribe((res) => { - expect(res).toBeTrue(); - expect(component.isNumber).toHaveBeenCalledOnceWith(mockExpenseData.tx_policy_amount); + component.isCriticalPolicyViolated$.subscribe((isCriticalPolicyViolated) => { + expect(isCriticalPolicyViolated).toBeTrue(); + expect(component.isNumber).toHaveBeenCalledOnceWith(mockExpenseData.policy_amount); }); }); it('should be able to edit expense attachments', fakeAsync(() => { - spyOn(component, 'getReceiptDetails').and.returnValue({ + spyOn(component.updateFlag$, 'next'); + + fileService.getReceiptsDetails.and.returnValue({ type: 'image', thumbnail: 'mock-thumbnail', }); - spyOn(component.updateFlag$, 'next'); - const mockDownloadUrl = { url: 'mock-url', }; - fileService.findByTransactionId.and.returnValue(of([fileObjectData])); fileService.downloadUrl.and.returnValue(of(mockDownloadUrl.url)); component.ionViewWillEnter(); tick(500); - component.etxn$.subscribe((res) => { - expect(fileService.findByTransactionId).toHaveBeenCalledOnceWith(res.tx_id); + component.expense$.subscribe((expense) => { expect(fileService.downloadUrl).toHaveBeenCalledOnceWith(fileObjectData.id); - expect(component.getReceiptDetails).toHaveBeenCalledOnceWith(fileObjectData); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith(fileObjectData.name, fileObjectData.url); }); tick(500); expect(component.updateFlag$.next).toHaveBeenCalledOnceWith(null); - component.attachments$.subscribe((res) => { - expect(res).toEqual([fileObjectData]); + component.attachments$.subscribe((attachments) => { + expect(attachments).toEqual([fileObjectData]); expect(component.isLoading).toBeFalse(); }); })); @@ -837,8 +849,8 @@ describe('ViewExpensePage', () => { activateRouteMock.snapshot.params.txnIds = '["tx3qwe4ty","tx6sd7gh","txD3cvb6"]'; activateRouteMock.snapshot.params.activeIndex = '20'; component.ionViewWillEnter(); - expect(component.numEtxnsInReport).toEqual(3); - expect(component.activeEtxnIndex).toEqual(20); + expect(component.reportExpenseCount).toEqual(3); + expect(component.activeExpenseIndex).toEqual(20); }); }); @@ -876,7 +888,6 @@ describe('ViewExpensePage', () => { describe('goBack', () => { it('should go to view team report if the expense is a team expense', () => { - component.reportId = 'rpWDg3QX3'; component.view = ExpenseView.team; component.goBack(); expect(router.navigate).toHaveBeenCalledOnceWith([ @@ -888,7 +899,6 @@ describe('ViewExpensePage', () => { }); it('should go to view report if the expense is an individual expense', () => { - component.reportId = 'rpJFg3Da4'; component.view = ExpenseView.individual; component.goBack(); expect(router.navigate).toHaveBeenCalledOnceWith([ @@ -900,79 +910,10 @@ describe('ViewExpensePage', () => { }); }); - describe('getReceiptExtension():', () => { - it('should return the receipt extention if present', () => { - const res = component.getReceiptExtension('dummyFile.pdf'); - expect(res).toEqual('pdf'); - }); - - it('should return null when a file name without extension is provided', () => { - const res = component.getReceiptExtension('dummyFile'); - expect(res).toEqual(null); - }); - }); - - describe('getReceiptDetails', () => { - it('should get the receipt details when the file extention is of type pdf', () => { - spyOn(component, 'getReceiptExtension').and.returnValue('pdf'); - const res = component.getReceiptDetails(fileObjectAdv1); - expect(component.getReceiptExtension).toHaveBeenCalledOnceWith(fileObjectAdv1.name); - expect(res.type).toEqual('pdf'); - expect(res.thumbnail).toEqual('img/fy-pdf.svg'); - }); - - it('should get the receipt details when the it is an image with jpeg as extention', () => { - spyOn(component, 'getReceiptExtension').and.returnValue('jpeg'); - const res = component.getReceiptDetails(fileObjectAdv[0]); - expect(component.getReceiptExtension).toHaveBeenCalledOnceWith(fileObjectAdv[0].name); - expect(res.type).toEqual('image'); - expect(res.thumbnail).toEqual(fileObjectAdv[0].url); - }); - - it('should get the receipt details when the it is an image with jpg as extention', () => { - const mockFileData = { - ...fileObjectAdv[0], - name: 'dummyFile.jpg', - url: 'https://fyle-storage-mumbai-3.s3.amazonaws.com/2023-02-23/orrjqbDbeP9p/receipts/fiSSsy2Bf4Se.000.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230223T151537Z&X-Amz-SignedHeaders=host&X-Amz-Expires=604800&X-Amz-Credential=AKIA54Z3LIXTX6CFH4VG%2F20230223%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Signature=d79c2711892e7cb3f072e223b7b416408c252da38e7df0995e3d256cd8509fee', - }; - spyOn(component, 'getReceiptExtension').and.returnValue('jpg'); - const res = component.getReceiptDetails(mockFileData); - expect(component.getReceiptExtension).toHaveBeenCalledOnceWith(mockFileData.name); - expect(res.type).toEqual('image'); - expect(res.thumbnail).toEqual(mockFileData.url); - }); - - it('should get the receipt details when the it is an image with png as extention', () => { - const mockFileData = { - ...fileObjectAdv[0], - name: 'dummyFile.png', - url: 'https://fyle-storage-mumbai-3.s3.amazonaws.com/2023-02-23/orrjqbDbeP9p/receipts/fiSSsy2Bf4Se.000.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230223T151537Z&X-Amz-SignedHeaders=host&X-Amz-Expires=604800&X-Amz-Credential=AKIA54Z3LIXTX6CFH4VG%2F20230223%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Signature=d79c2711892e7cb3f072e223b7b416408c252da38e7df0995e3d256cd8509fee', - }; - spyOn(component, 'getReceiptExtension').and.returnValue('png'); - const res = component.getReceiptDetails(mockFileData); - expect(component.getReceiptExtension).toHaveBeenCalledOnceWith(mockFileData.name); - expect(res.type).toEqual('image'); - expect(res.thumbnail).toEqual(mockFileData.url); - }); - - it('should get the receipt details when the it is an image with gif as extention', () => { - const mockFileData = { - ...fileObjectAdv[0], - name: 'dummyFile.gif', - url: 'https://fyle-storage-mumbai-3.s3.amazonaws.com/2023-02-23/orrjqbDbeP9p/receipts/fiSSsy2Bf4Se.000.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230223T151537Z&X-Amz-SignedHeaders=host&X-Amz-Expires=604800&X-Amz-Credential=AKIA54Z3LIXTX6CFH4VG%2F20230223%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Signature=d79c2711892e7cb3f072e223b7b416408c252da38e7df0995e3d256cd8509fee', - }; - spyOn(component, 'getReceiptExtension').and.returnValue('gif'); - const res = component.getReceiptDetails(mockFileData); - expect(component.getReceiptExtension).toHaveBeenCalledOnceWith(mockFileData.name); - expect(res.type).toEqual('image'); - expect(res.thumbnail).toEqual(mockFileData.url); - }); - }); - it('getDeleteDialogProps(): should return the props', () => { - const props = component.getDeleteDialogProps(expenseData1); + const props = component.getDeleteDialogProps(); props.componentProps.deleteMethod(); - expect(reportService.removeTransaction).toHaveBeenCalledOnceWith(expenseData1.tx_report_id, expenseData1.tx_id); + expect(reportService.removeTransaction).toHaveBeenCalledOnceWith(component.reportId, component.expenseId); }); describe('removeExpenseFromReport', () => { @@ -980,7 +921,6 @@ describe('ViewExpensePage', () => { activateRouteMock.snapshot.params = { id: 'tx5fBcPBAxLv', }; - transactionService.getEtxn.and.returnValue(of(expenseData1)); spyOn(component, 'getDeleteDialogProps'); const deletePopoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present', 'onDidDismiss']); @@ -989,8 +929,7 @@ describe('ViewExpensePage', () => { component.removeExpenseFromReport(); tick(500); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); - expect(popoverController.create).toHaveBeenCalledOnceWith(component.getDeleteDialogProps(expenseData1)); + expect(popoverController.create).toHaveBeenCalledOnceWith(component.getDeleteDialogProps()); expect(deletePopoverSpy.present).toHaveBeenCalledTimes(1); expect(deletePopoverSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.expenseRemovedByApprover).toHaveBeenCalledTimes(1); @@ -998,30 +937,17 @@ describe('ViewExpensePage', () => { '/', 'enterprise', 'view_team_report', - { id: expenseData1.tx_report_id, navigate_back: true }, + { id: component.reportId, navigate_back: true }, ]); })); }); describe('flagUnflagExpense', () => { it('should flag,unflagged expense', fakeAsync(() => { - activateRouteMock.snapshot.queryParams = { + activateRouteMock.snapshot.params = { id: 'tx5fBcPBAxLv', }; - const testComment = { - id: 'stjIdPp8BX8O', - created_at: '2022-11-17T06:07:38.590Z', - org_user_id: 'ouX8dwsbLCLv', - comment: 'This is a comment for flagging', - diff: null, - state: null, - transaction_id: null, - report_id: 'rpkpSa8guCuR', - advance_request_id: null, - }; - - transactionService.getEtxn.and.returnValue(of(expenseData1)); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); @@ -1033,9 +959,7 @@ describe('ViewExpensePage', () => { statusService.post.and.returnValue(of(txnStatusData)); transactionService.manualFlag.and.returnValue(of(expenseData2)); - component.flagUnflagExpense(expenseData1.tx_manual_flag); - tick(500); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); + component.flagUnflagExpense(false); tick(500); expect(popoverController.create).toHaveBeenCalledOnceWith({ @@ -1050,8 +974,8 @@ describe('ViewExpensePage', () => { expect(flagPopoverSpy.present).toHaveBeenCalledTimes(1); expect(flagPopoverSpy.onWillDismiss).toHaveBeenCalledTimes(1); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Please wait'); - expect(statusService.post).toHaveBeenCalledOnceWith('transactions', expenseData1.tx_id, data, true); - expect(transactionService.manualFlag).toHaveBeenCalledOnceWith(expenseData1.tx_id); + expect(statusService.post).toHaveBeenCalledOnceWith('transactions', component.expenseId, data, true); + expect(transactionService.manualFlag).toHaveBeenCalledOnceWith(component.expenseId); tick(500); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(trackingService.expenseFlagUnflagClicked).toHaveBeenCalledOnceWith({ action: title }); @@ -1062,22 +986,6 @@ describe('ViewExpensePage', () => { id: 'tx5fBcPBAxLv', }; - const mockExpenseData = { - ...expenseData1, - tx_manual_flag: true, - }; - const testComment = { - id: 'stjIdPp8BX8O', - created_at: '2022-11-17T06:07:38.590Z', - org_user_id: 'ouX8dwsbLCLv', - comment: 'a comment', - diff: null, - state: null, - transaction_id: null, - report_id: 'rpkpSa8guCuR', - advance_request_id: null, - }; - transactionService.getEtxn.and.returnValue(of(mockExpenseData)); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); @@ -1089,9 +997,7 @@ describe('ViewExpensePage', () => { statusService.post.and.returnValue(of(txnStatusData)); transactionService.manualUnflag.and.returnValue(of(expenseData1)); - component.flagUnflagExpense(mockExpenseData.tx_manual_flag); - tick(500); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); + component.flagUnflagExpense(true); tick(500); expect(popoverController.create).toHaveBeenCalledOnceWith({ @@ -1106,8 +1012,8 @@ describe('ViewExpensePage', () => { expect(flagPopoverSpy.present).toHaveBeenCalledTimes(1); expect(flagPopoverSpy.onWillDismiss).toHaveBeenCalledTimes(1); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Please wait'); - expect(statusService.post).toHaveBeenCalledOnceWith('transactions', mockExpenseData.tx_id, data, true); - expect(transactionService.manualUnflag).toHaveBeenCalledOnceWith(mockExpenseData.tx_id); + expect(statusService.post).toHaveBeenCalledOnceWith('transactions', component.expenseId, data, true); + expect(transactionService.manualUnflag).toHaveBeenCalledOnceWith(component.expenseId); tick(500); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(trackingService.expenseFlagUnflagClicked).toHaveBeenCalledOnceWith({ action: title }); diff --git a/src/app/fyle/view-expense/view-expense.page.ts b/src/app/fyle/view-expense/view-expense.page.ts index bc2c25c028..733665a968 100644 --- a/src/app/fyle/view-expense/view-expense.page.ts +++ b/src/app/fyle/view-expense/view-expense.page.ts @@ -1,6 +1,5 @@ import { Component, EventEmitter, ViewChild, ElementRef } from '@angular/core'; import { Observable, from, Subject, concat, noop, forkJoin } from 'rxjs'; -import { Expense } from 'src/app/core/models/expense.model'; import { LoaderService } from 'src/app/core/services/loader.service'; import { TransactionService } from 'src/app/core/services/transaction.service'; import { ActivatedRoute, Router } from '@angular/router'; @@ -17,23 +16,24 @@ import { ViewCommentComponent } from 'src/app/shared/components/comments-history import { ModalPropertiesService } from 'src/app/core/services/modal-properties.service'; import { TrackingService } from '../../core/services/tracking.service'; import { FyDeleteDialogComponent } from 'src/app/shared/components/fy-delete-dialog/fy-delete-dialog.component'; -import { CorporateCreditCardExpenseService } from 'src/app/core/services/corporate-credit-card-expense.service'; import { FyPopoverComponent } from 'src/app/shared/components/fy-popover/fy-popover.component'; import { getCurrencySymbol } from '@angular/common'; -import { MatchedCCCTransaction } from 'src/app/core/models/matchedCCCTransaction.model'; import { ExpenseView } from 'src/app/core/models/expense-view.enum'; import { ExtendedStatus } from 'src/app/core/models/extended_status.model'; import { CustomField } from 'src/app/core/models/custom_field.model'; -import { AccountType } from 'src/app/core/enums/account-type.enum'; import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { CategoriesService } from 'src/app/core/services/categories.service'; import { ExpenseField } from 'src/app/core/models/v1/expense-field.model'; import { DependentFieldsService } from 'src/app/core/services/dependent-fields.service'; -import { CCCExpUnflattened } from 'src/app/core/models/corporate-card-expense-unflattened.model'; import { CustomInput } from 'src/app/core/models/custom-input.model'; import { OrgSettings } from 'src/app/core/models/org-settings.model'; import { FileObject } from 'src/app/core/models/file-obj.model'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { AccountType } from 'src/app/core/models/platform/v1/account.model'; +import { ExpenseState } from 'src/app/core/models/expense-state.enum'; @Component({ selector: 'app-view-expense', @@ -43,7 +43,7 @@ import { FileObject } from 'src/app/core/models/file-obj.model'; export class ViewExpensePage { @ViewChild('comments') commentsContainer: ElementRef; - etxn$: Observable; + expense$: Observable; policyViloations$: Observable; @@ -53,7 +53,7 @@ export class ViewExpensePage { customProperties$: Observable; - etxnWithoutCustomProperties$: Observable; + expenseWithoutCustomProperties$: Observable; canFlagOrUnflag$: Observable; @@ -61,6 +61,8 @@ export class ViewExpensePage { orgSettings: OrgSettings; + expenseId: string; + reportId: string; attachments$: Observable; @@ -85,15 +87,13 @@ export class ViewExpensePage { isCCCTransaction = false; - matchingCCCTransaction$: Observable; - - numEtxnsInReport: number; + reportExpenseCount: number; - activeEtxnIndex: number; + activeExpenseIndex: number; paymentModeIcon: string; - etxnCurrencySymbol: string; + expenseCurrencySymbol: string; foreignCurrencySymbol: string; @@ -121,7 +121,7 @@ export class ViewExpensePage { isNewReportsFlowEnabled = false; - txnFields$: Observable<{ [key: string]: ExpenseField[] }>; + expenseFields$: Observable<{ [key: string]: ExpenseField[] }>; projectDependentCustomProperties$: Observable[]>; @@ -142,11 +142,12 @@ export class ViewExpensePage { private policyService: PolicyService, private modalProperties: ModalPropertiesService, private trackingService: TrackingService, - private corporateCreditCardExpenseService: CorporateCreditCardExpenseService, private expenseFieldsService: ExpenseFieldsService, private orgSettingsService: OrgSettingsService, private categoriesService: CategoriesService, - private dependentFieldsService: DependentFieldsService + private dependentFieldsService: DependentFieldsService, + private spenderExpensesService: SpenderExpensesService, + private approverExpensesService: ApproverExpensesService ) {} get ExpenseView(): typeof ExpenseView { @@ -177,12 +178,11 @@ export class ViewExpensePage { } async openCommentsModal(): Promise { - const etxn = await this.transactionService.getEtxn(this.activatedRoute.snapshot.params.id as string).toPromise(); const modal = await this.modalController.create({ component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: etxn.tx_id, + objectId: this.expenseId, }, ...this.modalProperties.getModalDefaultProperties(), }); @@ -232,15 +232,15 @@ export class ViewExpensePage { } } - setPaymentModeandIcon(etxn: Expense): void { - if (etxn.source_account_type === AccountType.ADVANCE) { + setPaymentModeandIcon(expense: Expense): void { + if (expense.source_account.type === AccountType.PERSONAL_ADVANCE_ACCOUNT) { this.paymentMode = 'Advance'; this.paymentModeIcon = 'fy-non-reimbursable'; - } else if (etxn.source_account_type === AccountType.CCC) { + } else if (expense.source_account.type === AccountType.PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT) { this.paymentMode = 'Corporate Card'; this.paymentModeIcon = 'fy-unmatched'; this.isCCCTransaction = true; - } else if (etxn.tx_skip_reimbursement) { + } else if (!expense.is_reimbursable) { this.paymentMode = 'Paid by Company'; this.paymentModeIcon = 'fy-non-reimbursable'; } else { @@ -251,7 +251,9 @@ export class ViewExpensePage { ionViewWillEnter(): void { this.setupNetworkWatcher(); - const txId = this.activatedRoute.snapshot.params.id as string; + + this.expenseId = this.activatedRoute.snapshot.params.id as string; + this.view = this.activatedRoute.snapshot.params.view as ExpenseView; this.systemCategories = this.categoriesService.getSystemCategories(); this.systemCategoriesWithTaxi = this.categoriesService.getSystemCategoriesWithTaxi(); @@ -259,123 +261,122 @@ export class ViewExpensePage { this.travelSystemCategories = this.categoriesService.getTravelSystemCategories(); this.flightSystemCategories = this.categoriesService.getFlightSystemCategories(); - this.etxnWithoutCustomProperties$ = this.updateFlag$.pipe( - switchMap(() => this.transactionService.getEtxn(txId)), + this.expenseWithoutCustomProperties$ = this.updateFlag$.pipe( + switchMap(() => + this.view === ExpenseView.team + ? this.approverExpensesService.getExpenseById(this.expenseId) + : this.spenderExpensesService.getExpenseById(this.expenseId) + ), shareReplay(1) ); - this.etxnWithoutCustomProperties$.subscribe((res) => { - this.reportId = res.tx_report_id; + this.expenseWithoutCustomProperties$.subscribe((res) => { + this.reportId = res.report_id; }); - this.customProperties$ = this.etxnWithoutCustomProperties$.pipe( - concatMap((etxn) => - this.customInputsService.fillCustomProperties(etxn.tx_org_category_id, etxn.tx_custom_properties, true) + this.customProperties$ = this.expenseWithoutCustomProperties$.pipe( + concatMap((expense) => + this.customInputsService.fillCustomProperties(expense.category_id, expense.custom_fields, true) ), shareReplay(1) ); - this.etxn$ = this.etxnWithoutCustomProperties$.pipe( + this.expense$ = this.expenseWithoutCustomProperties$.pipe( finalize(() => this.loaderService.hideLoader()), shareReplay(1) ); - this.txnFields$ = this.expenseFieldsService.getAllMap().pipe(shareReplay(1)); + this.expenseFields$ = this.expenseFieldsService.getAllMap().pipe(shareReplay(1)); this.projectDependentCustomProperties$ = forkJoin({ - etxn: this.etxn$.pipe(take(1)), - txnFields: this.txnFields$.pipe(take(1)), + expense: this.expense$.pipe(take(1)), + expenseFields: this.expenseFields$.pipe(take(1)), }).pipe( - filter(({ etxn, txnFields }) => etxn.tx_custom_properties && txnFields.project_id?.length > 0), - switchMap(({ etxn, txnFields }) => + filter(({ expense, expenseFields }) => expense.custom_fields && expenseFields.project_id?.length > 0), + switchMap(({ expense, expenseFields }) => this.dependentFieldsService.getDependentFieldValuesForBaseField( - etxn.tx_custom_properties, - txnFields.project_id[0]?.id + expense.custom_fields, + expenseFields.project_id[0]?.id ) ) ); this.costCenterDependentCustomProperties$ = forkJoin({ - etxn: this.etxn$.pipe(take(1)), - txnFields: this.txnFields$.pipe(take(1)), + expense: this.expense$.pipe(take(1)), + expenseFields: this.expenseFields$.pipe(take(1)), }).pipe( - filter(({ etxn, txnFields }) => etxn.tx_custom_properties && txnFields.cost_center_id?.length > 0), - switchMap(({ etxn, txnFields }) => + filter(({ expense, expenseFields }) => expense.custom_fields && expenseFields.cost_center_id?.length > 0), + switchMap(({ expense, expenseFields }) => this.dependentFieldsService.getDependentFieldValuesForBaseField( - etxn.tx_custom_properties, - txnFields.cost_center_id[0]?.id + expense.custom_fields, + expenseFields.cost_center_id[0]?.id ) ), shareReplay(1) ); - this.etxn$.subscribe((etxn) => { - this.isSplitExpense = etxn.tx_split_group_id !== etxn.tx_id; + this.expense$.subscribe((expense) => { + this.isSplitExpense = expense.is_split; - if (etxn.tx_amount && etxn.tx_orig_amount) { - this.exchangeRate = etxn.tx_amount / etxn.tx_orig_amount; + if (expense.amount && expense.foreign_amount) { + this.exchangeRate = expense.amount / expense.foreign_amount; } - this.setPaymentModeandIcon(etxn); - - if (this.isCCCTransaction) { - this.matchingCCCTransaction$ = this.corporateCreditCardExpenseService - .getEccceByGroupId(etxn.tx_corporate_credit_card_expense_group_id) - .pipe( - map( - (matchedExpense: CCCExpUnflattened[]) => - matchedExpense[0] && (this.paymentModeIcon = 'fy-matched') && matchedExpense[0].ccce - ) - ); - this.matchingCCCTransaction$.subscribe((cardTxn) => { - this.cardNumber = cardTxn?.card_or_account_number; - }); + this.setPaymentModeandIcon(expense); + + if (this.isCCCTransaction && expense.matched_corporate_card_transactions[0]) { + this.paymentModeIcon = 'fy-matched'; + + const matchedCCCTransaction = expense.matched_corporate_card_transactions[0]; + this.cardNumber = matchedCCCTransaction.corporate_card_number; } - this.foreignCurrencySymbol = getCurrencySymbol(etxn.tx_orig_currency, 'wide'); - this.etxnCurrencySymbol = getCurrencySymbol(etxn.tx_currency, 'wide'); + this.foreignCurrencySymbol = getCurrencySymbol(expense.foreign_currency, 'wide'); + this.expenseCurrencySymbol = getCurrencySymbol(expense.currency, 'wide'); }); - forkJoin([this.txnFields$, this.etxn$.pipe(take(1))]) + forkJoin([this.expenseFields$, this.expense$.pipe(take(1))]) .pipe( - map(([expenseFieldsMap, etxn]) => { + map(([expenseFieldsMap, expense]) => { this.projectFieldName = expenseFieldsMap?.project_id[0]?.field_name; const isProjectMandatory = expenseFieldsMap?.project_id && expenseFieldsMap?.project_id[0]?.is_mandatory; - this.isProjectShown = this.orgSettings.projects?.enabled && (!!etxn.tx_project_name || isProjectMandatory); + this.isProjectShown = this.orgSettings.projects?.enabled && (!!expense.project?.name || isProjectMandatory); }) ) .subscribe(noop); - this.policyViloations$ = this.etxnWithoutCustomProperties$.pipe( - concatMap((etxn) => this.statusService.find('transactions', etxn.tx_id)), + this.policyViloations$ = this.expenseWithoutCustomProperties$.pipe( + concatMap((expense) => this.statusService.find('transactions', expense.id)), map((comments) => comments.filter(this.isPolicyComment)) ); - this.comments$ = this.statusService.find('transactions', txId); - this.view = this.activatedRoute.snapshot.params.view as ExpenseView; + this.comments$ = this.statusService.find('transactions', this.expenseId); - this.canFlagOrUnflag$ = this.etxnWithoutCustomProperties$.pipe( + this.canFlagOrUnflag$ = this.expenseWithoutCustomProperties$.pipe( filter(() => this.view === ExpenseView.team), - map( - (etxn) => - ['COMPLETE', 'POLICY_APPROVED', 'APPROVER_PENDING', 'APPROVED', 'PAYMENT_PENDING'].indexOf(etxn.tx_state) > -1 + map((expense) => + [ + ExpenseState.COMPLETE, + ExpenseState.APPROVER_PENDING, + ExpenseState.APPROVED, + ExpenseState.PAYMENT_PENDING, + ].includes(expense.state) ) ); - this.canDelete$ = this.etxnWithoutCustomProperties$.pipe( + this.canDelete$ = this.expenseWithoutCustomProperties$.pipe( filter(() => this.view === ExpenseView.team), - switchMap((etxn) => - this.reportService.getTeamReport(etxn.tx_report_id).pipe(map((report) => ({ report, etxn }))) + switchMap((expense) => + this.reportService.getTeamReport(expense.report_id).pipe(map((report) => ({ report, expense }))) ), - map(({ report, etxn }) => { - if (report?.rp_num_transactions === 1) { - return false; - } - return ['PAYMENT_PENDING', 'PAYMENT_PROCESSING', 'PAID'].indexOf(etxn.tx_state) < 0; - }) + map(({ report, expense }) => + report?.rp_num_transactions === 1 + ? false + : ![ExpenseState.PAYMENT_PENDING, ExpenseState.PAYMENT_PROCESSING, ExpenseState.PAID].includes(expense.state) + ) ); - this.isAmountCapped$ = this.etxn$.pipe( - map((etxn) => this.isNumber(etxn.tx_admin_amount) || this.isNumber(etxn.tx_policy_amount)) + this.isAmountCapped$ = this.expense$.pipe( + map((expense) => this.isNumber(expense.admin_amount) || this.isNumber(expense.policy_amount)) ); this.orgSettingsService.get().subscribe((orgSettings) => { @@ -392,24 +393,26 @@ export class ViewExpensePage { ) .subscribe(noop); - this.isCriticalPolicyViolated$ = this.etxn$.pipe( - map((etxn) => this.isNumber(etxn.tx_policy_amount) && etxn.tx_policy_amount < 0.0001) + this.isCriticalPolicyViolated$ = this.expense$.pipe( + map((expense) => this.isNumber(expense.policy_amount) && expense.policy_amount < 0.0001) ); - this.getPolicyDetails(txId); + this.getPolicyDetails(this.expenseId); - const editExpenseAttachments = this.etxn$.pipe( + const editExpenseAttachments = this.expense$.pipe( take(1), - switchMap((etxn) => this.fileService.findByTransactionId(etxn.tx_id)), - switchMap((fileObjs) => from(fileObjs)), - concatMap((fileObj: FileObject) => + switchMap((expense) => from(expense.files)), + concatMap((fileObj) => this.fileService.downloadUrl(fileObj.id).pipe( map((downloadUrl) => { - fileObj.url = downloadUrl; - const details = this.getReceiptDetails(fileObj); - fileObj.type = details.type; - fileObj.thumbnail = details.thumbnail; - return fileObj; + const details = this.fileService.getReceiptsDetails(fileObj.name, downloadUrl); + const fileObjWithDetails: FileObject = { + url: downloadUrl, + type: details.type, + thumbnail: details.thumbnail, + }; + + return fileObjWithDetails; }) ) ), @@ -423,46 +426,13 @@ export class ViewExpensePage { }); if (this.activatedRoute.snapshot.params.txnIds) { - const etxnIds = JSON.parse(this.activatedRoute.snapshot.params.txnIds as string) as string[]; - this.numEtxnsInReport = etxnIds.length; - this.activeEtxnIndex = parseInt(this.activatedRoute.snapshot.params.activeIndex as string, 10); + const expenseIds = JSON.parse(this.activatedRoute.snapshot.params.txnIds as string) as string[]; + this.reportExpenseCount = expenseIds.length; + this.activeExpenseIndex = parseInt(this.activatedRoute.snapshot.params.activeIndex as string, 10); } } - getReceiptExtension(name: string): string { - let res: string = null; - - if (name) { - const filename = name.toLowerCase(); - const idx = filename.lastIndexOf('.'); - - if (idx > -1) { - res = filename.substring(idx + 1, filename.length); - } - } - - return res; - } - - getReceiptDetails(file: FileObject): { type: string; thumbnail: string } { - const ext = this.getReceiptExtension(file.name); - const res = { - type: 'unknown', - thumbnail: 'img/fy-receipt.svg', - }; - - if (ext && ['pdf'].indexOf(ext) > -1) { - res.type = 'pdf'; - res.thumbnail = 'img/fy-pdf.svg'; - } else if (ext && ['png', 'jpg', 'jpeg', 'gif'].indexOf(ext) > -1) { - res.type = 'image'; - res.thumbnail = file.url; - } - - return res; - } - - getDeleteDialogProps(etxn: Expense): { + getDeleteDialogProps(): { component: typeof FyDeleteDialogComponent; cssClass: string; backdropDismiss: boolean; @@ -485,27 +455,23 @@ export class ViewExpensePage { infoMessage: 'The report amount will be adjusted accordingly.', ctaText: 'Remove', ctaLoadingText: 'Removing', - deleteMethod: (): Observable => this.reportService.removeTransaction(etxn.tx_report_id, etxn.tx_id), + deleteMethod: (): Observable => this.reportService.removeTransaction(this.reportId, this.expenseId), }, }; } async removeExpenseFromReport(): Promise { - const etxn = await this.transactionService.getEtxn(this.activatedRoute.snapshot.params.id as string).toPromise(); - const deletePopover = await this.popoverController.create(this.getDeleteDialogProps(etxn)); + const deletePopover = await this.popoverController.create(this.getDeleteDialogProps()); await deletePopover.present(); const { data } = (await deletePopover.onDidDismiss()) as { data: { status: string } }; if (data && data.status === 'success') { this.trackingService.expenseRemovedByApprover(); - this.router.navigate(['/', 'enterprise', 'view_team_report', { id: etxn.tx_report_id, navigate_back: true }]); + this.router.navigate(['/', 'enterprise', 'view_team_report', { id: this.reportId, navigate_back: true }]); } } async flagUnflagExpense(isExpenseFlagged: boolean): Promise { - const id = this.activatedRoute.snapshot.params.id as string; - const etxn = await this.transactionService.getEtxn(id).toPromise(); - const title = isExpenseFlagged ? 'Unflag' : 'Flag'; const flagUnflagModal = await this.popoverController.create({ component: FyPopoverComponent, @@ -526,12 +492,12 @@ export class ViewExpensePage { const comment = { comment: data.comment, }; - return this.statusService.post('transactions', etxn.tx_id, comment, true); + return this.statusService.post('transactions', this.expenseId, comment, true); }), concatMap(() => - etxn.tx_manual_flag - ? this.transactionService.manualUnflag(etxn.tx_id) - : this.transactionService.manualFlag(etxn.tx_id) + isExpenseFlagged + ? this.transactionService.manualUnflag(this.expenseId) + : this.transactionService.manualFlag(this.expenseId) ), finalize(() => { this.updateFlag$.next(null); diff --git a/src/app/fyle/view-mileage/view-mileage.page.html b/src/app/fyle/view-mileage/view-mileage.page.html index 0a5deac531..56c09706e5 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.html +++ b/src/app/fyle/view-mileage/view-mileage.page.html @@ -8,11 +8,11 @@ View Mileage -

    - Showing {{ activeEtxnIndex + 1 }} of {{ numEtxnsInReport }} expenses +

    + Showing {{ activeExpenseIndex + 1 }} of {{ reportExpenseCount }} expenses

    @@ -21,11 +21,11 @@ - + @@ -42,8 +42,8 @@ -
    - +
    + - Claimed amount {{ extendedMileage.tx_user_amount | currency:extendedMileage.tx_currency: 'symbol-narrow' }} was - capped to {{ extendedMileage.tx_amount | currency:extendedMileage.tx_currency: 'symbol-narrow'}} due to policy. + Claimed amount {{ expense.claim_amount | currency:expense.currency: 'symbol-narrow' }} was capped to {{ + expense.amount | currency:expense.currency: 'symbol-narrow'}} due to policy.
    @@ -63,18 +63,17 @@
    Mileage
    - {{extendedMileage.tx_distance || 0}} {{extendedMileage.tx_distance_unit | titlecase}} + {{expense.distance || 0}} {{expense.distance_unit | titlecase}}
    - {{ projectFieldName | titlecase }} : {{ extendedMileage.tx_project_name ? extendedMileage.tx_project_name : - 'Unspecified' }} + {{ projectFieldName | titlecase }} : {{ expense.project?.name || 'Unspecified' }}
    - {{ etxnCurrencySymbol }} + {{ expenseCurrencySymbol }} - {{ extendedMileage.tx_amount || 0 | humanizeCurrency: extendedMileage.tx_currency:true }} + {{ expense.amount || 0 | humanizeCurrency: expense.currency:true }} -
    - {{ extendedMileage.tx_state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }} +
    + {{ expense.state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }}
    - +
    @@ -122,7 +121,7 @@
    - +
    @@ -131,7 +130,7 @@
    Round Trip
    -
    {{ extendedMileage.tx_mileage_is_round_trip ? 'Yes' : 'No' }}
    +
    {{ expense.mileage_is_round_trip ? 'Yes' : 'No' }}
    @@ -148,7 +147,7 @@
    Report Name
    -
    {{ extendedMileage.rp_purpose }}
    +
    {{ expense.report?.title }}
    @@ -162,7 +161,7 @@
    Employee
    -
    {{ extendedMileage.us_full_name }}
    +
    {{ expense.user.full_name }}
    @@ -176,7 +175,7 @@
    Expense ID
    -
    {{ extendedMileage.tx_expense_number }}
    +
    {{ expense.seq_num }}
    @@ -198,12 +197,12 @@
    Created Date
    -
    {{ extendedMileage.tx_created_at | date: 'MMM dd, YYYY' }}
    +
    {{ expense.created_at | date: 'MMM dd, YYYY' }}
    - + @@ -216,7 +215,7 @@
    Spend Date
    -
    {{ extendedMileage.tx_txn_dt | date: 'MMM dd, YYYY' }}
    +
    {{ expense.spent_at | date: 'MMM dd, YYYY' }}
    @@ -241,7 +240,7 @@ - +
    @@ -250,7 +249,7 @@
    Purpose
    -
    {{ extendedMileage.tx_purpose }}
    +
    {{ expense.purpose }}
    @@ -258,7 +257,7 @@ @@ -270,7 +269,7 @@
    Subcategory
    -
    {{ extendedMileage.tx_sub_category }}
    +
    {{ expense.category.sub_category }}
    @@ -278,7 +277,7 @@ - + @@ -294,9 +293,8 @@
    Mileage Rate
    - {{ extendedMileage.tx_mileage_vehicle_type | mileageRateName }} ({{extendedMileage.tx_mileage_rate | - currency:extendedMileage.tx_currency: 'symbol-narrow'}} / {{extendedMileage.tx_distance_unit | - titlecase}}) + {{ mileageRate.vehicle_type | mileageRateName }} ({{ mileageRate.rate | currency:expense.currency: + 'symbol-narrow'}} / {{ mileageRate.unit | titlecase}})
    @@ -305,12 +303,9 @@
    - + - + @@ -320,14 +315,12 @@
    Claimed Distance
    -
    - {{extendedMileage.tx_distance}} {{extendedMileage.tx_distance_unit | titlecase}} -
    +
    {{expense.distance}} {{expense.distance_unit | titlecase}}
    - + @@ -338,7 +331,7 @@
    Calculated Distance
    - {{extendedMileage.tx_mileage_calculated_distance}} {{extendedMileage.tx_distance_unit | titlecase}} + {{expense.mileage_calculated_distance}} {{expense.distance_unit | titlecase}}
    @@ -347,7 +340,7 @@
    - + @@ -363,8 +356,7 @@
    Calculated Amount
    - {{extendedMileage.tx_mileage_calculated_amount | currency:extendedMileage.tx_currency: - 'symbol-narrow'}} + {{expense.mileage_calculated_amount | currency:expense.currency: 'symbol-narrow'}}
    @@ -375,10 +367,7 @@ - + @@ -393,12 +382,12 @@
    Cost Center
    -
    {{ extendedMileage.tx_cost_center_name }}
    +
    {{ expense.cost_center?.name }}
    - + @@ -411,7 +400,7 @@
    Cost Center Code
    -
    {{ extendedMileage.tx_cost_center_code }}
    +
    {{ expense.cost_center?.code }}
    @@ -423,12 +412,12 @@ - - + + @@ -437,13 +426,13 @@ - - + + @@ -471,8 +460,9 @@
    + diff --git a/src/app/fyle/view-mileage/view-mileage.page.spec.ts b/src/app/fyle/view-mileage/view-mileage.page.spec.ts index 400f04075d..2ba7550a31 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.spec.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.spec.ts @@ -18,7 +18,7 @@ import { PopoverController, ModalController } from '@ionic/angular'; import { ExpenseView } from 'src/app/core/models/expense-view.enum'; import { EventEmitter } from '@angular/core'; import { of } from 'rxjs'; -import { etxncData, expenseData1, expenseData2 } from 'src/app/core/mock-data/expense.data'; +import { expenseData1, expenseData2 } from 'src/app/core/mock-data/expense.data'; import { ViewCommentComponent } from 'src/app/shared/components/comments-history/view-comment/view-comment.component'; import { individualExpPolicyStateData3 } from 'src/app/core/mock-data/individual-expense-policy-state.data'; import { @@ -38,6 +38,14 @@ import { apiTeamRptSingleRes, expectedReports } from 'src/app/core/mock-data/api import { cloneDeep, slice } from 'lodash'; import { isEmpty } from 'rxjs/operators'; import { txnStatusData } from 'src/app/core/mock-data/transaction-status.data'; +import { mileageExpense } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { ExpenseState } from 'src/app/core/models/expense-state.enum'; +import { AccountType } from 'src/app/core/models/platform/v1/account.model'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { MileageRatesService } from 'src/app/core/services/mileage-rates.service'; +import { platformMileageRatesSingleData } from 'src/app/core/mock-data/platform-mileage-rate.data'; describe('ViewMileagePage', () => { let component: ViewMileagePage; @@ -58,16 +66,14 @@ describe('ViewMileagePage', () => { let orgSettingsService: jasmine.SpyObj; let dependentFieldsService: jasmine.SpyObj; let fileService: jasmine.SpyObj; + let spenderExpensesService: jasmine.SpyObj; + let approverExpensesService: jasmine.SpyObj; + let mileageRatesService: jasmine.SpyObj; let activateRouteMock: ActivatedRoute; beforeEach(waitForAsync(() => { const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['hideLoader', 'showLoader']); - const transactionServiceSpy = jasmine.createSpyObj('TransactionService', [ - 'getEtxn', - 'manualUnflag', - 'manualFlag', - 'getExpenseV2', - ]); + const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['manualUnflag', 'manualFlag']); const reportServiceSpy = jasmine.createSpyObj('ReportService', ['getTeamReport', 'removeTransaction']); const customInputsServiceSpy = jasmine.createSpyObj('CustomInputsService', [ 'getCustomPropertyDisplayValue', @@ -99,6 +105,12 @@ describe('ViewMileagePage', () => { 'getDependentFieldValuesForBaseField', ]); const fileServiceSpy = jasmine.createSpyObj('FileService', ['findByTransactionId', 'downloadUrl']); + const spenderExpensesServiceSpy = jasmine.createSpyObj('SpenderExpensesService', ['getExpenseById']); + const approverExpensesServiceSpy = jasmine.createSpyObj('ApproverExpensesService', ['getExpenseById']); + const mileageRatesServiceSpy = jasmine.createSpyObj('MileageRatesService', [ + 'getSpenderMileageRateById', + 'getApproverMileageRateById', + ]); TestBed.configureTestingModule({ declarations: [ViewMileagePage], @@ -168,6 +180,18 @@ describe('ViewMileagePage', () => { useValue: fileServiceSpy, provide: FileService, }, + { + useValue: spenderExpensesServiceSpy, + provide: SpenderExpensesService, + }, + { + useValue: approverExpensesServiceSpy, + provide: ApproverExpensesService, + }, + { + useValue: mileageRatesServiceSpy, + provide: MileageRatesService, + }, { provide: ActivatedRoute, useValue: { @@ -201,6 +225,9 @@ describe('ViewMileagePage', () => { orgSettingsService = TestBed.inject(OrgSettingsService) as jasmine.SpyObj; dependentFieldsService = TestBed.inject(DependentFieldsService) as jasmine.SpyObj; fileService = TestBed.inject(FileService) as jasmine.SpyObj; + spenderExpensesService = TestBed.inject(SpenderExpensesService) as jasmine.SpyObj; + approverExpensesService = TestBed.inject(ApproverExpensesService) as jasmine.SpyObj; + mileageRatesService = TestBed.inject(MileageRatesService) as jasmine.SpyObj; activateRouteMock = TestBed.inject(ActivatedRoute); fixture.detectChanges(); @@ -263,7 +290,6 @@ describe('ViewMileagePage', () => { describe('goBack', () => { it('should go to view team report if the expense is a team expense', () => { - component.reportId = 'rpWDg3QX3'; component.view = ExpenseView.team; component.goBack(); expect(router.navigate).toHaveBeenCalledOnceWith([ @@ -275,7 +301,6 @@ describe('ViewMileagePage', () => { }); it('should go to view report if the expense is an individual expense', () => { - component.reportId = 'rpJFg3Da4'; component.view = ExpenseView.individual; component.goBack(); expect(router.navigate).toHaveBeenCalledOnceWith([ @@ -290,7 +315,6 @@ describe('ViewMileagePage', () => { describe('openCommentsModal', () => { it('on opening the comments modal it should add a comment if the data is updated', fakeAsync(() => { component.view = ExpenseView.individual; - transactionService.getEtxn.and.returnValue(of(expenseData1)); const modalSpy = jasmine.createSpyObj('HTMLIonModalElement', ['present', 'onDidDismiss']); modalController.create.and.resolveTo(modalSpy); modalSpy.onDidDismiss.and.resolveTo({ data: { updated: true } } as any); @@ -300,11 +324,10 @@ describe('ViewMileagePage', () => { component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: expenseData1.tx_id, + objectId: component.expenseId, }, ...modalProperties.getModalDefaultProperties(), }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); expect(modalSpy.present).toHaveBeenCalledTimes(1); expect(modalSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.addComment).toHaveBeenCalledOnceWith({ view: 'Individual' }); @@ -312,7 +335,6 @@ describe('ViewMileagePage', () => { it('on opening the comments modal it should show the comments if the data not updated', fakeAsync(() => { component.view = ExpenseView.individual; - transactionService.getEtxn.and.returnValue(of(expenseData1)); const modalSpy = jasmine.createSpyObj('HTMLIonModalElement', ['present', 'onDidDismiss']); modalController.create.and.resolveTo(modalSpy); modalSpy.onDidDismiss.and.resolveTo({ data: { updated: false } } as any); @@ -322,11 +344,10 @@ describe('ViewMileagePage', () => { component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: expenseData1.tx_id, + objectId: component.expenseId, }, ...modalProperties.getModalDefaultProperties(), }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); expect(modalSpy.present).toHaveBeenCalledTimes(1); expect(modalSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.viewComment).toHaveBeenCalledOnceWith({ view: 'Individual' }); @@ -334,9 +355,9 @@ describe('ViewMileagePage', () => { }); it('getDeleteDialogProps(): should return the props', () => { - const props = component.getDeleteDialogProps(expenseData1); + const props = component.getDeleteDialogProps(); props.componentProps.deleteMethod(); - expect(reportService.removeTransaction).toHaveBeenCalledOnceWith(expenseData1.tx_report_id, expenseData1.tx_id); + expect(reportService.removeTransaction).toHaveBeenCalledOnceWith(component.reportId, component.expenseId); }); describe('removeExpenseFromReport', () => { @@ -344,7 +365,6 @@ describe('ViewMileagePage', () => { activateRouteMock.snapshot.params = { id: 'tx5fBcPBAxLv', }; - transactionService.getEtxn.and.returnValue(of(expenseData1)); spyOn(component, 'getDeleteDialogProps'); const deletePopoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present', 'onDidDismiss']); @@ -353,8 +373,7 @@ describe('ViewMileagePage', () => { component.removeExpenseFromReport(); tick(500); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); - expect(popoverController.create).toHaveBeenCalledOnceWith(component.getDeleteDialogProps(expenseData1)); + expect(popoverController.create).toHaveBeenCalledOnceWith(component.getDeleteDialogProps()); expect(deletePopoverSpy.present).toHaveBeenCalledTimes(1); expect(deletePopoverSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.expenseRemovedByApprover).toHaveBeenCalledTimes(1); @@ -362,7 +381,7 @@ describe('ViewMileagePage', () => { '/', 'enterprise', 'view_team_report', - { id: expenseData1.tx_report_id, navigate_back: true }, + { id: component.reportId, navigate_back: true }, ]); })); }); @@ -373,19 +392,6 @@ describe('ViewMileagePage', () => { id: 'tx5fBcPBAxLv', }; - const testComment = { - id: 'stjIdPp8BX8O', - created_at: '2022-11-17T06:07:38.590Z', - org_user_id: 'ouX8dwsbLCLv', - comment: 'This is a comment for flagging', - diff: null, - state: null, - transaction_id: null, - report_id: 'rpkpSa8guCuR', - advance_request_id: null, - }; - - transactionService.getEtxn.and.returnValue(of(expenseData1)); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); @@ -397,9 +403,7 @@ describe('ViewMileagePage', () => { statusService.post.and.returnValue(of(txnStatusData)); transactionService.manualFlag.and.returnValue(of(expenseData2)); - component.flagUnflagExpense(expenseData1.tx_manual_flag); - tick(500); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); + component.flagUnflagExpense(false); tick(500); expect(popoverController.create).toHaveBeenCalledOnceWith({ @@ -414,8 +418,8 @@ describe('ViewMileagePage', () => { expect(flagPopoverSpy.present).toHaveBeenCalledTimes(1); expect(flagPopoverSpy.onWillDismiss).toHaveBeenCalledTimes(1); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Please wait'); - expect(statusService.post).toHaveBeenCalledOnceWith('transactions', expenseData1.tx_id, data, true); - expect(transactionService.manualFlag).toHaveBeenCalledOnceWith(expenseData1.tx_id); + expect(statusService.post).toHaveBeenCalledOnceWith('transactions', component.expenseId, data, true); + expect(transactionService.manualFlag).toHaveBeenCalledOnceWith(component.expenseId); tick(500); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(trackingService.expenseFlagUnflagClicked).toHaveBeenCalledOnceWith({ action: title }); @@ -426,22 +430,6 @@ describe('ViewMileagePage', () => { id: 'tx5fBcPBAxLv', }; - const mockExpenseData = { - ...expenseData1, - tx_manual_flag: true, - }; - const testComment = { - id: 'stjIdPp8BX8O', - created_at: '2022-11-17T06:07:38.590Z', - org_user_id: 'ouX8dwsbLCLv', - comment: 'a comment', - diff: null, - state: null, - transaction_id: null, - report_id: 'rpkpSa8guCuR', - advance_request_id: null, - }; - transactionService.getEtxn.and.returnValue(of(mockExpenseData)); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); @@ -453,9 +441,7 @@ describe('ViewMileagePage', () => { statusService.post.and.returnValue(of(txnStatusData)); transactionService.manualUnflag.and.returnValue(of(expenseData1)); - component.flagUnflagExpense(mockExpenseData.tx_manual_flag); - tick(500); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); + component.flagUnflagExpense(true); tick(500); expect(popoverController.create).toHaveBeenCalledOnceWith({ @@ -470,8 +456,8 @@ describe('ViewMileagePage', () => { expect(flagPopoverSpy.present).toHaveBeenCalledTimes(1); expect(flagPopoverSpy.onWillDismiss).toHaveBeenCalledTimes(1); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Please wait'); - expect(statusService.post).toHaveBeenCalledOnceWith('transactions', mockExpenseData.tx_id, data, true); - expect(transactionService.manualUnflag).toHaveBeenCalledOnceWith(mockExpenseData.tx_id); + expect(statusService.post).toHaveBeenCalledOnceWith('transactions', component.expenseId, data, true); + expect(transactionService.manualUnflag).toHaveBeenCalledOnceWith(component.expenseId); tick(500); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(trackingService.expenseFlagUnflagClicked).toHaveBeenCalledOnceWith({ action: title }); @@ -480,7 +466,9 @@ describe('ViewMileagePage', () => { describe('ionViewWillEnter', () => { beforeEach(() => { - component.reportId = 'rpFvmTgyeBjN'; + component.expenseId = 'tx5fBcPBAxLv'; + component.reportId = 'rpynbzxa3psU'; + spyOn(component, 'setupNetworkWatcher'); spyOn(component, 'getPolicyDetails'); @@ -491,14 +479,15 @@ describe('ViewMileagePage', () => { activeIndex: '0', }; - component.extendedMileage$ = of(etxncData.data[0]); + component.mileageExpense$ = of(mileageExpense); component.view = activateRouteMock.snapshot.params.view; loaderService.showLoader.and.resolveTo(); - transactionService.getExpenseV2.and.returnValue(of(etxncData.data[0])); + spenderExpensesService.getExpenseById.and.returnValue(of(mileageExpense)); + approverExpensesService.getExpenseById.and.returnValue(of(mileageExpense)); loaderService.hideLoader.and.resolveTo(); expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse4)); - component.txnFields$ = of(expenseFieldsMapResponse4); + component.expenseFields$ = of(expenseFieldsMapResponse4); dependentFieldsService.getDependentFieldValuesForBaseField.and.returnValue(of(dependentFieldValues)); orgSettingsService.get.and.returnValue(of(orgSettingsGetData)); @@ -513,29 +502,29 @@ describe('ViewMileagePage', () => { tick(500); expect(component.setupNetworkWatcher).toHaveBeenCalledTimes(1); - component.extendedMileage$.subscribe((data) => { - expect(data).toEqual(etxncData.data[0]); + component.mileageExpense$.subscribe((data) => { + expect(data).toEqual(mileageExpense); expect(loaderService.showLoader).toHaveBeenCalledTimes(1); - expect(transactionService.getExpenseV2).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); + expect(spenderExpensesService.getExpenseById).toHaveBeenCalledOnceWith(activateRouteMock.snapshot.params.id); expect(component.updateFlag$.next).toHaveBeenCalledOnceWith(null); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); }); - component.txnFields$.subscribe((data) => { + component.expenseFields$.subscribe((data) => { expect(data).toEqual(expenseFieldsMapResponse4); expect(expenseFieldsService.getAllMap).toHaveBeenCalledTimes(1); }); })); it('should get the project dependent custom properties', (done) => { - const customProps = etxncData.data[0].tx_custom_properties; + const customProps = mileageExpense.custom_fields; const projectIdNumber = expenseFieldsMapResponse4.project_id[0].id; - component.txnFields$ = of(expenseFieldsMapResponse4); + component.expenseFields$ = of(expenseFieldsMapResponse4); component.ionViewWillEnter(); component.projectDependentCustomProperties$.subscribe((data) => { expect(data).toEqual(dependentFieldValues); - expect(etxncData.data[0].tx_custom_properties).toBeDefined(); + expect(mileageExpense.custom_fields).toBeDefined(); expect(expenseFieldsMapResponse4.project_id.length).toBeGreaterThan(0); expect(dependentFieldsService.getDependentFieldValuesForBaseField).toHaveBeenCalledOnceWith( customProps, @@ -546,12 +535,12 @@ describe('ViewMileagePage', () => { }); it('should get the cost center dependent custom properties', (done) => { - const customProps = etxncData.data[0].tx_custom_properties; + const customProps = mileageExpense.custom_fields; const costCenterIdNumber = expenseFieldsMapResponse4.cost_center_id[0].id; component.ionViewWillEnter(); component.costCenterDependentCustomProperties$.subscribe((data) => { expect(data).toEqual(dependentFieldValues); - expect(etxncData.data[0].tx_custom_properties).toBeDefined(); + expect(mileageExpense.custom_fields).toBeDefined(); expect(expenseFieldsMapResponse4.cost_center_id.length).toBeGreaterThan(0); expect(dependentFieldsService.getDependentFieldValuesForBaseField).toHaveBeenCalledOnceWith( customProps, @@ -562,17 +551,20 @@ describe('ViewMileagePage', () => { }); it('should set the correct report id and set proper payment mode and icon', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - source_account_type: 'PERSONAL_ADVANCE_ACCOUNT', + const mockMileageExpense: Expense = { + ...mileageExpense, + source_account: { + ...mileageExpense.source_account, + type: AccountType.PERSONAL_ADVANCE_ACCOUNT, + }, }; - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); component.ionViewWillEnter(); - component.extendedMileage$.subscribe((data) => { - expect(data).toEqual(mockExtMileageData); - expect(component.reportId).toEqual(mockExtMileageData.tx_report_id); + component.mileageExpense$.subscribe((data) => { + expect(data).toEqual(mockMileageExpense); + expect(component.reportId).toEqual(mockMileageExpense.report_id); expect(component.paymentMode).toEqual('Paid from Advance'); expect(component.paymentModeIcon).toEqual('fy-non-reimbursable'); done(); @@ -580,17 +572,17 @@ describe('ViewMileagePage', () => { }); it('should set the correct payment mode and icon when reimbursement is skipped', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_skip_reimbursement: true, + const mockMileageExpense: Expense = { + ...mileageExpense, + is_reimbursable: false, }; - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); component.ionViewWillEnter(); - component.extendedMileage$.subscribe((data) => { - expect(data).toEqual(mockExtMileageData); - expect(component.reportId).toEqual(mockExtMileageData.tx_report_id); + component.mileageExpense$.subscribe((data) => { + expect(data).toEqual(mockMileageExpense); + expect(component.reportId).toEqual(mockMileageExpense.report_id); expect(component.paymentMode).toEqual('Paid by Company'); expect(component.paymentModeIcon).toEqual('fy-non-reimbursable'); done(); @@ -599,8 +591,8 @@ describe('ViewMileagePage', () => { it('should set the correct payment mode and icon when the expense is reimbursement', (done) => { component.ionViewWillEnter(); - component.extendedMileage$.subscribe((data) => { - expect(data).toEqual(etxncData.data[0]); + component.mileageExpense$.subscribe((data) => { + expect(data).toEqual(mileageExpense); expect(component.paymentMode).toEqual('Paid by Employee'); expect(component.paymentModeIcon).toEqual('fy-reimbursable'); done(); @@ -608,45 +600,57 @@ describe('ViewMileagePage', () => { }); it('should set the vehicle type to car if the mileage_vehicle type has the word four in it', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_mileage_vehicle_type: 'Four Wheeler - Type 1 (₹11.00/km)', + const mockMileageExpense: Expense = { + ...mileageExpense, + mileage_rate: { + ...mileageExpense.mileage_rate, + vehicle_type: 'Four Wheeler - Type 1 (₹11.00/km)', + }, }; - component.extendedMileage$ = of(mockExtMileageData); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); + + component.mileageExpense$ = of(mockMileageExpense); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); component.ionViewWillEnter(); - component.extendedMileage$.subscribe((data) => { - expect(data).toEqual(mockExtMileageData); + component.mileageExpense$.subscribe((data) => { + expect(data).toEqual(mockMileageExpense); expect(component.vehicleType).toEqual('car'); done(); }); }); it('should set the vehicle type to car if the mileage_vehicle type has the word car in it', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_mileage_vehicle_type: 'Electric Car', + const mockMileageExpense: Expense = { + ...mileageExpense, + mileage_rate: { + ...mileageExpense.mileage_rate, + vehicle_type: 'Electric Car', + }, }; - component.extendedMileage$ = of(mockExtMileageData); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); + + component.mileageExpense$ = of(mockMileageExpense); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.ionViewWillEnter(); - component.extendedMileage$.subscribe((data) => { - expect(data).toEqual(mockExtMileageData); + component.mileageExpense$.subscribe((data) => { + expect(data).toEqual(mockMileageExpense); expect(component.vehicleType).toEqual('car'); done(); }); }); it('should set the vehicle type to scooter if the mileage_vehicle type has neither of htese words - car or four', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_mileage_vehicle_type: 'Two Wheeler - Type 1 (₹11.00/km)', + const mockMileageExpense: Expense = { + ...mileageExpense, + mileage_rate: { + ...mileageExpense.mileage_rate, + vehicle_type: 'Two Wheeler - Type 1 (₹11.00/km)', + }, }; - component.extendedMileage$ = of(mockExtMileageData); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); + component.mileageExpense$ = of(mockMileageExpense); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); component.ionViewWillEnter(); - component.extendedMileage$.subscribe((data) => { - expect(data).toEqual(mockExtMileageData); + component.mileageExpense$.subscribe((data) => { + expect(data).toEqual(mockMileageExpense); expect(component.vehicleType).toEqual('scooter'); done(); }); @@ -654,9 +658,9 @@ describe('ViewMileagePage', () => { it('should get the correct currency symbol', (done) => { component.ionViewWillEnter(); - component.extendedMileage$.subscribe((data) => { - expect(data.tx_currency).toEqual('USD'); - expect(component.etxnCurrencySymbol).toEqual('$'); + component.mileageExpense$.subscribe((data) => { + expect(data.currency).toEqual('USD'); + expect(component.expenseCurrencySymbol).toEqual('$'); done(); }); }); @@ -667,10 +671,10 @@ describe('ViewMileagePage', () => { project_id: [], }; - transactionService.getExpenseV2.and.returnValue(of(etxncData.data[0])); - component.extendedMileage$ = of(etxncData.data[0]); + spenderExpensesService.getExpenseById.and.returnValue(of(mileageExpense)); + component.mileageExpense$ = of(mileageExpense); expenseFieldsService.getAllMap.and.returnValue(of(mockExpFieldData)); - component.txnFields$ = of(mockExpFieldData); + component.expenseFields$ = of(mockExpFieldData); orgSettingsService.get.and.returnValue(of(orgSettingsGetData)); component.ionViewWillEnter(); @@ -680,13 +684,13 @@ describe('ViewMileagePage', () => { expect(orgSettingsService.get).toHaveBeenCalledTimes(1); })); - it('should get the project details when project name is not present', fakeAsync(() => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_project_name: null, + it('should show the project details when project is not present but mandatory', fakeAsync(() => { + const mockMileageExpense: Expense = { + ...mileageExpense, + project: null, }; - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); component.ionViewWillEnter(); tick(500); @@ -699,10 +703,10 @@ describe('ViewMileagePage', () => { ...orgSettingsGetData, projects: null, }; - transactionService.getExpenseV2.and.returnValue(of(etxncData.data[0])); - component.extendedMileage$ = of(etxncData.data[0]); + spenderExpensesService.getExpenseById.and.returnValue(of(mileageExpense)); + component.mileageExpense$ = of(mileageExpense); expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse)); - component.txnFields$ = of(expenseFieldsMapResponse); + component.expenseFields$ = of(expenseFieldsMapResponse); orgSettingsService.get.and.returnValue(of(mockOrgSettData)); component.ionViewWillEnter(); tick(500); @@ -710,8 +714,8 @@ describe('ViewMileagePage', () => { })); it('should get the project field name and the value of project field name should be truthy', fakeAsync(() => { - transactionService.getExpenseV2.and.returnValue(of(etxncData.data[0])); - component.extendedMileage$ = of(etxncData.data[0]); + spenderExpensesService.getExpenseById.and.returnValue(of(mileageExpense)); + component.mileageExpense$ = of(mileageExpense); const mockExpFieldData = { ...expenseFieldsMapResponse4, project_id: [ @@ -722,7 +726,7 @@ describe('ViewMileagePage', () => { ], }; expenseFieldsService.getAllMap.and.returnValue(of(mockExpFieldData)); - component.txnFields$ = of(mockExpFieldData); + component.expenseFields$ = of(mockExpFieldData); orgSettingsService.get.and.returnValue(of(orgSettingsGetData)); component.ionViewWillEnter(); tick(500); @@ -763,7 +767,7 @@ describe('ViewMileagePage', () => { expect(orgSettingsService.get).toHaveBeenCalledTimes(1); }); - it('should get the custom mileage fileds', (done) => { + it('should get the custom mileage fields', (done) => { const mockfilledCustomProperties = cloneDeep(slice(filledCustomProperties, 0, 1)); customInputsService.fillCustomProperties.and.returnValue(of(mockfilledCustomProperties)); customInputsService.getCustomPropertyDisplayValue.and.returnValue(mockfilledCustomProperties[0].displayValue); @@ -771,8 +775,8 @@ describe('ViewMileagePage', () => { component.mileageCustomFields$.subscribe((data) => { expect(data).toEqual(mockfilledCustomProperties); expect(customInputsService.fillCustomProperties).toHaveBeenCalledOnceWith( - etxncData.data[0].tx_org_category_id, - etxncData.data[0].tx_custom_properties, + mileageExpense.category_id, + mileageExpense.custom_fields, true ); expect(customInputsService.getCustomPropertyDisplayValue).toHaveBeenCalledTimes( @@ -782,12 +786,37 @@ describe('ViewMileagePage', () => { }); }); + it('should get the mileage rate for spenders', (done) => { + mileageRatesService.getSpenderMileageRateById.and.returnValue(of(platformMileageRatesSingleData.data[0])); + component.mileageExpense$ = of(mileageExpense); + component.ionViewWillEnter(); + + component.mileageRate$.subscribe((mileageRate) => { + expect(mileageRate).toEqual(platformMileageRatesSingleData.data[0]); + expect(mileageRatesService.getSpenderMileageRateById).toHaveBeenCalledOnceWith(mileageExpense.mileage_rate_id); + done(); + }); + }); + + it('should get the mileage rate for approvers', (done) => { + mileageRatesService.getApproverMileageRateById.and.returnValue(of(platformMileageRatesSingleData.data[0])); + component.mileageExpense$ = of(mileageExpense); + activateRouteMock.snapshot.params.view = ExpenseView.team; + component.ionViewWillEnter(); + + component.mileageRate$.subscribe((mileageRate) => { + expect(mileageRate).toEqual(platformMileageRatesSingleData.data[0]); + expect(mileageRatesService.getApproverMileageRateById).toHaveBeenCalledOnceWith(mileageExpense.mileage_rate_id); + done(); + }); + }); + it('should get the flag status when the expense can be flagged', (done) => { activateRouteMock.snapshot.params.view = ExpenseView.team; - component.extendedMileage$ = of(etxncData.data[0]); + component.mileageExpense$ = of(mileageExpense); component.ionViewWillEnter(); component.canFlagOrUnflag$.subscribe((res) => { - expect(etxncData.data[0].tx_state).toEqual('APPROVER_PENDING'); + expect(mileageExpense.state).toEqual(ExpenseState.APPROVER_PENDING); expect(res).toBeTrue(); done(); }); @@ -795,11 +824,11 @@ describe('ViewMileagePage', () => { it('expense cannot be flagged when the view is set to indivivual', (done) => { activateRouteMock.snapshot.params.view = ExpenseView.individual; - const mockExtMileageData = { - ...etxncData.data[0], - tx_state: 'PAID', + const mockMileageExpense: Expense = { + ...mileageExpense, + state: ExpenseState.PAID, }; - component.extendedMileage$ = of(mockExtMileageData); + component.mileageExpense$ = of(mockMileageExpense); component.ionViewWillEnter(); component.canFlagOrUnflag$.pipe(isEmpty()).subscribe((isEmpty) => { expect(isEmpty).toBeTrue(); @@ -808,58 +837,58 @@ describe('ViewMileagePage', () => { }); it('should return false if there is only one transaction in the report and the state is PAID', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_state: 'PAID', - tx_report_id: 'rphNNUiCISkD', - tx_custom_properties: null, + const mockMileageExpense: Expense = { + ...mileageExpense, + state: ExpenseState.PAID, + report_id: 'rphNNUiCISkD', + custom_fields: null, }; reportService.getTeamReport.and.returnValue(of(apiTeamRptSingleRes.data[0])); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); - component.txnFields$ = of(expenseFieldsMapResponse4); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); + component.expenseFields$ = of(expenseFieldsMapResponse4); activateRouteMock.snapshot.params.view = ExpenseView.team; component.ionViewWillEnter(); component.canDelete$.subscribe((res) => { - expect(mockExtMileageData.tx_state).toEqual('PAID'); + expect(mockMileageExpense.state).toEqual(ExpenseState.PAID); expect(res).toBeFalse(); done(); }); }); it('should return true if the transaction state is APPROVER_PENDING and there are more than one transactions in the report', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_state: 'APPROVER_PENDING', - tx_report_id: 'rphNNUiCISkD', - tx_custom_properties: null, + const mockMileageExpense: Expense = { + ...mileageExpense, + state: ExpenseState.APPROVER_PENDING, + report_id: 'rphNNUiCISkD', + custom_fields: null, }; reportService.getTeamReport.and.returnValue(of(expectedReports.data[3])); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); - component.txnFields$ = of(expenseFieldsMapResponse4); + approverExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); + component.expenseFields$ = of(expenseFieldsMapResponse4); activateRouteMock.snapshot.params.view = ExpenseView.team; component.ionViewWillEnter(); component.canDelete$.subscribe((res) => { - expect(mockExtMileageData.tx_state).toEqual('APPROVER_PENDING'); + expect(mockMileageExpense.state).toEqual(ExpenseState.APPROVER_PENDING); expect(res).toBeTrue(); done(); }); }); it('should not delete expense when view is individual', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_state: 'APPROVER_PENDING', - tx_report_id: 'rphNNUiCISkD', - tx_custom_properties: null, + const mockMileageExpense: Expense = { + ...mileageExpense, + state: ExpenseState.APPROVER_PENDING, + report_id: 'rphNNUiCISkD', + custom_fields: null, }; reportService.getTeamReport.and.returnValue(of(expectedReports.data[3])); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); - component.txnFields$ = of(expenseFieldsMapResponse4); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); + component.expenseFields$ = of(expenseFieldsMapResponse4); component.view = ExpenseView.individual; component.ionViewWillEnter(); @@ -917,7 +946,7 @@ describe('ViewMileagePage', () => { }; component.ionViewWillEnter(); component.comments$.subscribe(() => { - expect(statusService.find).toHaveBeenCalledOnceWith('transactions', expenseData1.tx_id); + expect(statusService.find).toHaveBeenCalledOnceWith('transactions', component.expenseId); done(); }); expect(component.view).toEqual(activateRouteMock.snapshot.params.view); @@ -925,13 +954,13 @@ describe('ViewMileagePage', () => { it('should be true if expense policy is violated', (done) => { spyOn(component, 'isNumber').and.returnValue(true); - const mockExtMileageData = { - ...etxncData.data[0], - tx_policy_amount: -1, + const mockMileageExpense: Expense = { + ...mileageExpense, + policy_amount: -1, }; - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); component.ionViewWillEnter(); component.isCriticalPolicyViolated$.subscribe((res) => { expect(res).toBeTrue(); @@ -941,16 +970,16 @@ describe('ViewMileagePage', () => { }); it('should return true if the policy amount value is of type number should check if the amount is capped', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_policy_amount: 1000, - tx_admin_amount: null, + const mockMileageExpense: Expense = { + ...mileageExpense, + policy_amount: 1000, + admin_amount: null, }; spyOn(component, 'isNumber').and.callThrough(); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); component.ionViewWillEnter(); component.isAmountCapped$.subscribe((res) => { expect(res).toBeTrue(); @@ -962,16 +991,16 @@ describe('ViewMileagePage', () => { }); it('should return true if the admin amount value is of type number should check if the amount is capped', (done) => { - const mockExtMileageData = { - ...etxncData.data[0], - tx_admin_amount: 1000, - tx_policy_amount: null, + const mockMileageExpense: Expense = { + ...mileageExpense, + admin_amount: 1000, + policy_amount: null, }; spyOn(component, 'isNumber').and.callThrough(); - transactionService.getExpenseV2.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); component.ionViewWillEnter(); component.isAmountCapped$.subscribe((res) => { expect(res).toBeTrue(); @@ -982,14 +1011,14 @@ describe('ViewMileagePage', () => { it('should return false if the value is not of type number and check if the expense is capped', (done) => { spyOn(component, 'isNumber').and.returnValue(false); - const mockExtMileageData = { - ...etxncData.data[0], - tx_admin_amount: null, - tx_policy_amount: null, + const mockMileageExpense: Expense = { + ...mileageExpense, + admin_amount: null, + policy_amount: null, }; - transactionService.getEtxn.and.returnValue(of(mockExtMileageData)); - component.extendedMileage$ = of(mockExtMileageData); + spenderExpensesService.getExpenseById.and.returnValue(of(mockMileageExpense)); + component.mileageExpense$ = of(mockMileageExpense); component.ionViewWillEnter(); component.isAmountCapped$.subscribe((res) => { expect(res).toBeFalse(); @@ -1009,8 +1038,8 @@ describe('ViewMileagePage', () => { }; component.ionViewWillEnter(); expect(component.updateFlag$.next).toHaveBeenCalledOnceWith(null); - expect(component.numEtxnsInReport).toEqual(3); - expect(component.activeEtxnIndex).toEqual(2); + expect(component.reportExpenseCount).toEqual(3); + expect(component.activeExpenseIndex).toEqual(2); }); }); diff --git a/src/app/fyle/view-mileage/view-mileage.page.ts b/src/app/fyle/view-mileage/view-mileage.page.ts index a20cc7f53f..30d5448ada 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.ts @@ -1,6 +1,5 @@ import { Component, EventEmitter, ViewChild, ElementRef } from '@angular/core'; import { Observable, from, Subject, concat, noop, of, forkJoin } from 'rxjs'; -import { Expense } from 'src/app/core/models/expense.model'; import { CustomField } from 'src/app/core/models/custom_field.model'; import { ActivatedRoute, Router } from '@angular/router'; import { LoaderService } from 'src/app/core/services/loader.service'; @@ -20,7 +19,6 @@ import { FyPopoverComponent } from 'src/app/shared/components/fy-popover/fy-popo import { getCurrencySymbol } from '@angular/common'; import { ExpenseView } from 'src/app/core/models/expense-view.enum'; import { ExtendedStatus } from 'src/app/core/models/extended_status.model'; -import { AccountType } from 'src/app/core/enums/account-type.enum'; import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { ExpenseField } from 'src/app/core/models/v1/expense-field.model'; @@ -32,6 +30,13 @@ import { OrgSettings } from 'src/app/core/models/org-settings.model'; import { IndividualExpensePolicyState } from 'src/app/core/models/platform/platform-individual-expense-policy-state.model'; import { CustomInput } from 'src/app/core/models/custom-input.model'; import { ExpenseDeletePopoverParams } from 'src/app/core/models/expense-delete-popover-params.model'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { AccountType } from 'src/app/core/models/platform/v1/account.model'; +import { ExpenseState } from 'src/app/core/models/expense-state.enum'; +import { MileageRatesService } from 'src/app/core/services/mileage-rates.service'; +import { PlatformMileageRates } from 'src/app/core/models/platform/platform-mileage-rates.model'; @Component({ selector: 'app-view-mileage', @@ -41,7 +46,7 @@ import { ExpenseDeletePopoverParams } from 'src/app/core/models/expense-delete-p export class ViewMileagePage { @ViewChild('comments') commentsContainer: ElementRef; - extendedMileage$: Observable; + mileageExpense$: Observable; orgSettings: OrgSettings; @@ -59,6 +64,8 @@ export class ViewMileagePage { updateFlag$ = new Subject(); + expenseId: string; + reportId: string; policyDetails: IndividualExpensePolicyState[] | []; @@ -71,15 +78,15 @@ export class ViewMileagePage { isDeviceWidthSmall = window.innerWidth < 330; - numEtxnsInReport: number; + reportExpenseCount: number; - activeEtxnIndex: number; + activeExpenseIndex: number; paymentMode: string; paymentModeIcon: string; - etxnCurrencySymbol: string; + expenseCurrencySymbol: string; vehicleType: string; @@ -91,7 +98,7 @@ export class ViewMileagePage { isNewReportsFlowEnabled = false; - txnFields$: Observable<{ [key: string]: ExpenseField[] }>; + expenseFields$: Observable<{ [key: string]: ExpenseField[] }>; projectDependentCustomProperties$: Observable[]>; @@ -99,6 +106,8 @@ export class ViewMileagePage { mapAttachment$: Observable; + mileageRate$: Observable; + constructor( private activatedRoute: ActivatedRoute, private loaderService: LoaderService, @@ -116,7 +125,10 @@ export class ViewMileagePage { private expenseFieldsService: ExpenseFieldsService, private orgSettingsService: OrgSettingsService, private dependentFieldsService: DependentFieldsService, - private fileService: FileService + private fileService: FileService, + private approverExpensesService: ApproverExpensesService, + private spenderExpensesService: SpenderExpensesService, + private mileageRatesService: MileageRatesService ) {} get ExpenseView(): typeof ExpenseView { @@ -173,12 +185,11 @@ export class ViewMileagePage { } async openCommentsModal(): Promise { - const etxn = await this.transactionService.getEtxn(this.activatedRoute.snapshot.params.id as string).toPromise(); const modal = await this.modalController.create({ component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: etxn.tx_id, + objectId: this.expenseId, }, ...this.modalProperties.getModalDefaultProperties(), }); @@ -193,7 +204,7 @@ export class ViewMileagePage { } } - getDeleteDialogProps(etxn: Expense): ExpenseDeletePopoverParams { + getDeleteDialogProps(): ExpenseDeletePopoverParams { return { component: FyDeleteDialogComponent, cssClass: 'delete-dialog', @@ -204,29 +215,24 @@ export class ViewMileagePage { infoMessage: 'The report amount will be adjusted accordingly.', ctaText: 'Remove', ctaLoadingText: 'Removing', - deleteMethod: (): Observable => this.reportService.removeTransaction(etxn.tx_report_id, etxn.tx_id), + deleteMethod: (): Observable => this.reportService.removeTransaction(this.reportId, this.expenseId), }, }; } async removeExpenseFromReport(): Promise { - const etxn = await this.transactionService.getEtxn(this.activatedRoute.snapshot.params.id as string).toPromise(); - - const deletePopover = await this.popoverController.create(this.getDeleteDialogProps(etxn)); + const deletePopover = await this.popoverController.create(this.getDeleteDialogProps()); await deletePopover.present(); const { data } = (await deletePopover.onDidDismiss()) as { data: { status: string } }; if (data && data.status === 'success') { this.trackingService.expenseRemovedByApprover(); - this.router.navigate(['/', 'enterprise', 'view_team_report', { id: etxn.tx_report_id, navigate_back: true }]); + this.router.navigate(['/', 'enterprise', 'view_team_report', { id: this.reportId, navigate_back: true }]); } } async flagUnflagExpense(isExpenseFlagged: boolean): Promise { - const id = this.activatedRoute.snapshot.params.id as string; - const etxn = await this.transactionService.getEtxn(id).toPromise(); - const title = isExpenseFlagged ? 'Unflag' : 'Flag'; const flagUnflagModal = await this.popoverController.create({ component: FyPopoverComponent, @@ -247,12 +253,12 @@ export class ViewMileagePage { const comment = { comment: data.comment, }; - return this.statusService.post('transactions', etxn.tx_id, comment, true); + return this.statusService.post('transactions', this.expenseId, comment, true); }), concatMap(() => - etxn.tx_manual_flag - ? this.transactionService.manualUnflag(etxn.tx_id) - : this.transactionService.manualFlag(etxn.tx_id) + isExpenseFlagged + ? this.transactionService.manualUnflag(this.expenseId) + : this.transactionService.manualFlag(this.expenseId) ), finalize(() => { this.updateFlag$.next(null); @@ -266,75 +272,80 @@ export class ViewMileagePage { ionViewWillEnter(): void { this.setupNetworkWatcher(); - const id = this.activatedRoute.snapshot.params.id as string; - this.extendedMileage$ = this.updateFlag$.pipe( + this.expenseId = this.activatedRoute.snapshot.params.id as string; + this.view = this.activatedRoute.snapshot.params.view as ExpenseView; + + this.mileageExpense$ = this.updateFlag$.pipe( switchMap(() => - from(this.loaderService.showLoader()).pipe(switchMap(() => this.transactionService.getExpenseV2(id))) + from(this.loaderService.showLoader()).pipe( + switchMap(() => + this.view === ExpenseView.team + ? this.approverExpensesService.getExpenseById(this.expenseId) + : this.spenderExpensesService.getExpenseById(this.expenseId) + ) + ) ), finalize(() => from(this.loaderService.hideLoader())), shareReplay(1) ); - this.mapAttachment$ = this.extendedMileage$.pipe( + this.mapAttachment$ = this.mileageExpense$.pipe( take(1), - switchMap((etxn) => this.fileService.findByTransactionId(etxn.tx_id)), + map((expense) => expense.files), map((fileObjs) => fileObjs[0]), concatMap((fileObj) => this.fileService.downloadUrl(fileObj.id).pipe( map((downloadUrl) => { - const details = this.fileService.getReceiptsDetails(fileObj); - - fileObj.url = downloadUrl; - fileObj.type = details.type; - fileObj.thumbnail = details.thumbnail; + const details = this.fileService.getReceiptsDetails(fileObj.name, downloadUrl); + const fileObjWithDetails: FileObject = { + url: downloadUrl, + type: details.type, + thumbnail: details.thumbnail, + }; - return fileObj; + return fileObjWithDetails; }) ) ) ); - this.txnFields$ = this.expenseFieldsService.getAllMap().pipe(shareReplay(1)); + this.expenseFields$ = this.expenseFieldsService.getAllMap().pipe(shareReplay(1)); this.projectDependentCustomProperties$ = forkJoin({ - extendedMileage: this.extendedMileage$.pipe(take(1)), - txnFields: this.txnFields$.pipe(take(1)), + expense: this.mileageExpense$.pipe(take(1)), + expenseFields: this.expenseFields$.pipe(take(1)), }).pipe( - filter( - ({ extendedMileage, txnFields }) => extendedMileage.tx_custom_properties && txnFields.project_id?.length > 0 - ), - switchMap(({ extendedMileage, txnFields }) => + filter(({ expense, expenseFields }) => expense.custom_fields && expenseFields.project_id?.length > 0), + switchMap(({ expense, expenseFields }) => this.dependentFieldsService.getDependentFieldValuesForBaseField( - extendedMileage.tx_custom_properties, - txnFields.project_id[0]?.id + expense.custom_fields, + expenseFields.project_id[0]?.id ) ) ); this.costCenterDependentCustomProperties$ = forkJoin({ - extendedMileage: this.extendedMileage$.pipe(take(1)), - txnFields: this.txnFields$.pipe(take(1)), + expense: this.mileageExpense$.pipe(take(1)), + expenseFields: this.expenseFields$.pipe(take(1)), }).pipe( - filter( - ({ extendedMileage, txnFields }) => extendedMileage.tx_custom_properties && txnFields.cost_center_id?.length > 0 - ), - switchMap(({ extendedMileage, txnFields }) => + filter(({ expense, expenseFields }) => expense.custom_fields && expenseFields.cost_center_id?.length > 0), + switchMap(({ expense, expenseFields }) => this.dependentFieldsService.getDependentFieldValuesForBaseField( - extendedMileage.tx_custom_properties, - txnFields.cost_center_id[0]?.id + expense.custom_fields, + expenseFields.cost_center_id[0]?.id ) ), shareReplay(1) ); - this.extendedMileage$.subscribe((extendedMileage) => { - this.reportId = extendedMileage.tx_report_id; + this.mileageExpense$.subscribe((expense) => { + this.reportId = expense.report_id; - if (extendedMileage.source_account_type === AccountType.ADVANCE) { + if (expense.source_account.type === AccountType.PERSONAL_ADVANCE_ACCOUNT) { this.paymentMode = 'Paid from Advance'; this.paymentModeIcon = 'fy-non-reimbursable'; - } else if (extendedMileage.tx_skip_reimbursement) { + } else if (!expense.is_reimbursable) { this.paymentMode = 'Paid by Company'; this.paymentModeIcon = 'fy-non-reimbursable'; } else { @@ -342,25 +353,20 @@ export class ViewMileagePage { this.paymentModeIcon = 'fy-reimbursable'; } - if ( - extendedMileage.tx_mileage_vehicle_type?.toLowerCase().indexOf('four') > -1 || - extendedMileage.tx_mileage_vehicle_type?.toLowerCase().indexOf('car') > -1 - ) { - this.vehicleType = 'car'; - } else { - this.vehicleType = 'scooter'; + if (expense.mileage_rate) { + const vehicleType = expense.mileage_rate.vehicle_type.toLowerCase(); + this.vehicleType = vehicleType.includes('four') || vehicleType.includes('car') ? 'car' : 'scooter'; } - this.etxnCurrencySymbol = getCurrencySymbol(extendedMileage.tx_currency, 'wide'); + this.expenseCurrencySymbol = getCurrencySymbol(expense.currency, 'wide'); }); - forkJoin([this.txnFields$, this.extendedMileage$.pipe(take(1))]) + forkJoin([this.expenseFields$, this.mileageExpense$.pipe(take(1))]) .pipe( - map(([expenseFieldsMap, extendedMileage]) => { + map(([expenseFieldsMap, expense]) => { this.projectFieldName = expenseFieldsMap?.project_id && expenseFieldsMap?.project_id[0]?.field_name; const isProjectMandatory = expenseFieldsMap?.project_id && expenseFieldsMap?.project_id[0]?.is_mandatory; - this.isProjectShown = - this.orgSettings?.projects?.enabled && (!!extendedMileage.tx_project_name || isProjectMandatory); + this.isProjectShown = this.orgSettings?.projects?.enabled && (!!expense.project?.name || isProjectMandatory); }) ) .subscribe(noop); @@ -373,71 +379,78 @@ export class ViewMileagePage { this.isNewReportsFlowEnabled = orgSettings?.simplified_report_closure_settings?.enabled || false; }); - this.mileageCustomFields$ = this.extendedMileage$.pipe( - switchMap((res) => - this.customInputsService.fillCustomProperties(res.tx_org_category_id, res.tx_custom_properties, true) + this.mileageCustomFields$ = this.mileageExpense$.pipe( + switchMap((expense) => + this.customInputsService.fillCustomProperties(expense.category_id, expense.custom_fields, true) ), - map((res) => - res.map((customProperties) => { - customProperties.displayValue = this.customInputsService.getCustomPropertyDisplayValue(customProperties); - return customProperties; + map((customProperties) => + customProperties.map((customProperty) => { + customProperty.displayValue = this.customInputsService.getCustomPropertyDisplayValue(customProperty); + return customProperty; }) ) ); - this.view = this.activatedRoute.snapshot.params.view as ExpenseView; + this.mileageRate$ = this.mileageExpense$.pipe( + switchMap((expense) => { + const id = expense.mileage_rate_id; + return this.view === ExpenseView.team + ? this.mileageRatesService.getApproverMileageRateById(id) + : this.mileageRatesService.getSpenderMileageRateById(id); + }) + ); - this.canFlagOrUnflag$ = this.extendedMileage$.pipe( + this.canFlagOrUnflag$ = this.mileageExpense$.pipe( take(1), filter(() => this.view === ExpenseView.team), - map( - (etxn) => - ['COMPLETE', 'POLICY_APPROVED', 'APPROVER_PENDING', 'APPROVED', 'PAYMENT_PENDING'].indexOf(etxn.tx_state) > -1 + map((expense) => + [ExpenseState.COMPLETE, ExpenseState.APPROVER_PENDING, ExpenseState.APPROVED, ExpenseState.PAID].includes( + expense.state + ) ) ); - this.canDelete$ = this.extendedMileage$.pipe( + this.canDelete$ = this.mileageExpense$.pipe( take(1), filter(() => this.view === ExpenseView.team), - switchMap((etxn) => - this.reportService.getTeamReport(etxn.tx_report_id).pipe(map((report) => ({ report, etxn }))) + switchMap((expense) => + this.reportService.getTeamReport(expense.report_id).pipe(map((report) => ({ report, expense }))) ), - map(({ report, etxn }) => { - if (report.rp_num_transactions === 1) { - return false; - } - return ['PAYMENT_PENDING', 'PAYMENT_PROCESSING', 'PAID'].indexOf(etxn.tx_state) < 0; - }) + map(({ report, expense }) => + report.rp_num_transactions === 1 + ? false + : ![ExpenseState.PAYMENT_PENDING, ExpenseState.PAYMENT_PROCESSING, ExpenseState.PAID].includes(expense.state) + ) ); - if (id) { + if (this.expenseId) { this.policyViloations$ = this.view === ExpenseView.team - ? this.policyService.getApproverExpensePolicyViolations(id) - : this.policyService.getSpenderExpensePolicyViolations(id); + ? this.policyService.getApproverExpensePolicyViolations(this.expenseId) + : this.policyService.getSpenderExpensePolicyViolations(this.expenseId); } else { this.policyViloations$ = of(null); } - this.comments$ = this.statusService.find('transactions', id); + this.comments$ = this.statusService.find('transactions', this.expenseId); - this.isCriticalPolicyViolated$ = this.extendedMileage$.pipe( - map((res) => this.isNumber(res.tx_policy_amount) && res.tx_policy_amount < 0.0001) + this.isCriticalPolicyViolated$ = this.mileageExpense$.pipe( + map((expense) => this.isNumber(expense.policy_amount) && expense.policy_amount < 0.0001) ); - this.getPolicyDetails(id); + this.getPolicyDetails(this.expenseId); - this.isAmountCapped$ = this.extendedMileage$.pipe( - map((res) => this.isNumber(res.tx_admin_amount) || this.isNumber(res.tx_policy_amount)) + this.isAmountCapped$ = this.mileageExpense$.pipe( + map((expense) => this.isNumber(expense.admin_amount) || this.isNumber(expense.policy_amount)) ); this.updateFlag$.next(null); - const etxnIds = + const expenseIds = this.activatedRoute.snapshot.params.txnIds && (JSON.parse(this.activatedRoute.snapshot.params.txnIds as string) as string[]); - this.numEtxnsInReport = etxnIds.length; - this.activeEtxnIndex = parseInt(this.activatedRoute.snapshot.params.activeIndex as string, 10); + this.reportExpenseCount = expenseIds.length; + this.activeExpenseIndex = parseInt(this.activatedRoute.snapshot.params.activeIndex as string, 10); } getDisplayValue(customProperties: CustomField): boolean | string { diff --git a/src/app/fyle/view-per-diem/view-per-diem.page.html b/src/app/fyle/view-per-diem/view-per-diem.page.html index b03e477a97..faa4a39d54 100644 --- a/src/app/fyle/view-per-diem/view-per-diem.page.html +++ b/src/app/fyle/view-per-diem/view-per-diem.page.html @@ -8,11 +8,11 @@ View Per Diem -

    - Showing {{ activeEtxnIndex + 1 }} of {{ numEtxnsInReport }} expenses +

    + Showing {{ activeExpenseIndex + 1 }} of {{ reportExpenseCount }} expenses

    @@ -40,8 +40,8 @@ -
    - +
    + - Claimed amount {{extendedPerDiem.tx_user_amount | currency: extendedPerDiem.tx_currency: 'symbol-narrow'}} was - capped to {{extendedPerDiem.tx_amount | currency: extendedPerDiem.tx_currency: 'symbol-narrow'}} due to policy. + Claimed amount {{perDiemExpense.claim_amount | currency: perDiemExpense.currency: 'symbol-narrow'}} was capped + to {{perDiemExpense.amount | currency: perDiemExpense.currency: 'symbol-narrow'}} due to policy.
    Per Diem
    -
    - {{extendedPerDiem.tx_num_days}} {{extendedPerDiem.tx_num_days === 1 ? 'Day' : 'Days'}} +
    + {{perDiemExpense.per_diem_num_days}} {{perDiemExpense.per_diem_num_days === 1 ? 'Day' : 'Days'}}
    - {{ projectFieldName | titlecase }} : {{ extendedPerDiem.tx_project_name ? extendedPerDiem.tx_project_name : - 'Unspecified' }} + {{ projectFieldName | titlecase }} : {{ perDiemExpense.project?.name || 'Unspecified' }}
    - {{ etxnCurrencySymbol }} + {{ expenseCurrencySymbol }} - {{ extendedPerDiem.tx_amount || 0 | humanizeCurrency: extendedPerDiem.tx_currency:true }} + {{ perDiemExpense.amount || 0 | humanizeCurrency: perDiemExpense.currency:true }} -
    - {{ extendedPerDiem.tx_state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }} +
    + {{ perDiemExpense.state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }}
    @@ -100,7 +99,7 @@
    Report Name
    -
    {{ extendedPerDiem.rp_purpose }}
    +
    {{ perDiemExpense.report?.title }}
    @@ -114,7 +113,7 @@
    Employee
    -
    {{ extendedPerDiem.us_full_name }}
    +
    {{ perDiemExpense.user.full_name }}
    @@ -128,7 +127,7 @@
    Expense ID
    -
    {{ extendedPerDiem.tx_expense_number }}
    +
    {{ perDiemExpense.seq_num }}
    @@ -147,12 +146,12 @@
    Created Date
    -
    {{ extendedPerDiem.tx_created_at | date: 'MMM dd, YYYY' }}
    +
    {{ perDiemExpense.created_at | date: 'MMM dd, YYYY' }}
    - + @@ -167,12 +166,12 @@
    Onward Date
    -
    {{ extendedPerDiem.tx_from_dt | date: 'MMM dd, YYYY' }}
    +
    {{ perDiemExpense.started_at | date: 'MMM dd, YYYY' }}
    - + @@ -185,7 +184,7 @@
    Return Date
    -
    {{ extendedPerDiem.tx_to_dt | date: 'MMM dd, YYYY' }}
    +
    {{ perDiemExpense.ended_at | date: 'MMM dd, YYYY' }}
    @@ -210,7 +209,7 @@
    - +
    @@ -219,13 +218,13 @@
    Purpose
    -
    {{ extendedPerDiem.tx_purpose }}
    +
    {{ perDiemExpense.purpose }}
    @@ -236,7 +235,7 @@
    Subcategory
    -
    {{ extendedPerDiem.tx_sub_category }}
    +
    {{ perDiemExpense.category.sub_category }}
    @@ -260,11 +259,11 @@ - + @@ -277,12 +276,12 @@
    Cost Center
    -
    {{ extendedPerDiem.tx_cost_center_name }}
    +
    {{ perDiemExpense.cost_center?.name }}
    - + @@ -295,7 +294,7 @@
    Cost Center Code
    -
    {{ extendedPerDiem.tx_cost_center_code }}
    +
    {{ perDiemExpense.cost_center?.code }}
    @@ -307,12 +306,12 @@ - - + + @@ -321,13 +320,13 @@ - - + + @@ -355,8 +354,9 @@
    + diff --git a/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts b/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts index cdd5625d55..b0412c3c4c 100644 --- a/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts +++ b/src/app/fyle/view-per-diem/view-per-diem.page.spec.ts @@ -16,7 +16,7 @@ import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.servi import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { DependentFieldsService } from 'src/app/core/services/dependent-fields.service'; import { ExpenseView } from 'src/app/core/models/expense-view.enum'; -import { Subject, finalize, of } from 'rxjs'; +import { finalize, of } from 'rxjs'; import { ApproverExpensePolicyStatesData, expensePolicyStatesData, @@ -36,9 +36,13 @@ import { perDiemRatesData1 } from 'src/app/core/mock-data/per-diem-rates.data'; import { apiExtendedReportRes } from 'src/app/core/mock-data/report.data'; import { estatusData1 } from 'src/app/core/test-data/status.service.spec.data'; import { cloneDeep } from 'lodash'; -import { AccountType } from 'src/app/core/enums/account-type.enum'; import { FyPopoverComponent } from 'src/app/shared/components/fy-popover/fy-popover.component'; import { txnStatusData } from 'src/app/core/mock-data/transaction-status.data'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { perDiemExpense } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { ExpenseState } from 'src/app/core/models/expense-state.enum'; +import { AccountType } from 'src/app/core/models/platform/v1/account.model'; describe('ViewPerDiemPage', () => { let component: ViewPerDiemPage; @@ -58,15 +62,12 @@ describe('ViewPerDiemPage', () => { let expenseFieldsService: jasmine.SpyObj; let orgSettingsService: jasmine.SpyObj; let dependentFieldsService: jasmine.SpyObj; + let spenderExpensesService: jasmine.SpyObj; + let approverExpensesService: jasmine.SpyObj; let activatedRoute: ActivatedRoute; beforeEach(waitForAsync(() => { - const transactionServiceSpy = jasmine.createSpyObj('TransactionService', [ - 'getEtxn', - 'getExpenseV2', - 'manualUnflag', - 'manualFlag', - ]); + const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['manualUnflag', 'manualFlag']); const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['showLoader', 'hideLoader']); const customInputsServiceSpy = jasmine.createSpyObj('CustomInputsService', [ 'fillCustomProperties', @@ -94,6 +95,8 @@ describe('ViewPerDiemPage', () => { const dependentFieldsServiceSpy = jasmine.createSpyObj('DependentFieldsService', [ 'getDependentFieldValuesForBaseField', ]); + const spenderExpensesServiceSpy = jasmine.createSpyObj('SpenderExpensesService', ['getExpenseById']); + const approverExpensesServiceSpy = jasmine.createSpyObj('ApproverExpensesService', ['getExpenseById']); TestBed.configureTestingModule({ declarations: [ViewPerDiemPage], @@ -114,6 +117,8 @@ describe('ViewPerDiemPage', () => { { provide: ExpenseFieldsService, useValue: expenseFieldsServiceSpy }, { provide: OrgSettingsService, useValue: orgSettingsServiceSpy }, { provide: DependentFieldsService, useValue: dependentFieldsServiceSpy }, + { provide: SpenderExpensesService, useValue: spenderExpensesServiceSpy }, + { provide: ApproverExpensesService, useValue: approverExpensesServiceSpy }, { provide: ActivatedRoute, useValue: { @@ -147,6 +152,8 @@ describe('ViewPerDiemPage', () => { expenseFieldsService = TestBed.inject(ExpenseFieldsService) as jasmine.SpyObj; orgSettingsService = TestBed.inject(OrgSettingsService) as jasmine.SpyObj; dependentFieldsService = TestBed.inject(DependentFieldsService) as jasmine.SpyObj; + spenderExpensesService = TestBed.inject(SpenderExpensesService) as jasmine.SpyObj; + approverExpensesService = TestBed.inject(ApproverExpensesService) as jasmine.SpyObj; activatedRoute = TestBed.inject(ActivatedRoute); fixture.detectChanges(); })); @@ -172,7 +179,6 @@ describe('ViewPerDiemPage', () => { describe('goBack():', () => { beforeEach(() => { component.view = ExpenseView.team; - component.reportId = 'rpFE5X1Pqi9P'; }); it('should navigate to view team report page if current view is set to team', () => { @@ -181,7 +187,7 @@ describe('ViewPerDiemPage', () => { '/', 'enterprise', 'view_team_report', - { id: 'rpFE5X1Pqi9P', navigate_back: true }, + { id: component.reportId, navigate_back: true }, ]); }); @@ -192,7 +198,7 @@ describe('ViewPerDiemPage', () => { '/', 'enterprise', 'my_view_report', - { id: 'rpFE5X1Pqi9P', navigate_back: true }, + { id: component.reportId, navigate_back: true }, ]); }); }); @@ -222,7 +228,6 @@ describe('ViewPerDiemPage', () => { describe('openCommentsModal', () => { beforeEach(() => { component.view = ExpenseView.individual; - transactionService.getEtxn.and.returnValue(of(expenseData1)); modalProperties.getModalDefaultProperties.and.returnValue(properties); }); @@ -237,12 +242,11 @@ describe('ViewPerDiemPage', () => { component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: expenseData1.tx_id, + objectId: component.expenseId, }, ...properties, }); expect(modalProperties.getModalDefaultProperties).toHaveBeenCalledTimes(1); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith('tx3qwe4ty'); expect(modalSpy.present).toHaveBeenCalledTimes(1); expect(modalSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.addComment).toHaveBeenCalledOnceWith({ view: 'Individual' }); @@ -260,12 +264,11 @@ describe('ViewPerDiemPage', () => { component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: expenseData1.tx_id, + objectId: component.expenseId, }, ...properties, }); expect(modalProperties.getModalDefaultProperties).toHaveBeenCalledTimes(1); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith('tx3qwe4ty'); expect(modalSpy.present).toHaveBeenCalledTimes(1); expect(modalSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.viewComment).toHaveBeenCalledOnceWith({ view: 'Individual' }); @@ -275,9 +278,13 @@ describe('ViewPerDiemPage', () => { describe('ionViewWillEnter():', () => { const mockCustomFields = cloneDeep(customFields); beforeEach(() => { + component.expenseId = 'tx5fBcPBAxLv'; + component.reportId = 'rpFvmTgyeBjN'; + loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); - transactionService.getExpenseV2.and.returnValue(of(expenseData1)); + spenderExpensesService.getExpenseById.and.returnValue(of(perDiemExpense)); + approverExpensesService.getExpenseById.and.returnValue(of(perDiemExpense)); expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse4)); dependentFieldsService.getDependentFieldValuesForBaseField.and.returnValue(of(customInputData1)); orgSettingsService.get.and.returnValue(of(orgSettingsData)); @@ -293,19 +300,19 @@ describe('ViewPerDiemPage', () => { it('should set extendedPerDiem$ and txnFields$ correctly', (done) => { component.ionViewWillEnter(); - component.extendedPerDiem$ + component.perDiemExpense$ .pipe( finalize(() => { expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); }) ) .subscribe((extendedPerDiem) => { - expect(transactionService.getExpenseV2).toHaveBeenCalledOnceWith('tx3qwe4ty'); + expect(spenderExpensesService.getExpenseById).toHaveBeenCalledOnceWith('tx3qwe4ty'); expect(loaderService.showLoader).toHaveBeenCalledTimes(1); - expect(extendedPerDiem).toEqual(expenseData1); + expect(extendedPerDiem).toEqual(perDiemExpense); }); - component.txnFields$.subscribe((txnFields) => { + component.expenseFields$.subscribe((txnFields) => { expect(expenseFieldsService.getAllMap).toHaveBeenCalledTimes(1); expect(txnFields).toEqual(expenseFieldsMapResponse4); done(); @@ -336,7 +343,7 @@ describe('ViewPerDiemPage', () => { component.projectDependentCustomProperties$.subscribe((projectDependentCustomProperties) => { expect(dependentFieldsService.getDependentFieldValuesForBaseField).toHaveBeenCalledOnceWith( - expenseData1.tx_custom_properties, + perDiemExpense.custom_fields, undefined ); expect(projectDependentCustomProperties).toEqual(customInputData1); @@ -344,7 +351,7 @@ describe('ViewPerDiemPage', () => { component.costCenterDependentCustomProperties$.subscribe((costCenterDependentCustomProperties) => { expect(dependentFieldsService.getDependentFieldValuesForBaseField).not.toHaveBeenCalledOnceWith( - expenseData1.tx_custom_properties, + perDiemExpense.custom_fields, undefined ); expect(costCenterDependentCustomProperties).toEqual(customInputData1); @@ -370,9 +377,10 @@ describe('ViewPerDiemPage', () => { })); it('should set paymentMode and paymentMode icon correctly if account type is ADVANCE', fakeAsync(() => { - const mockExpense = cloneDeep(expenseData1); - mockExpense.source_account_type = AccountType.ADVANCE; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.source_account.type = AccountType.PERSONAL_ADVANCE_ACCOUNT; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); tick(100); expect(component.paymentMode).toEqual('Paid from Advance'); @@ -380,9 +388,10 @@ describe('ViewPerDiemPage', () => { })); it('should set paymentMode and paymentMode icon correctly if tx_skip_reimbursement is true', fakeAsync(() => { - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_skip_reimbursement = true; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.is_reimbursable = false; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); tick(100); expect(component.paymentMode).toEqual('Paid by Company'); @@ -403,10 +412,11 @@ describe('ViewPerDiemPage', () => { expect(component.isProjectShown).toBeTrue(); })); - it('should set isProjectShown to false if project name and is empty string and project is not mandatory', fakeAsync(() => { - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_project_name = ''; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + it('should set isProjectShown to false if project does not exist and project is not mandatory', fakeAsync(() => { + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.project = null; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); const mockExpenseField = cloneDeep(expenseFieldsMapResponse4); mockExpenseField.project_id[0].is_mandatory = false; expenseFieldsService.getAllMap.and.returnValue(of(mockExpenseField)); @@ -431,15 +441,16 @@ describe('ViewPerDiemPage', () => { })); it('should set perDiemCustomFields$ and perDiemRate$', (done) => { - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_per_diem_rate_id = '508'; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.per_diem_rate_id = 508; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.perDiemCustomFields$.subscribe((perDiemCustomFields) => { expect(customInputsService.fillCustomProperties).toHaveBeenCalledOnceWith( - expenseData1.tx_org_category_id, - expenseData1.tx_custom_properties, + perDiemExpense.category_id, + perDiemExpense.custom_fields, true ); // Called twice because of the two custom fields @@ -458,7 +469,8 @@ describe('ViewPerDiemPage', () => { it('should set view and canFlagOrUnflag$ to true if expense state is APPROVER_PENDING', (done) => { activatedRoute.snapshot.params.view = ExpenseView.team; - transactionService.getExpenseV2.and.returnValue(of(expenseData2)); + + approverExpensesService.getExpenseById.and.returnValue(of(perDiemExpense)); component.ionViewWillEnter(); expect(component.view).toEqual(ExpenseView.team); component.canFlagOrUnflag$.subscribe((canFlagOrUnflag) => { @@ -469,9 +481,10 @@ describe('ViewPerDiemPage', () => { it('should set canFlagOrUnflag$ to false if state is PAID', (done) => { activatedRoute.snapshot.params.view = ExpenseView.team; - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_state = 'PAID'; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.state = ExpenseState.PAID; + + approverExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.canFlagOrUnflag$.subscribe((canFlagOrUnflag) => { expect(canFlagOrUnflag).toBeFalse(); @@ -481,10 +494,11 @@ describe('ViewPerDiemPage', () => { it('should set canDelete$ to false if report transaction equals 1', (done) => { activatedRoute.snapshot.params.view = ExpenseView.team; - transactionService.getExpenseV2.and.returnValue(of(expenseData2)); + + approverExpensesService.getExpenseById.and.returnValue(of(perDiemExpense)); component.ionViewWillEnter(); component.canDelete$.subscribe((canDelete) => { - expect(reportService.getTeamReport).toHaveBeenCalledOnceWith('rpT7x1BFlLOi'); + expect(reportService.getTeamReport).toHaveBeenCalledOnceWith('rpFvmTgyeBjN'); expect(canDelete).toBeFalse(); done(); }); @@ -492,13 +506,18 @@ describe('ViewPerDiemPage', () => { it('should set canDelete$ to true if expense state is APPROVER_PENDING', (done) => { activatedRoute.snapshot.params.view = ExpenseView.team; + + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.state = ExpenseState.APPROVER_PENDING; const mockReport = cloneDeep(apiExtendedReportRes[0]); mockReport.rp_num_transactions = 2; + reportService.getTeamReport.and.returnValue(of(mockReport)); - transactionService.getExpenseV2.and.returnValue(of(expenseData2)); + approverExpensesService.getExpenseById.and.returnValue(of(mockExpense)); + component.ionViewWillEnter(); component.canDelete$.subscribe((canDelete) => { - expect(reportService.getTeamReport).toHaveBeenCalledOnceWith('rpT7x1BFlLOi'); + expect(reportService.getTeamReport).toHaveBeenCalledOnceWith('rpFvmTgyeBjN'); expect(canDelete).toBeTrue(); done(); }); @@ -549,9 +568,10 @@ describe('ViewPerDiemPage', () => { it('should set isCriticalPolicyViolated$ to true if policy amount is number and less than 0.0001', (done) => { spyOn(component, 'isNumber').and.returnValue(true); - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_policy_amount = 0; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.policy_amount = 0; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.isCriticalPolicyViolated$.subscribe((isCriticalPolicyViolated) => { @@ -563,9 +583,10 @@ describe('ViewPerDiemPage', () => { it('should set isCriticalPolicyViolated$ to false if policy amount is not a number', (done) => { spyOn(component, 'isNumber').and.returnValue(false); - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_policy_amount = null; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.policy_amount = null; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.isCriticalPolicyViolated$.subscribe((isCriticalPolicyViolated) => { @@ -577,9 +598,10 @@ describe('ViewPerDiemPage', () => { it('should set isAmountCapped$ to true if admin amount is number', (done) => { spyOn(component, 'isNumber').and.returnValue(true); - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_admin_amount = 0; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.admin_amount = 0; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.isAmountCapped$.subscribe((isAmountCapped) => { @@ -591,10 +613,11 @@ describe('ViewPerDiemPage', () => { it('should set isAmountCapped$ to true if policy amount is number', (done) => { spyOn(component, 'isNumber').and.returnValues(false, true); - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_admin_amount = null; - mockExpense.tx_policy_amount = 0; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.admin_amount = null; + mockExpense.policy_amount = 0; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.isAmountCapped$.subscribe((isAmountCapped) => { @@ -608,10 +631,11 @@ describe('ViewPerDiemPage', () => { it('should set isAmountCapped$ to false if policy amount and admin amount are not a number', (done) => { spyOn(component, 'isNumber').and.returnValues(false, false); - const mockExpense = cloneDeep(expenseData1); - mockExpense.tx_admin_amount = null; - mockExpense.tx_policy_amount = null; - transactionService.getExpenseV2.and.returnValue(of(mockExpense)); + const mockExpense = cloneDeep(perDiemExpense); + mockExpense.admin_amount = null; + mockExpense.policy_amount = null; + + spenderExpensesService.getExpenseById.and.returnValue(of(mockExpense)); component.ionViewWillEnter(); component.isAmountCapped$.subscribe((isAmountCapped) => { @@ -633,24 +657,22 @@ describe('ViewPerDiemPage', () => { }); })); - it('should set numEtxnsInReport and activeEtxnIndex', fakeAsync(() => { + it('should set reportExpenseCount and activeExpenseIndex', fakeAsync(() => { component.ionViewWillEnter(); tick(100); - expect(component.numEtxnsInReport).toEqual(3); - expect(component.activeEtxnIndex).toEqual(0); + expect(component.reportExpenseCount).toEqual(3); + expect(component.activeExpenseIndex).toEqual(0); })); }); it('getDeleteDialogProps(): should return modal params', () => { - const props = component.getDeleteDialogProps(expenseData1); + const props = component.getDeleteDialogProps(); props.componentProps.deleteMethod(); - expect(reportService.removeTransaction).toHaveBeenCalledOnceWith(expenseData1.tx_report_id, expenseData1.tx_id); + expect(reportService.removeTransaction).toHaveBeenCalledOnceWith(component.reportId, component.expenseId); }); it('removeExpenseFromReport(): should remove the expense from report', fakeAsync(() => { - transactionService.getEtxn.and.returnValue(of(expenseData1)); - spyOn(component, 'getDeleteDialogProps'); const deletePopoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present', 'onDidDismiss']); popoverController.create.and.returnValue(deletePopoverSpy); @@ -658,8 +680,7 @@ describe('ViewPerDiemPage', () => { component.removeExpenseFromReport(); tick(100); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activatedRoute.snapshot.params.id); - expect(popoverController.create).toHaveBeenCalledOnceWith(component.getDeleteDialogProps(expenseData1)); + expect(popoverController.create).toHaveBeenCalledOnceWith(component.getDeleteDialogProps()); expect(deletePopoverSpy.present).toHaveBeenCalledTimes(1); expect(deletePopoverSpy.onDidDismiss).toHaveBeenCalledTimes(1); expect(trackingService.expenseRemovedByApprover).toHaveBeenCalledTimes(1); @@ -667,15 +688,15 @@ describe('ViewPerDiemPage', () => { '/', 'enterprise', 'view_team_report', - { id: expenseData1.tx_report_id, navigate_back: true }, + { id: component.reportId, navigate_back: true }, ]); })); describe('flagUnflagExpense', () => { it('should flag unflagged expense', fakeAsync(() => { - transactionService.getEtxn.and.returnValue(of(expenseData1)); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); + component.isExpenseFlagged = false; const title = 'Flag'; const flagPopoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present', 'onWillDismiss']); @@ -687,7 +708,6 @@ describe('ViewPerDiemPage', () => { component.flagUnflagExpense(); tick(100); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activatedRoute.snapshot.params.id); expect(popoverController.create).toHaveBeenCalledOnceWith({ component: FyPopoverComponent, @@ -701,8 +721,8 @@ describe('ViewPerDiemPage', () => { expect(flagPopoverSpy.present).toHaveBeenCalledTimes(1); expect(flagPopoverSpy.onWillDismiss).toHaveBeenCalledTimes(1); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Please wait'); - expect(statusService.post).toHaveBeenCalledOnceWith('transactions', expenseData1.tx_id, data, true); - expect(transactionService.manualFlag).toHaveBeenCalledOnceWith(expenseData1.tx_id); + expect(statusService.post).toHaveBeenCalledOnceWith('transactions', component.expenseId, data, true); + expect(transactionService.manualFlag).toHaveBeenCalledOnceWith(component.expenseId); expect(transactionService.manualUnflag).not.toHaveBeenCalled(); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(trackingService.expenseFlagUnflagClicked).toHaveBeenCalledOnceWith({ action: title }); @@ -710,11 +730,6 @@ describe('ViewPerDiemPage', () => { })); it('should unflag flagged expense', fakeAsync(() => { - const mockExpenseData = { - ...expenseData1, - tx_manual_flag: true, - }; - transactionService.getEtxn.and.returnValue(of(mockExpenseData)); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); component.isExpenseFlagged = true; @@ -729,7 +744,6 @@ describe('ViewPerDiemPage', () => { component.flagUnflagExpense(); tick(100); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith(activatedRoute.snapshot.params.id); expect(popoverController.create).toHaveBeenCalledOnceWith({ component: FyPopoverComponent, @@ -743,8 +757,8 @@ describe('ViewPerDiemPage', () => { expect(flagPopoverSpy.present).toHaveBeenCalledTimes(1); expect(flagPopoverSpy.onWillDismiss).toHaveBeenCalledTimes(1); expect(loaderService.showLoader).toHaveBeenCalledOnceWith('Please wait'); - expect(statusService.post).toHaveBeenCalledOnceWith('transactions', mockExpenseData.tx_id, data, true); - expect(transactionService.manualUnflag).toHaveBeenCalledOnceWith(mockExpenseData.tx_id); + expect(statusService.post).toHaveBeenCalledOnceWith('transactions', component.expenseId, data, true); + expect(transactionService.manualUnflag).toHaveBeenCalledOnceWith(component.expenseId); expect(transactionService.manualFlag).not.toHaveBeenCalled(); expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); expect(trackingService.expenseFlagUnflagClicked).toHaveBeenCalledOnceWith({ action: title }); diff --git a/src/app/fyle/view-per-diem/view-per-diem.page.ts b/src/app/fyle/view-per-diem/view-per-diem.page.ts index 765e086261..f61b292d52 100644 --- a/src/app/fyle/view-per-diem/view-per-diem.page.ts +++ b/src/app/fyle/view-per-diem/view-per-diem.page.ts @@ -1,6 +1,5 @@ import { Component, ViewChild, ElementRef } from '@angular/core'; import { Observable, from, Subject, noop, of, forkJoin } from 'rxjs'; -import { Expense } from 'src/app/core/models/expense.model'; import { CustomField } from 'src/app/core/models/custom_field.model'; import { ActivatedRoute, Router } from '@angular/router'; import { TransactionService } from 'src/app/core/services/transaction.service'; @@ -20,7 +19,6 @@ import { FyPopoverComponent } from 'src/app/shared/components/fy-popover/fy-popo import { getCurrencySymbol } from '@angular/common'; import { ExpenseView } from 'src/app/core/models/expense-view.enum'; import { ExtendedStatus } from 'src/app/core/models/extended_status.model'; -import { AccountType } from 'src/app/core/enums/account-type.enum'; import { ExpenseFieldsService } from 'src/app/core/services/expense-fields.service'; import { OrgSettingsService } from 'src/app/core/services/org-settings.service'; import { ExpenseField } from 'src/app/core/models/v1/expense-field.model'; @@ -30,6 +28,11 @@ import { OrgSettings } from 'src/app/core/models/org-settings.model'; import { PerDiemRates } from 'src/app/core/models/v1/per-diem-rates.model'; import { IndividualExpensePolicyState } from 'src/app/core/models/platform/platform-individual-expense-policy-state.model'; import { ExpenseDeletePopoverParams } from 'src/app/core/models/expense-delete-popover-params.model'; +import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { AccountType } from 'src/app/core/models/platform/v1/account.model'; +import { ExpenseState } from 'src/app/core/models/expense-state.enum'; @Component({ selector: 'app-view-per-diem', @@ -39,7 +42,7 @@ import { ExpenseDeletePopoverParams } from 'src/app/core/models/expense-delete-p export class ViewPerDiemPage { @ViewChild('comments') commentsContainer: ElementRef; - extendedPerDiem$: Observable; + perDiemExpense$: Observable; orgSettings: OrgSettings; @@ -57,6 +60,8 @@ export class ViewPerDiemPage { canDelete$: Observable; + expenseId: string; + reportId: string; policyDetails; @@ -69,15 +74,15 @@ export class ViewPerDiemPage { isExpenseFlagged: boolean; - numEtxnsInReport: number; + reportExpenseCount: number; - activeEtxnIndex: number; + activeExpenseIndex: number; paymentMode: string; paymentModeIcon: string; - etxnCurrencySymbol: string; + expenseCurrencySymbol: string; view: ExpenseView; @@ -87,7 +92,7 @@ export class ViewPerDiemPage { isNewReportsFlowEnabled = false; - txnFields$: Observable<{ [key: string]: ExpenseField[] }>; + expenseFields$: Observable<{ [key: string]: ExpenseField[] }>; projectDependentCustomProperties$: Observable[]>; @@ -109,7 +114,9 @@ export class ViewPerDiemPage { private trackingService: TrackingService, private expenseFieldsService: ExpenseFieldsService, private orgSettingsService: OrgSettingsService, - private dependentFieldsService: DependentFieldsService + private dependentFieldsService: DependentFieldsService, + private spenderExpensesService: SpenderExpensesService, + private approverExpensesService: ApproverExpensesService ) {} get ExpenseView(): typeof ExpenseView { @@ -147,12 +154,11 @@ export class ViewPerDiemPage { } async openCommentsModal(): Promise { - const etxn = await this.transactionService.getEtxn(this.activatedRoute.snapshot.params.id as string).toPromise(); const modal = await this.modalController.create({ component: ViewCommentComponent, componentProps: { objectType: 'transactions', - objectId: etxn.tx_id, + objectId: this.expenseId, }, ...this.modalProperties.getModalDefaultProperties(), }); @@ -168,56 +174,63 @@ export class ViewPerDiemPage { } ionViewWillEnter(): void { - const id = this.activatedRoute.snapshot.params.id as string; + this.expenseId = this.activatedRoute.snapshot.params.id as string; + this.view = this.activatedRoute.snapshot.params.view as ExpenseView; - this.extendedPerDiem$ = this.updateFlag$.pipe( + this.perDiemExpense$ = this.updateFlag$.pipe( switchMap(() => - from(this.loaderService.showLoader()).pipe(switchMap(() => this.transactionService.getExpenseV2(id))) + from(this.loaderService.showLoader()).pipe( + switchMap(() => + this.view === ExpenseView.team + ? this.approverExpensesService.getExpenseById(this.expenseId) + : this.spenderExpensesService.getExpenseById(this.expenseId) + ) + ) ), finalize(() => from(this.loaderService.hideLoader())), shareReplay(1) ); - this.txnFields$ = this.expenseFieldsService.getAllMap().pipe(shareReplay(1)); + this.expenseFields$ = this.expenseFieldsService.getAllMap().pipe(shareReplay(1)); this.projectDependentCustomProperties$ = forkJoin({ - extendedPerDiem: this.extendedPerDiem$.pipe(take(1)), - txnFields: this.txnFields$.pipe(take(1)), + perDiemExpense: this.perDiemExpense$.pipe(take(1)), + expenseFields: this.expenseFields$.pipe(take(1)), }).pipe( filter( - ({ extendedPerDiem, txnFields }) => extendedPerDiem.tx_custom_properties && txnFields.project_id?.length > 0 + ({ perDiemExpense, expenseFields }) => perDiemExpense.custom_fields && expenseFields.project_id?.length > 0 ), - switchMap(({ extendedPerDiem, txnFields }) => + switchMap(({ perDiemExpense, expenseFields }) => this.dependentFieldsService.getDependentFieldValuesForBaseField( - extendedPerDiem.tx_custom_properties, - txnFields.project_id[0]?.id + perDiemExpense.custom_fields, + expenseFields.project_id[0]?.id ) ) ); this.costCenterDependentCustomProperties$ = forkJoin({ - extendedPerDiem: this.extendedPerDiem$.pipe(take(1)), - txnFields: this.txnFields$.pipe(take(1)), + perDiemExpense: this.perDiemExpense$.pipe(take(1)), + expenseFields: this.expenseFields$.pipe(take(1)), }).pipe( filter( - ({ extendedPerDiem, txnFields }) => extendedPerDiem.tx_custom_properties && txnFields.cost_center_id?.length > 0 + ({ perDiemExpense, expenseFields }) => perDiemExpense.custom_fields && expenseFields.cost_center_id?.length > 0 ), - switchMap(({ extendedPerDiem, txnFields }) => + switchMap(({ perDiemExpense, expenseFields }) => this.dependentFieldsService.getDependentFieldValuesForBaseField( - extendedPerDiem.tx_custom_properties, - txnFields.cost_center_id[0]?.id + perDiemExpense.custom_fields, + expenseFields.cost_center_id[0]?.id ) ), shareReplay(1) ); - this.extendedPerDiem$.subscribe((extendedPerDiem) => { - this.reportId = extendedPerDiem.tx_report_id; + this.perDiemExpense$.subscribe((perDiemExpense) => { + this.reportId = perDiemExpense.report_id; - if (extendedPerDiem.source_account_type === AccountType.ADVANCE) { + if (perDiemExpense.source_account.type === AccountType.PERSONAL_ADVANCE_ACCOUNT) { this.paymentMode = 'Paid from Advance'; this.paymentModeIcon = 'fy-non-reimbursable'; - } else if (extendedPerDiem.tx_skip_reimbursement) { + } else if (!perDiemExpense.is_reimbursable) { this.paymentMode = 'Paid by Company'; this.paymentModeIcon = 'fy-non-reimbursable'; } else { @@ -225,16 +238,16 @@ export class ViewPerDiemPage { this.paymentModeIcon = 'fy-reimbursable'; } - this.etxnCurrencySymbol = getCurrencySymbol(extendedPerDiem.tx_currency, 'wide'); + this.expenseCurrencySymbol = getCurrencySymbol(perDiemExpense.currency, 'wide'); }); - forkJoin([this.txnFields$, this.extendedPerDiem$.pipe(take(1))]) + forkJoin([this.expenseFields$, this.perDiemExpense$.pipe(take(1))]) .pipe( - map(([expenseFieldsMap, extendedPerDiem]) => { - this.projectFieldName = expenseFieldsMap?.project_id && expenseFieldsMap.project_id[0]?.field_name; - const isProjectMandatory = expenseFieldsMap?.project_id && expenseFieldsMap.project_id[0]?.is_mandatory; + map(([expenseFieldsMap, perDiemExpense]) => { + this.projectFieldName = expenseFieldsMap?.project_id && expenseFieldsMap?.project_id[0]?.field_name; + const isProjectMandatory = expenseFieldsMap?.project_id && expenseFieldsMap?.project_id[0]?.is_mandatory; this.isProjectShown = - this.orgSettings?.projects?.enabled && (!!extendedPerDiem.tx_project_name || isProjectMandatory); + this.orgSettings?.projects?.enabled && (!!perDiemExpense.project?.name || isProjectMandatory); }) ) .subscribe(noop); @@ -247,82 +260,84 @@ export class ViewPerDiemPage { this.isNewReportsFlowEnabled = orgSettings?.simplified_report_closure_settings?.enabled || false; }); - this.perDiemCustomFields$ = this.extendedPerDiem$.pipe( - switchMap((res) => - this.customInputsService.fillCustomProperties(res.tx_org_category_id, res.tx_custom_properties, true) + this.perDiemCustomFields$ = this.perDiemExpense$.pipe( + switchMap((expense) => + this.customInputsService.fillCustomProperties(expense.category_id, expense.custom_fields, true) ), - map((res) => - res.map((customProperties) => { - customProperties.displayValue = this.customInputsService.getCustomPropertyDisplayValue(customProperties); - return customProperties; + map((customProperties) => + customProperties.map((customProperty) => { + customProperty.displayValue = this.customInputsService.getCustomPropertyDisplayValue(customProperty); + return customProperty; }) ) ); - this.perDiemRate$ = this.extendedPerDiem$.pipe( - switchMap((res) => { - const perDiemRateId = parseInt(res.tx_per_diem_rate_id, 10); + this.perDiemRate$ = this.perDiemExpense$.pipe( + switchMap((perDiemExpense) => { + const perDiemRateId = perDiemExpense.per_diem_rate_id; return this.perDiemService.getRate(perDiemRateId); }) ); - this.view = this.activatedRoute.snapshot.params.view as ExpenseView; - - this.canFlagOrUnflag$ = this.extendedPerDiem$.pipe( + this.canFlagOrUnflag$ = this.perDiemExpense$.pipe( filter(() => this.view === ExpenseView.team), - map((etxn) => - ['COMPLETE', 'POLICY_APPROVED', 'APPROVER_PENDING', 'APPROVED', 'PAYMENT_PENDING'].includes(etxn.tx_state) + map((expense) => + [ + ExpenseState.COMPLETE, + ExpenseState.APPROVER_PENDING, + ExpenseState.APPROVED, + ExpenseState.PAYMENT_PENDING, + ].includes(expense.state) ) ); - this.canDelete$ = this.extendedPerDiem$.pipe( + this.canDelete$ = this.perDiemExpense$.pipe( filter(() => this.view === ExpenseView.team), - switchMap((etxn) => - this.reportService.getTeamReport(etxn.tx_report_id).pipe(map((report) => ({ report, etxn }))) + switchMap((expense) => + this.reportService.getTeamReport(expense.report_id).pipe(map((report) => ({ report, expense }))) ), - map(({ report, etxn }) => { - if (report.rp_num_transactions === 1) { - return false; - } - return ['PAYMENT_PENDING', 'PAYMENT_PROCESSING', 'PAID'].indexOf(etxn.tx_state) < 0; - }) + map(({ report, expense }) => + report.rp_num_transactions === 1 + ? false + : ![ExpenseState.PAYMENT_PENDING, ExpenseState.PAYMENT_PROCESSING, ExpenseState.PAID].includes(expense.state) + ) ); - if (id) { + if (this.expenseId) { this.policyViolations$ = this.view === ExpenseView.team - ? this.policyService.getApproverExpensePolicyViolations(id) - : this.policyService.getSpenderExpensePolicyViolations(id); + ? this.policyService.getApproverExpensePolicyViolations(this.expenseId) + : this.policyService.getSpenderExpensePolicyViolations(this.expenseId); } else { this.policyViolations$ = of(null); } - this.comments$ = this.statusService.find('transactions', id); + this.comments$ = this.statusService.find('transactions', this.expenseId); - this.isCriticalPolicyViolated$ = this.extendedPerDiem$.pipe( - map((res) => this.isNumber(res.tx_policy_amount) && res.tx_policy_amount < 0.0001) + this.isCriticalPolicyViolated$ = this.perDiemExpense$.pipe( + map((expense) => this.isNumber(expense.policy_amount) && expense.policy_amount < 0.0001) ); - this.getPolicyDetails(id); + this.getPolicyDetails(this.expenseId); - this.isAmountCapped$ = this.extendedPerDiem$.pipe( - map((res) => this.isNumber(res.tx_admin_amount) || this.isNumber(res.tx_policy_amount)) + this.isAmountCapped$ = this.perDiemExpense$.pipe( + map((expense) => this.isNumber(expense.admin_amount) || this.isNumber(expense.policy_amount)) ); - this.extendedPerDiem$.subscribe((etxn) => { - this.isExpenseFlagged = etxn.tx_manual_flag; + this.perDiemExpense$.subscribe((expense) => { + this.isExpenseFlagged = !!expense.is_manually_flagged; }); this.updateFlag$.next(null); - const etxnIds = + const expenseIds = this.activatedRoute.snapshot.params.txnIds && (JSON.parse(this.activatedRoute.snapshot.params.txnIds as string) as string[]); - this.numEtxnsInReport = etxnIds.length; - this.activeEtxnIndex = parseInt(this.activatedRoute.snapshot.params.activeIndex as string, 10); + this.reportExpenseCount = expenseIds.length; + this.activeExpenseIndex = parseInt(this.activatedRoute.snapshot.params.activeIndex as string, 10); } - getDeleteDialogProps(etxn: Expense): ExpenseDeletePopoverParams { + getDeleteDialogProps(): ExpenseDeletePopoverParams { return { component: FyDeleteDialogComponent, cssClass: 'delete-dialog', @@ -333,29 +348,24 @@ export class ViewPerDiemPage { infoMessage: 'The report amount will be adjusted accordingly.', ctaText: 'Remove', ctaLoadingText: 'Removing', - deleteMethod: () => this.reportService.removeTransaction(etxn.tx_report_id, etxn.tx_id), + deleteMethod: (): Observable => this.reportService.removeTransaction(this.reportId, this.expenseId), }, }; } async removeExpenseFromReport(): Promise { - const etxn = await this.transactionService.getEtxn(this.activatedRoute.snapshot.params.id as string).toPromise(); - - const deletePopover = await this.popoverController.create(this.getDeleteDialogProps(etxn)); + const deletePopover = await this.popoverController.create(this.getDeleteDialogProps()); await deletePopover.present(); const { data } = (await deletePopover.onDidDismiss()) as { data: { status: string } }; if (data && data.status === 'success') { this.trackingService.expenseRemovedByApprover(); - this.router.navigate(['/', 'enterprise', 'view_team_report', { id: etxn.tx_report_id, navigate_back: true }]); + this.router.navigate(['/', 'enterprise', 'view_team_report', { id: this.reportId, navigate_back: true }]); } } async flagUnflagExpense(): Promise { - const id = this.activatedRoute.snapshot.params.id as string; - const etxn = await this.transactionService.getEtxn(id).toPromise(); - const title = this.isExpenseFlagged ? 'Unflag' : 'Flag'; const flagUnflagModal = await this.popoverController.create({ component: FyPopoverComponent, @@ -376,12 +386,12 @@ export class ViewPerDiemPage { const comment = { comment: data.comment, }; - return this.statusService.post('transactions', etxn.tx_id, comment, true); + return this.statusService.post('transactions', this.expenseId, comment, true); }), concatMap(() => - etxn.tx_manual_flag - ? this.transactionService.manualUnflag(etxn.tx_id) - : this.transactionService.manualFlag(etxn.tx_id) + this.isExpenseFlagged + ? this.transactionService.manualUnflag(this.expenseId) + : this.transactionService.manualFlag(this.expenseId) ), finalize(() => { this.updateFlag$.next(null); @@ -390,7 +400,7 @@ export class ViewPerDiemPage { ) .subscribe(noop); } - this.isExpenseFlagged = etxn.tx_manual_flag; + this.trackingService.expenseFlagUnflagClicked({ action: title }); } diff --git a/src/app/fyle/view-team-advance-request/view-team-advance-request.page.spec.ts b/src/app/fyle/view-team-advance-request/view-team-advance-request.page.spec.ts index 8b3d50444a..af6bd75c4f 100644 --- a/src/app/fyle/view-team-advance-request/view-team-advance-request.page.spec.ts +++ b/src/app/fyle/view-team-advance-request/view-team-advance-request.page.spec.ts @@ -284,7 +284,7 @@ describe('ViewTeamAdvanceRequestPage', () => { fileService.downloadUrl.and.returnValue(of('mockdownloadurl.png')); fileService.findByAdvanceRequestId.and.returnValue(of([mockFileObject])); component.getAttachedReceipts('areqR1cyLgXdND').subscribe((res) => { - expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith(mockFileObject); + expect(fileService.getReceiptsDetails).toHaveBeenCalledOnceWith(mockFileObject.name, 'mockdownloadurl.png'); expect(fileService.downloadUrl).toHaveBeenCalledOnceWith('fiSSsy2Bf4Se'); expect(fileService.findByAdvanceRequestId).toHaveBeenCalledOnceWith('areqR1cyLgXdND'); expect(res).toEqual(fileObject10); diff --git a/src/app/fyle/view-team-advance-request/view-team-advance-request.page.ts b/src/app/fyle/view-team-advance-request/view-team-advance-request.page.ts index b730446625..83ce740ac4 100644 --- a/src/app/fyle/view-team-advance-request/view-team-advance-request.page.ts +++ b/src/app/fyle/view-team-advance-request/view-team-advance-request.page.ts @@ -101,7 +101,7 @@ export class ViewTeamAdvanceRequestPage implements OnInit { this.fileService.downloadUrl(fileObj.id).pipe( map((downloadUrl) => { fileObj.url = downloadUrl; - const details = this.fileService.getReceiptsDetails(fileObj); + const details = this.fileService.getReceiptsDetails(fileObj.name, fileObj.url); fileObj.type = details.type; fileObj.thumbnail = details.thumbnail; return fileObj; diff --git a/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts b/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts index d388d9084a..19cbc74e50 100644 --- a/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts +++ b/src/app/shared/components/expenses-card-v2/expenses-card.component.spec.ts @@ -176,7 +176,7 @@ describe('ExpensesCardComponent', () => { component.selectedElements = expenseResponseData; component.expense = { ...expenseData, - id: 'txe0bYaJlRJf', + id: 'txcSFe6efB6R', }; expect(component.isSelected).toBeTrue(); }); @@ -194,7 +194,7 @@ describe('ExpensesCardComponent', () => { component.selectedElements = null; component.expense = { ...expenseData, - id: 'txe0bYaJlRJf', + id: 'txcSFe6efB6R', }; expect(component.isSelected).toBeFalse(); }); @@ -457,10 +457,10 @@ describe('ExpensesCardComponent', () => { describe('canShowPaymentModeIcon', () => { it('should show payment mode icon if it is a personal expense and is reimbersable', () => { - component.expense = { - ...expenseData, - is_reimbursable: true, - }; + component.expense = cloneDeep(expenseData); + component.expense.is_reimbursable = true; + component.expense.source_account.type = AccountType.PERSONAL_CASH_ACCOUNT; + component.canShowPaymentModeIcon(); fixture.detectChanges(); expect(component.showPaymentModeIcon).toBeTrue(); @@ -590,6 +590,7 @@ describe('ExpensesCardComponent', () => { it('should set icon to fy-unmatched if the source account type is corporate credit card but matched_corporate_card_transaction_ids is not present', () => { component.expense = cloneDeep(expenseData); + component.expense.matched_corporate_card_transaction_ids = []; component.expense.source_account.type = AccountType.PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT; component.setOtherData(); @@ -598,21 +599,21 @@ describe('ExpensesCardComponent', () => { }); it('should set icon to fy-reimbersable if the source account type is not a corporate credit card and if the reimbersement is not skipped', () => { - component.expense = { - ...expenseData, - matched_corporate_card_transaction_ids: [], - is_reimbursable: true, - }; + component.expense = cloneDeep(expenseData); + component.expense.matched_corporate_card_transaction_ids = []; + component.expense.source_account.type = AccountType.PERSONAL_CASH_ACCOUNT; + component.expense.is_reimbursable = true; + component.setOtherData(); fixture.detectChanges(); expect(component.paymentModeIcon).toEqual('fy-reimbursable'); }); it('should set icon to fy-non-reimbersable if the source account type is not a corporate credit card and if the reimbersement is skipped', () => { - component.expense = { - ...expenseData, - is_reimbursable: false, - }; + component.expense = cloneDeep(expenseData); + component.expense.is_reimbursable = false; + component.expense.source_account.type = AccountType.PERSONAL_CASH_ACCOUNT; + component.setOtherData(); fixture.detectChanges(); expect(component.paymentModeIcon).toEqual('fy-non-reimbursable'); @@ -773,6 +774,7 @@ describe('ExpensesCardComponent', () => { const nativeElement1 = component.fileUpload.nativeElement as HTMLInputElement; spyOn(component, 'onFileUpload').and.stub(); + spyOn(component, 'canAddAttachment').and.returnValue(true); spyOn(nativeElement1, 'click').and.callThrough(); component.addAttachments(event as any); diff --git a/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.html b/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.html index d33deb2583..57ea23ee46 100644 --- a/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.html +++ b/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.html @@ -1,14 +1,18 @@ - + Previous - + Next { component = fixture.componentInstance; mockActiveEtxnIndex = 0; mockNumEtxnsInReport = 2; - component.activeEtxnIndex = mockActiveEtxnIndex; - component.numEtxnsInReport = mockNumEtxnsInReport; + component.activeExpenseIndex = mockActiveEtxnIndex; + component.reportExpenseCount = mockNumEtxnsInReport; fixture.detectChanges(); })); @@ -48,21 +48,21 @@ describe('FyNavFooterComponent', () => { }); it('should disable next button when activeEtxnIndex is numEtxnsInReport - 1', () => { - component.activeEtxnIndex = mockNumEtxnsInReport - 1; + component.activeExpenseIndex = mockNumEtxnsInReport - 1; fixture.detectChanges(); const nextButton = getAllElementsBySelector(fixture, '.btn-secondary')[1] as HTMLButtonElement; expect(nextButton.disabled).toBeTruthy(); }); it('should enable previous button when activeEtxnIndex is greater than 0', () => { - component.activeEtxnIndex = 1; + component.activeExpenseIndex = 1; fixture.detectChanges(); const prevButton = getElementBySelector(fixture, '.nav-footer__footer__btn') as HTMLButtonElement; expect(prevButton.disabled).toBeFalsy(); }); it('should enable next button when activeEtxnIndex is less than numEtxnsInReport - 1', () => { - component.activeEtxnIndex = 0; + component.activeExpenseIndex = 0; fixture.detectChanges(); const nextButton = getAllElementsBySelector(fixture, '.btn-secondary')[1] as HTMLButtonElement; expect(nextButton.disabled).toBeFalsy(); diff --git a/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.ts b/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.ts index e1213ccaa5..46d576de9a 100644 --- a/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.ts +++ b/src/app/shared/components/navigation-footer/fy-nav-footer/fy-nav-footer.component.ts @@ -1,28 +1,24 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-fy-nav-footer', templateUrl: './fy-nav-footer.component.html', styleUrls: ['./fy-nav-footer.component.scss'], }) -export class FyNavFooterComponent implements OnInit { - @Input() activeEtxnIndex: number; +export class FyNavFooterComponent { + @Input() activeExpenseIndex: number; - @Input() numEtxnsInReport: number; + @Input() reportExpenseCount: number; @Output() nextClicked = new EventEmitter(); @Output() prevClicked = new EventEmitter(); - constructor() {} - - ngOnInit() {} - - goToNext() { + goToNext(): void { this.nextClicked.emit(); } - goToPrev() { + goToPrev(): void { this.prevClicked.emit(); } } diff --git a/src/app/shared/components/navigation-footer/navigation-footer.component.html b/src/app/shared/components/navigation-footer/navigation-footer.component.html index 5fff1142b6..841f1b4652 100644 --- a/src/app/shared/components/navigation-footer/navigation-footer.component.html +++ b/src/app/shared/components/navigation-footer/navigation-footer.component.html @@ -1,6 +1,6 @@ diff --git a/src/app/shared/components/navigation-footer/navigation-footer.component.spec.ts b/src/app/shared/components/navigation-footer/navigation-footer.component.spec.ts index 41d7d538f4..cddd185534 100644 --- a/src/app/shared/components/navigation-footer/navigation-footer.component.spec.ts +++ b/src/app/shared/components/navigation-footer/navigation-footer.component.spec.ts @@ -1,23 +1,28 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; -import { TransactionService } from 'src/app/core/services/transaction.service'; import { TrackingService } from 'src/app/core/services/tracking.service'; import { NavigationFooterComponent } from './navigation-footer.component'; import { Router, ActivatedRoute } from '@angular/router'; import { FyNavFooterComponent } from './fy-nav-footer/fy-nav-footer.component'; -import { apiExpenseRes, etxncData, expenseData1 } from 'src/app/core/mock-data/expense.data'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; import { of } from 'rxjs'; +import { expenseData, mileageExpense, perDiemExpense } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { ExpenseView } from 'src/app/core/models/expense-view.enum'; describe('NavigationFooterComponent', () => { let component: NavigationFooterComponent; let fixture: ComponentFixture; let router: jasmine.SpyObj; let activatedRoute: jasmine.SpyObj; - let transactionService: jasmine.SpyObj; + let spenderExpensesService: jasmine.SpyObj; + let approverExpensesService: jasmine.SpyObj; let trackingService: jasmine.SpyObj; + beforeEach(waitForAsync(() => { const routerSpy = jasmine.createSpyObj('Router', ['navigate']); - const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['getEtxn']); + const spenderExpensesServiceSpy = jasmine.createSpyObj('SpenderExpensesService', ['getExpenseById']); + const approverExpensesServiceSpy = jasmine.createSpyObj('ApproverExpensesService', ['getExpenseById']); const trackingServiceSpy = jasmine.createSpyObj('TrackingService', ['expenseNavClicked']); TestBed.configureTestingModule({ declarations: [NavigationFooterComponent, FyNavFooterComponent], @@ -29,6 +34,7 @@ describe('NavigationFooterComponent', () => { snapshot: { params: { txnIds: JSON.stringify(['tx5fBcPBAxLv', 'txOJVaaPxo9O', 'tx3nHShG60zq']), + view: ExpenseView.individual, }, }, }, @@ -38,8 +44,12 @@ describe('NavigationFooterComponent', () => { useValue: routerSpy, }, { - provide: TransactionService, - useValue: transactionServiceSpy, + provide: SpenderExpensesService, + useValue: spenderExpensesServiceSpy, + }, + { + provide: ApproverExpensesService, + useValue: approverExpensesServiceSpy, }, { provide: TrackingService, @@ -51,10 +61,11 @@ describe('NavigationFooterComponent', () => { component = fixture.componentInstance; router = TestBed.inject(Router) as jasmine.SpyObj; activatedRoute = TestBed.inject(ActivatedRoute) as jasmine.SpyObj; - transactionService = TestBed.inject(TransactionService) as jasmine.SpyObj; + spenderExpensesService = TestBed.inject(SpenderExpensesService) as jasmine.SpyObj; + approverExpensesService = TestBed.inject(ApproverExpensesService) as jasmine.SpyObj; trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj; - component.numEtxnsInReport = 3; - component.activeEtxnIndex = 1; + component.reportExpenseCount = 3; + component.activeExpenseIndex = 1; fixture.detectChanges(); })); @@ -63,83 +74,107 @@ describe('NavigationFooterComponent', () => { }); describe('goToPrev():', () => { - it('should go to the previous txn', () => { - spyOn(component, 'goToTransaction'); - transactionService.getEtxn.and.returnValue(of(etxncData.data[0])); + it('should go to the previous expense', () => { + spyOn(component, 'goToExpense'); + spyOn(component, 'getExpense').and.returnValue(of(expenseData)); fixture.detectChanges(); component.goToPrev(1); expect(trackingService.expenseNavClicked).toHaveBeenCalledOnceWith({ to: 'prev' }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith('tx5fBcPBAxLv'); - expect(component.goToTransaction).toHaveBeenCalledOnceWith(etxncData.data[0], 0, 'prev'); + expect(component.getExpense).toHaveBeenCalledOnceWith('tx5fBcPBAxLv'); + expect(component.goToExpense).toHaveBeenCalledOnceWith(expenseData, 0); }); it('should not run if current index is 0', () => { - spyOn(component, 'goToTransaction'); + spyOn(component, 'goToExpense'); + spyOn(component, 'getExpense'); fixture.detectChanges(); component.goToPrev(0); - expect(transactionService.getEtxn).not.toHaveBeenCalled(); + expect(component.getExpense).not.toHaveBeenCalled(); expect(trackingService.expenseNavClicked).not.toHaveBeenCalled(); - expect(component.goToTransaction).not.toHaveBeenCalled(); + expect(component.goToExpense).not.toHaveBeenCalled(); }); - it('should go to the previous txn if txn index not provided', () => { - spyOn(component, 'goToTransaction'); - transactionService.getEtxn.and.returnValue(of(etxncData.data[0])); + it('should go to the previous expense if expense index not provided', () => { + spyOn(component, 'goToExpense'); + spyOn(component, 'getExpense').and.returnValue(of(expenseData)); fixture.detectChanges(); component.goToPrev(); expect(trackingService.expenseNavClicked).toHaveBeenCalledOnceWith({ to: 'prev' }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith('tx5fBcPBAxLv'); - expect(component.goToTransaction).toHaveBeenCalledOnceWith(etxncData.data[0], 0, 'prev'); + expect(component.getExpense).toHaveBeenCalledOnceWith('tx5fBcPBAxLv'); + expect(component.goToExpense).toHaveBeenCalledOnceWith(expenseData, 0); }); }); describe('goToNext():', () => { - it('should go to the next txn', () => { - spyOn(component, 'goToTransaction'); - transactionService.getEtxn.and.returnValue(of(apiExpenseRes[0])); + it('should go to the next expense', () => { + spyOn(component, 'goToExpense'); + spyOn(component, 'getExpense').and.returnValue(of(expenseData)); fixture.detectChanges(); component.goToNext(1); expect(trackingService.expenseNavClicked).toHaveBeenCalledOnceWith({ to: 'next' }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith('tx3nHShG60zq'); - expect(component.goToTransaction).toHaveBeenCalledOnceWith(apiExpenseRes[0], 2, 'next'); + expect(component.getExpense).toHaveBeenCalledOnceWith('tx3nHShG60zq'); + expect(component.goToExpense).toHaveBeenCalledOnceWith(expenseData, 2); }); - it('should not go to next txn if provided index is the last txn', () => { - spyOn(component, 'goToTransaction'); + it('should not go to next expense if provided index is the last expense', () => { + spyOn(component, 'goToExpense'); + spyOn(component, 'getExpense'); fixture.detectChanges(); component.goToNext(2); - expect(transactionService.getEtxn).not.toHaveBeenCalled(); + expect(component.getExpense).not.toHaveBeenCalled(); expect(trackingService.expenseNavClicked).not.toHaveBeenCalled(); - expect(component.goToTransaction).not.toHaveBeenCalled(); + expect(component.goToExpense).not.toHaveBeenCalled(); }); - it('should go to next txn if the current index is not provided', () => { - spyOn(component, 'goToTransaction'); - transactionService.getEtxn.and.returnValue(of(apiExpenseRes[0])); + it('should go to next expense if the current index is not provided', () => { + spyOn(component, 'goToExpense'); + spyOn(component, 'getExpense').and.returnValue(of(expenseData)); fixture.detectChanges(); component.goToNext(); expect(trackingService.expenseNavClicked).toHaveBeenCalledOnceWith({ to: 'next' }); - expect(transactionService.getEtxn).toHaveBeenCalledOnceWith('tx3nHShG60zq'); - expect(component.goToTransaction).toHaveBeenCalledOnceWith(apiExpenseRes[0], 2, 'next'); + expect(component.getExpense).toHaveBeenCalledOnceWith('tx3nHShG60zq'); + expect(component.goToExpense).toHaveBeenCalledOnceWith(expenseData, 2); + }); + }); + + describe('getExpense():', () => { + it('should get spender expense if view is individual', () => { + component.view = ExpenseView.individual; + spenderExpensesService.getExpenseById.and.returnValue(of(expenseData)); + + component.getExpense('tx5fBcPBAxLv').subscribe((expense) => { + expect(expense).toEqual(expenseData); + expect(spenderExpensesService.getExpenseById).toHaveBeenCalledOnceWith('tx5fBcPBAxLv'); + }); + }); + + it('should get approver expense if view is team', () => { + component.view = ExpenseView.team; + approverExpensesService.getExpenseById.and.returnValue(of(expenseData)); + + component.getExpense('tx5fBcPBAxLv').subscribe((expense) => { + expect(expense).toEqual(expenseData); + expect(approverExpensesService.getExpenseById).toHaveBeenCalledOnceWith('tx5fBcPBAxLv'); + }); }); }); - describe('goToTransaction():', () => { + describe('goToExpense():', () => { it('should go to transaction', () => { router.navigate.and.returnValue(Promise.resolve(null)); - component.goToTransaction(expenseData1, 0, 'next'); + component.goToExpense(expenseData, 0); expect(router.navigate).toHaveBeenCalledOnceWith([ '/enterprise/view_expense', { ...activatedRoute.snapshot.params, - id: expenseData1.tx_id, + id: expenseData.id, activeIndex: 0, }, ]); @@ -147,13 +182,13 @@ describe('NavigationFooterComponent', () => { it('should go to mileage', () => { router.navigate.and.returnValue(Promise.resolve(null)); - component.goToTransaction({ ...expenseData1, tx_org_category: 'mileage' }, 0, 'next'); + component.goToExpense(mileageExpense, 0); expect(router.navigate).toHaveBeenCalledOnceWith([ '/enterprise/view_mileage', { ...activatedRoute.snapshot.params, - id: expenseData1.tx_id, + id: mileageExpense.id, activeIndex: 0, }, ]); @@ -161,13 +196,13 @@ describe('NavigationFooterComponent', () => { it('should go to per-diem', () => { router.navigate.and.returnValue(Promise.resolve(null)); - component.goToTransaction({ ...expenseData1, tx_org_category: 'per diem' }, 0, 'next'); + component.goToExpense(perDiemExpense, 0); expect(router.navigate).toHaveBeenCalledOnceWith([ '/enterprise/view_per_diem', { ...activatedRoute.snapshot.params, - id: expenseData1.tx_id, + id: perDiemExpense.id, activeIndex: 0, }, ]); diff --git a/src/app/shared/components/navigation-footer/navigation-footer.component.ts b/src/app/shared/components/navigation-footer/navigation-footer.component.ts index 098be95d72..8717dde1bf 100644 --- a/src/app/shared/components/navigation-footer/navigation-footer.component.ts +++ b/src/app/shared/components/navigation-footer/navigation-footer.component.ts @@ -1,8 +1,11 @@ import { Component, OnInit, Input } from '@angular/core'; -import { Expense } from 'src/app/core/models/expense.model'; +import { Expense } from 'src/app/core/models/platform/v1/expense.model'; import { Router, ActivatedRoute } from '@angular/router'; -import { TransactionService } from 'src/app/core/services/transaction.service'; import { TrackingService } from 'src/app/core/services/tracking.service'; +import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; +import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { ExpenseView } from 'src/app/core/models/expense-view.enum'; +import { Observable } from 'rxjs'; @Component({ selector: 'app-navigation-footer', @@ -10,50 +13,66 @@ import { TrackingService } from 'src/app/core/services/tracking.service'; styleUrls: ['./navigation-footer.component.scss'], }) export class NavigationFooterComponent implements OnInit { - @Input() numEtxnsInReport: number; + @Input() reportExpenseCount: number; - @Input() activeEtxnIndex: number; + @Input() activeExpenseIndex: number; - reportEtxnIds: string[]; + reportExpenseIds: string[]; + + view: ExpenseView; constructor( private router: Router, private activatedRoute: ActivatedRoute, - private transactionService: TransactionService, - private trackingService: TrackingService + private trackingService: TrackingService, + private approverExpensesService: ApproverExpensesService, + private spenderExpensesService: SpenderExpensesService ) {} - ngOnInit() { - this.reportEtxnIds = - this.activatedRoute.snapshot.params.txnIds && JSON.parse(this.activatedRoute.snapshot.params.txnIds); + ngOnInit(): void { + const expenseIds = this.activatedRoute.snapshot.params.txnIds as string; + + this.reportExpenseIds = expenseIds && (JSON.parse(expenseIds) as string[]); + + this.view = this.activatedRoute.snapshot.params.view as ExpenseView; } - goToPrev(etxnIndex?: number) { - if (etxnIndex === 0) { + goToPrev(expenseIndex?: number): void { + if (expenseIndex === 0) { return; } - const prevIndex = etxnIndex ? etxnIndex - 1 : this.activeEtxnIndex - 1; + const prevIndex = expenseIndex ? expenseIndex - 1 : this.activeExpenseIndex - 1; this.trackingService.expenseNavClicked({ to: 'prev' }); - this.transactionService.getEtxn(this.reportEtxnIds[prevIndex]).subscribe((etxn) => { - this.goToTransaction(etxn, prevIndex, 'prev'); + + const expenseId = this.reportExpenseIds[prevIndex]; + this.getExpense(expenseId).subscribe((expense) => { + this.goToExpense(expense, prevIndex); }); } - goToNext(etxnIndex?: number) { - if (etxnIndex === this.numEtxnsInReport - 1) { + goToNext(expenseIndex?: number): void { + if (expenseIndex === this.reportExpenseCount - 1) { return; } - const nextIndex = etxnIndex ? etxnIndex + 1 : this.activeEtxnIndex + 1; + const nextIndex = expenseIndex ? expenseIndex + 1 : this.activeExpenseIndex + 1; this.trackingService.expenseNavClicked({ to: 'next' }); - this.transactionService.getEtxn(this.reportEtxnIds[nextIndex]).subscribe((etxn) => { - this.goToTransaction(etxn, nextIndex, 'next'); + + const expenseId = this.reportExpenseIds[nextIndex]; + this.getExpense(expenseId).subscribe((expense) => { + this.goToExpense(expense, nextIndex); }); } - goToTransaction(etxn: Expense, etxnIndex: number, goTo: 'prev' | 'next') { - const category = etxn && etxn.tx_org_category && etxn.tx_org_category.toLowerCase(); + getExpense(expenseId: string): Observable { + return this.view === ExpenseView.team + ? this.approverExpensesService.getExpenseById(expenseId) + : this.spenderExpensesService.getExpenseById(expenseId); + } + + goToExpense(expense: Expense, expenseIndex: number): void { + const category = expense.category.name.toLowerCase(); let route: string; if (category === 'mileage') { @@ -63,6 +82,9 @@ export class NavigationFooterComponent implements OnInit { } else { route = '/enterprise/view_expense'; } - this.router.navigate([route, { ...this.activatedRoute.snapshot.params, id: etxn.tx_id, activeIndex: etxnIndex }]); + this.router.navigate([ + route, + { ...this.activatedRoute.snapshot.params, id: expense.id, activeIndex: expenseIndex }, + ]); } } From 691f9e921a37f8ab7adf547aee88cd91de2ea929 Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Sun, 26 Nov 2023 21:02:50 +0530 Subject: [PATCH 06/15] feat: Transaction status visibility changes in expenses (#2582) * feat: Migrated GET of single expense call via /v2/expenses to platform (#2544) * feat: Moved GET of /v2/expense to platform in View Mileage (#2513) * feat: Moved GET of /v2/expenses to platform in View Per Diem (#2514) * test: Added unit tests for /v2/expenses API migration in View Mileage (#2515) * test: Added unit tests for /v2/expenses migration in View Per Diem (#2516) * feat: Moved /etxns/:id in View Expense to Platform (#2533) * test: Added tests for /etxns/:id to platform migration in view expense page (#2548) * feat: Cleaned up remaining public calls in view expense, per diem and mileage pages (#2549) * test: Added tests for cleanup changes (#2550) * fix: Renamed etxn, tx, txn terminology to expense in view expense, mileage and per diem pages (#2553) * feat: Moved /etxns/:id call in navigation footer component to platform (#2554) * feat: Added transaction status info in view expense page (#2566) * feat: Added transaction status in edit expense page (#2568) * Minor * test: Transaction status changes remaining branches (#2584) * Minor * feat: Use mileage rates API in view mileage page (#2591) * Merged master * Minor --- src/app/core/mock-data/org-settings.data.ts | 12 +++ .../mock-data/platform/v1/expense.data.ts | 4 +- .../core/models/platform/v1/expense.model.ts | 7 +- .../add-edit-expense-5.spec.ts | 27 +++++- .../add-edit-expense-6.spec.ts | 20 +++++ .../add-edit-expense.page.html | 14 +++ .../add-edit-expense.page.scss | 6 ++ .../add-edit-expense/add-edit-expense.page.ts | 38 +++++++- .../add-edit-expense.setup.spec.ts | 6 ++ .../fyle/view-expense/view-expense.page.html | 88 ++++++++++-------- .../fyle/view-expense/view-expense.page.scss | 6 ++ .../view-expense/view-expense.page.spec.ts | 71 ++++++++++++++- .../fyle/view-expense/view-expense.page.ts | 20 ++++- .../fyle/view-mileage/view-mileage.page.html | 2 +- .../view-per-diem/view-per-diem.page.html | 2 +- ...saction-status-info-popover.component.html | 27 ++++++ ...saction-status-info-popover.component.scss | 22 +++++ ...tion-status-info-popover.component.spec.ts | 89 +++++++++++++++++++ ...ansaction-status-info-popover.component.ts | 22 +++++ .../transaction-status.component.html | 19 ++++ .../transaction-status.component.scss | 37 ++++++++ .../transaction-status.component.spec.ts | 55 ++++++++++++ .../transaction-status.component.ts | 17 ++++ src/app/shared/shared.module.ts | 6 ++ src/assets/svg/info-circle-outline.svg | 9 ++ 25 files changed, 578 insertions(+), 48 deletions(-) create mode 100644 src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.html create mode 100644 src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.scss create mode 100644 src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.spec.ts create mode 100644 src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.ts create mode 100644 src/app/shared/components/transaction-status/transaction-status.component.html create mode 100644 src/app/shared/components/transaction-status/transaction-status.component.scss create mode 100644 src/app/shared/components/transaction-status/transaction-status.component.spec.ts create mode 100644 src/app/shared/components/transaction-status/transaction-status.component.ts create mode 100644 src/assets/svg/info-circle-outline.svg diff --git a/src/app/core/mock-data/org-settings.data.ts b/src/app/core/mock-data/org-settings.data.ts index d839e5bc37..d29cfbcda6 100644 --- a/src/app/core/mock-data/org-settings.data.ts +++ b/src/app/core/mock-data/org-settings.data.ts @@ -1262,6 +1262,18 @@ export const orgSettingsWoTax: OrgSettings = { }, }; +export const orgSettingsWoTaxAndRtf: OrgSettings = { + ...orgSettingsWoTax, + visa_enrollment_settings: { + allowed: true, + enabled: false, + }, + mastercard_enrollment_settings: { + allowed: true, + enabled: false, + }, +}; + export const orgSettingsCCCDisabled2: OrgSettings = { ...orgSettingsRes, corporate_credit_card_settings: null, diff --git a/src/app/core/mock-data/platform/v1/expense.data.ts b/src/app/core/mock-data/platform/v1/expense.data.ts index 43a9c0b79c..ce9d7662eb 100644 --- a/src/app/core/mock-data/platform/v1/expense.data.ts +++ b/src/app/core/mock-data/platform/v1/expense.data.ts @@ -4,7 +4,7 @@ import { MileageUnitEnum } from 'src/app/core/models/platform/platform-mileage-r import { ReportState } from 'src/app/core/models/platform/platform-report.model'; import { ApprovalState } from 'src/app/core/models/platform/report-approvals.model'; import { AccountType } from 'src/app/core/models/platform/v1/account.model'; -import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { Expense, TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; import { FileType } from 'src/app/core/models/platform/v1/file.model'; export const expenseData: Expense = { @@ -137,7 +137,7 @@ export const expenseData: Expense = { merchant: 'Merchant1', posted_at: null, spent_at: new Date('2023-10-15T00:00:00+00:00'), - status: 'PENDING', + status: TransactionStatus.PENDING, }, ], merchant: null, diff --git a/src/app/core/models/platform/v1/expense.model.ts b/src/app/core/models/platform/v1/expense.model.ts index f71f7e0406..fc22d8eca7 100644 --- a/src/app/core/models/platform/v1/expense.model.ts +++ b/src/app/core/models/platform/v1/expense.model.ts @@ -142,13 +142,18 @@ export interface MatchedCorporateCardTransaction { posted_at: Date; description: string; foreign_currency: string; - status: string; + status: TransactionStatus; foreign_amount: number; merchant: string; category: string; matched_by: string; } +export enum TransactionStatus { + PENDING = 'PENDING', + POSTED = 'POSTED', +} + export interface PolicyChecks { are_approvers_added: boolean; is_amount_limit_applied: boolean; diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts index 09a34a0b87..1d8c7d7601 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-5.spec.ts @@ -27,7 +27,7 @@ import { } from 'src/app/core/mock-data/org-category.data'; import { orgSettingsWithProjectAndAutofill, - orgSettingsWoTax, + orgSettingsWoTaxAndRtf, taxSettingsData, taxSettingsData2, } from 'src/app/core/mock-data/org-settings.data'; @@ -106,6 +106,8 @@ import { apiV2ResponseMultiple, expectedProjectsResponse } from 'src/app/core/te import { getEstatusApiResponse } from 'src/app/core/test-data/status.service.spec.data'; import { AddEditExpensePage } from './add-edit-expense.page'; import { txnFieldsData2, txnFieldsFlightData } from 'src/app/core/mock-data/expense-fields-map.data'; +import { expenseData } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; export function TestCases5(getTestBed) { return describe('AddEditExpensePage-5', () => { @@ -155,6 +157,7 @@ export function TestCases5(getTestBed) { let storageService: jasmine.SpyObj; let launchDarklyService: jasmine.SpyObj; let platform: jasmine.SpyObj; + let expensesService: jasmine.SpyObj; beforeEach(() => { const TestBed = getTestBed(); @@ -211,6 +214,7 @@ export function TestCases5(getTestBed) { orgUserSettingsService = TestBed.inject(OrgUserSettingsService) as jasmine.SpyObj; storageService = TestBed.inject(StorageService) as jasmine.SpyObj; launchDarklyService = TestBed.inject(LaunchDarklyService) as jasmine.SpyObj; + expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; component.fg = formBuilder.group({ currencyObj: [, component.currencyObjValidator], @@ -1351,6 +1355,7 @@ export function TestCases5(getTestBed) { spyOn(component, 'getActiveCategories').and.returnValue(of(sortedCategory)); spyOn(component, 'getNewExpenseObservable').and.returnValue(of(expectedExpenseObservable)); spyOn(component, 'getEditExpenseObservable').and.returnValue(of(expectedUnflattendedTxnData1)); + expensesService.getExpenseById.and.returnValue(of(expenseData)); fileService.findByTransactionId.and.returnValue(of(expectedFileData1)); fileService.downloadUrl.and.returnValue(of('url')); spyOn(component, 'getReceiptDetails').and.returnValue({ @@ -1396,6 +1401,10 @@ export function TestCases5(getTestBed) { expect(currencyService.getHomeCurrency).toHaveBeenCalledTimes(1); expect(accountsService.getEMyAccounts).toHaveBeenCalledTimes(1); + component.isRTFEnabled$.subscribe((isRTFEnabled) => { + expect(isRTFEnabled).toBeTrue(); + }); + component.isAdvancesEnabled$.subscribe((res) => { expect(res).toBeTrue(); }); @@ -1460,6 +1469,12 @@ export function TestCases5(getTestBed) { expect(res).toBeFalse(); }); + component.platformExpense$.subscribe((expense) => { + expect(expense).toEqual(expenseData); + }); + + expect(expensesService.getExpenseById).toHaveBeenCalledOnceWith('txyeiYbLDSOy'); + component.attachments$.subscribe((res) => { expect(res).toEqual(expectedFileData1); }); @@ -1610,7 +1625,8 @@ export function TestCases5(getTestBed) { spyOn(component, 'setupSelectedCostCenterObservable'); spyOn(component, 'getCCCpaymentMode'); spyOn(component, 'setUpTaxCalculations'); - orgSettingsService.get.and.returnValue(of(orgSettingsWoTax)); + + orgSettingsService.get.and.returnValue(of(orgSettingsWoTaxAndRtf)); orgUserSettingsService.get.and.returnValue(of(orgUserSettingsData)); currencyService.getHomeCurrency.and.returnValue(of('USD')); accountsService.getEMyAccounts.and.returnValue(of(multiplePaymentModesData)); @@ -1674,6 +1690,10 @@ export function TestCases5(getTestBed) { expect(currencyService.getHomeCurrency).toHaveBeenCalledTimes(1); expect(accountsService.getEMyAccounts).toHaveBeenCalledTimes(1); + component.isRTFEnabled$.subscribe((isRTFEnabled) => { + expect(isRTFEnabled).toBeFalse(); + }); + component.isAdvancesEnabled$.subscribe((res) => { expect(res).toBeTrue(); }); @@ -1737,6 +1757,9 @@ export function TestCases5(getTestBed) { expect(res).toBeTrue(); }); + expect(component.platformExpense$).toBeUndefined(); + expect(expensesService.getExpenseById).not.toHaveBeenCalled(); + component.attachments$.subscribe((res) => { expect(res).toEqual(expectedFileData1); }); diff --git a/src/app/fyle/add-edit-expense/add-edit-expense-6.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense-6.spec.ts index c07378a827..21f288408d 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense-6.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense-6.spec.ts @@ -74,6 +74,8 @@ import { TransactionsOutboxService } from 'src/app/core/services/transactions-ou import { multiplePaymentModesData, orgSettingsData } from 'src/app/core/test-data/accounts.service.spec.data'; import { expectedProjectsResponse } from 'src/app/core/test-data/projects.spec.data'; import { AddEditExpensePage } from './add-edit-expense.page'; +import { TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; +import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component'; export function TestCases6(getTestBed) { describe('AddEditExpensePage-6', () => { @@ -1120,5 +1122,23 @@ export function TestCases6(getTestBed) { expect(result).toBeTrue(); }); }); + + it('openTransactionStatusInfoModal(): should open the transaction status info modal', fakeAsync(() => { + const popoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present']); + popoverController.create.and.resolveTo(popoverSpy); + + component.openTransactionStatusInfoModal(TransactionStatus.PENDING); + + tick(); + + expect(popoverController.create).toHaveBeenCalledOnceWith({ + component: TransactionStatusInfoPopoverComponent, + componentProps: { + transactionStatus: TransactionStatus.PENDING, + }, + cssClass: 'fy-dialog-popover', + }); + expect(popoverSpy.present).toHaveBeenCalledTimes(1); + })); }); } diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.html b/src/app/fyle/add-edit-expense/add-edit-expense.page.html index e6e84bf1ef..3d4b9dcab8 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.html +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.html @@ -181,6 +181,20 @@
    + + + +
    + +
    +
    +
    diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.scss b/src/app/fyle/add-edit-expense/add-edit-expense.page.scss index d55aa6ae36..808e76fb17 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.scss +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.scss @@ -689,6 +689,12 @@ color: $green; font-weight: 500; } + + &__transaction-status { + border-top: 1px solid $grey; + padding-top: 12px; + margin-top: 12px; + } } &--icon-dots { diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts index 22417ff575..2c1dae3284 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.ts @@ -129,6 +129,9 @@ import { TrackingService } from '../../core/services/tracking.service'; import { CameraOptionsPopupComponent } from './camera-options-popup/camera-options-popup.component'; import { SuggestedDuplicatesComponent } from './suggested-duplicates/suggested-duplicates.component'; import { InstaFyleImageData } from 'src/app/core/models/insta-fyle-image-data.model'; +import { Expense as PlatformExpense, TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; +import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component'; type FormValue = { currencyObj: { @@ -183,6 +186,8 @@ export class AddEditExpensePage implements OnInit { etxn$: Observable; + platformExpense$: Observable; + paymentModes$: Observable; recentlyUsedValues$: Observable; @@ -416,6 +421,8 @@ export class AddEditExpensePage implements OnInit { recentCategoriesOriginal: OrgCategoryListItem[]; + isRTFEnabled$: Observable; + constructor( private activatedRoute: ActivatedRoute, private accountsService: AccountsService, @@ -461,7 +468,8 @@ export class AddEditExpensePage implements OnInit { private orgUserSettingsService: OrgUserSettingsService, private storageService: StorageService, private launchDarklyService: LaunchDarklyService, - private platformHandlerService: PlatformHandlerService + private platformHandlerService: PlatformHandlerService, + private expensesService: ExpensesService ) {} get isExpandedView(): boolean { @@ -2882,6 +2890,14 @@ export class AddEditExpensePage implements OnInit { this.homeCurrency$ = this.currencyService.getHomeCurrency(); const accounts$ = this.accountsService.getEMyAccounts(); + this.isRTFEnabled$ = orgSettings$.pipe( + map( + (orgSettings) => + (orgSettings.visa_enrollment_settings.allowed && orgSettings.visa_enrollment_settings.enabled) || + (orgSettings.mastercard_enrollment_settings.allowed && orgSettings.mastercard_enrollment_settings.enabled) + ) + ); + this.isAdvancesEnabled$ = orgSettings$.pipe( map( (orgSettings) => @@ -3016,6 +3032,14 @@ export class AddEditExpensePage implements OnInit { shareReplay(1) ) as Observable; + /** + * Fetching the expense from platform APIs in edit case, this is required because corporate card transaction status (PENDING or POSTED) is not available in public transactions API + */ + if (this.activatedRoute.snapshot.params.id) { + const id = this.activatedRoute.snapshot.params.id as string; + this.platformExpense$ = this.expensesService.getExpenseById(id); + } + this.attachments$ = this.loadAttachments$.pipe( switchMap(() => this.etxn$.pipe( @@ -4936,4 +4960,16 @@ export class AddEditExpensePage implements OnInit { await sizeLimitExceededPopover.present(); } + + async openTransactionStatusInfoModal(transactionStatus: TransactionStatus): Promise { + const popover = await this.popoverController.create({ + component: TransactionStatusInfoPopoverComponent, + componentProps: { + transactionStatus, + }, + cssClass: 'fy-dialog-popover', + }); + + await popover.present(); + } } diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts b/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts index e00815917a..c01fba62ee 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts +++ b/src/app/fyle/add-edit-expense/add-edit-expense.setup.spec.ts @@ -58,6 +58,7 @@ import { TestCases4 } from './add-edit-expense-4.spec'; import { TestCases5 } from './add-edit-expense-5.spec'; import { TestCases6 } from './add-edit-expense-6.spec'; import { AddEditExpensePage } from './add-edit-expense.page'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; export function setFormValid(component) { Object.defineProperty(component.fg, 'valid', { @@ -208,6 +209,7 @@ describe('AddEditExpensePage', () => { const launchDarklyServiceSpy = jasmine.createSpyObj('LaunchDarklyService', ['getVariation']); const platformSpy = jasmine.createSpyObj('Platform', ['is']); const platformHandlerServiceSpy = jasmine.createSpyObj('PlatformHandlerService', ['registerBackButtonAction']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getExpenseById']); TestBed.configureTestingModule({ declarations: [AddEditExpensePage, MaskNumber, FySelectComponent, EllipsisPipe, DependentFieldComponent], @@ -398,6 +400,10 @@ describe('AddEditExpensePage', () => { provide: PlatformHandlerService, useValue: platformHandlerServiceSpy, }, + { + provide: ExpensesService, + useValue: expensesServiceSpy, + }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA], }); diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index a27c74fc38..9b569fce2d 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -63,52 +63,64 @@
    -
    -
    -
    - {{ expense.category?.display_name || 'Unspecified' }} -
    -
    - {{ expense.merchant || merchantFieldName + ' Unspecified'}} -
    -
    - {{ projectFieldName | titlecase }}: {{ expense.project?.name || 'Unspecified' }} +
    +
    +
    +
    {{ expense.category.display_name }}
    +
    {{ expense.merchant || '-' }}
    +
    + {{ projectFieldName | titlecase }}: {{ expense.project?.name || '-' }} +
    +
    Card Number: {{ cardNumber | maskNumber }}
    -
    Card Number: {{ cardNumber | maskNumber }}
    -
    -
    -
    - {{ expenseCurrencySymbol }} - - {{ expense.amount | humanizeCurrency: expense.currency:true }} - -
    -
    - {{ expense.amount > 0 ? 'DR' : 'CR' }} + +
    +
    + {{ expenseCurrencySymbol }} + + {{ expense.amount | humanizeCurrency: expense.currency:true }} + +
    +
    + {{ expense.amount > 0 ? 'DR' : 'CR' }} +
    +
    - -
    -
    - {{ expense.foreign_amount | humanizeCurrency: expense.foreign_currency }} at {{ expense.amount / - expense.foreign_amount | currency: expense.currency: 'symbol-narrow' }} +
    + {{ expense.foreign_amount | humanizeCurrency: expense.foreign_currency }} at {{ expense.amount / + expense.foreign_amount | currency: expense.currency: 'symbol-narrow' }} +
    + +
    +
    + {{ expense.state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }} +
    +
    +
    + +
    -
    - {{ expense.state | expenseState : isNewReportsFlowEnabled | snakeCaseToSpaceCase | titlecase }} -
    +
    -
    +
    diff --git a/src/app/fyle/view-expense/view-expense.page.scss b/src/app/fyle/view-expense/view-expense.page.scss index 9cc45bc364..380742cf60 100644 --- a/src/app/fyle/view-expense/view-expense.page.scss +++ b/src/app/fyle/view-expense/view-expense.page.scss @@ -120,6 +120,12 @@ color: $green; font-weight: 500; } + + &__transaction-status { + border-top: 1px solid $grey; + padding-top: 12px; + margin-top: 12px; + } } &--label { diff --git a/src/app/fyle/view-expense/view-expense.page.spec.ts b/src/app/fyle/view-expense/view-expense.page.spec.ts index 7fdbc8e124..6814251d07 100644 --- a/src/app/fyle/view-expense/view-expense.page.spec.ts +++ b/src/app/fyle/view-expense/view-expense.page.spec.ts @@ -43,9 +43,11 @@ import { txnStatusData } from 'src/app/core/mock-data/transaction-status.data'; import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; import { expenseData } from 'src/app/core/mock-data/platform/v1/expense.data'; -import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { Expense, TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; import { AccountType } from 'src/app/core/models/platform/v1/account.model'; import { ExpenseState } from 'src/app/core/models/expense-state.enum'; +import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component'; +import { OrgSettings } from 'src/app/core/models/org-settings.model'; describe('ViewExpensePage', () => { let component: ViewExpensePage; @@ -786,6 +788,55 @@ describe('ViewExpensePage', () => { expect(orgSettingsService.get).toHaveBeenCalledTimes(1); }); + it('should set isRTFEnabled to true if only visa rtf is enabled', () => { + const mockOrgSettings: OrgSettings = { + ...orgSettingsGetData, + mastercard_enrollment_settings: { + allowed: false, + enabled: false, + }, + }; + + orgSettingsService.get.and.returnValue(of(mockOrgSettings)); + component.ionViewWillEnter(); + expect(component.isRTFEnabled).toBeTrue(); + expect(orgSettingsService.get).toHaveBeenCalledTimes(1); + }); + + it('should set isRTFEnabled to true if only mastercard rtf is enabled', () => { + const mockOrgSettings: OrgSettings = { + ...orgSettingsGetData, + visa_enrollment_settings: { + allowed: false, + enabled: false, + }, + }; + + orgSettingsService.get.and.returnValue(of(mockOrgSettings)); + component.ionViewWillEnter(); + expect(component.isRTFEnabled).toBeTrue(); + expect(orgSettingsService.get).toHaveBeenCalledTimes(1); + }); + + it('should set isRTFEnabled to false if both visa/mastercard rtf are not enabled', () => { + const mockOrgSettings: OrgSettings = { + ...orgSettingsGetData, + visa_enrollment_settings: { + allowed: false, + enabled: false, + }, + mastercard_enrollment_settings: { + allowed: false, + enabled: false, + }, + }; + + orgSettingsService.get.and.returnValue(of(mockOrgSettings)); + component.ionViewWillEnter(); + expect(component.isRTFEnabled).toBeFalse(); + expect(orgSettingsService.get).toHaveBeenCalledTimes(1); + }); + it('should get the merchant field name', () => { expenseFieldsService.getAllMap.and.returnValue(of(expenseFieldsMapResponse4)); component.ionViewWillEnter(); @@ -1059,4 +1110,22 @@ describe('ViewExpensePage', () => { expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); })); }); + + it('openTransactionStatusInfoModal(): should open the transaction status info modal', fakeAsync(() => { + const popoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present']); + popoverController.create.and.resolveTo(popoverSpy); + + component.openTransactionStatusInfoModal(TransactionStatus.PENDING); + + tick(); + + expect(popoverController.create).toHaveBeenCalledOnceWith({ + component: TransactionStatusInfoPopoverComponent, + componentProps: { + transactionStatus: TransactionStatus.PENDING, + }, + cssClass: 'fy-dialog-popover', + }); + expect(popoverSpy.present).toHaveBeenCalledTimes(1); + })); }); diff --git a/src/app/fyle/view-expense/view-expense.page.ts b/src/app/fyle/view-expense/view-expense.page.ts index 733665a968..83498e18e4 100644 --- a/src/app/fyle/view-expense/view-expense.page.ts +++ b/src/app/fyle/view-expense/view-expense.page.ts @@ -31,9 +31,10 @@ import { OrgSettings } from 'src/app/core/models/org-settings.model'; import { FileObject } from 'src/app/core/models/file-obj.model'; import { ExpensesService as ApproverExpensesService } from 'src/app/core/services/platform/v1/approver/expenses.service'; import { ExpensesService as SpenderExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; -import { Expense } from 'src/app/core/models/platform/v1/expense.model'; +import { Expense, TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; import { AccountType } from 'src/app/core/models/platform/v1/account.model'; import { ExpenseState } from 'src/app/core/models/expense-state.enum'; +import { TransactionStatusInfoPopoverComponent } from 'src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component'; @Component({ selector: 'app-view-expense', @@ -127,6 +128,8 @@ export class ViewExpensePage { costCenterDependentCustomProperties$: Observable[]>; + isRTFEnabled: boolean; + constructor( private loaderService: LoaderService, private transactionService: TransactionService, @@ -381,6 +384,9 @@ export class ViewExpensePage { this.orgSettingsService.get().subscribe((orgSettings) => { this.orgSettings = orgSettings; + this.isRTFEnabled = + (orgSettings.visa_enrollment_settings.allowed && orgSettings.visa_enrollment_settings.enabled) || + (orgSettings.mastercard_enrollment_settings.allowed && orgSettings.mastercard_enrollment_settings.enabled); this.isNewReportsFlowEnabled = orgSettings.simplified_report_closure_settings?.enabled || false; }); @@ -527,4 +533,16 @@ export class ViewExpensePage { await attachmentsModal.present(); }); } + + async openTransactionStatusInfoModal(transactionStatus: TransactionStatus): Promise { + const popover = await this.popoverController.create({ + component: TransactionStatusInfoPopoverComponent, + componentProps: { + transactionStatus, + }, + cssClass: 'fy-dialog-popover', + }); + + await popover.present(); + } } diff --git a/src/app/fyle/view-mileage/view-mileage.page.html b/src/app/fyle/view-mileage/view-mileage.page.html index 56c09706e5..3dfca59292 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.html +++ b/src/app/fyle/view-mileage/view-mileage.page.html @@ -66,7 +66,7 @@ {{expense.distance || 0}} {{expense.distance_unit | titlecase}}
    - {{ projectFieldName | titlecase }} : {{ expense.project?.name || 'Unspecified' }} + {{ projectFieldName | titlecase }} : {{ expense.project?.name || '-' }}
    diff --git a/src/app/fyle/view-per-diem/view-per-diem.page.html b/src/app/fyle/view-per-diem/view-per-diem.page.html index faa4a39d54..6b5d538d29 100644 --- a/src/app/fyle/view-per-diem/view-per-diem.page.html +++ b/src/app/fyle/view-per-diem/view-per-diem.page.html @@ -64,7 +64,7 @@ {{perDiemExpense.per_diem_num_days}} {{perDiemExpense.per_diem_num_days === 1 ? 'Day' : 'Days'}}
    - {{ projectFieldName | titlecase }} : {{ perDiemExpense.project?.name || 'Unspecified' }} + {{ projectFieldName | titlecase }} : {{ perDiemExpense.project?.name || '-' }}
    diff --git a/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.html b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.html new file mode 100644 index 0000000000..b092fcf1d7 --- /dev/null +++ b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.html @@ -0,0 +1,27 @@ + + + + + + + + + Transaction Status: {{ transactionStatus | titlecase }} + + + + +
    + Your transaction status is Pending until your bank processes the transaction. + + The transaction has been processed by your bank. +
    +
    diff --git a/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.scss b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.scss new file mode 100644 index 0000000000..1d7b82ce7a --- /dev/null +++ b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.scss @@ -0,0 +1,22 @@ +@import '../../../../theme/colors.scss'; + +.transaction-status-info-popover { + position: relative; + + &__toolbar-title { + color: $black; + font-size: 18px; + line-height: normal; + font-weight: 500; + } + + &__toolbar-close-btn { + position: absolute; + } + + &__body { + font-size: 16px; + color: $black; + padding: 28px 16px; + } +} diff --git a/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.spec.ts b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.spec.ts new file mode 100644 index 0000000000..1f52ad5520 --- /dev/null +++ b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.spec.ts @@ -0,0 +1,89 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule, PopoverController } from '@ionic/angular'; + +import { TransactionStatusInfoPopoverComponent } from './transaction-status-info-popover.component'; +import { getElementBySelector } from 'src/app/core/dom-helpers'; +import { TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; + +describe('TransactionStatusInfoComponent', () => { + let component: TransactionStatusInfoPopoverComponent; + let popoverController: jasmine.SpyObj; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['dismiss']); + + TestBed.configureTestingModule({ + declarations: [TransactionStatusInfoPopoverComponent], + imports: [IonicModule.forRoot()], + providers: [ + { + provide: PopoverController, + useValue: popoverControllerSpy, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(TransactionStatusInfoPopoverComponent); + popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj; + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should close the popover when clicked on close button', () => { + const closeBtn = getElementBySelector(fixture, '[data-testid="close-btn"') as HTMLButtonElement; + closeBtn.click(); + + fixture.detectChanges(); + + expect(popoverController.dismiss).toHaveBeenCalled(); + }); + + describe('template', () => { + describe('title', () => { + it('should display the correct title when transaction status is PENDING', () => { + component.transactionStatus = TransactionStatus.PENDING; + + fixture.detectChanges(); + + const title = getElementBySelector(fixture, '[data-testid="title"'); + expect(title.textContent).toEqual('Transaction Status: Pending'); + }); + + it('should display the correct title when transaction status is POSTED', () => { + component.transactionStatus = TransactionStatus.POSTED; + + fixture.detectChanges(); + + const title = getElementBySelector(fixture, '[data-testid="title"'); + expect(title.textContent).toEqual('Transaction Status: Posted'); + }); + }); + + describe('content', () => { + it('should display the correct content when transaction status is PENDING', () => { + component.transactionStatus = TransactionStatus.PENDING; + + fixture.detectChanges(); + + const content = getElementBySelector(fixture, '[data-testid="content"'); + expect(content.textContent).toEqual( + 'Your transaction status is Pending until your bank processes the transaction.' + ); + }); + + it('should display the correct content when transaction status is POSTED', () => { + component.transactionStatus = TransactionStatus.POSTED; + + fixture.detectChanges(); + + const content = getElementBySelector(fixture, '[data-testid="content"'); + expect(content.textContent).toEqual('The transaction has been processed by your bank.'); + }); + }); + }); +}); diff --git a/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.ts b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.ts new file mode 100644 index 0000000000..bd1226e36e --- /dev/null +++ b/src/app/shared/components/transaction-status-info-popover/transaction-status-info-popover.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import { PopoverController } from '@ionic/angular'; +import { TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; + +@Component({ + selector: 'app-transaction-status-info-popover', + templateUrl: './transaction-status-info-popover.component.html', + styleUrls: ['./transaction-status-info-popover.component.scss'], +}) +export class TransactionStatusInfoPopoverComponent { + @Input() transactionStatus: TransactionStatus; + + constructor(private popoverController: PopoverController) {} + + get TransactionStatus(): typeof TransactionStatus { + return TransactionStatus; + } + + closePopover(): void { + this.popoverController.dismiss(); + } +} diff --git a/src/app/shared/components/transaction-status/transaction-status.component.html b/src/app/shared/components/transaction-status/transaction-status.component.html new file mode 100644 index 0000000000..d0662e4fe8 --- /dev/null +++ b/src/app/shared/components/transaction-status/transaction-status.component.html @@ -0,0 +1,19 @@ +
    +
    + + Transaction Status: + {{ transactionStatus | titlecase }} + + +
    diff --git a/src/app/shared/components/transaction-status/transaction-status.component.scss b/src/app/shared/components/transaction-status/transaction-status.component.scss new file mode 100644 index 0000000000..fd58c2a218 --- /dev/null +++ b/src/app/shared/components/transaction-status/transaction-status.component.scss @@ -0,0 +1,37 @@ +@import '../../../../theme/colors.scss'; + +.transaction-status { + &__container { + display: flex; + align-items: center; + } + + &__status-indicator { + width: 2px; + height: 14px; + border-radius: 1px; + margin-right: 6px; + + &.posted { + background-color: $green; + } + + &.pending { + background-color: $red; + } + } + + &__status-value { + color: $blue-black; + font-weight: 500; + margin-left: 6px; + margin-right: 2px; + line-height: 1.4; + } + + &__info-icon { + color: $blue-black; + width: 16px; + height: 16px; + } +} diff --git a/src/app/shared/components/transaction-status/transaction-status.component.spec.ts b/src/app/shared/components/transaction-status/transaction-status.component.spec.ts new file mode 100644 index 0000000000..8a3b02bf31 --- /dev/null +++ b/src/app/shared/components/transaction-status/transaction-status.component.spec.ts @@ -0,0 +1,55 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { TransactionStatusComponent } from './transaction-status.component'; +import { TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; +import { getElementBySelector } from 'src/app/core/dom-helpers'; + +describe('TransactionStatusComponent', () => { + let component: TransactionStatusComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [TransactionStatusComponent], + imports: [IonicModule.forRoot()], + }).compileComponents(); + + fixture = TestBed.createComponent(TransactionStatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit the infoClick event when clicked on the info icon', () => { + spyOn(component.infoClick, 'emit'); + + const infoIcon = getElementBySelector(fixture, '[data-testid="info-icon"]') as HTMLButtonElement; + infoIcon.click(); + + expect(component.infoClick.emit).toHaveBeenCalledTimes(1); + }); + + describe('template', () => { + it('should display the transaction status value properly when pending', () => { + component.transactionStatus = TransactionStatus.PENDING; + + fixture.detectChanges(); + + const statusValue = getElementBySelector(fixture, '[data-testid="status-value"]'); + expect(statusValue.textContent).toEqual('Pending'); + }); + + it('should display the transaction status value properly when posted', () => { + component.transactionStatus = TransactionStatus.POSTED; + + fixture.detectChanges(); + + const statusValue = getElementBySelector(fixture, '[data-testid="status-value"]'); + expect(statusValue.textContent).toEqual('Posted'); + }); + }); +}); diff --git a/src/app/shared/components/transaction-status/transaction-status.component.ts b/src/app/shared/components/transaction-status/transaction-status.component.ts new file mode 100644 index 0000000000..a379df78a5 --- /dev/null +++ b/src/app/shared/components/transaction-status/transaction-status.component.ts @@ -0,0 +1,17 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { TransactionStatus } from 'src/app/core/models/platform/v1/expense.model'; + +@Component({ + selector: 'app-transaction-status', + templateUrl: './transaction-status.component.html', + styleUrls: ['./transaction-status.component.scss'], +}) +export class TransactionStatusComponent { + @Input() transactionStatus: TransactionStatus; + + @Output() infoClick: EventEmitter = new EventEmitter(); + + get TransactionStatus(): typeof TransactionStatus { + return TransactionStatus; + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index b3b64ec422..1350347150 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -142,6 +142,8 @@ import { CardNumberComponent } from './components/card-number/card-number.compon import { ArrayToCommaListPipe } from './pipes/array-to-comma-list.pipe'; import { CorporateCardComponent } from './components/corporate-card/corporate-card.component'; import { AutofocusDirective } from './directive/autofocus.directive'; +import { TransactionStatusInfoPopoverComponent } from './components/transaction-status-info-popover/transaction-status-info-popover.component'; +import { TransactionStatusComponent } from './components/transaction-status/transaction-status.component'; @NgModule({ declarations: [ @@ -261,6 +263,8 @@ import { AutofocusDirective } from './directive/autofocus.directive'; ArrayToCommaListPipe, CorporateCardComponent, AutofocusDirective, + TransactionStatusComponent, + TransactionStatusInfoPopoverComponent, ], imports: [ CommonModule, @@ -385,6 +389,8 @@ import { AutofocusDirective } from './directive/autofocus.directive'; ArrayToCommaListPipe, CorporateCardComponent, AutofocusDirective, + TransactionStatusComponent, + TransactionStatusInfoPopoverComponent, ], providers: [DecimalPipe, DatePipe, HumanizeCurrencyPipe, ImagePicker, FyCurrencyPipe, ReportState], }) diff --git a/src/assets/svg/info-circle-outline.svg b/src/assets/svg/info-circle-outline.svg new file mode 100644 index 0000000000..72903d98a1 --- /dev/null +++ b/src/assets/svg/info-circle-outline.svg @@ -0,0 +1,9 @@ + + + + + + + + + From b90143bd0933b3fe31a959561c2fa99fe25e743f Mon Sep 17 00:00:00 2001 From: Dimple K H <31147415+Dimple16@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:08:11 +0530 Subject: [PATCH 07/15] feat: Standardize icons - Milestone-5 (#2612) --- src/app/core/mock-data/sidemenu.data.ts | 4 +- src/app/core/models/task-icon.enum.ts | 4 +- src/app/core/services/status.service.ts | 23 +++--- .../test-data/status.service.spec.data.ts | 6 +- .../tasks-card/tasks-card.component.html | 4 +- .../card-transaction-preview.component.html | 2 +- .../add-expense-popover.component.html | 53 ------------- .../add-expense-popover.component.scss | 50 ------------ .../add-expense-popover.component.ts | 79 ------------------- .../fyle/my-expenses/my-expenses.module.ts | 3 +- .../audit-history.component.html | 5 +- .../audit-history.component.scss | 4 + .../fy-alert/fy-alert.component.html | 9 --- .../fy-alert/fy-alert.component.scss | 32 -------- .../fy-alert/fy-alert.component.spec.ts | 25 ------ .../components/fy-alert/fy-alert.component.ts | 16 ---- .../fy-policy-violation.component.html | 2 +- .../components/sidemenu/sidemenu.component.ts | 4 +- src/app/shared/icon/icon.module.ts | 16 +--- src/app/shared/shared.module.ts | 3 - src/assets/svg/call.svg | 3 - src/assets/svg/camera-pointer-bottom-left.svg | 3 - .../svg/camera-pointer-bottom-right.svg | 3 - src/assets/svg/camera-pointer-top-left.svg | 3 - src/assets/svg/camera-pointer-top-right.svg | 3 - src/assets/svg/capped-amount.svg | 3 - src/assets/svg/capture.svg | 3 - src/assets/svg/car.svg | 4 +- src/assets/svg/card-filled.svg | 3 - src/assets/svg/card.svg | 2 +- src/assets/svg/create-expense.svg | 18 ----- src/assets/svg/create-mileage.svg | 18 ----- src/assets/svg/create-per-diem.svg | 26 ------ src/assets/svg/fy-card.svg | 6 -- src/assets/svg/fy-cards-new.svg | 3 - src/assets/svg/fy-corporate-card.svg | 3 - src/assets/svg/insta-fyle.svg | 14 ---- src/assets/svg/phone.svg | 3 + src/assets/svg/plus-minus.svg | 3 + 39 files changed, 44 insertions(+), 424 deletions(-) delete mode 100644 src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.html delete mode 100644 src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.scss delete mode 100644 src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.ts delete mode 100644 src/app/shared/components/fy-alert/fy-alert.component.html delete mode 100644 src/app/shared/components/fy-alert/fy-alert.component.scss delete mode 100644 src/app/shared/components/fy-alert/fy-alert.component.spec.ts delete mode 100644 src/app/shared/components/fy-alert/fy-alert.component.ts delete mode 100644 src/assets/svg/call.svg delete mode 100644 src/assets/svg/camera-pointer-bottom-left.svg delete mode 100644 src/assets/svg/camera-pointer-bottom-right.svg delete mode 100644 src/assets/svg/camera-pointer-top-left.svg delete mode 100644 src/assets/svg/camera-pointer-top-right.svg delete mode 100644 src/assets/svg/capped-amount.svg delete mode 100644 src/assets/svg/capture.svg delete mode 100644 src/assets/svg/card-filled.svg delete mode 100644 src/assets/svg/create-expense.svg delete mode 100644 src/assets/svg/create-mileage.svg delete mode 100644 src/assets/svg/create-per-diem.svg delete mode 100755 src/assets/svg/fy-card.svg delete mode 100644 src/assets/svg/fy-cards-new.svg delete mode 100644 src/assets/svg/fy-corporate-card.svg delete mode 100644 src/assets/svg/insta-fyle.svg create mode 100644 src/assets/svg/phone.svg create mode 100644 src/assets/svg/plus-minus.svg diff --git a/src/app/core/mock-data/sidemenu.data.ts b/src/app/core/mock-data/sidemenu.data.ts index 74d258442f..76079acadd 100644 --- a/src/app/core/mock-data/sidemenu.data.ts +++ b/src/app/core/mock-data/sidemenu.data.ts @@ -38,7 +38,7 @@ export const PrimaryOptionsRes1: Partial[] = [ title: 'Personal Cards', isVisible: true, route: ['/', 'enterprise', 'personal_cards'], - icon: 'fy-corporate-card', + icon: 'card', disabled: false, }, { @@ -61,7 +61,7 @@ export const UpdatedOptionsRes: Partial = { title: 'Personal Cards', isVisible: true, route: ['/', 'enterprise', 'personal_cards'], - icon: 'fy-corporate-card', + icon: 'card', disabled: false, }; diff --git a/src/app/core/models/task-icon.enum.ts b/src/app/core/models/task-icon.enum.ts index 00a592a05d..b4fcefd51e 100644 --- a/src/app/core/models/task-icon.enum.ts +++ b/src/app/core/models/task-icon.enum.ts @@ -1,6 +1,6 @@ export enum TaskIcon { REPORT = 'fy-expense', WARNING = 'warning-new', - ADVANCE = 'advances', - MOBILE = 'call', + ADVANCE = 'wallet', + MOBILE = 'phone', } diff --git a/src/app/core/services/status.service.ts b/src/app/core/services/status.service.ts index b7e1ad9343..e610b6d757 100644 --- a/src/app/core/services/status.service.ts +++ b/src/app/core/services/status.service.ts @@ -14,13 +14,12 @@ export class StatusService { find(objectType: string, objectId: string): Observable { return this.apiService.get('/' + objectType + '/' + objectId + '/estatuses').pipe( - map( - (estatuses: ExtendedStatus[]) => - estatuses?.map((estatus) => { - estatus.st_created_at = new Date(estatus.st_created_at); - return estatus; - }), - ), + map((estatuses: ExtendedStatus[]) => + estatuses?.map((estatus) => { + estatus.st_created_at = new Date(estatus.st_created_at); + return estatus; + }) + ) ); } @@ -28,7 +27,7 @@ export class StatusService { objectType: string, objectId: string, status: { comment: string | ExtendedStatus }, - notify: boolean = false, + notify: boolean = false ): Observable { return this.apiService.post('/' + objectType + '/' + objectId + '/statuses', { status, @@ -203,19 +202,19 @@ export class StatusService { case lowerCaseComment.indexOf('auto-matched by') > -1: statusCategory = { category: 'Card Transaction Matched', - icon: 'card-filled', + icon: 'card', }; break; case lowerCaseComment.indexOf('unmatched by') > -1: statusCategory = { category: 'Expense Unmatched', - icon: 'fy-corporate-card', + icon: 'card', }; break; case lowerCaseComment.indexOf('matched by') > -1: statusCategory = { category: 'Expense Matched', - icon: 'card-filled', + icon: 'card', }; break; case lowerCaseComment.indexOf('expense is a possible duplicate') > -1: @@ -260,7 +259,7 @@ export class StatusService { if (sortedStatus.length) { return sortedStatus[0].st_comment; } - }), + }) ); } diff --git a/src/app/core/test-data/status.service.spec.data.ts b/src/app/core/test-data/status.service.spec.data.ts index 24767583c0..becf167228 100644 --- a/src/app/core/test-data/status.service.spec.data.ts +++ b/src/app/core/test-data/status.service.spec.data.ts @@ -1182,7 +1182,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'Card Transaction Matched', - icon: 'card-filled', + icon: 'card', }, }, { @@ -1202,7 +1202,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'Expense Unmatched', - icon: 'fy-corporate-card', + icon: 'card', }, }, { @@ -1222,7 +1222,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'Expense Matched', - icon: 'card-filled', + icon: 'card', }, }, { diff --git a/src/app/fyle/dashboard/tasks/tasks-card/tasks-card.component.html b/src/app/fyle/dashboard/tasks/tasks-card/tasks-card.component.html index aac972f6f7..3b6051c958 100644 --- a/src/app/fyle/dashboard/tasks/tasks-card/tasks-card.component.html +++ b/src/app/fyle/dashboard/tasks/tasks-card/tasks-card.component.html @@ -10,8 +10,8 @@ [ngClass]="{ 'task--icon__warning': task.icon === 'warning-new', 'task--icon__expense': task.icon === 'fy-expense', - 'task--icon__advance': task.icon === 'advances', - 'task--icon__mobile': task.icon === 'call' + 'task--icon__advance': task.icon === 'wallet', + 'task--icon__mobile': task.icon === 'phone' }" [src]="'../../../../../assets/svg/' + task.icon + '.svg'" > diff --git a/src/app/fyle/merge-expense/card-transaction-preview/card-transaction-preview.component.html b/src/app/fyle/merge-expense/card-transaction-preview/card-transaction-preview.component.html index d3fc055677..c9240cbf47 100644 --- a/src/app/fyle/merge-expense/card-transaction-preview/card-transaction-preview.component.html +++ b/src/app/fyle/merge-expense/card-transaction-preview/card-transaction-preview.component.html @@ -1,7 +1,7 @@
    Card Transaction
    - +
    {{ transactionDetails.vendor || transactionDetails.description | ellipsis : 20 }}, diff --git a/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.html b/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.html deleted file mode 100644 index 90e1ecba80..0000000000 --- a/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.html +++ /dev/null @@ -1,53 +0,0 @@ -
    -
    -
    - Instafyle -
    -
    -
    Capture Receipt
    -
    Scan paper receipts using camera
    -
    -
    - -
    -
    - createExpense -
    -
    -
    Add Expense
    -
    Enter details manually
    -
    -
    - -
    -
    - createMileage -
    -
    -
    Add Mileage
    -
    For your daily travels
    -
    -
    - -
    -
    - createPerDiem -
    -
    -
    Add Per Diem
    -
    Log your daily per diems
    -
    -
    -
    diff --git a/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.scss b/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.scss deleted file mode 100644 index 2b91720e99..0000000000 --- a/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.scss +++ /dev/null @@ -1,50 +0,0 @@ -$details-color: #ababab; - -.add-expense-popover-internal { - display: flex; - flex-direction: column; - height: 100%; - padding: 0 16px; - - &--element { - display: flex; - justify-content: flex-start; - align-items: center; - flex: 1; - border-bottom: 1px solid #e2e2e2; - padding: 16px 8px 16px 16px; - } - - &--icon { - max-height: 32px; - min-height: 32px; - max-width: 32px; - min-width: 32px; - margin: 12px; - } - - &--head { - font-size: 18px; - color: #000; - line-height: 1.33; - font-weight: 500; - } - - &--info { - font-size: 16px; - color: #ababab; - line-height: 1.5; - } - - &--highlight { - font-size: 12px; - background: #ff9900; - padding: 3px 5px; - border-radius: 4px; - color: #fff; - line-height: 1; - display: inline-block; - vertical-align: middle; - margin-left: 5px; - } -} diff --git a/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.ts b/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.ts deleted file mode 100644 index 6e9ea1f724..0000000000 --- a/src/app/fyle/my-expenses/add-expense-popover/add-expense-popover.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { PopoverController } from '@ionic/angular'; -import { Router } from '@angular/router'; -import { TransactionsOutboxService } from '../../../core/services/transactions-outbox.service'; -import { TrackingService } from '../../../core/services/tracking.service'; - -@Component({ - selector: 'app-add-expense-popover', - templateUrl: './add-expense-popover.component.html', - styleUrls: ['./add-expense-popover.component.scss'], -}) -export class AddExpensePopoverComponent implements OnInit { - @Input() isInstaFyleEnabled: boolean; - - @Input() isMileageEnabled: boolean; - - @Input() isPerDiemEnabled: boolean; - - @Input() isBulkFyleEnabled: boolean; - - constructor( - private popoverController: PopoverController, - private router: Router, - private transactionOutboxService: TransactionsOutboxService, - private trackingService: TrackingService - ) {} - - ngOnInit() {} - - async instafyle(event) { - await this.popoverController.dismiss(); - await this.router.navigate([ - '/', - 'enterprise', - 'camera_overlay', - { - from: 'my_expenses', - }, - ]); - } - - async createExpense(event) { - await this.popoverController.dismiss(); - this.trackingService.eventTrack('Click Add Expense'); - await this.router.navigate([ - '/', - 'enterprise', - 'add_edit_expense', - { - persist_filters: true, - }, - ]); - } - - async createMileage(event) { - this.trackingService.eventTrack('Click Add Mileage'); - await this.popoverController.dismiss(); - await this.router.navigate([ - '/', - 'enterprise', - 'add_edit_mileage', - { - persist_filters: true, - }, - ]); - } - - async createPerDiem(event) { - await this.popoverController.dismiss(); - await this.router.navigate([ - '/', - 'enterprise', - 'add_edit_per_diem', - { - persist_filters: true, - }, - ]); - } -} diff --git a/src/app/fyle/my-expenses/my-expenses.module.ts b/src/app/fyle/my-expenses/my-expenses.module.ts index 5da137942d..3aedc22dd8 100644 --- a/src/app/fyle/my-expenses/my-expenses.module.ts +++ b/src/app/fyle/my-expenses/my-expenses.module.ts @@ -12,7 +12,6 @@ import { MatNativeDateModule, MatRippleModule } from '@angular/material/core'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatIconModule } from '@angular/material/icon'; import { SharedModule } from 'src/app/shared/shared.module'; -import { AddExpensePopoverComponent } from './add-expense-popover/add-expense-popover.component'; import { AddTxnToReportDialogComponent } from './add-txn-to-report-dialog/add-txn-to-report-dialog.component'; import { ReportStates } from '../dashboard/stat-badge/report-states'; import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; @@ -42,6 +41,6 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; SharedModule, MatCheckboxModule, ], - declarations: [MyExpensesPage, AddExpensePopoverComponent, AddTxnToReportDialogComponent], + declarations: [MyExpensesPage, AddTxnToReportDialogComponent], }) export class MyExpensesPageModule {} diff --git a/src/app/shared/components/comments-history/audit-history/audit-history.component.html b/src/app/shared/components/comments-history/audit-history/audit-history.component.html index 4d850b934d..fd4b12b25b 100644 --- a/src/app/shared/components/comments-history/audit-history/audit-history.component.html +++ b/src/app/shared/components/comments-history/audit-history/audit-history.component.html @@ -11,12 +11,13 @@ 'audit-history--delete': estatus.st.icon === 'fy-delete', 'icon--alert-warning': estatus.st.icon === 'danger', 'icon--flagged': estatus.st.category === 'Flagged', - 'icon--unflagged': estatus.st.category === 'Unflagged' + 'icon--unflagged': estatus.st.category === 'Unflagged', + 'audit-history--card-filled': estatus.st.category === ('Card Transaction Matched' || 'Expense Matched') }" > {{ estatus.st.category }}
    - {{ estatus.st_created_at | date: 'MMM d, YYYY h:mm a' }} + {{ estatus.st_created_at | date : 'MMM d, YYYY h:mm a' }}
    diff --git a/src/app/shared/components/comments-history/audit-history/audit-history.component.scss b/src/app/shared/components/comments-history/audit-history/audit-history.component.scss index c14d6db973..bbdb2b1e3d 100644 --- a/src/app/shared/components/comments-history/audit-history/audit-history.component.scss +++ b/src/app/shared/components/comments-history/audit-history/audit-history.component.scss @@ -68,4 +68,8 @@ &--diff { margin-left: 12px; } + + &--card-filled { + color: $green; + } } diff --git a/src/app/shared/components/fy-alert/fy-alert.component.html b/src/app/shared/components/fy-alert/fy-alert.component.html deleted file mode 100644 index 79c0a33f0b..0000000000 --- a/src/app/shared/components/fy-alert/fy-alert.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - info - warning - -
    {{ message }}
    -
    diff --git a/src/app/shared/components/fy-alert/fy-alert.component.scss b/src/app/shared/components/fy-alert/fy-alert.component.scss deleted file mode 100644 index f2fcc163c8..0000000000 --- a/src/app/shared/components/fy-alert/fy-alert.component.scss +++ /dev/null @@ -1,32 +0,0 @@ -.alert { - padding: 8px 12px; - margin-top: 10px; - margin-bottom: 10px; - font-size: 12px; - border-radius: 4px; - color: #5a5d72; - border: 1px solid; - display: flex; - &__warning { - background-color: #fdd13a08; - border-color: #fdd13a1f; - } - - &__info { - background-color: rgba(0, 98, 255, 0.02); - border-color: rgba(0, 98, 255, 0.039); - } - - &--icon { - margin-right: 12px; - font-size: 18px; - - &__warning { - color: #e1af05; - } - - &__info { - color: #5c98e5; - } - } -} diff --git a/src/app/shared/components/fy-alert/fy-alert.component.spec.ts b/src/app/shared/components/fy-alert/fy-alert.component.spec.ts deleted file mode 100644 index 3ec7cdc20a..0000000000 --- a/src/app/shared/components/fy-alert/fy-alert.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { MatIconModule } from '@angular/material/icon'; -import { IonicModule } from '@ionic/angular'; - -import { FyAlertComponent } from './fy-alert.component'; - -describe('FyAlertComponent', () => { - let fyAlertComponent: FyAlertComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [FyAlertComponent], - imports: [IonicModule.forRoot(), MatIconModule], - }).compileComponents(); - - fixture = TestBed.createComponent(FyAlertComponent); - fyAlertComponent = fixture.componentInstance; - fixture.detectChanges(); - })); - - it('should create', () => { - expect(fyAlertComponent).toBeTruthy(); - }); -}); diff --git a/src/app/shared/components/fy-alert/fy-alert.component.ts b/src/app/shared/components/fy-alert/fy-alert.component.ts deleted file mode 100644 index a6f438710d..0000000000 --- a/src/app/shared/components/fy-alert/fy-alert.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; - -@Component({ - selector: 'app-fy-alert', - templateUrl: './fy-alert.component.html', - styleUrls: ['./fy-alert.component.scss'], -}) -export class FyAlertComponent implements OnInit { - @Input() type: string; - - @Input() message: string; - - constructor() {} - - ngOnInit() {} -} diff --git a/src/app/shared/components/fy-policy-violation/fy-policy-violation.component.html b/src/app/shared/components/fy-policy-violation/fy-policy-violation.component.html index b2532c4c00..1afdc1ff1c 100644 --- a/src/app/shared/components/fy-policy-violation/fy-policy-violation.component.html +++ b/src/app/shared/components/fy-policy-violation/fy-policy-violation.component.html @@ -43,7 +43,7 @@ diff --git a/src/app/shared/components/sidemenu/sidemenu.component.ts b/src/app/shared/components/sidemenu/sidemenu.component.ts index 226c05059d..3b2b3229f1 100644 --- a/src/app/shared/components/sidemenu/sidemenu.component.ts +++ b/src/app/shared/components/sidemenu/sidemenu.component.ts @@ -224,7 +224,7 @@ export class SidemenuComponent implements OnInit { { title: 'Cards', isVisible: cardOptions.length ? true : false, - icon: 'fy-corporate-card', + icon: 'card', disabled: !isConnected, isDropdownOpen: false, dropdownOptions: cardOptions, @@ -258,7 +258,7 @@ export class SidemenuComponent implements OnInit { if (cardOptions.length === 1) { this.updateSidemenuOption(primaryOptions, 'Cards', { ...cardOptions[0], - icon: 'fy-corporate-card', + icon: 'card', disabled: !isConnected, }); } diff --git a/src/app/shared/icon/icon.module.ts b/src/app/shared/icon/icon.module.ts index c5af145b28..0d7f282083 100644 --- a/src/app/shared/icon/icon.module.ts +++ b/src/app/shared/icon/icon.module.ts @@ -18,20 +18,12 @@ export class IconModule { 'attachment.svg', 'building.svg', 'car.svg', + 'card.svg', 'chevron-right.svg', - 'create-expense.svg', - 'create-mileage.svg', - 'create-per-diem.svg', 'curve.svg', 'comments-zero-state', 'circle.svg', - 'card-filled.svg', 'close.svg', - 'capture.svg', - 'camera-pointer-top-left.svg', - 'camera-pointer-top-right.svg', - 'camera-pointer-bottom-left.svg', - 'camera-pointer-bottom-right.svg', 'danger.svg', 'duplicate.svg', 'expense.svg', @@ -51,12 +43,9 @@ export class IconModule { 'fy-camera.svg', 'fy-car-mini.svg', 'fy-car.svg', - 'fy-card.svg', - 'fy-cards-new.svg', 'fy-chat.svg', 'fy-classify-ccc.svg', 'fy-close.svg', - 'fy-corporate-card.svg', 'fy-dashboard-new.svg', 'fy-delegate-switch.svg', 'fy-delete.svg', @@ -109,7 +98,6 @@ export class IconModule { 'fy-unmatched.svg', 'fy-location.svg', 'gallery.svg', - 'insta-fyle.svg', 'ionic-log-out-outline.svg', 'information.svg', 'logo-icon-white.svg', @@ -118,8 +106,10 @@ export class IconModule { 'navigate-left.svg', 'navigate-right.svg', 'no-attachment.svg', + 'phone.svg', 'professional-service.svg', 'plus.svg', + 'plus-minus.svg', 'profile.svg', 'rectangle.svg', 'return-home.svg', diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 1350347150..7d2d80a0fb 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -27,7 +27,6 @@ import { FyLocationModalComponent } from './components/fy-location/fy-location-m import { FyMultiselectModalComponent } from './components/fy-multiselect/fy-multiselect-modal/fy-multiselect-modal.component'; import { FyUserlistModalComponent } from './components/fy-userlist/fy-userlist-modal/fy-userlist-modal.component'; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { FyAlertComponent } from './components/fy-alert/fy-alert.component'; import { AdvanceState } from './pipes/advance-state.pipe'; import { InitialsPipe } from './pipes/initials.pipe'; import { ApproverDialogComponent } from './components/fy-approver/add-approvers-popover/approver-dialog/approver-dialog.component'; @@ -165,7 +164,6 @@ import { TransactionStatusComponent } from './components/transaction-status/tran FyLocationModalComponent, FyMultiselectModalComponent, FyUserlistModalComponent, - FyAlertComponent, DelegatedAccMessageComponent, CommentsHistoryComponent, ViewCommentComponent, @@ -302,7 +300,6 @@ import { TransactionStatusComponent } from './components/transaction-status/tran FyLocationComponent, FyMultiselectComponent, FyUserlistComponent, - FyAlertComponent, AdvanceState, SnakeCaseToSpaceCase, InitialsPipe, diff --git a/src/assets/svg/call.svg b/src/assets/svg/call.svg deleted file mode 100644 index 1379ac2432..0000000000 --- a/src/assets/svg/call.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/camera-pointer-bottom-left.svg b/src/assets/svg/camera-pointer-bottom-left.svg deleted file mode 100644 index bb67c26773..0000000000 --- a/src/assets/svg/camera-pointer-bottom-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/camera-pointer-bottom-right.svg b/src/assets/svg/camera-pointer-bottom-right.svg deleted file mode 100644 index 628c607c83..0000000000 --- a/src/assets/svg/camera-pointer-bottom-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/camera-pointer-top-left.svg b/src/assets/svg/camera-pointer-top-left.svg deleted file mode 100644 index 6f3be65a8b..0000000000 --- a/src/assets/svg/camera-pointer-top-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/camera-pointer-top-right.svg b/src/assets/svg/camera-pointer-top-right.svg deleted file mode 100644 index eac645cca3..0000000000 --- a/src/assets/svg/camera-pointer-top-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/capped-amount.svg b/src/assets/svg/capped-amount.svg deleted file mode 100644 index 5a86fb7c00..0000000000 --- a/src/assets/svg/capped-amount.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/capture.svg b/src/assets/svg/capture.svg deleted file mode 100644 index 449fa7907c..0000000000 --- a/src/assets/svg/capture.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/car.svg b/src/assets/svg/car.svg index a3a184072c..896a91709c 100644 --- a/src/assets/svg/car.svg +++ b/src/assets/svg/car.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/svg/card-filled.svg b/src/assets/svg/card-filled.svg deleted file mode 100644 index e38b5a1d4b..0000000000 --- a/src/assets/svg/card-filled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/card.svg b/src/assets/svg/card.svg index a33bf41ed0..5621c1490d 100644 --- a/src/assets/svg/card.svg +++ b/src/assets/svg/card.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/svg/create-expense.svg b/src/assets/svg/create-expense.svg deleted file mode 100644 index 092b4ae973..0000000000 --- a/src/assets/svg/create-expense.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - Icon - - Created with Sketch. - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/svg/create-mileage.svg b/src/assets/svg/create-mileage.svg deleted file mode 100644 index 402913eca5..0000000000 --- a/src/assets/svg/create-mileage.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - Icon - - Created with Sketch. - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/svg/create-per-diem.svg b/src/assets/svg/create-per-diem.svg deleted file mode 100644 index 7ec8dc15b4..0000000000 --- a/src/assets/svg/create-per-diem.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - Group 3newicon - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/assets/svg/fy-card.svg b/src/assets/svg/fy-card.svg deleted file mode 100755 index 94c392dc3c..0000000000 --- a/src/assets/svg/fy-card.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/assets/svg/fy-cards-new.svg b/src/assets/svg/fy-cards-new.svg deleted file mode 100644 index 05bfe55f12..0000000000 --- a/src/assets/svg/fy-cards-new.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/assets/svg/fy-corporate-card.svg b/src/assets/svg/fy-corporate-card.svg deleted file mode 100644 index 8f39044959..0000000000 --- a/src/assets/svg/fy-corporate-card.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/svg/insta-fyle.svg b/src/assets/svg/insta-fyle.svg deleted file mode 100644 index 24c7c22732..0000000000 --- a/src/assets/svg/insta-fyle.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - Icon - - Created with Sketch. - - - - - - - - - \ No newline at end of file diff --git a/src/assets/svg/phone.svg b/src/assets/svg/phone.svg new file mode 100644 index 0000000000..c3959ab315 --- /dev/null +++ b/src/assets/svg/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/plus-minus.svg b/src/assets/svg/plus-minus.svg new file mode 100644 index 0000000000..d488d964a7 --- /dev/null +++ b/src/assets/svg/plus-minus.svg @@ -0,0 +1,3 @@ + + + From c3d4461941ab23a067f77d54aa7616bddd4673bf Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Tue, 28 Nov 2023 18:35:15 +0530 Subject: [PATCH 08/15] fix: Added a safeguard for transaction status field (#2614) --- src/app/fyle/add-edit-expense/add-edit-expense.page.html | 4 ++-- src/app/fyle/view-expense/view-expense.page.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.html b/src/app/fyle/add-edit-expense/add-edit-expense.page.html index 3d4b9dcab8..29dcf9f031 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.html +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.html @@ -185,11 +185,11 @@
    diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index 9b569fce2d..f3833bddb7 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -112,11 +112,11 @@
    From b2f8c8612766783c64a77f44e00cc7f69b5a7796 Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:45:53 +0530 Subject: [PATCH 09/15] fix: Fixed category dependent location fields not coming up in view expense page (#2613) --- src/app/fyle/view-expense/view-expense.page.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index f3833bddb7..dc7bd31d66 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -411,7 +411,7 @@
    - + @@ -426,12 +426,12 @@
    From
    -
    {{ expense.locations[0].display }}
    +
    {{ expense.locations[0].city }}
    - + @@ -444,7 +444,7 @@
    To
    -
    {{ expense.locations[1].display }}
    +
    {{ expense.locations[1].city }}
    @@ -559,9 +559,9 @@
    - +
    @@ -573,7 +573,7 @@
    City
    -
    {{ expense.locations[0].display }}
    +
    {{ expense.locations[0].city }}
    From c2495d016a63ca465a2206e7c9eb151752bbf25e Mon Sep 17 00:00:00 2001 From: Aiyush Date: Wed, 29 Nov 2023 13:11:26 +0530 Subject: [PATCH 10/15] fix: Added fix for cost center not getting assigned upon non-manual selection (#2606) * Added fix for cost center not getting assigned upon non-manual selection * Fixed tests --------- Co-authored-by: julias0 Co-authored-by: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Co-authored-by: OmkarJ13 --- src/app/fyle/merge-expense/merge-expense-2.page.spec.ts | 2 +- src/app/fyle/merge-expense/merge-expense.page.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/fyle/merge-expense/merge-expense-2.page.spec.ts b/src/app/fyle/merge-expense/merge-expense-2.page.spec.ts index d542e58160..5d9b9eca85 100644 --- a/src/app/fyle/merge-expense/merge-expense-2.page.spec.ts +++ b/src/app/fyle/merge-expense/merge-expense-2.page.spec.ts @@ -272,7 +272,7 @@ export function TestCases2(getTestBed) { const costCenterCall = mergeExpensesService.getFieldValueOnChange.calls.argsFor(9); expect(costCenterCall[0]).toEqual(optionsData13); expect(costCenterCall[1]).toEqual(true); - expect(costCenterCall[2]).toEqual('213'); + expect(costCenterCall[2]).toEqual(12488); expect(costCenterCall[3]).toEqual('COST_CENTER_1'); }); diff --git a/src/app/fyle/merge-expense/merge-expense.page.ts b/src/app/fyle/merge-expense/merge-expense.page.ts index 8a8b2eb496..4b982408f4 100644 --- a/src/app/fyle/merge-expense/merge-expense.page.ts +++ b/src/app/fyle/merge-expense/merge-expense.page.ts @@ -403,7 +403,7 @@ export class MergeExpensePage implements OnInit, AfterViewChecked { costCenter: this.mergeExpensesService.getFieldValueOnChange( constCenterOptionsData, this.touchedGenericFields?.includes('costCenter'), - this.expenses[selectedIndex]?.tx_cost_center_name, + this.expenses[selectedIndex]?.tx_cost_center_id, this.genericFieldsFormValues?.costCenter ), purpose: this.mergeExpensesService.getFieldValueOnChange( From 7e0f7a3e5f4895a7caa8bfbc527e8994ae3ff563 Mon Sep 17 00:00:00 2001 From: jayfyle <115472256+jayfyle@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:35:12 +0530 Subject: [PATCH 11/15] feat: Move My Create Report To Platform: Moved API call of expenses to platform (#2572) * fix: Per Diem scan fix and redirection fix for view report (#2535) * fix: Per Diem scan fix and redirection fix for view report * small fix * pr comments * moved loading flag * fix: Contains fix for items getting filtered in merchant field : https://app.clickup.com/t/85ztztwg6 (#2539) * fix: Added fix for Item not in list: https://app.clickup.com/t/85ztztwg6 * fix: console.log removed * fix: Hack for diaplay_name field * fix: moved the exception to a seprate function * feat: Added Display Name Additional condition * fix; comment added * feat: added tests * test: UT fix * test: test file formatting * test: text change * feat: added any type in template ref * fix: review based typing * fix: typing added to noop methods * feat: typing fix * feat: comment updated * moved report expenses to platform * resolved comments * used variables in condition * resolved comments * test: Move My Create To Platform: Fixing Tests (#2577) * fixed missing methods in tests * fixed tests * removed fdescribe * fixed issues due to resolving comments * renaming and updating conditions --------- Co-authored-by: Jay Budhadev * fixing broken tests * fixing failing test -1 * fixing failing test 2 * increased coverage * increased coverage -2 * added coverage of getAllExpenses * removed fdescribe --------- Co-authored-by: Suyash Patil <127177049+suyashpatil78@users.noreply.github.com> Co-authored-by: Jay Budhadev Co-authored-by: Suraj Kumar --- .../mock-data/platform/v1/expense.data.ts | 33 ++++ .../v1/expenses-query-params.model.ts | 1 + .../platform/v1/shared/expense.service.ts | 2 - .../v1/spender/expenses.service.spec.ts | 42 ++++- .../platform/v1/spender/expenses.service.ts | 18 +++ .../my-create-report.page.html | 19 +-- .../my-create-report.page.spec.ts | 112 +++++++------ .../my-create-report/my-create-report.page.ts | 147 ++++++++---------- 8 files changed, 234 insertions(+), 140 deletions(-) diff --git a/src/app/core/mock-data/platform/v1/expense.data.ts b/src/app/core/mock-data/platform/v1/expense.data.ts index ce9d7662eb..0cbeaa9866 100644 --- a/src/app/core/mock-data/platform/v1/expense.data.ts +++ b/src/app/core/mock-data/platform/v1/expense.data.ts @@ -1003,3 +1003,36 @@ export const perDiemExpenseWithMultipleNumDays: Expense = { category: perDiemCategory, per_diem_num_days: 3, }; + +export const readyToReportExpensesData: Expense[] = [ + { + ...expenseData, + amount: 50, + id: 'tx3rb9ZcrTRn', + }, + { + ...expenseData, + amount: 100, + id: 'txWjW9qT2Vf1', + }, +]; + +export const readyToReportExpensesData2: Expense[] = [ + { + ...expenseData, + amount: 100, + is_reimbursable: true, + id: 'tx3rb9ZcrTRn', + }, + { + ...expenseData, + amount: 100, + is_reimbursable: true, + id: 'txWjW9qT2Vf1', + }, +]; + +export const nonReimbursableExpense: Expense = { + ...expenseData, + is_reimbursable: false, +}; diff --git a/src/app/core/models/platform/v1/expenses-query-params.model.ts b/src/app/core/models/platform/v1/expenses-query-params.model.ts index beaf667045..8b67ceecff 100644 --- a/src/app/core/models/platform/v1/expenses-query-params.model.ts +++ b/src/app/core/models/platform/v1/expenses-query-params.model.ts @@ -2,4 +2,5 @@ import { APIQueryParams } from './query-params.model'; export interface ExpensesQueryParams extends APIQueryParams { report_id?: string; + queryParams?: Record; } diff --git a/src/app/core/services/platform/v1/shared/expense.service.ts b/src/app/core/services/platform/v1/shared/expense.service.ts index 1e2c8652b8..3ad83dac7f 100644 --- a/src/app/core/services/platform/v1/shared/expense.service.ts +++ b/src/app/core/services/platform/v1/shared/expense.service.ts @@ -6,8 +6,6 @@ import { Expense } from 'src/app/core/models/platform/v1/expense.model'; providedIn: 'root', }) export class ExpenseService { - constructor() {} - getIsDraft(expense: Expense): boolean { return expense.state && expense.state === ExpenseState.DRAFT; } diff --git a/src/app/core/services/platform/v1/spender/expenses.service.spec.ts b/src/app/core/services/platform/v1/spender/expenses.service.spec.ts index da20a7b0ee..41dc43069a 100644 --- a/src/app/core/services/platform/v1/spender/expenses.service.spec.ts +++ b/src/app/core/services/platform/v1/spender/expenses.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { of } from 'rxjs'; import { ExpensesService } from './expenses.service'; import { SpenderService } from './spender.service'; -import { expenseData } from 'src/app/core/mock-data/platform/v1/expense.data'; +import { expenseData, readyToReportExpensesData2 } from 'src/app/core/mock-data/platform/v1/expense.data'; import { PAGINATION_SIZE } from 'src/app/constants'; import { expensesResponse } from 'src/app/core/mock-data/platform/v1/expenses-response.data'; import { getExpensesQueryParams } from 'src/app/core/mock-data/platform/v1/expenses-query-params.data'; @@ -75,6 +75,46 @@ describe('ExpensesService', () => { }); }); + describe('getAllExpenses(): ', () => { + it('should get all expenses for multiple pages', (done) => { + spyOn(service, 'getExpensesCount').and.returnValue(of(4)); + spyOn(service, 'getExpenses').and.returnValue(of(readyToReportExpensesData2)); + + const queryParams = { + report_id: 'is.null', + state: 'in.(COMPLETE)', + order: 'spent_at.desc', + or: ['(policy_amount.is.null,policy_amount.gt.0.0001)'], + }; + + service.getAllExpenses({ queryParams }).subscribe((res) => { + expect(res.length).toEqual(4); + expect(service.getExpensesCount).toHaveBeenCalledOnceWith(queryParams); + expect(service.getExpenses).toHaveBeenCalledTimes(2); + done(); + }); + }); + + it('should get all expenses in a single page', (done) => { + spyOn(service, 'getExpensesCount').and.returnValue(of(2)); + spyOn(service, 'getExpenses').and.returnValue(of(readyToReportExpensesData2)); + + const queryParams = { + report_id: 'is.null', + state: 'in.(COMPLETE)', + order: 'spent_at.desc', + or: ['(policy_amount.is.null,policy_amount.gt.0.0001)'], + }; + + service.getAllExpenses({ queryParams }).subscribe((res) => { + expect(res.length).toEqual(2); + expect(service.getExpensesCount).toHaveBeenCalledOnceWith(queryParams); + expect(service.getExpenses).toHaveBeenCalledTimes(1); + done(); + }); + }); + }); + it('getReportExpenses(): should return all expenses', (done) => { spyOn(service, 'getExpensesCount').and.returnValue(of(2)); spyOn(service, 'getExpenses').and.returnValue(of(expensesResponse.data)); diff --git a/src/app/core/services/platform/v1/spender/expenses.service.ts b/src/app/core/services/platform/v1/spender/expenses.service.ts index eea8f98dfb..53fafaad28 100644 --- a/src/app/core/services/platform/v1/spender/expenses.service.ts +++ b/src/app/core/services/platform/v1/spender/expenses.service.ts @@ -34,6 +34,24 @@ export class ExpensesService { .pipe(map((expenses) => expenses.data)); } + getAllExpenses(params: ExpensesQueryParams): Observable { + return this.getExpensesCount(params.queryParams).pipe( + switchMap((count) => { + count = count > this.paginationSize ? count / this.paginationSize : 1; + return range(0, count); + }), + concatMap((page) => + this.getExpenses({ + offset: this.paginationSize * page, + limit: this.paginationSize, + ...params.queryParams, + order: params.order || 'spent_at.desc,created_at.desc,id.desc', + }) + ), + reduce((acc, curr) => acc.concat(curr), [] as Expense[]) + ); + } + getReportExpenses(reportId: string): Observable { const params = { report_id: `eq.${reportId}`, diff --git a/src/app/fyle/my-create-report/my-create-report.page.html b/src/app/fyle/my-create-report/my-create-report.page.html index 22042bcaee..6fb9916432 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.html +++ b/src/app/fyle/my-create-report/my-create-report.page.html @@ -36,7 +36,7 @@
    Please enter report name.
    -
    +
    Create First Expenses
    -
    EXPENSES
    -
    +
    EXPENSES
    +
    -
    -
    - +
    + - +
    diff --git a/src/app/fyle/my-create-report/my-create-report.page.spec.ts b/src/app/fyle/my-create-report/my-create-report.page.spec.ts index 7eeaaa6d55..b793bce577 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.spec.ts +++ b/src/app/fyle/my-create-report/my-create-report.page.spec.ts @@ -9,13 +9,13 @@ import { IonicModule } from '@ionic/angular'; import { cloneDeep } from 'lodash'; import { of } from 'rxjs'; import { getElementBySelector } from 'src/app/core/dom-helpers'; +import { selectedExpense1, selectedExpenses } from 'src/app/core/mock-data/expense.data'; import { - apiExpenseRes, - etxncListData, - perDiemExpenseSingleNumDays, - selectedExpense1, - selectedExpenses, -} from 'src/app/core/mock-data/expense.data'; + expenseData, + nonReimbursableExpense, + readyToReportExpensesData, + readyToReportExpensesData2, +} from 'src/app/core/mock-data/platform/v1/expense.data'; import { reportUnflattenedData } from 'src/app/core/mock-data/report-v1.data'; import { CurrencyService } from 'src/app/core/services/currency.service'; import { LoaderService } from 'src/app/core/services/loader.service'; @@ -27,6 +27,7 @@ import { HumanizeCurrencyPipe } from 'src/app/shared/pipes/humanize-currency.pip import { StorageService } from '../../core/services/storage.service'; import { TrackingService } from '../../core/services/tracking.service'; import { MyCreateReportPage } from './my-create-report.page'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; describe('MyCreateReportPage', () => { let component: MyCreateReportPage; @@ -40,6 +41,7 @@ describe('MyCreateReportPage', () => { let trackingService: jasmine.SpyObj; let storageService: jasmine.SpyObj; let refinerService: jasmine.SpyObj; + let expensesService: jasmine.SpyObj; beforeEach(waitForAsync(() => { const transactionServiceSpy = jasmine.createSpyObj('TransactionService', ['getAllExpenses']); @@ -56,6 +58,7 @@ describe('MyCreateReportPage', () => { const trackingServiceSpy = jasmine.createSpyObj('TrackingService', ['createFirstReport', 'createReport']); const storageServiceSpy = jasmine.createSpyObj('StorageService', ['get', 'set']); const refinerServiceSpy = jasmine.createSpyObj('RefinerService', ['startSurvey']); + const expensesServiceSpy = jasmine.createSpyObj('ExpensesService', ['getAllExpenses']); TestBed.configureTestingModule({ declarations: [MyCreateReportPage, HumanizeCurrencyPipe], @@ -105,6 +108,10 @@ describe('MyCreateReportPage', () => { provide: RefinerService, useValue: refinerServiceSpy, }, + { + provide: ExpensesService, + useValue: expensesServiceSpy, + }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); @@ -120,6 +127,7 @@ describe('MyCreateReportPage', () => { trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj; storageService = TestBed.inject(StorageService) as jasmine.SpyObj; refinerService = TestBed.inject(RefinerService) as jasmine.SpyObj; + expensesService = TestBed.inject(ExpensesService) as jasmine.SpyObj; currencyService.getHomeCurrency.and.returnValue(of('USD')); fixture.detectChanges(); @@ -151,7 +159,7 @@ describe('MyCreateReportPage', () => { describe('cancel():', () => { it('should navigate to my expenses if there are any selected txns', () => { - component.selectedTxnIds = ['txfCdl3TEZ7K']; + component.selectedExpenseIDs = ['txfCdl3TEZ7K']; fixture.detectChanges(); component.cancel(); @@ -159,7 +167,7 @@ describe('MyCreateReportPage', () => { }); it('should navigate to my reports if there are no selected txns', () => { - component.selectedTxnIds = []; + component.selectedExpenseIDs = []; fixture.detectChanges(); component.cancel(); @@ -170,13 +178,20 @@ describe('MyCreateReportPage', () => { it('sendFirstReportCreated(): should set a new report if first report not created', fakeAsync(() => { storageService.get.and.resolveTo(false); reportService.getMyReportsCount.and.returnValue(of(0)); - component.readyToReportEtxns = cloneDeep(selectedExpenses); + spyOn(component, 'getTotalSelectedExpensesAmount').and.returnValue(150); + component.readyToReportExpenses = [...readyToReportExpensesData, expenseData]; + component.selectedElements = readyToReportExpensesData; fixture.detectChanges(); component.sendFirstReportCreated(); - tick(500); + tick(1000); expect(reportService.getMyReportsCount).toHaveBeenCalledOnceWith({}); + expect(trackingService.createFirstReport).toHaveBeenCalledOnceWith({ + Expense_Count: 2, + Report_Value: 150, + }); + expect(component.getTotalSelectedExpensesAmount).toHaveBeenCalledOnceWith(readyToReportExpensesData); expect(storageService.get).toHaveBeenCalledOnceWith('isFirstReportCreated'); expect(storageService.set).toHaveBeenCalledOnceWith('isFirstReportCreated', true); })); @@ -189,7 +204,8 @@ describe('MyCreateReportPage', () => { it('should create a draft report and add transactions to it, if there are any selected expenses', () => { reportService.addTransactions.and.returnValue(of(null)); - component.selectedElements = cloneDeep(selectedExpenses); + component.readyToReportExpenses = cloneDeep(readyToReportExpensesData); + component.selectedElements = cloneDeep([readyToReportExpensesData[0]]); fixture.detectChanges(); component.ctaClickedEvent('create_draft_report'); @@ -200,12 +216,11 @@ describe('MyCreateReportPage', () => { source: 'MOBILE', }); expect(trackingService.createReport).toHaveBeenCalledOnceWith({ - Expense_Count: selectedExpenses.length, + Expense_Count: 1, Report_Value: component.selectedTotalAmount, }); expect(reportService.addTransactions).toHaveBeenCalledOnceWith(reportUnflattenedData.id, [ - selectedExpenses[0].tx_id, - selectedExpenses[1].tx_id, + readyToReportExpensesData[0].id, ]); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_reports']); }); @@ -230,7 +245,7 @@ describe('MyCreateReportPage', () => { it('should create report', () => { reportService.create.and.returnValue(of(reportUnflattenedData)); - component.selectedElements = cloneDeep(selectedExpenses); + component.selectedElements = cloneDeep(readyToReportExpensesData); fixture.detectChanges(); component.ctaClickedEvent('create_report'); @@ -241,7 +256,7 @@ describe('MyCreateReportPage', () => { purpose: component.reportTitle, source: 'MOBILE', }, - [selectedExpenses[0].tx_id, selectedExpenses[1].tx_id], + [readyToReportExpensesData[0].id, readyToReportExpensesData[1].id] ); expect(trackingService.createReport).toHaveBeenCalledOnceWith({ Expense_Count: 2, @@ -272,20 +287,21 @@ describe('MyCreateReportPage', () => { describe('selectExpense():', () => { beforeEach(() => { spyOn(component, 'getReportTitle'); - component.selectedElements = cloneDeep(selectedExpenses); - component.readyToReportEtxns = []; + component.readyToReportExpenses = []; }); it('should add the expense in selected list', () => { - component.selectExpense(apiExpenseRes[0]); + component.selectedElements = cloneDeep([readyToReportExpensesData[1]]); + component.selectExpense(readyToReportExpensesData[0]); expect(component.getReportTitle).toHaveBeenCalledTimes(1); - expect(component.selectedElements.length).toEqual(3); + expect(component.selectedElements.length).toEqual(2); expect(component.isSelectedAll).toBeFalse(); }); it('should remove an expense from the selected list', () => { - component.selectExpense(selectedExpenses[0]); + component.selectedElements = cloneDeep(readyToReportExpensesData); + component.selectExpense(readyToReportExpensesData[1]); expect(component.getReportTitle).toHaveBeenCalledTimes(1); expect(component.selectedElements.length).toEqual(1); @@ -295,7 +311,7 @@ describe('MyCreateReportPage', () => { describe('toggleSelectAll():', () => { beforeEach(() => { - component.readyToReportEtxns = cloneDeep(apiExpenseRes); + component.readyToReportExpenses = cloneDeep(readyToReportExpensesData); spyOn(component, 'getReportTitle'); fixture.detectChanges(); }); @@ -303,7 +319,7 @@ describe('MyCreateReportPage', () => { it('should select all ready expenses', () => { component.toggleSelectAll(true); - expect(component.selectedElements).toEqual(apiExpenseRes); + expect(component.selectedElements).toEqual(readyToReportExpensesData); expect(component.getReportTitle).toHaveBeenCalledTimes(1); }); @@ -315,22 +331,9 @@ describe('MyCreateReportPage', () => { }); }); - describe('getVendorDetails():', () => { - it('should return distance with units if expense is of type mileage', () => { - const result = component.getVendorDetails(etxncListData.data[0]); - - expect(result).toEqual('13.17 KM'); - }); - - it('should return number of days if expense is of type per diem', () => { - const result = component.getVendorDetails(perDiemExpenseSingleNumDays); - - expect(result).toEqual('1 Days'); - }); - }); - it('getReportTitle(): get report title', fakeAsync(() => { - component.selectedElements = cloneDeep(selectedExpenses); + component.selectedElements = cloneDeep(readyToReportExpensesData); + spyOn(component, 'getTotalSelectedExpensesAmount').and.returnValue(150); reportService.getReportPurpose.and.returnValue(of('#Sept 24')); const el = getElementBySelector(fixture, "[data-testid='report-name']") as HTMLInputElement; el.value = 'New Report'; @@ -346,9 +349,10 @@ describe('MyCreateReportPage', () => { component.getReportTitle(); expect(reportService.getReportPurpose).toHaveBeenCalledOnceWith({ - ids: [selectedExpenses[0].tx_id, selectedExpenses[1].tx_id], + ids: [readyToReportExpensesData[0].id, readyToReportExpensesData[1].id], }); expect(component.reportTitle).toEqual('#Sept 24'); + expect(component.getTotalSelectedExpensesAmount).toHaveBeenCalledOnceWith(component.selectedElements); })); it('toggleTransaction(): should toggle selected transaction to unselected', () => { @@ -367,7 +371,7 @@ describe('MyCreateReportPage', () => { component.checkTxnIds(); - expect(component.selectedTxnIds).toEqual([selectedExpenses[0].tx_id, null]); + expect(component.selectedExpenseIDs).toEqual([selectedExpenses[0].tx_id, null]); }); it('should set selected txn IDs as empty array if not found in route', () => { @@ -376,7 +380,7 @@ describe('MyCreateReportPage', () => { component.checkTxnIds(); - expect(component.selectedTxnIds).toEqual([]); + expect(component.selectedExpenseIDs).toEqual([]); }); }); @@ -384,26 +388,36 @@ describe('MyCreateReportPage', () => { loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); transactionService.getAllExpenses.and.returnValue(of(cloneDeep(selectedExpenses))); - spyOn(component, 'getVendorDetails').and.returnValue('vendor'); + expensesService.getAllExpenses.and.returnValue(of(readyToReportExpensesData)); spyOn(component, 'getReportTitle').and.returnValue(null); spyOn(component, 'checkTxnIds'); - component.selectedTxnIds = [selectedExpenses[0].tx_id]; + component.selectedExpenseIDs = [selectedExpenses[0].tx_id]; fixture.detectChanges(); component.ionViewWillEnter(); tick(500); - expect(transactionService.getAllExpenses).toHaveBeenCalledOnceWith({ + expect(expensesService.getAllExpenses).toHaveBeenCalledOnceWith({ queryParams: { - tx_report_id: 'is.null', - tx_state: 'in.(COMPLETE)', - order: 'tx_txn_dt.desc', - or: ['(tx_policy_amount.is.null,tx_policy_amount.gt.0.0001)'], + report_id: 'is.null', + state: 'in.(COMPLETE)', + order: 'spent_at.desc', + or: ['(policy_amount.is.null,policy_amount.gt.0.0001)'], }, }); expect(loaderService.showLoader).toHaveBeenCalledTimes(1); expect(component.getReportTitle).toHaveBeenCalledTimes(1); - expect(component.getVendorDetails).toHaveBeenCalledTimes(2); + expect(component.checkTxnIds).toHaveBeenCalledTimes(1); })); + + describe('getTotalSelectedExpensesAmount()', () => { + it('should return total amount', () => { + expect(component.getTotalSelectedExpensesAmount(cloneDeep(readyToReportExpensesData2))).toEqual(200); + }); + + it('should return 0 if there are no re imbursable expenses', () => { + expect(component.getTotalSelectedExpensesAmount([nonReimbursableExpense])).toEqual(0); + }); + }); }); diff --git a/src/app/fyle/my-create-report/my-create-report.page.ts b/src/app/fyle/my-create-report/my-create-report.page.ts index 54fcf4bb47..bfebc66cce 100644 --- a/src/app/fyle/my-create-report/my-create-report.page.ts +++ b/src/app/fyle/my-create-report/my-create-report.page.ts @@ -12,7 +12,8 @@ import { ReportService } from 'src/app/core/services/report.service'; import { TransactionService } from 'src/app/core/services/transaction.service'; import { StorageService } from '../../core/services/storage.service'; import { TrackingService } from '../../core/services/tracking.service'; - +import { Expense as PlatformExpense } from '../../core/models/platform/v1/expense.model'; +import { ExpensesService } from 'src/app/core/services/platform/v1/spender/expenses.service'; @Component({ selector: 'app-my-create-report', templateUrl: './my-create-report.page.html', @@ -21,9 +22,9 @@ import { TrackingService } from '../../core/services/tracking.service'; export class MyCreateReportPage implements OnInit { @ViewChild('reportTitleInput') reportTitleInput: NgModel; - readyToReportEtxns: Expense[]; + readyToReportExpenses: PlatformExpense[]; - selectedElements: Expense[]; + selectedElements: PlatformExpense[]; reportTitle = ''; @@ -33,7 +34,7 @@ export class MyCreateReportPage implements OnInit { selectedTotalTxns = 0; - selectedTxnIds: string[]; + selectedExpenseIDs: string[]; saveDraftReportLoading = false; @@ -57,6 +58,7 @@ export class MyCreateReportPage implements OnInit { private trackingService: TrackingService, private storageService: StorageService, private refinerService: RefinerService, + private expensesService: ExpensesService ) {} detectTitleChange(): void { @@ -71,7 +73,7 @@ export class MyCreateReportPage implements OnInit { } cancel(): void { - if (this.selectedTxnIds.length > 0) { + if (this.selectedExpenseIDs.length > 0) { this.router.navigate(['/', 'enterprise', 'my_expenses']); } else { this.router.navigate(['/', 'enterprise', 'my_reports']); @@ -84,14 +86,11 @@ export class MyCreateReportPage implements OnInit { if (!isFirstReportCreated) { this.reportService.getMyReportsCount({}).subscribe(async (allReportsCount) => { if (allReportsCount === 0) { - const etxns = this.readyToReportEtxns?.filter((etxn) => etxn.isSelected); - const txnIds = etxns.map((etxn) => etxn.tx_id); - const selectedTotalAmount = etxns.reduce( - (acc, obj) => acc + (obj.tx_skip_reimbursement ? 0 : obj.tx_amount), - 0, - ); + const expenses = this.readyToReportExpenses.filter((expense) => this.selectedElements.includes(expense)); + const expenesIDs = expenses.map((expense) => expense.id); + const selectedTotalAmount = this.getTotalSelectedExpensesAmount(expenses); this.trackingService.createFirstReport({ - Expense_Count: txnIds.length, + Expense_Count: expenesIDs.length, Report_Value: selectedTotalAmount, }); await this.storageService.set('isFirstReportCreated', true); @@ -102,7 +101,7 @@ export class MyCreateReportPage implements OnInit { ctaClickedEvent(reportActionType): Subscription { this.showReportNameError = false; - if (!this.reportTitle && this.reportTitle?.trim().length <= 0 && this.emptyInput) { + if (!this.reportTitle && this.reportTitle.trim().length <= 0 && this.emptyInput) { this.showReportNameError = true; return; } @@ -115,7 +114,8 @@ export class MyCreateReportPage implements OnInit { this.sendFirstReportCreated(); - const txnIds = this.selectedElements?.map((expense) => expense.tx_id); + let expenseIDs: string[] = []; + expenseIDs = this.selectedElements.map((expense) => expense.id); if (reportActionType === 'create_draft_report') { this.saveDraftReportLoading = true; @@ -124,13 +124,13 @@ export class MyCreateReportPage implements OnInit { .pipe( tap(() => this.trackingService.createReport({ - Expense_Count: txnIds.length, + Expense_Count: expenseIDs.length, Report_Value: this.selectedTotalAmount, - }), + }) ), switchMap((report: ReportV1) => { - if (txnIds.length > 0) { - return this.reportService.addTransactions(report.id, txnIds).pipe(map(() => report)); + if (expenseIDs.length) { + return this.reportService.addTransactions(report.id, expenseIDs).pipe(map(() => report)); } else { return of(report); } @@ -138,76 +138,58 @@ export class MyCreateReportPage implements OnInit { finalize(() => { this.saveDraftReportLoading = false; this.router.navigate(['/', 'enterprise', 'my_reports']); - }), + }) ) .subscribe(noop); } else { this.saveReportLoading = true; this.reportService - .create(report, txnIds) + .create(report, expenseIDs) .pipe( tap(() => this.trackingService.createReport({ - Expense_Count: txnIds.length, + Expense_Count: expenseIDs.length, Report_Value: this.selectedTotalAmount, - }), + }) ), finalize(() => { this.saveReportLoading = false; this.router.navigate(['/', 'enterprise', 'my_reports']); this.refinerService.startSurvey({ actionName: 'Submit Newly Created Report' }); - }), + }) ) .subscribe(noop); } } } - selectExpense(expense: Expense): void { - const isSelectedElementsIncludesExpense = this.selectedElements.some((txn) => expense.tx_id === txn.tx_id); + selectExpense(expense: PlatformExpense): void { + const isSelectedElementsIncludesExpense = this.selectedElements.some((exp) => exp.id === expense.id); if (isSelectedElementsIncludesExpense) { - this.selectedElements = this.selectedElements.filter((txn) => txn.tx_id !== expense.tx_id); + this.selectedElements = this.selectedElements.filter((exp) => exp.id !== expense.id); } else { this.selectedElements.push(expense); } this.getReportTitle(); - this.isSelectedAll = this.selectedElements.length === this.readyToReportEtxns.length; + this.isSelectedAll = this.selectedElements.length === this.readyToReportExpenses.length; } toggleSelectAll(value: boolean): void { if (value) { - this.selectedElements = this.readyToReportEtxns; + this.selectedElements = this.readyToReportExpenses; } else { this.selectedElements = []; } this.getReportTitle(); } - getVendorDetails(expense: Expense): string { - const category = expense.tx_org_category && expense.tx_org_category.toLowerCase(); - let vendorName: string | number = expense.tx_vendor || 'Expense'; - - if (category === 'mileage') { - vendorName = expense.tx_distance.toString(); - vendorName += ' ' + expense.tx_distance_unit; - } else if (category === 'per diem') { - vendorName = expense.tx_num_days.toString(); - vendorName += ' Days'; - } - - return vendorName; - } - getReportTitle(): Subscription { - const txnIds = this.selectedElements.map((etxn) => etxn.tx_id); - this.selectedTotalAmount = this.selectedElements.reduce( - (acc, obj) => acc + (obj.tx_skip_reimbursement ? 0 : obj.tx_amount), - 0, - ); + const expenseIDs = this.selectedElements.map((ele) => ele.id); + this.selectedTotalAmount = this.getTotalSelectedExpensesAmount(this.selectedElements); if (this.reportTitleInput && !this.reportTitleInput.dirty) { - return this.reportService.getReportPurpose({ ids: txnIds }).subscribe((res) => { + return this.reportService.getReportPurpose({ ids: expenseIDs }).subscribe((res) => { this.reportTitle = res; }); } @@ -219,65 +201,72 @@ export class MyCreateReportPage implements OnInit { } checkTxnIds(): void { - const txn_ids = this.activatedRoute.snapshot.params.txn_ids as string; - this.selectedTxnIds = (txn_ids ? JSON.parse(txn_ids) : []) as string[]; + const expenseIDs = this.activatedRoute.snapshot.params.txn_ids as string; + this.selectedExpenseIDs = (expenseIDs ? JSON.parse(expenseIDs) : []) as string[]; } ionViewWillEnter(): void { this.isSelectedAll = true; + this.selectedElements = []; this.checkTxnIds(); const queryParams = { - tx_report_id: 'is.null', - tx_state: 'in.(COMPLETE)', - order: 'tx_txn_dt.desc', - or: ['(tx_policy_amount.is.null,tx_policy_amount.gt.0.0001)'], + report_id: 'is.null', + state: 'in.(COMPLETE)', + order: 'spent_at.desc', + or: ['(policy_amount.is.null,policy_amount.gt.0.0001)'], }; from(this.loaderService.showLoader()) .pipe( switchMap(() => - this.transactionService.getAllExpenses({ queryParams }).pipe( - map((etxns) => { - etxns.forEach((etxn, i) => { - etxn.vendorDetails = this.getVendorDetails(etxn); - etxn.showDt = true; - if ( - i > 0 && - etxn.tx_txn_dt && - etxns[i - 1].tx_txn_dt && - etxn.tx_txn_dt.toDateString() === etxns[i - 1].tx_txn_dt.toDateString() - ) { - etxn.showDt = false; - } - etxn.isSelected = true; - - if (this.selectedTxnIds.length > 0) { - if (this.selectedTxnIds.indexOf(etxn.tx_id) === -1) { - etxn.isSelected = false; + this.expensesService.getAllExpenses({ queryParams }).pipe( + map((expenses) => { + this.selectedElements = expenses; + expenses.forEach((expense) => { + if (this.selectedExpenseIDs.length > 0) { + if (this.selectedExpenseIDs.indexOf(expense.id) === -1) { + this.selectedElements.filter((element) => element.id !== expense.id); } } }); - return etxns; - }), - ), + return expenses; + }) + ) ), finalize(() => from(this.loaderService.hideLoader())), - shareReplay(1), + shareReplay(1) ) .subscribe((res) => { - this.readyToReportEtxns = res; - this.selectedElements = this.readyToReportEtxns; + this.readyToReportExpenses = res; this.getReportTitle(); }); this.homeCurrency$ = this.currencyService.getHomeCurrency(); } + checkShowDt(expense: PlatformExpense, i: number): boolean { + const spentAtDt = expense.spent_at; + const prevExpenseSpentAtDt = this.readyToReportExpenses[i - 1]?.spent_at; + if ( + i > 0 && + spentAtDt && + prevExpenseSpentAtDt && + spentAtDt.toDateString() === prevExpenseSpentAtDt.toDateString() + ) { + return false; + } + return true; + } + ngOnInit(): void { this.currencyService.getHomeCurrency().subscribe((homeCurrency) => { this.homeCurrency = homeCurrency; }); } + + getTotalSelectedExpensesAmount(expenses: PlatformExpense[]): number { + return expenses.reduce((acc, obj) => acc + (!obj.is_reimbursable ? 0 : obj.amount), 0); + } } From 171cc270196313c5fbef795d3a4b312a384fd045 Mon Sep 17 00:00:00 2001 From: Dimple K H <31147415+Dimple16@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:44:49 +0530 Subject: [PATCH 12/15] feat: Standardize icons - Milestone-6 (#2617) Co-authored-by: Dimple --- src/app/core/services/status.service.ts | 10 ++++---- .../test-data/status.service.spec.data.ts | 24 +++++++++---------- .../add-corporate-card.component.html | 2 +- .../add-corporate-card.component.scss | 1 + .../fyle/view-mileage/view-mileage.page.html | 2 +- .../audit-history.component.html | 2 +- .../audit-history.component.scss | 1 - src/app/shared/icon/icon.module.ts | 3 ++- src/assets/svg/arrow-up-primary.svg | 9 ------- src/assets/svg/arrow-up.svg | 3 +++ src/assets/svg/radio-circle-outline.svg | 3 +++ 11 files changed, 29 insertions(+), 31 deletions(-) delete mode 100644 src/assets/svg/arrow-up-primary.svg create mode 100644 src/assets/svg/arrow-up.svg create mode 100644 src/assets/svg/radio-circle-outline.svg diff --git a/src/app/core/services/status.service.ts b/src/app/core/services/status.service.ts index e610b6d757..bd0913c36d 100644 --- a/src/app/core/services/status.service.ts +++ b/src/app/core/services/status.service.ts @@ -64,7 +64,7 @@ export class StatusService { case lowerCaseComment.indexOf('created') > -1 && lowerCaseComment.indexOf('reversal') > -1: statusCategory = { category: type + ' Reversed', - icon: 'circle', + icon: 'radio-circle-outline', }; break; case lowerCaseComment.indexOf('expense rule') > -1: @@ -76,7 +76,7 @@ export class StatusService { case lowerCaseComment.indexOf('created') > -1: statusCategory = { category: type + ' Created', - icon: 'circle', + icon: 'radio-circle-outline', }; break; case lowerCaseComment.indexOf('updated') > -1: @@ -94,7 +94,7 @@ export class StatusService { case lowerCaseComment.indexOf('added to the report') > -1: statusCategory = { category: 'Expense added', - icon: 'circle', + icon: 'radio-circle-outline', }; break; case lowerCaseComment.indexOf('added') > -1: @@ -166,7 +166,7 @@ export class StatusService { case lowerCaseComment.indexOf('approver_pending') > -1: statusCategory = { category: 'Approver Pending', - icon: 'circle', + icon: 'radio-circle-outline', }; break; case lowerCaseComment.indexOf('approved') > -1: @@ -232,7 +232,7 @@ export class StatusService { default: statusCategory = { category: 'Others', - icon: 'circle', + icon: 'radio-circle-outline', }; break; } diff --git a/src/app/core/test-data/status.service.spec.data.ts b/src/app/core/test-data/status.service.spec.data.ts index becf167228..5b53dba9c8 100644 --- a/src/app/core/test-data/status.service.spec.data.ts +++ b/src/app/core/test-data/status.service.spec.data.ts @@ -722,7 +722,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'reports Created', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -762,7 +762,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'Others', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -842,7 +842,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'reports Reversed', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -1042,7 +1042,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'Approver Pending', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -1282,7 +1282,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'Others', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -1302,7 +1302,7 @@ export const updateReponseWithFlattenedEStatus: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'Expense added', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -1605,7 +1605,7 @@ export const estatusSample = [ isOthersComment: true, st: { category: 'Expense added', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -1625,7 +1625,7 @@ export const estatusSample = [ isOthersComment: true, st: { category: 'Others', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -1666,7 +1666,7 @@ export const estatusSample = [ show_dt: true, st: { category: 'Others', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { @@ -1727,7 +1727,7 @@ export const eStatusWithProjectName = [ show_dt: true, st: { category: 'Others', - icon: 'circle', + icon: 'radio-circle-outline', }, }, ]; @@ -1754,7 +1754,7 @@ export const eStatusWithProjectName2 = [ show_dt: true, st: { category: 'Others', - icon: 'circle', + icon: 'radio-circle-outline', }, }, ]; @@ -2054,7 +2054,7 @@ export const systemCommentsWithSt: ExtendedStatus[] = [ isOthersComment: true, st: { category: 'repots Reversed', - icon: 'circle', + icon: 'radio-circle-outline', }, }, { diff --git a/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.html b/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.html index 67b11afceb..9ebb5bed61 100644 --- a/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.html +++ b/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.html @@ -102,7 +102,7 @@
    View Terms and Conditions
    diff --git a/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.scss b/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.scss index 4b4ec3eaa7..dae030d242 100644 --- a/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.scss +++ b/src/app/fyle/manage-corporate-cards/add-corporate-card/add-corporate-card.component.scss @@ -115,6 +115,7 @@ &__view-tnc-btn-icon { width: 18px; height: 18px; + color: $brand-primary; } &__tnc { diff --git a/src/app/fyle/view-mileage/view-mileage.page.html b/src/app/fyle/view-mileage/view-mileage.page.html index 3dfca59292..843019cfae 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.html +++ b/src/app/fyle/view-mileage/view-mileage.page.html @@ -111,7 +111,7 @@
    diff --git a/src/app/shared/components/comments-history/audit-history/audit-history.component.html b/src/app/shared/components/comments-history/audit-history/audit-history.component.html index fd4b12b25b..1cdd4b4344 100644 --- a/src/app/shared/components/comments-history/audit-history/audit-history.component.html +++ b/src/app/shared/components/comments-history/audit-history/audit-history.component.html @@ -7,7 +7,7 @@ [svgIcon]="estatus.st.icon" *ngIf="!estatus.us_full_name" [ngClass]="{ - 'audit-history--circle': estatus.st.icon === 'circle', + 'audit-history--circle': estatus.st.icon === 'radio-circle-outline', 'audit-history--delete': estatus.st.icon === 'fy-delete', 'icon--alert-warning': estatus.st.icon === 'danger', 'icon--flagged': estatus.st.category === 'Flagged', diff --git a/src/app/shared/components/comments-history/audit-history/audit-history.component.scss b/src/app/shared/components/comments-history/audit-history/audit-history.component.scss index bbdb2b1e3d..b1bd3ac8b8 100644 --- a/src/app/shared/components/comments-history/audit-history/audit-history.component.scss +++ b/src/app/shared/components/comments-history/audit-history/audit-history.component.scss @@ -30,7 +30,6 @@ width: 10px; margin: 0 20px 0 -6px; background-color: transparent; - color: $pure-white; } &--delete { diff --git a/src/app/shared/icon/icon.module.ts b/src/app/shared/icon/icon.module.ts index 0d7f282083..0ef36083af 100644 --- a/src/app/shared/icon/icon.module.ts +++ b/src/app/shared/icon/icon.module.ts @@ -15,6 +15,7 @@ export class IconModule { svgImageArray = [ 'arrow-prev.svg', 'arrow-next.svg', + 'arrow-up', 'attachment.svg', 'building.svg', 'car.svg', @@ -22,7 +23,6 @@ export class IconModule { 'chevron-right.svg', 'curve.svg', 'comments-zero-state', - 'circle.svg', 'close.svg', 'danger.svg', 'duplicate.svg', @@ -111,6 +111,7 @@ export class IconModule { 'plus.svg', 'plus-minus.svg', 'profile.svg', + 'radio-circle-outline.svg', 'rectangle.svg', 'return-home.svg', 'search.svg', diff --git a/src/assets/svg/arrow-up-primary.svg b/src/assets/svg/arrow-up-primary.svg deleted file mode 100644 index 0c1de46906..0000000000 --- a/src/assets/svg/arrow-up-primary.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/svg/arrow-up.svg b/src/assets/svg/arrow-up.svg new file mode 100644 index 0000000000..555167c017 --- /dev/null +++ b/src/assets/svg/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/radio-circle-outline.svg b/src/assets/svg/radio-circle-outline.svg new file mode 100644 index 0000000000..0420660965 --- /dev/null +++ b/src/assets/svg/radio-circle-outline.svg @@ -0,0 +1,3 @@ + + + From abcdd04dd095033af75f358d36c6043aac5316ba Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:12:00 +0530 Subject: [PATCH 13/15] fix: Fixed minor edge case with mileage map loading (#2615) --- src/app/fyle/view-mileage/view-mileage.page.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/fyle/view-mileage/view-mileage.page.ts b/src/app/fyle/view-mileage/view-mileage.page.ts index 30d5448ada..73ea2bdb6d 100644 --- a/src/app/fyle/view-mileage/view-mileage.page.ts +++ b/src/app/fyle/view-mileage/view-mileage.page.ts @@ -292,8 +292,7 @@ export class ViewMileagePage { this.mapAttachment$ = this.mileageExpense$.pipe( take(1), - map((expense) => expense.files), - map((fileObjs) => fileObjs[0]), + switchMap((expense) => from(expense.files)), concatMap((fileObj) => this.fileService.downloadUrl(fileObj.id).pipe( map((downloadUrl) => { From 23a2935c1b608466ce55efc1ed0cf23881aa7560 Mon Sep 17 00:00:00 2001 From: Omkar Joshi <65808188+OmkarJ13@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:18:19 +0530 Subject: [PATCH 14/15] fix: Make the entire transaction status clickable (#2618) --- .../add-edit-expense/add-edit-expense.page.html | 2 +- src/app/fyle/view-expense/view-expense.page.html | 2 +- .../transaction-status.component.html | 15 ++++++++------- .../transaction-status.component.scss | 5 +++++ .../transaction-status.component.spec.ts | 6 +++--- .../transaction-status.component.ts | 2 +- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/app/fyle/add-edit-expense/add-edit-expense.page.html b/src/app/fyle/add-edit-expense/add-edit-expense.page.html index 29dcf9f031..ca6ee61c02 100644 --- a/src/app/fyle/add-edit-expense/add-edit-expense.page.html +++ b/src/app/fyle/add-edit-expense/add-edit-expense.page.html @@ -190,7 +190,7 @@ >
    diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index dc7bd31d66..325650827d 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -117,7 +117,7 @@ >
    diff --git a/src/app/shared/components/transaction-status/transaction-status.component.html b/src/app/shared/components/transaction-status/transaction-status.component.html index d0662e4fe8..03dd79effb 100644 --- a/src/app/shared/components/transaction-status/transaction-status.component.html +++ b/src/app/shared/components/transaction-status/transaction-status.component.html @@ -8,12 +8,13 @@ >
    Transaction Status: - {{ transactionStatus | titlecase }} +
    + {{ transactionStatus | titlecase }} - + +
    diff --git a/src/app/shared/components/transaction-status/transaction-status.component.scss b/src/app/shared/components/transaction-status/transaction-status.component.scss index fd58c2a218..f37695ef5c 100644 --- a/src/app/shared/components/transaction-status/transaction-status.component.scss +++ b/src/app/shared/components/transaction-status/transaction-status.component.scss @@ -21,6 +21,11 @@ } } + &__status-value-container { + display: flex; + align-items: center; + } + &__status-value { color: $blue-black; font-weight: 500; diff --git a/src/app/shared/components/transaction-status/transaction-status.component.spec.ts b/src/app/shared/components/transaction-status/transaction-status.component.spec.ts index 8a3b02bf31..8a3ba00171 100644 --- a/src/app/shared/components/transaction-status/transaction-status.component.spec.ts +++ b/src/app/shared/components/transaction-status/transaction-status.component.spec.ts @@ -24,13 +24,13 @@ describe('TransactionStatusComponent', () => { expect(component).toBeTruthy(); }); - it('should emit the infoClick event when clicked on the info icon', () => { - spyOn(component.infoClick, 'emit'); + it('should emit the statusClick event when clicked on the info icon', () => { + spyOn(component.statusClick, 'emit'); const infoIcon = getElementBySelector(fixture, '[data-testid="info-icon"]') as HTMLButtonElement; infoIcon.click(); - expect(component.infoClick.emit).toHaveBeenCalledTimes(1); + expect(component.statusClick.emit).toHaveBeenCalledTimes(1); }); describe('template', () => { diff --git a/src/app/shared/components/transaction-status/transaction-status.component.ts b/src/app/shared/components/transaction-status/transaction-status.component.ts index a379df78a5..87d7c31617 100644 --- a/src/app/shared/components/transaction-status/transaction-status.component.ts +++ b/src/app/shared/components/transaction-status/transaction-status.component.ts @@ -9,7 +9,7 @@ import { TransactionStatus } from 'src/app/core/models/platform/v1/expense.model export class TransactionStatusComponent { @Input() transactionStatus: TransactionStatus; - @Output() infoClick: EventEmitter = new EventEmitter(); + @Output() statusClick: EventEmitter = new EventEmitter(); get TransactionStatus(): typeof TransactionStatus { return TransactionStatus; From 9472848353032d69585a9ea79bd1782d50966de8 Mon Sep 17 00:00:00 2001 From: Dimple K H <31147415+Dimple16@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:55:14 +0530 Subject: [PATCH 15/15] fix: Fix for the travel class icon on view expense page (#2623) Co-authored-by: Dimple --- src/app/fyle/view-expense/view-expense.page.html | 2 +- src/app/shared/icon/icon.module.ts | 3 ++- src/assets/svg/bus.svg | 3 +++ src/assets/svg/train.svg | 5 +++++ 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/assets/svg/bus.svg create mode 100644 src/assets/svg/train.svg diff --git a/src/app/fyle/view-expense/view-expense.page.html b/src/app/fyle/view-expense/view-expense.page.html index 325650827d..29185a5b5d 100644 --- a/src/app/fyle/view-expense/view-expense.page.html +++ b/src/app/fyle/view-expense/view-expense.page.html @@ -501,7 +501,7 @@
    diff --git a/src/app/shared/icon/icon.module.ts b/src/app/shared/icon/icon.module.ts index 0ef36083af..598dab22f7 100644 --- a/src/app/shared/icon/icon.module.ts +++ b/src/app/shared/icon/icon.module.ts @@ -18,6 +18,7 @@ export class IconModule { 'arrow-up', 'attachment.svg', 'building.svg', + 'bus.svg', 'car.svg', 'card.svg', 'chevron-right.svg', @@ -38,7 +39,6 @@ export class IconModule { 'fy-arrow-down.svg', 'fy-attachment.svg', 'fy-bot.svg', - 'fy-bus.svg', 'fy-calendar.svg', 'fy-camera.svg', 'fy-car-mini.svg', @@ -129,6 +129,7 @@ export class IconModule { 'tick-circle-outline-white.svg', 'toll-charge.svg', 'tick-square-filled.svg', + 'train.svg', 'warning-inverted.svg', 'warning.svg', 'fy-merge.svg', diff --git a/src/assets/svg/bus.svg b/src/assets/svg/bus.svg new file mode 100644 index 0000000000..447bb8475f --- /dev/null +++ b/src/assets/svg/bus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/train.svg b/src/assets/svg/train.svg new file mode 100644 index 0000000000..4c6052ccbd --- /dev/null +++ b/src/assets/svg/train.svg @@ -0,0 +1,5 @@ + + + + +