Compare commits

...

219 Commits
v2.2.1 ... dev

Author SHA1 Message Date
vchikalkin
3c25ba30d0 merge branch 'release/dyn-4885' 2024-12-10 10:11:43 +03:00
vchikalkin
ba36eb502b merge branch 'release/dyn-4872' 2024-12-03 12:09:07 +03:00
vchikalkin
2c9a2bd30c fix selectDealerPerson validation 2024-10-18 16:29:07 +03:00
vchikalkin
f35acee64b fix tbxVATInLeaseObjectPrice block/unblock by cbxPartialVAT 2024-10-18 14:56:50 +03:00
vchikalkin
a4c02a747f merge branch 'release/dyn-4790' 2024-10-18 13:39:03 +03:00
vchikalkin
66b1e2cdcb merge branch 'fix/irr-bonus-reaction' 2024-09-23 09:59:51 +03:00
vchikalkin
81eca1c6e0 merge branch 'release/dyn-4762' 2024-09-19 10:12:18 +03:00
vchikalkin
89f2b3b68c merge branch 'release/dyn-4760' 2024-09-19 10:11:30 +03:00
vchikalkin
549bd901da partial fix release/4710 2024-09-12 16:50:38 +03:00
vchikalkin
6aad2d3aa2 apps/web: countSeats default value = 4 2024-09-12 16:25:04 +03:00
vchikalkin
09959e3ac3 apps/web: graphql: GetInsuranceCompanies -> sort совком first 2024-09-09 16:00:20 +03:00
vchikalkin
7014462d42 merge branch 'release/dyn-4744' 2024-09-09 15:39:50 +03:00
vchikalkin
62417b88ba merge branch release/dyn-4710 2024-09-05 17:36:38 +03:00
vchikalkin
bf0b5c9907 apps/web: sort evo_planpayments by evo_plandate 2024-08-20 17:42:24 +03:00
vchikalkin
5347c3ef6c apps/web: add check min bonus 2024-08-20 15:52:21 +03:00
vchikalkin
194292c083 apps/web: fix elt kasko list filter 2024-08-19 10:23:14 +03:00
vchikalkin
7d023dba1e apps/web: change elt/kasko error text 2024-08-19 10:03:13 +03:00
vchikalkin
9c63cadf29 merge branch release/dyn-4497 2024-08-15 11:54:28 +03:00
vchikalkin
88b7501b4d apps/web: pass device info to create-kp 2024-08-01 13:06:33 +03:00
vchikalkin
8f53187a3c apps/web: elt kasko fix outsideRoads 2024-07-23 11:09:05 +03:00
vchikalkin
ef8059b3b1 Reapply "web: process/add-product: disable selectInsNSIB reaction"
This reverts commit 0eccd799c79840fe8aa93461880b6ae61a217857.
2024-07-22 16:38:22 +03:00
vchikalkin
8627233a17 favicon: change icons for android & iphone 2024-07-19 13:48:51 +03:00
vchikalkin
2ece25dcb6 apps/web: changes site.webmanifest & add favicon.ico to <head> 2024-07-18 23:08:29 +03:00
vchikalkin
0eccd799c7 Revert "web: process/add-product: disable selectInsNSIB reaction"
This reverts commit 671b25a087ac9dc32bd25c209a7d5797d58d8bcd.
2024-07-18 11:37:29 +03:00
vchikalkin
2e8c333374 web\process\configurator\lib\helper: fix getRates 2024-07-18 11:33:32 +03:00
vchikalkin
671b25a087 web: process/add-product: disable selectInsNSIB reaction 2024-07-16 10:13:15 +03:00
vchikalkin
4a314326fb packages: upgrade next@14.2.5 2024-07-15 17:44:44 +03:00
vchikalkin
d0270a3c9e web: add service worker for pwa install 2024-07-15 15:08:11 +03:00
vchikalkin
71fc44e7a6 Dockerfile: specify pnpm version 2024-07-15 11:07:39 +03:00
vchikalkin
d6f5c73b9d merge branch dyn-4250_migrate-to-new-graphql 2024-07-15 11:04:47 +03:00
vchikalkin
d7908bb5da merge branch release/dyn-4515 2024-07-09 11:16:30 +03:00
vchikalkin
ed5391076c merge branch release/dyn-4514 2024-07-05 11:08:17 +03:00
vchikalkin
4a190e962a fix build: specify turbo version 2024-06-21 16:10:28 +03:00
vchikalkin
4644dbb404 merge branch feature/clear-cache-button 2024-06-21 16:05:49 +03:00
vchikalkin
5c6b419452 merge branch release/dyn-4312_recalc-after-kk 2024-05-22 09:31:51 +03:00
vchikalkin
048d132da2 apps/web: increase proxyTimeout up to 90s 2024-05-13 12:24:47 +03:00
vchikalkin
dd154a3004 merge branch fix/dyn-4368_credit-rate 2024-05-04 11:08:59 +03:00
vchikalkin
8dbcf4bb7f build: fix antd generate css for production 2024-04-24 15:23:13 +03:00
vchikalkin
c0f825a587 merge branch release/dyn-4331_fingap-rat 2024-04-20 10:26:00 +03:00
vchikalkin
2b3dd5e7b9 merge branch release/dyn-4284_dangerous-goods 2024-04-15 10:18:13 +03:00
vchikalkin
5d1aa99d75 apps/web: globals.css: revert text-decoration: none for 'a' tag 2024-04-11 19:55:48 +03:00
vchikalkin
4be47fced5 apps/web: replace normalize.css -> modern-normalize 2024-04-11 16:32:30 +03:00
vchikalkin
66fc8ee70e merge branch release/dyn-4114_filter-models 2024-04-11 15:00:34 +03:00
vchikalkin
09fc6956d8 apps/web (Admin/Cache): temp remove ReloadButton 2024-04-11 14:58:36 +03:00
vchikalkin
501ed13f8a scripts: add antd generate css script 2024-04-11 14:09:29 +03:00
vchikalkin
61132928b5 apps/web: beautify Loading spinner 2024-04-11 11:08:15 +03:00
vchikalkin
21b009c833 apps/web: add loading spinner between pages 2024-04-11 10:47:24 +03:00
vchikalkin
c616b981f1 apps/web: disable Link prefetch 2024-04-11 09:39:30 +03:00
vchikalkin
93d16cf35c merge branch feature/cache-manager-admin 2024-04-11 00:53:31 +03:00
vchikalkin
bd7cf3284b merge branch release/dyn-3905_elt-osago 2024-04-08 21:09:28 +03:00
vchikalkin
a0e6c4ff2f apps/web: fix get fingap from kp 2024-03-24 11:43:48 +03:00
vchikalkin
88fbb616b5 apps/web: fix ELT Kasko sum check 2024-03-24 10:59:02 +03:00
vchikalkin
7efea2acc9 merge branch fix/ui-ux-2203 2024-03-24 10:56:29 +03:00
vchikalkin
907f288815 DYN-4145: apps/web: корректируем КПП и Адреса в методе OSAGO 2024-03-18 17:55:40 +03:00
vchikalkin
2fea400060 apps/api: do not cache graphql responses with errors 2024-03-18 17:19:54 +03:00
vchikalkin
f695835c61 apps/web: process/used-pl: add ( ) symbols to vin regex 2024-03-07 13:39:13 +03:00
vchikalkin
47d47a0bfd apps/web: create Content component to separate from error 2024-03-04 16:41:22 +03:00
vchikalkin
540d5642a1 apps/web: fix show statusCode error 2024-03-04 16:26:54 +03:00
vchikalkin
2ec2a14706 merge branch release/dyn-3987_early-redemption-sums 2024-03-04 14:23:09 +03:00
vchikalkin
f7110497d3 apps/web: show apollo error for user 2024-03-01 11:28:36 +03:00
vchikalkin
c9326697f6 axios: capture more error information to sentry 2024-03-01 10:29:06 +03:00
vchikalkin
c2982cf31b apps/web: fix apollo modifyDataLink: check values are exist 2024-02-29 17:03:34 +03:00
vchikalkin
8c69b3b9c4 apps/web: add extra data for sentry 2024-02-29 16:56:43 +03:00
vchikalkin
24987be78e apps/web: fix telematic/tracker reaction 2024-02-29 16:06:15 +03:00
vchikalkin
defc625324 apps/api: check graphql response status & forward to response 2024-02-29 16:01:23 +03:00
vchikalkin
eee91d5f2d build: actualize .dockerignore 2024-02-29 15:17:10 +03:00
vchikalkin
a65202c653 apps/web: fix tbxVehicleTaxInYear addonBefore 2024-02-28 16:58:47 +03:00
vchikalkin
ffaad142e8 Docker: apps/api: fix build 2024-02-28 16:38:44 +03:00
vchikalkin
9e40e5141c merge branch feature/migrate-yarn-to-pnpm 2024-02-28 16:33:15 +03:00
vchikalkin
79e707a232 apps/web: add sentry scope to set user on client 2024-02-24 17:14:06 +03:00
vchikalkin
d374208097 Sentry: disable mask inputs 2024-02-24 16:24:00 +03:00
vchikalkin
557053caa6 apps/web: fix sentry trpc capture exception 2024-02-23 12:47:12 +03:00
vchikalkin
e258873976 apps/web: reconfigure sentry 2024-02-22 15:36:20 +03:00
vchikalkin
59e45ddc63 project: upgrade packages 2024-02-22 14:37:37 +03:00
vchikalkin
eaf95c6007 apps/web: upgrade next@14.1.0 2024-02-22 13:16:25 +03:00
vchikalkin
f3518244ab apps/api: reduce TTL GetConfigurations, GetDealers, GetModels 2024-02-21 22:38:07 +03:00
vchikalkin
c5683f7edd apps/web: fix irrInfo empty after loadKP 2024-02-18 18:34:32 +03:00
vchikalkin
acdacbf18e apps/api: manual headers for fetching from graphql 2024-02-18 17:40:36 +03:00
vchikalkin
ef88c12e74 apps/api: configure queries ttl 2024-02-18 17:12:12 +03:00
vchikalkin
ab0d455afb apps/api: add seconds util 2024-02-18 15:23:36 +03:00
vchikalkin
e0d2836f4a app/api: add feature to disable cache for some queries 2024-02-18 15:15:53 +03:00
vchikalkin
4bc5234d09 apps/api: add query-ttl config
proxyController: pass all request headers to proxied server
2024-02-18 15:00:36 +03:00
vchikalkin
6041082fec apps/web: move initial data fetch to client 2024-02-18 12:23:50 +03:00
vchikalkin
f426ca7f23 apps/web: disable disable webpack for build 2024-02-17 20:36:27 +03:00
vchikalkin
5115551b0e apps/api: fix expose port 2024-02-17 20:06:13 +03:00
vchikalkin
5ce9484e43 docker: fix build api 2024-02-17 19:24:01 +03:00
vchikalkin
8a117e250c merge branch feature/optimize-graphql-queries 2024-02-17 17:39:31 +03:00
vchikalkin
8089f62c31 process/fingap: fix getFingapRisks 2024-02-16 17:24:45 +03:00
vchikalkin
bd775babf9 server: make getTarif public procedure 2024-02-16 13:53:41 +03:00
vchikalkin
e222a3093a process/fingap & process/supplier-agent: remove inline graphql queries 2024-02-16 13:32:16 +03:00
vchikalkin
53debf2cff server: combine miltiple quote query to single 2024-02-16 13:22:01 +03:00
vchikalkin
c092b8f47c init/get-main-data: disable some queries 2024-02-16 12:08:14 +03:00
vchikalkin
6b7c679a0d process/configurator: fix irr-addon reset loading indication 2024-02-14 16:01:14 +03:00
vchikalkin
7a00478727 Form/CreateKP: изменили расположение элементов 2024-02-12 15:26:52 +03:00
vchikalkin
677ab1d75b process/configurator: удалили реакцию заполнения tbxIRR_Perc из тарифа
добавили валидацию tbxIRR_Perc на диапазон irrInfo
2024-02-12 15:16:51 +03:00
vchikalkin
126302a10a merge branch feature/loading-tarif-indicator 2024-02-12 13:25:11 +03:00
vchikalkin
35ce71c149 process/configurator: abort prev getTarif query 2024-02-08 13:19:51 +03:00
vchikalkin
47b57c0c73 apps/web: get selectTarif options on load-kp 2024-02-08 10:56:30 +03:00
obarykina
46c0b305b6 [2] merge branch release/dyn-3974_full-price-nds 2024-02-08 09:46:56 +03:00
vchikalkin
eb6f0ef949 merge branch fix/dyn-3977_get-tarifs 2024-02-07 16:21:22 +03:00
obarykina
7ca4e41980 components/calulation/addons/product-addon: show cbxFloatingRate 2024-02-07 15:48:34 +03:00
obarykina
662b04be0f merge branch release/dyn-3965_filter-telematic-electric-car 2024-02-07 15:42:52 +03:00
obarykina
e2d37712ad merge branch release/dyn-3974_full-price-nds 2024-02-07 15:34:17 +03:00
vchikalkin
4c3d305e7f apps/web: hide cbxFloatingRate from product-addon
add cbxPartialVAT & cbxFloatingRate to unlimited
2024-02-01 09:30:02 +03:00
vchikalkin
1aaa84e31d merge branch release/dyn-3854_floating-rate_partial-nds 2024-02-01 09:26:44 +03:00
vchikalkin
5a290d5be9 merge branch release/dyn-3855_kasko-validation 2024-02-01 08:58:19 +03:00
vchikalkin
ac14a86c33 process/recalc: fix firstPaymentPerc validation 2024-01-23 16:58:48 +03:00
vchikalkin
175cb63515 apps/web: calculate: use values.disableChecks to DISABLE_CHECKS_RESULTS 2024-01-16 11:24:36 +03:00
vchikalkin
803b4a1cfb project: remove .npmrc 2024-01-14 16:31:55 +03:00
vchikalkin
709eda1b50 build: add SENTRYCLI_CDNURL to docker configs 2024-01-14 16:23:25 +03:00
vchikalkin
bdc6d032aa apps/web: remove sharp from packages 2024-01-14 15:29:23 +03:00
vchikalkin
3462395818 apps/web: fix Dockerfile 2023-12-24 01:28:00 +03:00
vchikalkin
9308417c4d docker-compose.yml: remove depends_on 2023-12-24 01:20:14 +03:00
vchikalkin
d0f11cbc63 project: add docker-compose.yml 2023-12-24 01:06:36 +03:00
vchikalkin
eb1eb44769 merge release/dyn-3749_promotion 2023-12-12 08:56:09 +03:00
vchikalkin
6c300c8688 Revert "merge refactor/grid-styles"
This reverts commit 0c0dc7de5333a9d3710a161ec6432408b38174d9.
2023-12-11 15:43:15 +03:00
vchikalkin
3898fc1bdf apps/web: fix manifest 401 2023-12-01 16:27:55 +03:00
vchikalkin
0c0dc7de53 merge refactor/grid-styles 2023-12-01 16:16:14 +03:00
vchikalkin
703cee467c merge release/dyn-3643_minor-updates-november-2023 2023-12-01 10:48:45 +03:00
vchikalkin
c154568bb4 merge release/dyn-2803_sale-post-pi 2023-11-27 11:06:14 +03:00
vchikalkin
8dd9627849 merge refactor/error-monitoring 2023-10-04 17:36:52 +03:00
vchikalkin
07c87d583f merge fix/evo_baseproducts-filter 2023-09-21 10:36:25 +03:00
vchikalkin
0ff6ba3f74 merge fix/elt-remove-caching 2023-09-18 10:21:18 +03:00
vchikalkin
ae0b72aebd merge fix/dyn-3363_osago-kasko-leaseback 2023-09-06 15:40:08 +03:00
vchikalkin
10f3760c63 merge release/dyn-3089_year-parallel-import 2023-08-28 10:25:03 +03:00
vchikalkin
db29a77d37 merge release/dyn-3268_result-bonus-dop-prod 2023-08-25 12:59:56 +03:00
vchikalkin
0936d1cdd7 merge fix/dyn-3291_safe-finance 2023-08-24 15:21:00 +03:00
vchikalkin
5c039597b3 constants/value: change RATE to 15.9 2023-08-23 13:38:05 +03:00
vchikalkin
17a9810878 merge branch fix/dyn-3267_elt 2023-08-23 13:33:16 +03:00
vchikalkin
438242ac31 eslint: specify next app rootDir 2023-08-01 14:14:18 +03:00
vchikalkin
78064edf81 merge project/eslint-rules 2023-08-01 13:41:44 +03:00
vchikalkin
565cabb9bd merge fix/dyn-3167_elt 2023-08-01 10:03:10 +03:00
vchikalkin
903b32084a [2] Calculation/config: increase Min/Max PriceChange 2023-07-19 14:49:27 +03:00
vchikalkin
0f61d4a399 merge branch fix/kp-data_price-change 2023-07-17 14:04:58 +03:00
vchikalkin
7df69f92e8 eslint: upgrade config-canonical 2023-07-12 12:27:49 +03:00
vchikalkin
58d7a05591 merge experimental/error-monitoring 2023-07-12 11:14:13 +03:00
vchikalkin
802885e726 Calculation/config: increase Min/Max PriceChange 2023-07-06 13:30:01 +03:00
vchikalkin
caeb304d15 process/configurator: format min, max validation number 2023-07-06 13:28:52 +03:00
vchikalkin
d4c70101f2 merge branch release/dyn-2954_elt-restriction 2023-07-06 11:18:06 +03:00
vchikalkin
6fd64a62bf merge branch release/dyn-3047_min-max-validation 2023-07-05 12:58:22 +03:00
vchikalkin
11820a7b5d process/insurance: add fingap validation 2023-07-04 11:19:43 +03:00
vchikalkin
f9eda3c6b0 merge branch fix/dyn-3021_fingap 2023-07-04 10:42:55 +03:00
vchikalkin
c77f60fdc3 merge branch upgrade/packages-2606 2023-07-04 10:40:25 +03:00
vchikalkin
84de7a84e8 fix: process/supplier-agent: get-kp-data 2023-06-20 14:52:14 +03:00
vchikalkin
498fa217fe process/leasing-object: fix importerRewardPerc, importerRewardRub (get-kp-data) 2023-06-15 10:28:06 +03:00
vchikalkin
0f32abc6b2 process/payments: fix degression & firstPaymentPerc 2023-06-09 15:13:16 +03:00
vchikalkin
7ce8104b87 process/fingap: pass insCost from kp 2023-06-08 17:14:46 +03:00
vchikalkin
0952ce12ee process/insurance: add fingap.policyType validation 2023-06-08 16:45:05 +03:00
vchikalkin
b90bfc74d9 process/fingap: fix reaction 2023-06-08 16:21:51 +03:00
vchikalkin
d06b6e20d5 process/insurance: add fingap.insCost validation 2023-06-08 14:05:55 +03:00
vchikalkin
654865e845 fix: Expected number, received null 2023-06-07 09:48:34 +03:00
vchikalkin
4fd5c3602e process/insurance: add fingap.insured validation 2023-06-06 18:30:00 +03:00
vchikalkin
4a65a39ba3 process:subsidy: fix subsidySum ,importProgramSum 2023-06-06 12:23:45 +03:00
vchikalkin
e9edf189e7 fix: insurance-table fingap row 2023-06-05 13:42:46 +03:00
vchikalkin
e6289b7174 [2] fix: recalc disabled agents fields 2023-06-05 11:55:35 +03:00
vchikalkin
aaa8cadc02 fix: reset result values after create-kp with recalc 2023-06-05 11:31:40 +03:00
vchikalkin
554a702e6c fix: recalc disabled agents fields 2023-06-05 10:28:48 +03:00
vchikalkin
8e65d864ef fix: при подгрузке КП если в Салоне 2 ЮЛ, то слетает выбранный второй из списка и указывается первый по списку и соответственно Ав второго из списка тоже слетает 2023-06-05 10:18:29 +03:00
vchikalkin
2090056b81 fix: при подгрузке КП отрабатывает реакция на изменение продукта и есть продукт с частичным НДС то БУ ставится автоматом Да 2023-06-05 10:09:58 +03:00
vchikalkin
159fb85e63 fix: selectDealerPerson evo_inn/evo_kpp 2023-06-02 15:44:52 +03:00
vchikalkin
56b6510933 Form/ELT: fix kasko empty error message 2023-06-01 17:42:15 +03:00
vchikalkin
6a828ce82b process/configurator: fix tbxIRR_Perc reset 2023-06-01 16:14:42 +03:00
vchikalkin
074de953c9 process/gibdd: add selectRegionRegistration filter 2023-06-01 15:57:22 +03:00
vchikalkin
fe614b2529 fix: resetValues after get calculate results 2023-06-01 15:13:31 +03:00
vchikalkin
b7ae72d4c0 server/calculate: fix preparedValues districtRate 2023-06-01 14:24:42 +03:00
vchikalkin
805dc4dba3 process/add-product: fix telematic/tracker, recalcWithRevision 2023-06-01 14:22:36 +03:00
vchikalkin
290526b933 fix: RetryButton & SupportButton 2023-06-01 12:34:35 +03:00
vchikalkin
fd972aadad ui: remove page margin for desktop 2023-06-01 11:54:58 +03:00
vchikalkin
78c2446945 Calculation/Settings: move btnCreateKP & linkDownloadKp elements 2023-05-31 17:22:24 +03:00
vchikalkin
9a7e074863 ui: fix Form/InsuranceTable on laptops 2023-05-31 16:53:42 +03:00
vchikalkin
9ea4eeb6ce Revert "process/create-kp: auto download kp file"
This reverts commit cadfec5fc2c5cf85697aa4218e6b0c95de93d7d2.
2023-05-31 12:39:26 +03:00
vchikalkin
cadfec5fc2 process/create-kp: auto download kp file 2023-05-31 11:56:16 +03:00
vchikalkin
5c8560e266 store: add useErrors hook
Calculation/render: override btnCalculate, btnCreateKP, btnCreateKPMini
2023-05-31 11:45:59 +03:00
vchikalkin
2d0117bc4c Calculation/Form: disable elements when Calculate || CreateKP 2023-05-31 11:02:03 +03:00
vchikalkin
05ed1b6aef Calculation/Form: disable elements when LoadKP 2023-05-31 10:26:50 +03:00
vchikalkin
4f509aefdf process/configurator: fix selectRate value when load-kp 2023-05-30 15:25:11 +03:00
vchikalkin
befa92fbce fix: download-kp url 2023-05-30 14:45:47 +03:00
vchikalkin
f4acf8c316 elements: fix linkDownloadKp 2023-05-30 14:03:44 +03:00
vchikalkin
c9523328da fix: exclude values reset results after create-kp 2023-05-30 13:45:25 +03:00
vchikalkin
0ed21711db Components/Common: fix extra error Buttons
config/users: add roles
2023-05-30 11:40:28 +03:00
vchikalkin
c98167e609 ui: Form/AddProduct: fix selectInsNSIB 2023-05-30 11:25:03 +03:00
vchikalkin
feb72a3021 ui: Output/Results: sort results values 2023-05-30 11:20:46 +03:00
vchikalkin
8e3fb86169 leaseObjectUsed improvements 2023-05-29 11:59:02 +03:00
vchikalkin
6a95fbb3f2 process/price: fix NaN 2023-05-25 16:39:56 +03:00
vchikalkin
ab5700dd92 merge fix/migration/elt 2023-05-25 13:46:47 +03:00
vchikalkin
383b70434f process: make elt error notifications silent 2023-05-24 15:12:12 +03:00
vchikalkin
f8adc2e8c7 Form/Settings: fix tbxIRR_Perc min/max validation 2023-05-24 12:58:23 +03:00
vchikalkin
0246916fca process/calculate: reset results on change form values 2023-05-24 12:55:53 +03:00
vchikalkin
4383bc54b3 Components/Output: set active key validation if hasErrors 2023-05-24 11:46:01 +03:00
vchikalkin
2800184cfe Components/Output: set active key after calculation results 2023-05-24 11:29:12 +03:00
vchikalkin
616313eb20 merge release/dyn-2846_pi 2023-05-19 13:11:42 +03:00
vchikalkin
96e792b91d fix PaymentsTable 2023-05-16 14:13:19 +03:00
vchikalkin
1d5328db73 beautify imports/exports after 4549448 2023-05-16 13:50:47 +03:00
vchikalkin
45494483a0 fix build 2023-05-16 13:42:46 +03:00
vchikalkin
7fb706cdaf ui: fix elements layout on mobile devices 2023-05-16 12:04:01 +03:00
vchikalkin
d620cf0c92 optimize package/ui/elements exports 2023-05-16 12:03:54 +03:00
vchikalkin
5940f4c048 elt/validation: remove supplierDiscountRub 2023-05-15 09:22:34 +03:00
vchikalkin
bb6a3986b1 elt: extend validation
extend reset reaction triggers
fix make-request/osago
2023-05-15 09:15:31 +03:00
vchikalkin
2bea56fa87 process/configurator: fix get IRR_Perc from kp 2023-05-12 12:38:35 +03:00
vchikalkin
d195530b15 tbxVIN: capitalize letters 2023-05-12 12:07:49 +03:00
vchikalkin
04b7def049 merge migration/elt 2023-05-12 11:34:09 +03:00
vchikalkin
a2471a0ca8 style: global scrollbar style 2023-04-21 13:26:21 +03:00
vchikalkin
c9a9f3a66d Components/Output/PaymentsTable: disable pagination 2023-04-21 13:01:05 +03:00
vchikalkin
910c5627d2 merge release/unlimited-1904 2023-04-21 11:13:58 +03:00
vchikalkin
860ecb9384 merge migration/fix-4 2023-04-19 22:34:50 +03:00
vchikalkin
87e4783dd5 merge migration/fix-3 & migration/upgrade-code-130423 2023-04-14 10:57:02 +03:00
vchikalkin
0d35042e41 merge migration/fix-2 2023-04-10 11:58:50 +03:00
vchikalkin
95040c5a10 06e7687: fix download-kp url 2023-04-04 15:10:14 +03:00
vchikalkin
06e7687fc1 trpc/create-kp: add downloadKp url to result 2023-04-04 13:27:07 +03:00
vchikalkin
79179c10f3 merge migration/fix-1 2023-04-03 12:50:47 +03:00
vchikalkin
5e2fd20520 eslint-staged: ignore /graphql 2023-04-03 12:50:23 +03:00
vchikalkin
3e7d350248 Revert "trpc: check unlimited in middleware"
This reverts commit 90c0ef481b03bfb7d1a822dfa9e667f59f2c27b2.
2023-04-03 10:16:46 +03:00
vchikalkin
618d8cca58 project: add lint-staged 2023-04-03 10:10:18 +03:00
vchikalkin
90c0ef481b trpc: check unlimited in middleware 2023-03-29 15:10:34 +03:00
vchikalkin
1ea11f8eff change support url 2023-03-29 12:24:16 +03:00
vchikalkin
e8d1057c69 merge feature/unlimited 2023-03-29 10:07:34 +03:00
vchikalkin
5442905966 merge migration/random-4 2023-03-28 09:33:17 +03:00
vchikalkin
9d45a7bff0 merge migration/random-3 2023-03-16 15:35:44 +03:00
vchikalkin
29460f4509 fix load-kp error 2023-02-28 15:28:39 +03:00
426 changed files with 46460 additions and 19074 deletions

View File

@ -1,3 +0,0 @@
.git
README.md
node_modules

12
.env
View File

@ -1,12 +0,0 @@
####### COMMON #######
USE_DEV_COLORS=
BASE_PATH=
####### USERS ########
USERS_SUPER=["akalinina","vchikalkin"]
####### URLS ########
URL_GET_USER_DIRECT=
URL_CRM_GRAPHQL_DIRECT=
URL_CORE_FINGAP_DIRECT=
URL_1C_TRANSTAX_DIRECT=

View File

@ -8,6 +8,5 @@ coverage
.eslintrc.js
**/*.config.js
**/scripts
packages/eslint-config-custom/*
**/package.json
turbo.json

View File

@ -1,10 +1,8 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ['custom', 'custom/rules'],
settings: {
next: {
rootDir: ['packages/web/'],
rootDir: ['apps/*/'],
},
react: {
version: 'detect',

2
.gitignore vendored
View File

@ -68,3 +68,5 @@ yarn-error.log*
.turbo
# End of https://www.toptal.com/developers/gitignore/api/turbo
.pnpm

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn precommit
npx lint-staged --concurrent false

View File

@ -13,9 +13,9 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true,
"source.removeUnusedImports": true
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.removeUnusedImports": "explicit"
},
"workbench.editor.labelFormat": "short",
"eslint.workingDirectories": [{ "directory": "apps/web", "changeProcessCWD": true }],

104
README.md
View File

@ -1,59 +1,28 @@
# Turborepo Docker starter
# Turborepo starter
This is an official Docker starter Turborepo.
## What's inside?
This turborepo uses [Yarn](https://classic.yarnpkg.com/lang/en/) as a package manager. It includes the following packages/apps:
### Apps and Packages
- `web`: a [Next.js](https://nextjs.org/) app
- `api`: an [Express](https://expressjs.com/) server
- `ui`: ui: a React component library
- `eslint-config-custom`: `eslint` configurations for client side applications (includes `eslint-config-next` and `eslint-config-prettier`)
- `eslint-config-custom-server`: `eslint` configurations for server side applications (includes `eslint-config-next` and `eslint-config-prettier`)
- `scripts`: Jest configurations
- `logger`: Isomorphic logger (a small wrapper around console.log)
- `tsconfig`: tsconfig.json;s used throughout the monorepo
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
This is an official starter Turborepo.
## Using this example
Run the following command:
```sh
npx degit vercel/turbo/examples/with-docker with-docker
cd with-docker
yarn install
git init . && git add . && git commit -m "Init"
npx create-turbo@latest
```
### Docker
## What's inside?
This repo is configured to be built with Docker, and Docker compose. To build all apps in this repo:
This Turborepo includes the following packages/apps:
```
# Create a network, which allows containers to communicate
# with each other, by using their container name as a hostname
docker network create app_network
### Apps and Packages
# Build prod using new BuildKit engine
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml build --parallel
- `docs`: a [Next.js](https://nextjs.org/) app
- `web`: another [Next.js](https://nextjs.org/) app
- `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
- `@repo/tsconfig`: `tsconfig.json`s used throughout the monorepo
# Start prod in detached mode
docker-compose -f docker-compose.yml up -d
```
Open http://localhost:3000.
To shutdown all running containers:
```
# Stop all running containers
docker kill $(docker ps -q) && docker rm $(docker ps -a -q)
```
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
### Utilities
@ -61,5 +30,52 @@ This Turborepo has some additional tools already setup for you:
- [TypeScript](https://www.typescriptlang.org/) for static type checking
- [ESLint](https://eslint.org/) for code linting
- [Jest](https://jestjs.io) test runner for all things JavaScript
- [Prettier](https://prettier.io) for code formatting
### Build
To build all apps and packages, run the following command:
```
cd my-turborepo
pnpm build
```
### Develop
To develop all apps and packages, run the following command:
```
cd my-turborepo
pnpm dev
```
### Remote Caching
Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup), then enter the following commands:
```
cd my-turborepo
npx turbo login
```
This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
```
npx turbo link
```
## Useful Links
Learn more about the power of Turborepo:
- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
- [Caching](https://turbo.build/repo/docs/core-concepts/caching)
- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
- [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)

7
apps/api/.dockerignore Normal file
View File

@ -0,0 +1,7 @@
.git
Dockerfile
.dockerignore
node_modules
*.log
dist
README.md

13
apps/api/.eslintrc.js Normal file
View File

@ -0,0 +1,13 @@
const { createConfig } = require('@vchikalkin/eslint-config-awesome');
module.exports = createConfig('typescript', {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
ignorePatterns: ['*.config.js', '.eslintrc.js'],
rules: {
'import/no-duplicates': 'off',
'import/consistent-type-specifier-style': 'off',
},
});

56
apps/api/.gitignore vendored Normal file
View File

@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

4
apps/api/.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

45
apps/api/Dockerfile Normal file
View File

@ -0,0 +1,45 @@
# This Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker.
# Make sure you update both files!
FROM node:alpine AS builder
RUN corepack enable && corepack prepare pnpm@8.9.0 --activate
ENV PNPM_HOME=/usr/local/bin
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
WORKDIR /app
RUN pnpm add -g turbo@1.12.4 dotenv-cli
COPY . .
RUN turbo prune --scope=api --docker
# Add lockfile and package.json's of isolated subworkspace
FROM node:alpine AS installer
RUN corepack enable && corepack prepare pnpm@latest --activate
ENV PNPM_HOME=/usr/local/bin
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
# First install dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=builder /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
RUN pnpm install
# Build the project and its dependencies
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
RUN pnpm dotenv -e .env turbo run build --filter=api...
FROM node:alpine AS runner
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 nestjs
RUN adduser --system --uid 1001 nestjs
USER nestjs
COPY --from=installer /app .
CMD node apps/api/dist/main.js

73
apps/api/README.md Normal file
View File

@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ pnpm install
```
## Running the app
```bash
# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prod
```
## Test
```bash
# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

8
apps/api/nest-cli.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

74
apps/api/package.json Normal file
View File

@ -0,0 +1,74 @@
{
"name": "api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/cache-manager": "^2.2.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-fastify": "^10.3.3",
"cache-manager": "^5.4.0",
"cache-manager-ioredis": "^2.1.0",
"ioredis": "^5.3.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@vchikalkin/eslint-config-awesome": "^1.1.6",
"eslint": "^8.52.0",
"fastify": "^4.26.1",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"shared": "workspace:*",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "29.1.1",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -0,0 +1,16 @@
import { ProxyModule } from './proxy/proxy.module';
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
ProxyModule,
],
providers: [],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AppModule {}

View File

@ -0,0 +1,3 @@
import { seconds } from 'src/utils/time';
export const DEFAULT_CACHE_TTL = seconds().fromMinutes(15);

View File

@ -0,0 +1,3 @@
import envSchema from './schema/env';
export const env = envSchema.parse(process.env);

View File

@ -0,0 +1,21 @@
import { DEFAULT_CACHE_TTL } from '../constants';
import { z } from 'zod';
const envSchema = z.object({
CACHE_TTL: z
.string()
.transform((val) => Number.parseInt(val, 10))
.default(DEFAULT_CACHE_TTL.toString()),
PORT: z
.string()
.transform((val) => Number.parseInt(val, 10))
.default('3001'),
REDIS_HOST: z.string(),
REDIS_PORT: z
.string()
.transform((val) => Number.parseInt(val, 10))
.default('6379'),
URL_CRM_GRAPHQL_DIRECT: z.string(),
});
export default envSchema;

15
apps/api/src/main.ts Normal file
View File

@ -0,0 +1,15 @@
import { AppModule } from './app.module';
import { env } from './config/env';
import { NestFactory } from '@nestjs/core';
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
import { FastifyAdapter } from '@nestjs/platform-fastify';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
await app.listen(env.PORT, '0.0.0.0');
}
bootstrap();

View File

@ -0,0 +1,64 @@
import { seconds } from 'src/utils/time';
export const queryTTL: Record<string, number | false> = {
GetAddProductType: seconds().fromHours(12),
GetAddproductTypes: seconds().fromHours(12),
GetAgent: seconds().fromHours(12),
GetBrand: seconds().fromHours(3),
GetBrands: seconds().fromHours(3),
GetCoefficients: seconds().fromHours(12),
GetConfiguration: seconds().fromHours(3),
GetConfigurations: seconds().fromMinutes(15),
GetCurrencyChanges: seconds().fromHours(1),
GetDealer: seconds().fromHours(1),
GetDealerPerson: seconds().fromHours(1),
GetDealerPersons: seconds().fromHours(1),
GetDealers: seconds().fromMinutes(15),
GetEltInsuranceRules: seconds().fromHours(12),
GetFuelCards: seconds().fromHours(12),
GetGPSBrands: seconds().fromHours(24),
GetGPSModels: seconds().fromHours(24),
GetImportProgram: seconds().fromHours(12),
GetInsNSIBTypes: seconds().fromHours(12),
GetInsuranceCompanies: seconds().fromHours(12),
GetInsuranceCompany: seconds().fromHours(12),
GetLead: false,
GetLeadUrl: seconds().fromHours(12),
GetLeads: false,
GetLeaseObjectType: seconds().fromHours(24),
GetLeaseObjectTypes: seconds().fromHours(24),
GetLeasingWithoutKaskoTypes: seconds().fromHours(12),
GetModel: seconds().fromHours(3),
GetModels: seconds().fromMinutes(15),
GetOpportunities: false,
GetOpportunity: false,
GetOpportunityUrl: seconds().fromHours(12),
GetOsagoAddproductTypes: seconds().fromHours(12),
GetProduct: seconds().fromHours(12),
GetProducts: seconds().fromHours(12),
GetQuote: false,
GetQuoteData: false,
GetQuoteUrl: seconds().fromHours(12),
GetQuotes: false,
GetRate: seconds().fromHours(12),
GetRates: seconds().fromHours(12),
GetRegion: seconds().fromHours(24),
GetRegions: seconds().fromHours(24),
GetRegistrationTypes: seconds().fromHours(12),
GetRewardCondition: seconds().fromHours(1),
GetRewardConditions: seconds().fromHours(1),
GetRoles: seconds().fromHours(12),
GetSotCoefficientType: seconds().fromHours(12),
GetSubsidies: seconds().fromHours(12),
GetSubsidy: seconds().fromHours(12),
GetSystemUser: seconds().fromHours(12),
GetTarif: seconds().fromHours(12),
GetTarifs: seconds().fromHours(12),
GetTechnicalCards: seconds().fromHours(12),
GetTelematicTypes: seconds().fromHours(12),
GetTown: seconds().fromHours(24),
GetTowns: seconds().fromHours(24),
GetTrackerTypes: seconds().fromHours(12),
GetTransactionCurrencies: seconds().fromHours(12),
GetTransactionCurrency: seconds().fromHours(12),
};

View File

@ -0,0 +1,127 @@
import { queryTTL } from './lib/config';
import type { GQLRequest, GQLResponse } from './types';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import {
All,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Inject,
Query,
Req,
Res,
} from '@nestjs/common';
import type { Cache } from 'cache-manager';
import { FastifyReply, FastifyRequest } from 'fastify';
import type { QueryItem } from 'shared/types/cache';
import { env } from 'src/config/env';
type RedisStore = Omit<Cache, 'set'> & {
set: (key: string, value: unknown, { ttl }: { ttl: number }) => Promise<void>;
};
@Controller('proxy')
export class ProxyController {
constructor(
@Inject(CACHE_MANAGER) private readonly cacheManager: RedisStore,
) {}
@All('/graphql')
public async graphql(@Req() req: FastifyRequest, @Res() reply: FastifyReply) {
const { operationName, query, variables } = req.body as GQLRequest;
const key = `${operationName} ${JSON.stringify(variables)}`;
const cached = await this.cacheManager.get(key);
if (cached) return reply.send(cached);
const response = await fetch(env.URL_CRM_GRAPHQL_DIRECT, {
body: JSON.stringify({ operationName, query, variables }),
headers: {
Authorization: req.headers.authorization,
'Content-Type': 'application/json',
Cookie: req.headers.cookie,
},
method: req.method,
});
const data = (await response.json()) as GQLResponse;
if (!response.ok || data?.error || data?.errors?.length)
throw new HttpException(
response.statusText,
response.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
const ttl = queryTTL[operationName];
if (data && ttl !== false)
await this.cacheManager.set(key, data, { ttl: ttl || env.CACHE_TTL });
return reply.send(data);
}
@Get('/get-queries')
public async getQueriesList(@Res() reply: FastifyReply) {
const res = await this.getAllQueries();
return reply.send(res);
}
private async getAllQueries() {
const list = await this.cacheManager.store.keys('*');
return (Object.keys(queryTTL) as Array<keyof typeof queryTTL>).reduce(
(acc, queryName) => {
const queries = list.filter((x) => x.split(' ').at(0) === queryName);
if (queries.length) {
const ttl = queryTTL[queryName];
acc[queryName] = { queries, ttl };
}
return acc;
},
{} as Record<string, QueryItem>,
);
}
@Delete('/delete-query')
public async deleteQuery(
@Query('queryKey') queryKey: string,
@Res() reply: FastifyReply,
) {
try {
await this.cacheManager.del(queryKey);
return reply.send('ok');
} catch (error) {
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Delete('/reset')
public async reset(@Res() reply: FastifyReply) {
try {
await this.cacheManager.reset();
return reply.send('ok');
} catch (error) {
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Get('/get-query')
public async getQueryValue(
@Query('queryKey') queryKey: string,
@Res() reply: FastifyReply,
) {
try {
const value = await this.cacheManager.get(queryKey);
return reply.send(value);
} catch (error) {
throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}

View File

@ -0,0 +1,20 @@
import { ProxyController } from './proxy.controller';
import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common';
import * as redisStore from 'cache-manager-ioredis';
import type { RedisOptions } from 'ioredis';
import { env } from 'src/config/env';
@Module({
controllers: [ProxyController],
imports: [
CacheModule.register<RedisOptions>({
host: env.REDIS_HOST,
port: env.REDIS_PORT,
store: redisStore,
ttl: env.CACHE_TTL,
}),
],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class ProxyModule {}

View File

@ -0,0 +1,11 @@
export type GQLRequest = {
operationName: string;
query: string;
variables: string;
};
export type GQLResponse = {
data: unknown;
error?: unknown;
errors?: unknown[];
};

View File

@ -0,0 +1,13 @@
export function seconds() {
return {
fromDays(days: number) {
return days * 24 * 60 * 60;
},
fromHours(hours: number) {
return hours * 60 * 60;
},
fromMinutes(minutes: number) {
return minutes * 60;
},
};
}

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

22
apps/api/tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
},
"exclude": ["node_modules"]
}

8
apps/web/.dockerignore Normal file
View File

@ -0,0 +1,8 @@
.git
Dockerfile
.dockerignore
node_modules
*.log
dist
.next
README.md

View File

@ -1,8 +1,14 @@
module.exports = {
root: true,
extends: ['custom', 'custom/rules'],
const { createConfig } = require('@vchikalkin/eslint-config-awesome');
module.exports = createConfig('next-typescript', {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
};
rules: {
'import/no-duplicates': 'off',
'react/forbid-component-props': 'off',
'import/consistent-type-specifier-style': 'off',
},
ignorePatterns: ['*.config.js', '.eslintrc.js'],
});

4
apps/web/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Sentry Config File
.sentryclirc
/styles/antd.min.css

View File

@ -1,6 +1,6 @@
overwrite: true
schema: './graphql/crm.schema.graphql'
documents: '**/*.{graphql,js,ts,jsx,tsx}'
documents: [./**/!(*.schema).graphql]
generates:
./graphql/crm.types.ts:
plugins:
@ -16,7 +16,7 @@ generates:
object: true
defaultValue: true
scalars:
Uuid: string
UUID: string
Decimal: number
DateTime: string
# exclude: './graphql/crm.schema.graphql'

19
apps/web/@types/errors.ts Normal file
View File

@ -0,0 +1,19 @@
import elementsToValues from '@/Components/Calculation/config/map/values';
export const ERR_ELT_KASKO = 'ERR_ELT_KASKO';
export const ERR_ELT_OSAGO = 'ERR_ELT_OSAGO';
export const ERR_FINGAP_TABLE = 'ERR_FINGAP_TABLE';
export const ERR_INSURANCE_TABLE = 'ERR_INSURANCE_TABLE';
export const ERR_PAYMENTS_TABLE = 'ERR_PAYMENTS_TABLE';
export const ERROR_TABLE_KEYS = [
ERR_ELT_KASKO,
ERR_ELT_OSAGO,
ERR_FINGAP_TABLE,
ERR_INSURANCE_TABLE,
ERR_PAYMENTS_TABLE,
];
export const ERROR_ELEMENTS_KEYS = Object.keys(elementsToValues);
export const ERROR_KEYS = [...ERROR_ELEMENTS_KEYS, ...ERROR_TABLE_KEYS];

View File

@ -0,0 +1,77 @@
import * as cacheApi from '@/api/cache/query';
import { min } from '@/styles/mq';
import { useQuery } from '@tanstack/react-query';
import { memo, useState } from 'react';
import styled from 'styled-components';
import { Button, Collapse } from 'ui/elements';
import { Flex } from 'ui/grid';
type QueryProps = {
readonly onDeleteQuery: () => Promise<void>;
readonly queryKey: string;
};
const StyledPre = styled.pre`
max-height: 300px;
overflow-y: auto;
${min('desktop')} {
max-height: 800px;
}
`;
export const Query = memo(({ onDeleteQuery, queryKey }: QueryProps) => {
const { data, refetch } = useQuery({
enabled: false,
queryFn: ({ signal }) => signal && cacheApi.getQueryValue(queryKey, { signal }),
queryKey: ['admin', 'cache', 'query', queryKey],
refetchOnWindowFocus: false,
});
const [activeKey, setActiveKey] = useState<string | undefined>(undefined);
const [deletePending, setDeletePending] = useState(false);
const content = (
<>
<StyledPre>{JSON.stringify(data, null, 2)}</StyledPre>
<Flex justifyContent="flex-end">
<Button
type="primary"
danger
disabled={deletePending}
onClick={() => {
setDeletePending(true);
onDeleteQuery().finally(() => {
setDeletePending(false);
});
}}
>
Удалить
</Button>
</Flex>
</>
);
return (
<Collapse
bordered={false}
activeKey={activeKey}
items={[
{
children: data ? content : 'Загрузка...',
key: queryKey,
label: queryKey,
},
]}
onChange={() => {
if (activeKey) {
setActiveKey(undefined);
} else {
setActiveKey(queryKey);
refetch();
}
}}
/>
);
});

View File

@ -0,0 +1,22 @@
import { Query } from './Query';
import * as cacheApi from '@/api/cache/query';
import { useState } from 'react';
import type { QueryItem } from 'shared/types/cache';
type QueryListProps = QueryItem;
export const QueryList = ({ queries }: QueryListProps) => {
const [deletedQueries, setDeletedQueries] = useState<QueryItem['queries']>([]);
function handleDeleteQuery(queryKey: string) {
return cacheApi
.deleteQuery(queryKey)
.then(() => setDeletedQueries([...deletedQueries, queryKey]));
}
const activeQueries = queries.filter((queryKey) => !deletedQueries.includes(queryKey));
return activeQueries.map((queryKey) => (
<Query key={queryKey} queryKey={queryKey} onDeleteQuery={() => handleDeleteQuery(queryKey)} />
));
};

View File

@ -0,0 +1,24 @@
import { useState } from 'react';
import { Button } from 'ui/elements';
import { ReloadOutlined } from 'ui/elements/icons';
export function ReloadButton({ onClick }: { readonly onClick: () => Promise<unknown> }) {
const [pending, setPending] = useState(false);
return (
<Button
loading={pending}
onClick={() => {
setPending(true);
onClick().finally(() => {
setTimeout(() => {
setPending(false);
}, 1000);
});
}}
icon={<ReloadOutlined rev="" />}
>
Обновить
</Button>
);
}

View File

@ -0,0 +1,70 @@
import Background from '../../Layout/Background';
import { useFilteredQueries } from './lib/hooks';
import { QueryList } from './QueryList';
import { reset } from '@/api/cache/query';
import { min } from '@/styles/mq';
import styled from 'styled-components';
import { Button, Collapse, Divider, Input } from 'ui/elements';
const Wrapper = styled(Background)`
padding: 4px 6px;
width: 100vw;
${min('tablet')} {
min-height: 790px;
}
${min('laptop')} {
padding: 4px 18px 10px;
width: 1280px;
}
`;
const Flex = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
`;
const ButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
`;
export function Cache() {
const { filteredQueries, refetch, setFilterString } = useFilteredQueries();
function handleDeleteQuery() {
return reset().then(() => refetch());
}
if (!filteredQueries) {
return <div>Загрузка...</div>;
}
return (
<Wrapper>
<Divider>Управление кэшем</Divider>
<Flex>
<Input
placeholder="Поиск по запросу"
allowClear
onChange={(e) => setFilterString(e.target.value)}
/>
<Collapse
accordion
items={Object.keys(filteredQueries).map((queryGroupName) => ({
children: <QueryList {...filteredQueries[queryGroupName]} />,
key: queryGroupName,
label: queryGroupName,
}))}
/>
<ButtonWrapper>
<Button type="primary" danger disabled={false} onClick={() => handleDeleteQuery()}>
Очистить кэш
</Button>
</ButtonWrapper>
</Flex>
</Wrapper>
);
}

View File

@ -0,0 +1,30 @@
import { filterQueries } from './utils';
import * as cacheApi from '@/api/cache/query';
import type { ResponseQueries } from '@/api/cache/types';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useDebounce } from 'use-debounce';
export function useFilteredQueries() {
const { data: queries, refetch } = useQuery({
enabled: false,
queryFn: ({ signal }) => signal && cacheApi.getQueries({ signal }),
queryKey: ['admin', 'cache', 'queries'],
refetchOnWindowFocus: false,
});
const [filteredQueries, setFilteredQueries] = useState<ResponseQueries | undefined>(queries);
const [filterString, setFilterString] = useState('');
const [debouncedFilterString] = useDebounce(filterString, 350);
useEffect(() => {
if (!debouncedFilterString) {
setFilteredQueries(queries);
}
if (queries && debouncedFilterString) {
setFilteredQueries(filterQueries(queries, debouncedFilterString));
}
}, [debouncedFilterString, queries]);
return { filteredQueries, queries, refetch, setFilterString };
}

View File

@ -0,0 +1,23 @@
import type { ResponseQueries } from '@/api/cache/types';
export function filterQueries(queriesObj: ResponseQueries, searchStr: string): ResponseQueries {
const filteredObj: ResponseQueries = {};
for (const key in queriesObj) {
if (key.includes(searchStr)) {
filteredObj[key] = queriesObj[key];
} else {
const queries: string[] = [];
queriesObj[key].queries.forEach((queryKey) => {
if (queryKey.toLowerCase().includes(searchStr.toLowerCase())) {
queries.push(queryKey);
}
});
if (queries.length) {
filteredObj[key] = { ...queriesObj[key], queries };
}
}
}
return filteredObj;
}

View File

@ -0,0 +1,17 @@
import { min } from '@/styles/mq';
import type { PropsWithChildren } from 'react';
import styled from 'styled-components';
const Flex = styled.div`
display: flex;
flex-direction: column;
${min('laptop')} {
flex-direction: row;
justify-content: center;
}
`;
export function Layout({ children }: PropsWithChildren) {
return <Flex>{children}</Flex>;
}

View File

@ -0,0 +1,2 @@
export * from './Cache';
export * from './Layout';

View File

@ -7,7 +7,7 @@ export const rows: FormTabRows = [
{
title: 'Регистрация',
},
[['radioObjectRegistration', 'radioTypePTS'], { gridTemplateColumns: ['1fr 1fr'] }],
[['radioObjectRegistration', 'radioTypePTS'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['selectRegionRegistration', 'selectTownRegistration', 'selectObjectRegionRegistration']],
[['selectObjectCategoryTax', 'selectObjectTypeTax', 'tbxVehicleTaxInYear']],
[['tbxLeaseObjectYear', 'tbxLeaseObjectMotorPower', 'tbxVehicleTaxInLeasingPeriod']],
@ -26,6 +26,6 @@ export const rows: FormTabRows = [
{
title: 'Доп. продукты',
},
[['selectTechnicalCard', 'selectInsNSIB'], { gridTemplateColumns: '1fr 2fr' }],
[['selectTechnicalCard', 'selectInsNSIB'], { gridTemplateColumns: ['1fr', '1fr 2fr'] }],
[['selectRequirementTelematic', 'selectTracker', 'selectTelematic']],
];

View File

@ -4,9 +4,14 @@ export const id = 'create-kp';
export const title = 'Создание КП';
export const rows: FormTabRows = [
[['cbxPriceWithDiscount', 'cbxFullPriceWithDiscount', 'cbxCostIncrease']],
[['cbxInsurance', 'cbxRegistrationQuote', 'cbxTechnicalCardQuote']],
[['cbxNSIB', 'cbxQuoteRedemptionGraph', 'cbxShowFinGAP']],
[['tbxQuoteName', 'radioQuoteContactGender'], { gridTemplateColumns: '1fr 1fr' }],
[['btnCreateKP', 'linkDownloadKp'], { gridTemplateColumns: '1fr 1fr' }],
[
['cbxPriceWithDiscount', 'cbxFullPriceWithDiscount'],
{ gridTemplateColumns: ['1fr', '1fr 1fr'] },
],
[['cbxQuotePriceWithFullVAT', 'cbxCostIncrease'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxInsurance', 'cbxRegistrationQuote'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxTechnicalCardQuote', 'cbxNSIB'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxQuoteRedemptionGraph', 'cbxShowFinGAP'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['cbxQuoteShowAcceptLimit'], { gridTemplateColumns: ['1fr', '1fr 1fr'] }],
[['tbxQuoteName', 'radioQuoteContactGender'], { gridTemplateColumns: ['1fr', '2fr 1fr'] }],
];

View File

@ -0,0 +1,60 @@
import type { columns } from '../lib/config';
import type { Row, StoreSelector } from '../types';
import { message } from '@/Components/Common/Notification';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Table } from 'ui/elements';
export const PolicyTable = observer(
({
onSelectRow,
storeSelector,
...props
}: {
columns: typeof columns;
onSelectRow: (row: Row) => void;
storeSelector: StoreSelector;
}) => {
const { $process, $tables } = useStore();
const { getRows, getSelectedRow, setSelectedKey } = storeSelector($tables.elt);
return (
<Table
size="small"
pagination={false}
dataSource={getRows}
scroll={{
x: true,
}}
rowSelection={{
getCheckboxProps: (record) => ({
disabled:
!record.sum || record.status !== null || getRows.some((x) => x.status === 'fetching'),
}),
hideSelectAll: true,
onSelect: (record) => {
if (record.sum > 0) {
$process.add('ELT');
setSelectedKey(record.key);
onSelectRow(record);
message.success({
content: 'Выбранный расчет ЭЛТ применен',
duration: 1,
key: record.key,
onClick: () => message.destroy(record.key),
});
$process.delete('ELT');
}
},
selectedRowKeys: getSelectedRow ? [getSelectedRow.key] : [],
type: 'radio',
}}
expandable={{
expandedRowRender: (record) => record.message,
rowExpandable: (record) => Boolean(record.message),
}}
{...props}
/>
);
}
);

View File

@ -0,0 +1,32 @@
import type { StoreSelector } from '../types';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Button } from 'ui/elements';
import { ReloadOutlined } from 'ui/elements/icons';
import { Flex } from 'ui/grid';
export const ReloadButton = observer(
({ storeSelector, onClick }: { onClick: () => void; storeSelector: StoreSelector }) => {
const { $tables, $process } = useStore();
const { validation, getRows: rows } = storeSelector($tables.elt);
const hasErrors = validation.hasErrors;
return (
<Flex justifyContent="center">
<Button
onClick={onClick}
disabled={
hasErrors ||
$process.has('LoadKP') ||
$process.has('Calculate') ||
$process.has('CreateKP')
}
loading={rows.some((x) => x.status === 'fetching')}
shape="circle"
icon={<ReloadOutlined rev="" />}
/>
</Flex>
);
}
);

View File

@ -0,0 +1,23 @@
import type { StoreSelector } from '../types';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Alert } from 'ui/elements';
export const Validation = observer(({ storeSelector }: { storeSelector: StoreSelector }) => {
const { $tables, $process } = useStore();
const { validation } = storeSelector($tables.elt);
const errors = validation.getErrors();
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
}
return null;
});

View File

@ -0,0 +1,3 @@
export * from './PolicyTable';
export * from './ReloadButton';
export * from './Validation';

View File

@ -0,0 +1,75 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { PolicyTable, ReloadButton, Validation } from './Components';
import { columns } from './lib/config';
import { resetRow } from './lib/tools';
import type { Row, StoreSelector } from './types';
import { useStore } from '@/stores/hooks';
import { trpcClient } from '@/trpc/client';
import { observer } from 'mobx-react-lite';
import { Flex } from 'ui/grid';
const storeSelector: StoreSelector = ({ kasko }) => kasko;
export const Kasko = observer(() => {
const store = useStore();
const { $calculation, $tables } = store;
const calculateKasko = trpcClient.eltKasko.useMutation({
onError() {
$tables.elt.kasko.setRows(
$tables.elt.kasko.getRows.map((row) => ({ ...row, status: 'error' }))
);
},
onMutate: () => {
const rows = $tables.elt.kasko.getRows;
$tables.elt.kasko.setRows(rows.map((row) => ({ ...resetRow(row), status: 'fetching' })));
},
onSuccess: ({ rows }) => {
$tables.elt.kasko.setRows(rows);
},
});
function handleOnClick() {
calculateKasko.mutate({
calculation: {
values: store.$calculation.$values.getValues(),
},
});
}
function handleOnSelectRow(row: Row) {
$tables.insurance.row('kasko').column('insuranceCompany').setValue(row.key);
$tables.insurance.row('kasko').column('insCost').setValue(row.sum);
$calculation.element('tbxInsFranchise').setValue(row.totalFranchise);
}
type Column = (typeof columns)[number];
const kaskoColumns = columns.map((column: Column) => {
if (column.key === 'name') {
return {
...column,
title: 'Страховая компания КАСКО',
};
}
if (column.key === 'status') {
return {
...column,
title: <ReloadButton storeSelector={storeSelector} onClick={() => handleOnClick()} />,
};
}
return column;
});
return (
<Flex flexDirection="column">
<Validation storeSelector={storeSelector} />
<PolicyTable
storeSelector={storeSelector}
columns={kaskoColumns}
onSelectRow={(row) => handleOnSelectRow(row)}
/>
</Flex>
);
});

View File

@ -0,0 +1,80 @@
/* eslint-disable no-negated-condition */
/* eslint-disable sonarjs/cognitive-complexity */
import { PolicyTable, ReloadButton, Validation } from './Components';
import { columns } from './lib/config';
import { resetRow } from './lib/tools';
import type { Row, StoreSelector } from './types';
import { useStore } from '@/stores/hooks';
import { trpcClient } from '@/trpc/client';
import { observer } from 'mobx-react-lite';
import { Flex } from 'ui/grid';
const storeSelector: StoreSelector = ({ osago }) => osago;
export const Osago = observer(() => {
const store = useStore();
const { $tables } = store;
const calculateOsago = trpcClient.eltOsago.useMutation({
onError() {
$tables.elt.osago.setRows(
$tables.elt.osago.getRows.map((row) => ({ ...row, status: 'error' }))
);
},
onMutate: () => {
const rows = $tables.elt.osago.getRows;
$tables.elt.osago.setRows(rows.map((row) => ({ ...resetRow(row), status: 'fetching' })));
},
onSuccess: ({ rows }) => {
$tables.elt.osago.setRows(rows);
},
});
async function handleOnClick() {
calculateOsago.mutate({
calculation: {
values: store.$calculation.$values.getValues(),
},
});
}
function handleOnSelectRow(row: Row) {
$tables.insurance.row('osago').column('insuranceCompany').setValue(row.key);
$tables.insurance.row('osago').column('insCost').setValue(row.sum);
}
type Column = (typeof columns)[number];
const osagoColumns = columns.map((column: Column) => {
if (column.key === 'name') {
return {
...column,
title: 'Страховая компания ОСАГО',
};
}
if (column.key === 'status') {
return {
...column,
title: <ReloadButton storeSelector={storeSelector} onClick={() => handleOnClick()} />,
};
}
if (column.key === 'totalFranchise') {
return {
...column,
render: () => 'Не требуется',
};
}
return column;
});
return (
<Flex flexDirection="column">
<Validation storeSelector={storeSelector} />
<PolicyTable
storeSelector={storeSelector}
columns={osagoColumns}
onSelectRow={(row) => handleOnSelectRow(row)}
/>
</Flex>
);
});

View File

@ -0,0 +1,20 @@
import { Kasko } from './Kasko';
import { Osago } from './Osago';
const id = 'elt';
const title = 'ЭЛТ';
function ELT() {
return (
<>
<Osago />
<Kasko />
</>
);
}
export default {
Component: ELT,
id,
title,
};

View File

@ -0,0 +1,56 @@
import type { Row } from '../types';
import type { ColumnsType } from 'antd/lib/table';
import { CloseOutlined, LoadingOutlined } from 'ui/elements/icons';
import { Flex } from 'ui/grid';
const formatter = Intl.NumberFormat('ru', {
currency: 'RUB',
style: 'currency',
}).format;
export const columns: ColumnsType<Row> = [
{
dataIndex: 'name',
key: 'name',
title: 'Страховая компания',
width: '50%',
},
{
dataIndex: 'sum',
key: 'sum',
render: formatter,
sortDirections: ['descend', 'ascend'],
sorter: (a, b) => a.sum - b.sum,
title: 'Сумма',
width: '20%',
},
{
dataIndex: 'totalFranchise',
key: 'totalFranchise',
render: formatter,
title: 'Франшиза',
width: '20%',
},
{
dataIndex: 'status',
key: 'status',
render: (_, record) => {
if (record.status === 'fetching')
return (
<Flex justifyContent="center">
<LoadingOutlined spin rev="" />
</Flex>
);
if (record.status === 'error')
return (
<Flex justifyContent="center">
<CloseOutlined rev="" />
</Flex>
);
return false;
},
title: undefined,
width: '5%',
},
];

View File

@ -0,0 +1,12 @@
import type { Row } from '@/Components/Calculation/Form/ELT/types';
import { defaultRow } from '@/stores/tables/elt/default-values';
export function resetRow(row: Row): Row {
return {
...defaultRow,
id: row.id,
key: row.key,
metodCalc: row.metodCalc,
name: row.name,
};
}

View File

@ -0,0 +1,7 @@
import type { RowSchema } from '@/config/schema/elt';
import type ELTStore from '@/stores/tables/elt';
import type PolicyStore from '@/stores/tables/elt/policy';
import type { z } from 'zod';
export type Row = z.infer<typeof RowSchema>;
export type StoreSelector = (eltStore: ELTStore) => PolicyStore;

View File

@ -3,38 +3,46 @@ import { useStore } from '@/stores/hooks';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import { Flex } from 'ui';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
import { Alert, Table } from 'ui/elements';
import { Flex } from 'ui/grid';
const Grid = styled(Flex)`
flex-direction: column;
`;
const Validation = observer(() => {
const store = useStore();
const { $tables, $process } = useStore();
const messages = store.$tables.fingap.validation.getMessages();
const errors = $tables.fingap.validation.getErrors();
if (messages?.length) {
return <Alert type="error" banner message={messages[0]} />;
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
}
return null;
});
const FinGAP = observer(() => {
const { $tables } = useStore();
const { $tables, $process } = useStore();
const { fingap } = $tables;
const dataSource = toJS(fingap.risks);
const selectedRowKeys = [...toJS(fingap.selectedKeys)];
const disabled = $process.has('LoadKP') || $process.has('Calculate') || $process.has('CreateKP');
return (
<Table
columns={columns}
dataSource={dataSource}
rowSelection={{
getCheckboxProps: () => ({ disabled }),
onChange: (_, selectedRows) => {
const selectedKeys = selectedRows.reduce((acc, row) => {
acc.push(row.key);

View File

@ -13,10 +13,17 @@ export function buildOptionComponent<T>(
const [value, setValue] = useInsuranceValue(key, valueName);
const { getOptions, getStatus } = useRow(key);
const options = getOptions(valueName);
const statuses = getStatus(valueName);
const status = getStatus(valueName);
return (
<Component options={options} setValue={setValue} status={statuses} value={value} {...props} />
<Component
options={options}
onChange={setValue}
disabled={status === 'Disabled'}
loading={status === 'Loading'}
value={value}
{...props}
/>
);
});
}
@ -29,8 +36,10 @@ export function buildValueComponent<T>(
return observer((props: T) => {
const [value, setValue] = useInsuranceValue(key, valueName);
const { getStatus } = useRow(key);
const statuses = getStatus(valueName);
const status = getStatus(valueName);
return <Component setValue={setValue} status={statuses} value={value} {...props} />;
return (
<Component onChange={setValue} disabled={status === 'Disabled'} value={value} {...props} />
);
});
}

View File

@ -1,27 +1,60 @@
/* eslint-disable react/forbid-component-props */
/* eslint-disable canonical/sort-keys */
import { buildOptionComponent, buildValueComponent } from './builders';
import type * as Insurance from './types';
import { MAX_INSURANCE } from '@/constants/values';
import { useStore } from '@/stores/hooks';
import type { ColumnsType } from 'antd/lib/table';
import { formatter, parser } from 'tools/number';
import InputNumber from 'ui/elements/InputNumber';
import Select from 'ui/elements/Select';
import { observer } from 'mobx-react-lite';
import { parser } from 'tools/number';
import { InputNumber, Select } from 'ui/elements';
import { CheckOutlined } from 'ui/elements/icons';
import { createFormatter } from 'ui/elements/InputNumber';
export const columns: ColumnsType<Insurance.RowValues> = [
{
key: 'elt',
dataIndex: 'elt',
render: (_, record) => {
const Check = observer(() => {
const { $tables } = useStore();
if (
(record.key === 'osago' && $tables.elt.osago.getSelectedRow?.key) ||
(record.key === 'kasko' && $tables.elt.kasko.getSelectedRow?.key)
) {
return <CheckOutlined rev="" />;
}
return null;
});
return <Check />;
},
title: 'ЭЛТ',
width: '1%',
},
{
key: 'policyType',
dataIndex: 'policyType',
title: 'Тип полиса',
width: '11%',
},
{
key: 'insuranceCompany',
dataIndex: 'insuranceCompany',
title: 'Страховая компания',
width: 300,
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insuranceCompany');
return <Component optionFilterProp="label" showSearch />;
return (
<Component
optionFilterProp="label"
showSearch
style={{
width: '100%',
}}
/>
);
},
},
{
@ -31,28 +64,40 @@ export const columns: ColumnsType<Insurance.RowValues> = [
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insured');
return <Component />;
return (
<Component
style={{
width: '100%',
maxWidth: '100px',
}}
/>
);
},
width: '100px',
},
{
key: 'insCost',
dataIndex: 'insCost',
title: 'Стоимость за 1-й период',
title: 'Сумма за 1-й период',
render: (_, record) => {
const Component = buildValueComponent(record.key, InputNumber, 'insCost');
return (
<Component
addonAfter="₽"
formatter={formatter}
formatter={createFormatter({ minimumFractionDigits: 2, maximumFractionDigits: 2 })}
max={MAX_INSURANCE}
min={0}
parser={parser}
precision={2}
step={1000}
style={{
width: '150px',
}}
/>
);
},
width: '150px',
},
{
key: 'insTerm',
@ -61,7 +106,15 @@ export const columns: ColumnsType<Insurance.RowValues> = [
render: (_, record) => {
const Component = buildOptionComponent(record.key, Select, 'insTerm');
return <Component />;
return (
<Component
style={{
width: '150px',
maxWidth: '150px',
}}
/>
);
},
width: '150px',
},
];

View File

@ -2,9 +2,8 @@ import { columns } from './config';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import { Flex } from 'ui';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
import { Alert, Table } from 'ui/elements';
import { Flex } from 'ui/grid';
const Grid = styled(Flex)`
flex-direction: column;
@ -17,12 +16,18 @@ const TableWrapper = styled.div`
`;
const Validation = observer(() => {
const store = useStore();
const { $tables, $process } = useStore();
const messages = store.$tables.insurance.validation.getMessages();
const errors = $tables.insurance.validation.getErrors();
if (messages?.length) {
return <Alert type="error" banner message={messages[0]} />;
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
}
return null;

View File

@ -1,5 +1,6 @@
import type { KeysSchema, RowSchema } from '@/config/schema/insurance';
import type { BaseOption, Status } from 'ui/elements/types';
import type { Status } from '@/stores/calculation/statuses/types';
import type { BaseOption } from 'ui/elements/types';
import type { z } from 'zod';
export type Keys = z.infer<typeof KeysSchema>;

View File

@ -1,13 +1,14 @@
import type { FormTabRows } from '../../lib/render-rows';
import { transformRowsForMobile } from '../lib/utils';
export const id = 'insurance';
export const title = 'Страхование';
export const rows: FormTabRows = [
[['tbxLeaseObjectYear', 'selectLeaseObjectUseFor', 'selectLegalClientRegion']],
[['selectEngineType', 'tbxInsFranchise', 'selectLegalClientTown']],
[['selectLeaseObjectCategory', 'tbxMileage', 'tbxINNForCalc']],
[['tbxLeaseObjectMotorPower', 'cbxWithTrailer', 'selectGPSBrand']],
[['tbxEngineVolume', 'cbxInsDecentral', 'selectGPSModel']],
[['selectLeasingWithoutKasko', 'selectInsNSIB']],
[['tbxMileage', 'tbxInsFranchise', 'selectLegalClientTown']],
[['selectGPSBrand', 'cbxWithTrailer', 'selectInsNSIB']],
[['selectGPSModel', 'cbxInsDecentral', 'selectLeasingWithoutKasko']],
];
export const mobileRows = transformRowsForMobile(rows);

View File

@ -1,15 +1,15 @@
import renderFormRows from '../../lib/render-rows';
import { id, rows, title } from './config';
import { id, mobileRows, rows, title } from './config';
import FinGAPTable from './FinGAPTable';
import InsuranceTable from './InsuranceTable';
import { Flex } from 'ui';
import { Media } from '@/styles/media';
import { Flex } from 'ui/grid';
function Insurance() {
const renderedRows = renderFormRows(rows);
return (
<Flex flexDirection="column">
{renderedRows}
<Media lessThan="laptop">{renderFormRows(mobileRows)}</Media>
<Media greaterThanOrEqual="laptop">{renderFormRows(rows)}</Media>
<InsuranceTable />
<FinGAPTable />
</Flex>

View File

@ -8,8 +8,8 @@ export const rows: FormTabRows = [
[['tbxLeaseObjectPrice', 'tbxVATInLeaseObjectPrice', 'tbxLeaseObjectPriceWthtVAT']],
[['selectSupplierCurrency', 'tbxSupplierDiscountRub', 'tbxSupplierDiscountPerc']],
[['tbxFirstPaymentPerc', 'tbxFirstPaymentRub']],
[['tbxLeasingPeriod', 'tbxSaleBonus', 'tbxRedemptionPaymentSum']],
[['selectSubsidy', 'tbxSubsidySum'], { gridTemplateColumns: '2fr 1fr' }],
[['tbxLeasingPeriod', 'tbxSaleBonus']],
[['selectSubsidy', 'tbxSubsidySum'], { gridTemplateColumns: ['1fr', '2fr 1fr'] }],
[['selectImportProgram', 'tbxImportProgramSum', 'tbxAddEquipmentPrice']],
[['radioLastPaymentRule'], { gridTemplateColumns: '1fr' }],
[['tbxLastPaymentPerc', 'tbxLastPaymentRub']],

View File

@ -1,4 +1,5 @@
import type { FormTabRows } from '../../lib/render-rows';
import { transformRowsForMobile } from '../lib/utils';
export const id = 'leasing-object';
export const title = 'ПЛ';
@ -16,3 +17,5 @@ export const rows: FormTabRows = [
[['selectLeaseObjectCategory', 'tbxEngineVolume', 'tbxMileage']],
[['tbxMaxMass', 'tbxEngineHours', 'tbxVIN']],
];
export const mobileRows = transformRowsForMobile(rows);

View File

@ -1,8 +1,14 @@
import renderFormRows from '../../lib/render-rows';
import { id, rows, title } from './config';
import { id, mobileRows, rows, title } from './config';
import { Media } from '@/styles/media';
function LeasingObject() {
return renderFormRows(rows);
return (
<>
<Media lessThan="laptop">{renderFormRows(mobileRows)}</Media>
<Media greaterThanOrEqual="laptop">{renderFormRows(rows)}</Media>
</>
);
}
export default {

View File

@ -2,7 +2,7 @@ import elementsRender from '../../config/elements-render';
import { elements } from './config';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { Flex } from 'ui';
import { Flex } from 'ui/grid';
function PaymentsParams() {
const renderedElements = elements.map((elementName) => {

View File

@ -1,4 +1,4 @@
import { usePaymentValue } from './hooks';
import { usePaymentSum, usePaymentValue } from './hooks';
import { useRowStatus } from '@/stores/tables/payments/hooks';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
@ -8,6 +8,19 @@ export function buildValueComponent<T>(index: number, Component: ComponentType<T
const [value, setValue] = usePaymentValue(index);
const status = useRowStatus(index);
return <Component setValue={setValue} status={status} value={value} {...props} />;
return (
<Component onChange={setValue} disabled={status === 'Disabled'} value={value} {...props} />
);
});
}
export function buildSumComponent<T>(index: number, Component: ComponentType<T>) {
return observer((props: T) => {
const [value, setValue] = usePaymentSum(index);
const status = useRowStatus(index);
return (
<Component onChange={setValue} disabled={status === 'Disabled'} value={value} {...props} />
);
});
}

View File

@ -1,14 +1,23 @@
/* eslint-disable react/forbid-component-props */
/* eslint-disable canonical/sort-keys */
import { buildValueComponent } from './builders';
import { buildSumComponent, buildValueComponent } from './builders';
import type { ColumnsType } from 'antd/lib/table';
import InputNumber from 'ui/elements/InputNumber';
import { parser } from 'tools/number';
import { InputNumber } from 'ui/elements';
import { createFormatter } from 'ui/elements/InputNumber';
type Payment = {
key: number;
num: number;
paymentRelation: number;
paymentSum: number;
};
const formatter = createFormatter({
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
export const columns: ColumnsType<Payment> = [
{
key: 'num',
@ -24,7 +33,38 @@ export const columns: ColumnsType<Payment> = [
render: (_value, payment) => {
const Component = buildValueComponent(payment.num, InputNumber);
return <Component max={100} min={0} precision={payment.num === 0 ? 4 : 2} step={1} />;
return (
<Component
max={100}
min={0}
precision={payment.num === 0 ? 4 : 2}
step={1}
style={{
width: '100%',
}}
/>
);
},
},
{
key: 'paymentSum',
dataIndex: 'paymentSum',
title: 'Сумма',
render: (_value, payment) => {
const Component = buildSumComponent(payment.num, InputNumber);
return (
<Component
min={0}
precision={2}
step={1000}
formatter={formatter}
parser={parser}
style={{
width: '100%',
}}
/>
);
},
},
];

View File

@ -1,4 +1,4 @@
import { useRowValue } from '@/stores/tables/payments/hooks';
import { useRowSum, useRowValue } from '@/stores/tables/payments/hooks';
import { useEffect, useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
@ -20,3 +20,22 @@ export function usePaymentValue(index) {
return [value, setValue];
}
export function usePaymentSum(index) {
const [storeValue, setStoreValue] = useRowSum(index);
const [value, setValue] = useState(storeValue);
const debouncedSetStoreValue = useDebouncedCallback(setStoreValue, 350, { maxWait: 1000 });
useEffect(() => {
if (storeValue !== value) {
debouncedSetStoreValue(value);
}
}, [value]);
useEffect(() => {
setValue(storeValue);
}, [storeValue]);
return [value, setValue];
}

View File

@ -3,10 +3,11 @@ import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { computed } from 'mobx';
import { observer } from 'mobx-react-lite';
import { createContext, useContext, useMemo, useState } from 'react';
import styled from 'styled-components';
import Alert from 'ui/elements/Alert';
import Table from 'ui/elements/Table';
import { Alert, Segmented, Table } from 'ui/elements';
import { Box, Flex } from 'ui/grid';
import { useDebouncedCallback } from 'use-debounce';
const Grid = styled(Flex)`
flex-direction: column;
@ -30,18 +31,26 @@ const TablesGroupGrid = styled(Box)`
`;
const Validation = observer(() => {
const store = useStore();
const { payments } = store.$tables;
const { $tables, $process } = useStore();
const { payments } = $tables;
const messages = payments.validation.getMessages();
const errors = payments.validation.getErrors();
if (messages?.length) {
return <Alert type="error" banner message={messages[0]} />;
if (errors?.length) {
return (
<Alert
type={$process.has('Unlimited') ? 'warning' : 'error'}
banner
message={errors[0].message}
/>
);
}
return null;
});
export const ModeContext = createContext('paymentRelation');
const SPLIT_NUMBER = 12;
function TablePart({ num }) {
@ -50,16 +59,25 @@ function TablePart({ num }) {
const values = payments.values.slice(num * SPLIT_NUMBER, num * SPLIT_NUMBER + SPLIT_NUMBER);
const dataSource = values.map((value, index) => ({
const dataSource = values.map((_, index) => ({
key: index + num * SPLIT_NUMBER,
num: index + num * SPLIT_NUMBER,
paymentRelation: value,
}));
return (
<TableWrapper>
<Table size="small" columns={columns} dataSource={dataSource} pagination={false} />
</TableWrapper>
const mode = useContext(ModeContext);
return useMemo(
() => (
<TableWrapper>
<Table
size="small"
columns={columns.filter((x) => ['num', mode].includes(x.key))}
dataSource={dataSource}
pagination={false}
/>
</TableWrapper>
),
[dataSource, mode]
);
}
@ -79,11 +97,36 @@ const TablesGroup = observer(() => {
return <TablesGroupGrid>{tables}</TablesGroupGrid>;
});
function Mode({ setMode }) {
const { $process } = useStore();
if (!$process.has('Unlimited')) {
return false;
}
return (
<Segmented
block
options={[
{ label: 'Процент', value: 'paymentRelation' },
{ label: 'Сумма', value: 'paymentSum' },
]}
onChange={(value) => setMode(value)}
/>
);
}
export default function TablePayments() {
const [mode, setMode] = useState('paymentRelation');
const debouncedSetMode = useDebouncedCallback(setMode, 300);
return (
<Grid>
<Validation />
<TablesGroup />
<ModeContext.Provider value={mode}>
<Validation />
<Mode setMode={debouncedSetMode} />
<TablesGroup />
</ModeContext.Provider>
</Grid>
);
}

View File

@ -1,4 +1,5 @@
import type { FormTabRows } from '../../lib/render-rows';
import { transformRowsForMobile } from '../lib/utils';
export const id = 'supplier-agent';
export const title = 'Поставщик/агент';
@ -20,3 +21,5 @@ export const rows: FormTabRows = [
[['selectCalcBrokerRewardCondition', 'selectFinDepartmentRewardCondtion'], defaultRowStyle],
[['tbxCalcBrokerRewardSum', 'tbxFinDepartmentRewardSumm'], defaultRowStyle],
];
export const mobileRows = transformRowsForMobile(rows);

View File

@ -1,8 +1,14 @@
import renderFormRows from '../../lib/render-rows';
import { id, rows, title } from './config';
import { id, mobileRows, rows, title } from './config';
import { Media } from '@/styles/media';
function Leasing() {
return renderFormRows(rows);
return (
<>
<Media lessThan="laptop">{renderFormRows(mobileRows)}</Media>
<Media greaterThanOrEqual="laptop">{renderFormRows(rows)}</Media>
</>
);
}
export default {

View File

@ -0,0 +1,14 @@
import type { FormTabRows } from '../../lib/render-rows';
export const id = 'unlimited';
export const title = 'Без ограничений';
export const rows: FormTabRows = [
[['cbxDisableChecks', 'selectUser']],
[['selectTarif', 'tbxCreditRate', 'selectRate']],
[['tbxMinPriceChange', 'tbxMaxPriceChange']],
[['tbxImporterRewardPerc', 'tbxImporterRewardRub']],
[['tbxBonusCoefficient', 'tbxComissionRub', 'tbxComissionPerc']],
[['cbxSupplierFinancing', 'cbxPartialVAT', 'cbxFloatingRate']],
[['cbxQuotePriceWithFullVAT']],
];

View File

@ -0,0 +1,12 @@
import renderFormRows from '../../lib/render-rows';
import { id, rows, title } from './config';
function Unlimited() {
return renderFormRows(rows);
}
export default {
Component: Unlimited,
id,
title,
};

View File

@ -1,16 +1,30 @@
import AddProduct from './AddProduct';
import CreateKP from './CreateKP';
import ELT from './ELT';
import Insurance from './Insurance';
import Leasing from './Leasing';
import LeasingObject from './LeasingObject';
import Payments from './Payments';
import SupplierAgent from './SupplierAgent';
import Unlimited from './Unlimited';
import Background from '@/Components/Layout/Background';
import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { memo } from 'react';
import styled from 'styled-components';
import Tabs from 'ui/elements/layout/Tabs';
import { Tabs } from 'ui/elements';
const formTabs = [Leasing, Payments, LeasingObject, SupplierAgent, Insurance, AddProduct, CreateKP];
const formTabs = [
Leasing,
Payments,
LeasingObject,
SupplierAgent,
Insurance,
ELT,
AddProduct,
CreateKP,
Unlimited,
];
const Wrapper = styled(Background)`
padding: 4px 6px;
@ -32,11 +46,16 @@ const ComponentWrapper = styled.div`
}
`;
function Form() {
export const Form = memo(() => {
const { $process } = useStore();
const filteredTabs =
$process.has('Unlimited') === false ? formTabs.filter((x) => x.id !== 'unlimited') : formTabs;
return (
<Wrapper>
<Tabs type="card" tabBarGutter="5px">
{formTabs.map(({ id, title, Component }) => (
{filteredTabs.map(({ Component, id, title }) => (
<Tabs.TabPane tab={title} key={id}>
<ComponentWrapper>
<Component />
@ -46,6 +65,4 @@ function Form() {
</Tabs>
</Wrapper>
);
}
export default Form;
});

View File

@ -0,0 +1,32 @@
/**
*
* @param {import('../../lib/render-rows').FormTabRows} rows
* @returns {import('../../lib/render-rows').FormTabRows}
*/
export function transformRowsForMobile(rows) {
const mobileRows = [];
let columnGroups = {};
rows.forEach((row) => {
if (Array.isArray(row)) {
row[0].forEach((item, index) => {
if (!columnGroups[index]) {
columnGroups[index] = [];
}
columnGroups[index].push(item);
});
} else {
Object.values(columnGroups).forEach((group) => {
mobileRows.push([group, { gridTemplateColumns: '1fr' }]);
});
columnGroups = {};
mobileRows.push(row);
}
});
Object.values(columnGroups).forEach((group) => {
mobileRows.push([group, { gridTemplateColumns: '1fr' }]);
});
return mobileRows;
}

View File

@ -0,0 +1,20 @@
import { min } from '@/styles/mq';
import styled from 'styled-components';
import { Box } from 'ui/grid';
export const Layout = styled(Box)`
display: flex;
flex-direction: column;
gap: 10px;
${min('laptop')} {
display: grid;
align-items: flex-start;
grid-template-columns: 2fr 1fr;
}
${min('desktop')} {
grid-template-columns: 2fr 1fr 1.5fr;
/* margin: 8px 5%; */
}
`;

View File

@ -0,0 +1,84 @@
/* eslint-disable canonical/sort-keys */
import type { ResultPayment } from '@/stores/results/types';
import type { ColumnsType } from 'antd/lib/table';
export const columns: ColumnsType<ResultPayment> = [
{
key: 'num',
dataIndex: 'num',
title: '#',
width: '10%',
},
{
key: 'paymentSum',
dataIndex: 'paymentSum',
title: 'Сумма платежа',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: 'ndsCompensation',
dataIndex: 'ndsCompensation',
title: 'НДС к возмещению',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: 'redemptionAmount',
dataIndex: 'redemptionAmount',
title: 'Сумма досрочного выкупа',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_piColumn',
dataIndex: '_piColumn',
title: 'PI Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_cashflowMsfoWithCfColumn',
dataIndex: '_cashflowMsfoWithCfColumn',
title: 'CashflowMSFOWithCF Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_creditPaymentColumn',
dataIndex: '_creditPaymentColumn',
title: 'CreditPayment Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_cashflowMsfoColumn',
dataIndex: '_cashflowMsfoColumn',
title: 'CashflowMSFO Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
{
key: '_interestColumn',
dataIndex: '_interestColumn',
title: 'Interest Column',
render: Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB',
}).format,
},
];

View File

@ -0,0 +1,41 @@
/* eslint-disable no-negated-condition */
import { columns } from './config';
import { MAX_LEASING_PERIOD } from '@/constants/values';
import { useStore } from '@/stores/hooks';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import { Table } from 'ui/elements';
const PaymentsTable = observer(() => {
const { $process, $results } = useStore();
const unlimited = $process.has('Unlimited');
const dataSource = toJS($results.payments);
const dataColumns = !unlimited ? columns.filter((x) => !x.key.includes('_')) : columns;
return (
<Table
columns={dataColumns}
dataSource={dataSource}
size="small"
pagination={{
defaultPageSize: MAX_LEASING_PERIOD,
hideOnSinglePage: true,
responsive: true,
// showLessItems: true,
showSizeChanger: false,
}}
scroll={{
x: dataColumns.length > 5 ? 1000 : undefined,
y: dataSource.length > 16 ? 630 : undefined,
}}
/>
);
});
export default {
Component: PaymentsTable,
id: 'payments-table',
title: 'Таблица платежей',
};

View File

@ -0,0 +1,102 @@
import type { ResultValues } from '@/stores/results/types';
export const id = 'output';
export const title = 'Результаты';
export const titles: Record<keyof ResultValues, string> = {
_resultContractEconomy: 'Экономика',
_resultContractEconomyWithVAT: 'Экономика, с НДС',
_resultPi: 'PI',
_resultPiRepayment: 'PI для досрочки',
_resultSumCredit: 'Сумма кредита',
_resultSumCreditPayment: 'Сумма платежей по кредиту',
_resultVatRecoverable: 'НДС к возмещению',
resultAB_FL: 'АВ ФЛ, без НДФЛ.',
resultAB_UL: 'АВ ЮЛ, с НДС.',
resultBonusDopProd: 'Бонус МПЛ за доп.продукты, без НДФЛ',
resultBonusMPL: 'Бонус МПЛ за лизинг, без НДФЛ',
resultBonusSafeFinance: 'Бонус за Safe Finance без НДФЛ',
resultDopMPLLeasing: 'Доп.бонус МПЛ за лизинг, без НДФЛ',
resultDopProdSum: 'Общая сумма доп.продуктов',
resultFirstPayment: 'Первый платеж',
resultFirstPaymentRiskPolicy: 'Первый платеж по риск политике, %',
resultIRRGraphPerc: 'IRR по графику клиента, %',
resultIRRNominalPerc: 'IRR (номинал), %',
resultInsKasko: 'КАСКО, НС, ДГО в графике',
resultInsOsago: 'ОСАГО в графике',
resultLastPayment: 'Последний платеж',
resultParticipationAmount: 'Сумма участия (для принятия решения)',
resultPlPrice: 'Стоимость ПЛ с НДС',
resultPriceUpPr: 'Удорожание, год',
resultTerm: 'Срок, мес.',
resultTotalGraphwithNDS: 'Итого по графику, с НДС',
};
const moneyFormatter = Intl.NumberFormat('ru', {
currency: 'RUB',
style: 'currency',
}).format;
const percentFormatter = Intl.NumberFormat('ru', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
style: 'percent',
}).format;
export const formatters = {
_resultContractEconomy: moneyFormatter,
_resultContractEconomyWithVAT: moneyFormatter,
_resultPi: percentFormatter,
_resultPiRepayment: percentFormatter,
_resultSumCredit: moneyFormatter,
_resultSumCreditPayment: moneyFormatter,
_resultVatRecoverable: moneyFormatter,
resultAB_FL: moneyFormatter,
resultAB_UL: moneyFormatter,
resultBonusDopProd: moneyFormatter,
resultBonusMPL: moneyFormatter,
resultBonusSafeFinance: moneyFormatter,
resultDopMPLLeasing: moneyFormatter,
resultDopProdSum: moneyFormatter,
resultFirstPayment: moneyFormatter,
resultFirstPaymentRiskPolicy: percentFormatter,
resultIRRGraphPerc: percentFormatter,
resultIRRNominalPerc: percentFormatter,
resultInsKasko: moneyFormatter,
resultInsOsago: moneyFormatter,
resultLastPayment: moneyFormatter,
resultParticipationAmount: moneyFormatter,
resultPlPrice: moneyFormatter,
resultPriceUpPr: percentFormatter,
resultTerm: Intl.NumberFormat('ru').format,
resultTotalGraphwithNDS: moneyFormatter,
};
export const elements: Array<keyof ResultValues> = [
'_resultContractEconomy',
'_resultContractEconomyWithVAT',
'_resultPi',
'_resultPiRepayment',
'_resultSumCredit',
'_resultSumCreditPayment',
'_resultVatRecoverable',
'resultTotalGraphwithNDS',
'resultPlPrice',
'resultPriceUpPr',
'resultIRRGraphPerc',
'resultIRRNominalPerc',
'resultInsKasko',
'resultInsOsago',
'resultDopProdSum',
'resultFirstPayment',
'resultLastPayment',
'resultFirstPaymentRiskPolicy',
'resultTerm',
'resultAB_FL',
'resultAB_UL',
'resultBonusMPL',
'resultDopMPLLeasing',
'resultBonusDopProd',
'resultBonusSafeFinance',
'resultParticipationAmount',
];

View File

@ -0,0 +1,55 @@
import { elements, formatters, id, title, titles } from './config';
import { Container, Head } from '@/Components/Layout/Element';
import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { toJS } from 'mobx';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import { Text } from 'ui/elements';
import { Box } from 'ui/grid';
const Grid = styled(Box)`
display: grid;
grid-template-columns: 1fr;
${min('tablet')} {
grid-template-columns: 1fr 1fr;
}
`;
const Wrapper = styled.div`
margin-bottom: 18px;
`;
const Results = observer(() => {
const { $process, $results } = useStore();
const resultsValues = toJS($results.values);
// eslint-disable-next-line no-negated-condition
const values = !$process.has('Unlimited') ? elements.filter((x) => !x.startsWith('_')) : elements;
return (
<Grid>
{values.map((valueName) => {
const formatter = formatters[valueName];
const storeValue = resultsValues[valueName];
const value = formatter(storeValue);
return (
<Wrapper key={valueName}>
<Container key={valueName}>
<Head title={titles[valueName]} />
<Text>{value}</Text>
</Container>
</Wrapper>
);
})}
</Grid>
);
});
export default {
Component: Results,
id,
title,
};

View File

@ -2,7 +2,7 @@
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import Alert from 'ui/elements/Alert';
import { Alert } from 'ui/elements';
import { Box, Flex } from 'ui/grid';
const Bold = styled.span`
@ -23,53 +23,57 @@ const AlertWrapper = styled(Box)`
margin: 0 0 5px 0;
`;
function getElementsErrors($calculation) {
function getAlerts(errors, title, $process) {
return errors.map(({ key, message }) => (
<AlertWrapper>
<Alert
key={key}
type={$process.has('Unlimited') ? 'warning' : 'error'}
showIcon
message={Message(title, message)}
/>
</AlertWrapper>
));
}
function getElementsErrors({ $calculation, $process }) {
return Object.values($calculation.$validation).map((validation) => {
const elementErrors = validation.getMessages();
const elementErrors = validation.getErrors();
const elementTitle = validation.params.err_title;
return elementErrors.map((error) => (
<AlertWrapper>
<Alert type="error" showIcon message={Message(elementTitle, error)} />
</AlertWrapper>
));
return getAlerts(elementErrors, elementTitle, $process);
});
}
function getPaymentsTableErrors($tables) {
const { payments } = $tables;
const messages = payments.validation.getMessages();
const title = payments.validation.params.err_title;
function getTableErrors(tableName, { $process, $tables }) {
const table = $tables[tableName];
const errors = table.validation.getErrors();
const title = table.validation.params.err_title;
return messages.map((text) => <Alert type="error" showIcon message={Message(title, text)} />);
}
function getInsuranceTableErrors($tables) {
const { insurance } = $tables;
const messages = insurance.validation.getMessages();
const title = insurance.validation.params.err_title;
return messages.map((text) => <Alert type="error" showIcon message={Message(title, text)} />);
return getAlerts(errors, title, $process);
}
const Errors = observer(() => {
const { $calculation, $tables } = useStore();
const store = useStore();
const { $calculation, $tables } = store;
const hasElementsErrors = Object.values($calculation.$validation).some(
(validation) => validation.hasErrors
);
const hasPaymentsErrors = $tables.payments.validation.hasErrors;
const hasInsuranceErrors = $tables.insurance.validation.hasErrors;
const hasFingapErrors = $tables.fingap.validation.hasErrors;
if (!hasElementsErrors && !hasPaymentsErrors && !hasInsuranceErrors) {
if (!hasElementsErrors && !hasPaymentsErrors && !hasInsuranceErrors && !hasFingapErrors) {
return <Alert type="success" showIcon message="Ошибок нет 🙂" />;
}
const elementsErrors = getElementsErrors($calculation);
const paymentsErrors = getPaymentsTableErrors($tables);
const insuranceErrors = getInsuranceTableErrors($tables);
const elementsErrors = getElementsErrors(store);
const paymentsErrors = getTableErrors('payments', store);
const insuranceErrors = getTableErrors('insurance', store);
const fingapErrors = getTableErrors('fingap', store);
const errors = [...elementsErrors, ...paymentsErrors, ...insuranceErrors];
const errors = [...elementsErrors, ...paymentsErrors, ...insuranceErrors, ...fingapErrors];
return <Flex flexDirection="column">{errors}</Flex>;
});

View File

@ -0,0 +1,71 @@
import PaymentsTable from './PaymentsTable';
import Results from './Results';
import Validation from './Validation';
import Background from '@/Components/Layout/Background';
import { useErrors, useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { observer } from 'mobx-react-lite';
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { Badge, Tabs } from 'ui/elements';
const outputTabs = [PaymentsTable, Results, Validation];
const items = outputTabs.map(({ Component, id, title }) => {
let Label = () => title;
if (id === 'validation') {
Label = observer(() => {
const { hasErrors } = useErrors();
return (
<Badge offset={[5, 0]} dot={hasErrors}>
{title}
</Badge>
);
});
}
return {
children: <Component />,
key: id,
label: <Label />,
};
});
const Wrapper = styled(Background)`
padding: 4px 10px;
min-height: 200px;
${min('laptop')} {
padding: 4px 18px;
min-height: 790px;
}
`;
export const Output = observer(({ tabs }) => {
const { $results } = useStore();
const [activeKey, setActiveKey] = useState(undefined);
const { hasErrors } = useErrors();
useEffect(() => {
if ($results.payments.length > 0) {
setActiveKey('payments-table');
}
if (!tabs && hasErrors) {
setActiveKey('validation');
}
}, [$results.payments.length, hasErrors, tabs]);
return (
<Wrapper>
<Tabs
items={tabs ? items.filter((x) => x.key !== 'validation') : items}
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
}}
/>
</Wrapper>
);
});

View File

@ -2,17 +2,61 @@ import type { FormTabRows } from '../lib/render-rows';
const defaultRowStyle = { gridTemplateColumns: '1fr' };
export const rows: FormTabRows = [
export const mainRows: FormTabRows = [
{ title: 'Выбор Интереса/ЛС' },
[['selectLead'], defaultRowStyle],
[['selectOpportunity'], defaultRowStyle],
[['cbxRecalcWithRevision'], defaultRowStyle],
[['selectQuote'], defaultRowStyle],
[['btnCalculate'], defaultRowStyle],
[
['btnCalculate'],
{
gridTemplateColumns: '1fr',
marginBottom: ['5px'],
},
],
[
['btnCreateKP', 'linkDownloadKp'],
{
gap: ['10px'],
gridTemplateColumns: ['1fr 1fr'],
},
],
];
export const unlimitedMainRows: FormTabRows = [
{ title: 'Выбор Интереса/ЛС' },
[['selectUser'], defaultRowStyle],
[['selectLead'], defaultRowStyle],
[['selectOpportunity'], defaultRowStyle],
[['cbxRecalcWithRevision'], defaultRowStyle],
[['selectQuote'], defaultRowStyle],
[
['btnCalculate'],
{
gridTemplateColumns: '1fr',
marginBottom: ['5px'],
},
],
[
['btnCreateKP', 'linkDownloadKp'],
{
gap: ['10px'],
gridTemplateColumns: ['1fr 1fr'],
},
],
];
export const paramsRows: FormTabRows = [
{ title: 'Параметры расчета' },
[['labelIrrInfo'], defaultRowStyle],
[['radioCalcType'], defaultRowStyle],
[['tbxIRR_Perc'], defaultRowStyle],
[['tbxTotalPayments'], defaultRowStyle],
];
export const unlimitedParamsRows: FormTabRows = [
{ title: 'Параметры расчета' },
[['radioCalcType'], defaultRowStyle],
[['tbxIRR_Perc', 'tbxPi'], { gridTemplateColumns: '1fr 1fr' }],
[['tbxTotalPayments'], defaultRowStyle],
];

View File

@ -1,7 +1,9 @@
import renderFormRows from '../lib/render-rows';
import { rows } from './config';
import * as config from './config';
import Background from '@/Components/Layout/Background';
import { useStore } from '@/stores/hooks';
import { min } from '@/styles/mq';
import { memo } from 'react';
import styled from 'styled-components';
const Wrapper = styled(Background)`
@ -16,6 +18,20 @@ const Wrapper = styled(Background)`
}
`;
export default function Settings() {
return <Wrapper>{renderFormRows(rows)}</Wrapper>;
}
export const Settings = memo(() => {
const { $process } = useStore();
const mainRows = $process.has('Unlimited')
? renderFormRows(config.unlimitedMainRows)
: renderFormRows(config.mainRows);
const paramsRows = $process.has('Unlimited')
? renderFormRows(config.unlimitedParamsRows)
: renderFormRows(config.paramsRows);
return (
<Wrapper>
{mainRows}
{paramsRows}
</Wrapper>
);
});

View File

@ -0,0 +1,19 @@
import Validation from '../Output/Validation';
import Background from '@/Components/Layout/Background';
import { min } from '@/styles/mq';
import { memo } from 'react';
import styled from 'styled-components';
const Wrapper = styled(Background)`
padding: 4px 10px;
${min('laptop')} {
padding: 4px 18px;
}
`;
export const Component = memo(() => (
<Wrapper>
<Validation.Component />
</Wrapper>
));

View File

@ -0,0 +1,31 @@
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import styled from 'styled-components';
import { LoadingOutlined } from 'ui/elements/icons';
const TextAddon = styled.span`
font-size: 14px;
`;
const formatter = Intl.NumberFormat('ru', {
minimumFractionDigits: 2,
}).format;
export const IRRAddon = observer(() => {
const { $calculation, $process } = useStore();
if ($process.has('Tarif')) {
return (
<TextAddon>
<LoadingOutlined rev="" />
{' Подбирается тариф...'}
</TextAddon>
);
}
const tarif = $calculation.element('selectTarif').getValue();
if (!tarif) return <TextAddon>Тариф не найден</TextAddon>;
const { min, max } = $calculation.$values.getValue('irrInfo');
return <TextAddon>{`${formatter(min)}% - ${formatter(max)}%`}</TextAddon>;
});

View File

@ -0,0 +1,53 @@
/* eslint-disable react/forbid-component-props */
import titles from '../config/elements-titles';
import { useStore } from '@/stores/hooks';
import { observer } from 'mobx-react-lite';
import { pick } from 'radash';
import styled from 'styled-components';
import { Tag } from 'ui/elements';
const Container = styled.div`
display: flex;
flex-direction: row;
gap: 5px;
`;
const TagWrapper = styled.div<{ disabled: boolean }>`
> span {
pointer-events: ${(props) => (props.disabled ? 'none' : 'auto')};
opacity: ${(props) => (props.disabled ? '50%' : '')};
}
`;
const tagsData = pick(titles, ['cbxPartialVAT', 'cbxFloatingRate']);
const { CheckableTag } = Tag;
export const ProductAddon = observer(() => {
const { $calculation } = useStore();
function handleChange(elementName: keyof typeof tagsData, checked: boolean) {
$calculation.element(elementName).setValue(checked);
}
return (
<Container>
{(Object.keys(tagsData) as Array<keyof typeof tagsData>).map((elementName) => {
const visible = $calculation.$status.getStatus(elementName);
return (
<TagWrapper key={elementName} disabled={visible === 'Disabled'}>
<CheckableTag
checked={$calculation.element(elementName).getValue()}
onChange={(checked) => handleChange(elementName, checked)}
key={elementName}
style={{ marginInlineEnd: 0 }}
>
{tagsData[elementName]}
</CheckableTag>
</TagWrapper>
);
})}
</Container>
);
});

View File

@ -1,24 +1,39 @@
import type { Elements } from '../config/map/actions';
import { useProcessContext } from '@/process/hooks/common';
import { useStatus } from '@/stores/calculation/statuses/hooks';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { useThrottledCallback } from 'use-debounce';
type BuilderProps = {
elementName: Elements;
valueName: string;
};
export default function buildAction<T>(
export function buildAction<T>(
Component: ComponentType<T>,
{ elementName, valueName: processName }: BuilderProps
) {
return observer((props: T) => {
const status = useStatus(elementName);
const context = useProcessContext();
const throttledAction = useThrottledCallback(
() => {
import(`process/${processName}/action`).then((module) => module.action(context));
},
1200,
{
trailing: false,
}
);
return (
<Component
action={() => import(`process/${processName}/action`).then((module) => module.action())}
onClick={throttledAction}
status={status}
disabled={status === 'Disabled'}
loading={status === 'Loading'}
{...props}
/>
);

View File

@ -0,0 +1,38 @@
import type { Elements } from '../config/map/values';
import { useStoreValue } from './hooks';
import { useStatus } from '@/stores/calculation/statuses/hooks';
import { useValidation } from '@/stores/calculation/validation/hooks';
import type { Values } from '@/stores/calculation/values/types';
import type { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { Form } from 'ui/elements';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildCheck<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { validateStatus, help } = useValidation(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={(event: CheckboxChangeEvent) => {
setValue(event.target.checked);
}}
disabled={status === 'Disabled'}
checked={value}
{...props}
/>
</Form.Item>
);
});
}

View File

@ -0,0 +1,30 @@
import type { Elements } from '../config/map/values';
import { useStoreValue } from './hooks';
import { useStatus } from '@/stores/calculation/statuses/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
export type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildLink<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value] = useStoreValue(valueName);
const status = useStatus(elementName);
return (
<Component
href={status === 'Disabled' ? undefined : value || undefined}
disabled={!value || status === 'Disabled'}
loading={status === 'Loading'}
{...props}
/>
);
});
}

View File

@ -6,32 +6,34 @@ import { useValidation } from '@/stores/calculation/validation/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { Form } from 'ui/elements';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export default function buildOptions<T>(
export function buildOptions<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { isValid, help } = useValidation(elementName);
const { validateStatus, help } = useValidation(elementName);
const options = useOptions(elementName);
return (
<Component
help={help}
isValid={isValid}
options={options}
setValue={setValue}
status={status}
value={value}
{...props}
/>
<Form.Item help={help} validateStatus={validateStatus}>
<Component
disabled={status === 'Disabled'}
loading={status === 'Loading'}
options={options}
onChange={setValue}
value={value}
{...props}
/>
</Form.Item>
);
});
}

View File

@ -4,13 +4,18 @@ import { useValue } from '@/stores/calculation/values/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import styled from 'styled-components';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export default function buildReadonly<T>(
const Wrapper = styled.div`
margin-bottom: 24px;
`;
export function buildReadonly<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
@ -18,6 +23,10 @@ export default function buildReadonly<T>(
const [value] = useValue(valueName);
const status = useStatus(elementName);
return <Component readOnly status={status} value={value} {...props} />;
return (
<Wrapper>
<Component readOnly status={status} value={value} {...props} />
</Wrapper>
);
});
}

View File

@ -0,0 +1,40 @@
import type { Elements } from '../config/map/values';
import { useStoreValue } from './hooks';
import { useStatus } from '@/stores/calculation/statuses/hooks';
import { useValidation } from '@/stores/calculation/validation/hooks';
import type { Values } from '@/stores/calculation/values/types';
import type { SwitchChangeEventHandler } from 'antd/lib/switch';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import { Form } from 'ui/elements';
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export function buildSwitch<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { validateStatus, help } = useValidation(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={
((checked) => {
setValue(checked);
}) as SwitchChangeEventHandler
}
disabled={status === 'Disabled'}
checked={value}
{...props}
/>
</Form.Item>
);
});
}

View File

@ -4,31 +4,58 @@ import { useStatus } from '@/stores/calculation/statuses/hooks';
import { useValidation } from '@/stores/calculation/validation/hooks';
import type { Values } from '@/stores/calculation/values/types';
import { observer } from 'mobx-react-lite';
import type { ComponentType } from 'react';
import type { ChangeEvent, ComponentType } from 'react';
import { Form } from 'ui/elements';
export type BuilderProps = {
type BuilderProps = {
elementName: Elements;
valueName: Values;
};
export default function buildValue<T>(
export function buildValue<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { isValid, help } = useValidation(elementName);
const { validateStatus, help } = useValidation(elementName);
return (
<Component
help={help}
isValid={isValid}
setValue={setValue}
status={status}
value={value}
{...props}
/>
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
}}
disabled={status === 'Disabled'}
value={value}
{...props}
/>
</Form.Item>
);
});
}
export function buildNumberValue<T>(
Component: ComponentType<T>,
{ elementName, valueName }: BuilderProps
) {
return observer((props: T) => {
const [value, setValue] = useStoreValue(valueName);
const status = useStatus(elementName);
const { validateStatus, help } = useValidation(elementName);
return (
<Form.Item help={help} validateStatus={validateStatus}>
<Component
onChange={(inputValue: number | null) => {
setValue(inputValue || 0);
}}
disabled={status === 'Disabled'}
value={value}
{...props}
/>
</Form.Item>
);
});
}

View File

@ -1,4 +0,0 @@
export { default as buildAction } from './build-action';
export { default as buildOptions } from './build-options';
export { default as buildReadonly } from './build-readonly';
export { default as buildValue } from './build-value';

View File

@ -0,0 +1,7 @@
export * from './build-action';
export * from './build-check';
export * from './build-link';
export * from './build-options';
export * from './build-readonly';
export * from './build-switch';
export * from './build-value';

View File

@ -1,4 +1,3 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
@ -73,18 +72,15 @@ const components = wrapComponentsMap({
selectCalcFinDepartment: e.Select,
selectFinDepartmentRewardCondtion: e.Select,
tbxFinDepartmentRewardSumm: e.InputNumber,
cbxInsDecentral: e.Switch,
radioInsKaskoType: e.Radio,
cbxInsDecentral: e.Checkbox,
tbxInsFranchise: e.InputNumber,
cbxInsUnlimitDrivers: e.Switch,
tbxInsAgeDrivers: e.InputNumber,
tbxInsExpDrivers: e.InputNumber,
tbxINNForCalc: e.InputNumber,
selectGPSBrand: e.Select,
selectGPSModel: e.Select,
selectRegionRegistration: e.Select,
selectTownRegistration: e.Select,
radioInfuranceOPF: e.Radio,
selectRegistration: e.Select,
selectInsNSIB: e.Select,
selectRequirementTelematic: e.Select,
@ -134,6 +130,13 @@ const components = wrapComponentsMap({
tbxBonusCoefficient: e.InputNumber,
selectLeasingWithoutKasko: e.Select,
tbxVIN: e.Input,
selectUser: e.Select,
cbxSupplierFinancing: e.Switch,
tbxPi: e.InputNumber,
cbxPartialVAT: e.Switch,
cbxFloatingRate: e.Switch,
cbxQuotePriceWithFullVAT: e.Switch,
cbxQuoteShowAcceptLimit: e.Switch,
/** Readonly Elements */
labelLeaseObjectRisk: e.Text,
@ -145,6 +148,7 @@ const components = wrapComponentsMap({
/** Button Elements */
btnCreateKP: e.Button,
btnCreateKPMini: e.Button,
btnCalculate: e.Button,
/** Link Elements */

View File

@ -1,11 +1,13 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
import CurrencyAddon from '../addons/currency-addon';
import type { ElementsProps } from './elements-components';
import { MAX_FRANCHISE, MAX_LEASING_PERIOD } from '@/constants/values';
import dayjs from 'dayjs';
import { formatter, formatterExtra, parser } from 'tools/number';
import { DownloadOutlined } from 'ui/elements/icons';
import { parser } from 'tools/number';
import { DownloadOutlined, PlusOutlined } from 'ui/elements/icons';
import { createFormatter } from 'ui/elements/InputNumber';
const formatter = createFormatter({ minimumFractionDigits: 2, maximumFractionDigits: 2 });
const props: Partial<ElementsProps> = {
tbxLeaseObjectPrice: {
@ -16,6 +18,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxLeaseObjectPriceWthtVAT: {
min: 0,
@ -25,6 +30,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxVATInLeaseObjectPrice: {
min: 0,
@ -34,12 +42,18 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxEngineHours: {
min: 0,
step: 10,
precision: 2,
addonAfter: 'ч.',
style: {
width: '100%',
},
},
tbxSupplierDiscountRub: {
min: 0,
@ -49,6 +63,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: CurrencyAddon,
style: {
width: '100%',
},
},
tbxSupplierDiscountPerc: {
min: 0,
@ -56,6 +73,9 @@ const props: Partial<ElementsProps> = {
precision: 2,
addonAfter: '%',
style: {
width: '100%',
},
},
radioBalanceHolder: {
optionType: 'button',
@ -68,6 +88,9 @@ const props: Partial<ElementsProps> = {
precision: 2,
addonAfter: '%',
style: {
width: '100%',
},
},
radioLastPaymentRule: {
block: true,
@ -77,8 +100,11 @@ const props: Partial<ElementsProps> = {
max: 50,
precision: 4,
parser,
formatter: formatterExtra,
formatter: createFormatter({ minimumFractionDigits: 4, maximumFractionDigits: 4 }),
addonAfter: '%',
style: {
width: '100%',
},
},
tbxFirstPaymentRub: {
min: 0,
@ -88,6 +114,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxLastPaymentPerc: {
min: 0,
@ -95,8 +124,11 @@ const props: Partial<ElementsProps> = {
step: 1,
precision: 6,
parser,
formatter: formatterExtra,
formatter: createFormatter({ minimumFractionDigits: 6, maximumFractionDigits: 6 }),
addonAfter: '%',
style: {
width: '100%',
},
},
tbxLastPaymentRub: {
min: 0,
@ -106,6 +138,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxRedemptionPaymentSum: {
min: 1000,
@ -115,34 +150,55 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxLeasingPeriod: {
min: 13,
max: MAX_LEASING_PERIOD,
addonAfter: 'мес.',
style: {
width: '100%',
},
},
tbxSubsidySum: {
min: 0,
precision: 2,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxImportProgramSum: {
min: 0,
precision: 2,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxAddEquipmentPrice: {
min: 0,
precision: 2,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxParmentsDecreasePercent: {
min: 50,
max: 99,
style: {
width: '100%',
},
},
tbxComissionPerc: {
min: 0,
max: 100,
style: {
width: '100%',
},
},
tbxComissionRub: {
min: 0,
@ -151,6 +207,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
selectLeaseObjectType: {
showSearch: true,
@ -172,6 +231,9 @@ const props: Partial<ElementsProps> = {
min: 1,
max: 1000,
addonAfter: 'шт.',
style: {
width: '100%',
},
},
selectLeaseObjectUseFor: {
showSearch: true,
@ -180,6 +242,9 @@ const props: Partial<ElementsProps> = {
tbxLeaseObjectYear: {
min: 1994,
max: dayjs().year(),
style: {
width: '100%',
},
},
selectLeaseObjectCategory: {
showSearch: false,
@ -196,6 +261,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'л.с.',
style: {
width: '100%',
},
},
tbxEngineVolume: {
min: 0,
@ -203,8 +271,11 @@ const props: Partial<ElementsProps> = {
step: 0.5,
precision: 4,
parser,
formatter: formatterExtra,
formatter: createFormatter({ minimumFractionDigits: 4, maximumFractionDigits: 4 }),
addonAfter: 'л',
style: {
width: '100%',
},
},
tbxMaxMass: {
min: 0,
@ -213,12 +284,18 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'кг',
style: {
width: '100%',
},
},
tbxCountSeats: {
min: 0,
max: 2000,
precision: 0,
parser,
style: {
width: '100%',
},
},
tbxMaxSpeed: {
min: 0,
@ -226,6 +303,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: 'км/ч',
style: {
width: '100%',
},
},
selectDealer: {
showSearch: true,
@ -236,40 +316,54 @@ const props: Partial<ElementsProps> = {
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxDealerBrokerRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxIndAgentRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxCalcDoubleAgentRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxCalcBrokerRewardSum: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxFinDepartmentRewardSumm: {
min: 0,
max: 20,
step: 0.1,
precision: 2,
},
radioInsKaskoType: {
optionType: 'button',
buttonStyle: 'solid',
style: {
width: '100%',
},
},
tbxInsFranchise: {
min: 0,
@ -279,14 +373,23 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxInsAgeDrivers: {
// min: 18,
// max: 99,
style: {
width: '100%',
},
},
tbxInsExpDrivers: {
// min: 0,
// max: 99,
style: {
width: '100%',
},
},
selectRegionRegistration: {
showSearch: true,
@ -300,34 +403,58 @@ const props: Partial<ElementsProps> = {
optionType: 'button',
buttonStyle: 'solid',
},
btnCalculate: {
children: 'Рассчитать график',
type: 'primary',
block: true,
},
btnCreateKP: {
type: 'primary',
text: 'Создать КП',
children: 'Создать КП',
icon: <PlusOutlined rev="" />,
},
btnCreateKPMini: {
children: '',
type: 'primary',
icon: <PlusOutlined rev="" />,
block: true,
},
tbxCreditRate: {
min: 0,
max: 99.99,
step: 0.1,
style: {
width: '100%',
},
},
tbxMaxPriceChange: {
min: 0,
max: 34_999_990,
max: 1_000_000_000,
step: 10_000,
parser,
formatter,
style: {
width: '100%',
},
},
tbxMinPriceChange: {
min: 0,
max: 34_999_990,
max: 1_000_000_000,
step: 10_000,
parser,
formatter,
style: {
width: '100%',
},
},
tbxImporterRewardPerc: {
min: 0,
max: 99.99,
step: 0.1,
precision: 2,
style: {
width: '100%',
},
},
tbxImporterRewardRub: {
min: 0,
@ -336,6 +463,9 @@ const props: Partial<ElementsProps> = {
precision: 2,
parser,
formatter,
style: {
width: '100%',
},
},
selectLead: {
showSearch: true,
@ -349,35 +479,42 @@ const props: Partial<ElementsProps> = {
showSearch: true,
optionFilterProp: 'label',
},
btnCalculate: {
text: 'Рассчитать график',
type: 'primary',
},
tbxIRR_Perc: {
min: 0,
min: -500,
max: 500,
step: 0.0001,
precision: 6,
parser,
formatter: formatterExtra,
formatter: createFormatter({ minimumFractionDigits: 6, maximumFractionDigits: 6 }),
addonAfter: '%',
style: {
width: '100%',
},
},
linkDownloadKp: {
type: 'primary',
text: 'Скачать КП',
icon: <DownloadOutlined />,
tbxPi: {
min: -500,
max: 500,
step: 0.0001,
precision: 6,
parser,
formatter: createFormatter({ minimumFractionDigits: 6, maximumFractionDigits: 6 }),
addonAfter: '%',
style: {
width: '100%',
},
},
linkDownloadKp: { children: 'Скачать КП', icon: <DownloadOutlined rev="" />, type: 'primary' },
tbxMileage: {
min: 0,
step: 100,
precision: 2,
addonAfter: 'км',
style: {
width: '100%',
},
},
cbxRecalcWithRevision: {
text: 'Пересчет без пересмотра',
style: {
marginBottom: '8px',
},
children: 'Пересчет без пересмотра',
},
tbxTotalPayments: {
min: 0,
@ -386,6 +523,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxVehicleTaxInYear: {
min: 0,
@ -395,6 +535,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
tbxVehicleTaxInLeasingPeriod: {
min: 0,
@ -404,6 +547,9 @@ const props: Partial<ElementsProps> = {
parser,
formatter,
addonAfter: '₽',
style: {
width: '100%',
},
},
selectObjectRegionRegistration: {
showSearch: true,
@ -416,6 +562,9 @@ const props: Partial<ElementsProps> = {
formatter,
readOnly: true,
controls: false,
style: {
width: '100%',
},
},
selectLegalClientRegion: {
showSearch: true,
@ -425,10 +574,6 @@ const props: Partial<ElementsProps> = {
showSearch: true,
optionFilterProp: 'label',
},
radioInfuranceOPF: {
optionType: 'button',
buttonStyle: 'solid',
},
radioGraphType: {
spaceProps: {
direction: 'vertical',
@ -444,6 +589,35 @@ const props: Partial<ElementsProps> = {
direction: 'vertical',
},
},
selectUser: {
showSearch: true,
optionFilterProp: 'label',
},
tbxBonusCoefficient: {
min: 0,
max: 10,
step: 0.1,
precision: 4,
formatter: createFormatter({ minimumFractionDigits: 4, maximumFractionDigits: 4 }),
style: {
width: '100%',
},
},
tbxVIN: {
onInput: (e) => {
e.currentTarget.value = e.currentTarget.value.toUpperCase();
},
},
labelIrrInfo: {
style: {
marginBottom: '18px',
},
},
labelDepreciationGroup: {
style: {
marginBottom: '18px',
},
},
};
export default props;

View File

@ -1,4 +1,6 @@
import buildReadonly from '../../builders/build-readonly';
import { IRRAddon } from '../../addons/irr-addon';
import { ProductAddon } from '../../addons/product-addon';
import { buildLink } from '../../builders';
import components from '../elements-components';
import elementsProps from '../elements-props';
import titles from '../elements-titles';
@ -6,20 +8,131 @@ import types from '../elements-types';
import map from '../map';
import type { RenderProps } from './types';
import { Container, Head } from '@/Components/Layout/Element';
import { useStore } from '@/stores/hooks';
import { useErrors, useStore } from '@/stores/hooks';
import { useIsFetching } from '@tanstack/react-query';
import { observer } from 'mobx-react-lite';
import type { ComponentProps } from 'react';
import { Link, Tooltip } from 'ui/elements';
import { LoadingOutlined } from 'ui/elements/icons';
import Link from 'ui/elements/Link';
import Tooltip from 'ui/elements/Tooltip';
const defaultLinkProps: ComponentProps<typeof Link> = {
text: 'Открыть в CRM',
children: 'Открыть в CRM',
size: 'small',
style: { padding: 0 },
type: 'link',
};
const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
btnCalculate: {
render: () => {
const elementName = 'btnCalculate';
const title = titles.btnCalculate;
const valueName = map.btnCalculate;
const Component = components.btnCalculate;
const props = elementsProps.btnCalculate;
const { builder } = types.btnCalculate();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $process } = useStore();
const { hasErrors } = useErrors();
return (
<Container>
<Head htmlFor={elementName} title={title} />
<Element
{...props}
loading={$process.has('Calculate') || $process.has('CreateKP')}
disabled={
$process.has('LoadKP') ||
(!$process.has('Unlimited') && hasErrors) ||
$process.has('Tarif')
}
/>
</Container>
);
});
return <RenderedComponent />;
},
},
btnCreateKP: {
render: () => {
const elementName = 'btnCreateKP';
const title = titles.btnCreateKP;
const valueName = map.btnCreateKP;
const Component = components.btnCreateKP;
const props = elementsProps.btnCreateKP;
const { builder } = types.btnCreateKP();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $process } = useStore();
const { hasErrors } = useErrors();
return (
<Container>
<Head htmlFor={elementName} title={title} />
<Element
{...props}
loading={$process.has('Calculate') || $process.has('CreateKP')}
disabled={
$process.has('LoadKP') ||
(!$process.has('Unlimited') && hasErrors) ||
$process.has('Tarif')
}
/>
</Container>
);
});
return <RenderedComponent />;
},
},
btnCreateKPMini: {
render: () => {
const elementName = 'btnCreateKPMini';
const title = titles.btnCreateKPMini;
const valueName = map.btnCreateKPMini;
const Component = components.btnCreateKPMini;
const props = elementsProps.btnCreateKPMini;
const { builder } = types.btnCreateKPMini();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $process } = useStore();
const { hasErrors } = useErrors();
return (
<Container>
<Head htmlFor={elementName} title={title} />
<Element
{...props}
loading={$process.has('Calculate') || $process.has('CreateKP')}
disabled={$process.has('LoadKP') || (!$process.has('Unlimited') && hasErrors)}
/>
</Container>
);
});
return <RenderedComponent />;
},
},
selectHighSeasonStart: {
render: () => {
const elementName = 'selectHighSeasonStart';
@ -59,7 +172,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
valueName,
});
const LinkComponent = buildReadonly(Link, {
const LinkComponent = buildLink(Link, {
elementName: 'linkLeadUrl',
valueName: 'leadUrl',
});
@ -91,7 +204,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
valueName,
});
const LinkComponent = buildReadonly(Link, {
const LinkComponent = buildLink(Link, {
elementName: 'linkOpportunityUrl',
valueName: 'opportunityUrl',
});
@ -109,6 +222,29 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
},
},
selectProduct: {
render: () => {
const elementName = 'selectProduct';
const title = titles.selectProduct;
const valueName = map.selectProduct;
const Component = components.selectProduct;
const props = elementsProps.selectProduct;
const { builder } = types.selectProduct();
const Element = builder(Component, {
elementName,
valueName,
});
return (
<Container key={elementName}>
<Head addon={<ProductAddon />} htmlFor={elementName} title={title} />
<Element {...props} id={elementName} />
</Container>
);
},
},
selectQuote: {
render: () => {
const elementName = 'selectQuote';
@ -123,7 +259,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
valueName,
});
const LinkComponent = buildReadonly(Link, {
const LinkComponent = buildLink(Link, {
elementName: 'linkQuoteUrl',
valueName: 'quoteUrl',
});
@ -180,6 +316,36 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
},
},
tbxIRR_Perc: {
render: () => {
const elementName = 'tbxIRR_Perc';
const title = titles.tbxIRR_Perc;
const valueName = map.tbxIRR_Perc;
const Component = components.tbxIRR_Perc;
const props = elementsProps.tbxIRR_Perc;
const { builder } = types.tbxIRR_Perc();
const Element = builder(Component, {
elementName,
valueName,
});
const RenderedComponent = observer(() => {
const { $calculation } = useStore();
const { max, min } = $calculation.$values.getValue('irrInfo');
return (
<Container>
<Head htmlFor={elementName} title={title} addon={<IRRAddon />} />
<Element {...props} min={min > 0 ? min : undefined} max={max > 0 ? max : undefined} />
</Container>
);
});
return <RenderedComponent />;
},
},
tbxVehicleTaxInYear: {
render: () => {
const elementName = 'tbxVehicleTaxInYear';
@ -204,7 +370,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
<Element
{...props}
id={elementName}
addonBefore={isFetching && <LoadingOutlined spin />}
addonBefore={isFetching ? <LoadingOutlined spin rev="" /> : null}
/>
</Container>
</Tooltip>

View File

@ -1,4 +1,3 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
@ -74,8 +73,6 @@ const titles: Record<ActionElements | ValuesElements, string> = {
selectGPSModel: 'Модель GPS',
selectRegionRegistration: 'Регион регистрации',
selectTownRegistration: 'Город регистрации',
radioInfuranceOPF: 'ОПФ для расчета страховки',
radioInsKaskoType: 'Тип страхования КАСКО',
cbxInsDecentral: 'Децентрализованное страхование',
tbxInsFranchise: 'Франшиза',
cbxInsUnlimitDrivers: 'Неограниченное число водителей',
@ -96,7 +93,7 @@ const titles: Record<ActionElements | ValuesElements, string> = {
selectTarif: 'Тариф',
tbxCreditRate: 'Ставка привлечения, %',
selectRate: 'Ставка привлечения',
tbxMaxPriceChange: 'Макс.возможное изменение стоимости ПЛ',
tbxMaxPriceChange: 'Макс. возможное изменение стоимости ПЛ',
tbxImporterRewardPerc: 'АВ импортера, %',
tbxImporterRewardRub: 'АВ импортера, руб.',
cbxDisableChecks: 'Отключить все проверки',
@ -116,7 +113,6 @@ const titles: Record<ActionElements | ValuesElements, string> = {
selectObjectCategoryTax: 'Кат. по ТР ТС 018/2011',
selectObjectTypeTax: 'Тип ТС для ТН',
radioTypePTS: 'Тип ПТС',
tbxINNForCalc: 'ИНН контрагента',
selectLegalClientRegion: 'Регион по юр.адресу клиента',
selectLegalClientTown: 'Город по юр.адресу клиента',
selectSubsidy: 'Субсидия',
@ -128,6 +124,13 @@ const titles: Record<ActionElements | ValuesElements, string> = {
tbxBonusCoefficient: 'Коэффициент снижения бонуса',
selectLeasingWithoutKasko: 'Лизинг без КАСКО',
tbxVIN: 'VIN',
selectUser: 'Пользователь',
cbxSupplierFinancing: 'Финансирование поставщика',
tbxPi: 'PI',
cbxPartialVAT: 'Частичный НДС',
cbxFloatingRate: 'Плавающая ставка',
cbxQuotePriceWithFullVAT: 'Отображать Стоимость ПЛ с полным НДС',
cbxQuoteShowAcceptLimit: 'Отображать одобренный лимит',
/** Link Elements */
linkDownloadKp: '',
@ -146,6 +149,7 @@ const titles: Record<ActionElements | ValuesElements, string> = {
/** Action Elements */
btnCalculate: '',
btnCreateKP: '',
btnCreateKPMini: '',
};
export default titles;

View File

@ -1,11 +1,22 @@
/* eslint-disable jsdoc/multiline-blocks */
/* eslint-disable canonical/sort-keys */
import { buildAction, buildOptions, buildReadonly, buildValue } from '../builders';
import * as b from '../builders';
import type { Elements as ActionElements } from './map/actions';
import type { Elements as ValuesElements } from './map/values';
type ElementTypes = 'Action' | 'Options' | 'Readonly' | 'Value';
type Builders = typeof buildAction | typeof buildOptions | typeof buildReadonly | typeof buildValue;
type ElementTypes =
| 'Action'
| 'Check'
| 'Link'
| 'Number'
| 'Options'
| 'Readonly'
| 'Switch'
| 'Value';
type Builders =
| typeof b.buildAction
| typeof b.buildOptions
| typeof b.buildReadonly
| typeof b.buildValue;
type GetType = () => {
builder: Builders;
@ -19,19 +30,35 @@ function wrapTypeGetters<G extends GetType, E extends Record<ElementTypes, G>>(a
const t = wrapTypeGetters({
Value: () => ({
typeName: 'Value',
builder: buildValue,
builder: b.buildValue,
}),
Number: () => ({
typeName: 'Number',
builder: b.buildNumberValue,
}),
Readonly: () => ({
typeName: 'Readonly',
builder: buildReadonly,
builder: b.buildReadonly,
}),
Options: () => ({
typeName: 'Options',
builder: buildOptions,
builder: b.buildOptions,
}),
Action: () => ({
typeName: 'Action',
builder: buildAction,
builder: b.buildAction,
}),
Link: () => ({
typeName: 'Link',
builder: b.buildLink,
}),
Check: () => ({
typeName: 'Check',
builder: b.buildCheck,
}),
Switch: () => ({
typeName: 'Switch',
builder: b.buildSwitch,
}),
});
@ -40,69 +67,68 @@ function wrapElementsTypes<T, E extends Record<ActionElements | ValuesElements,
}
const types = wrapElementsTypes({
cbxRecalcWithRevision: t.Value,
tbxLeaseObjectPrice: t.Value,
tbxLeaseObjectPriceWthtVAT: t.Value,
tbxVATInLeaseObjectPrice: t.Value,
tbxEngineHours: t.Value,
tbxSupplierDiscountRub: t.Value,
tbxSupplierDiscountPerc: t.Value,
tbxLeasingPeriod: t.Value,
tbxFirstPaymentPerc: t.Value,
tbxFirstPaymentRub: t.Value,
tbxLastPaymentPerc: t.Value,
tbxLastPaymentRub: t.Value,
cbxRecalcWithRevision: t.Check,
tbxLeaseObjectPrice: t.Number,
tbxLeaseObjectPriceWthtVAT: t.Number,
tbxVATInLeaseObjectPrice: t.Number,
tbxEngineHours: t.Number,
tbxSupplierDiscountRub: t.Number,
tbxSupplierDiscountPerc: t.Number,
tbxLeasingPeriod: t.Number,
tbxFirstPaymentPerc: t.Number,
tbxFirstPaymentRub: t.Number,
tbxLastPaymentPerc: t.Number,
tbxLastPaymentRub: t.Number,
selectImportProgram: t.Options,
tbxImportProgramSum: t.Readonly,
tbxAddEquipmentPrice: t.Value,
tbxRedemptionPaymentSum: t.Value,
tbxParmentsDecreasePercent: t.Value,
tbxComissionPerc: t.Value,
tbxComissionRub: t.Value,
tbxSaleBonus: t.Value,
tbxIRR_Perc: t.Value,
tbxLeaseObjectCount: t.Value,
cbxWithTrailer: t.Value,
cbxLeaseObjectUsed: t.Value,
tbxMaxMass: t.Value,
tbxCountSeats: t.Value,
tbxMaxSpeed: t.Value,
tbxLeaseObjectYear: t.Value,
tbxLeaseObjectMotorPower: t.Value,
tbxEngineVolume: t.Value,
tbxDealerRewardSumm: t.Value,
tbxDealerBrokerRewardSumm: t.Value,
tbxIndAgentRewardSumm: t.Value,
tbxCalcDoubleAgentRewardSumm: t.Value,
tbxCalcBrokerRewardSum: t.Value,
tbxFinDepartmentRewardSumm: t.Value,
cbxInsDecentral: t.Value,
tbxInsFranchise: t.Value,
cbxInsUnlimitDrivers: t.Value,
tbxInsAgeDrivers: t.Value,
tbxInsExpDrivers: t.Value,
tbxINNForCalc: t.Value,
cbxLastPaymentRedemption: t.Value,
cbxPriceWithDiscount: t.Value,
cbxFullPriceWithDiscount: t.Value,
cbxCostIncrease: t.Value,
cbxInsurance: t.Value,
cbxRegistrationQuote: t.Value,
cbxTechnicalCardQuote: t.Value,
cbxNSIB: t.Value,
tbxAddEquipmentPrice: t.Number,
tbxRedemptionPaymentSum: t.Number,
tbxParmentsDecreasePercent: t.Number,
tbxComissionPerc: t.Number,
tbxComissionRub: t.Number,
tbxSaleBonus: t.Number,
tbxIRR_Perc: t.Number,
tbxLeaseObjectCount: t.Number,
cbxWithTrailer: t.Check,
cbxLeaseObjectUsed: t.Check,
tbxMaxMass: t.Number,
tbxCountSeats: t.Number,
tbxMaxSpeed: t.Number,
tbxLeaseObjectYear: t.Number,
tbxLeaseObjectMotorPower: t.Number,
tbxEngineVolume: t.Number,
tbxDealerRewardSumm: t.Number,
tbxDealerBrokerRewardSumm: t.Number,
tbxIndAgentRewardSumm: t.Number,
tbxCalcDoubleAgentRewardSumm: t.Number,
tbxCalcBrokerRewardSum: t.Number,
tbxFinDepartmentRewardSumm: t.Number,
cbxInsDecentral: t.Check,
tbxInsFranchise: t.Number,
cbxInsUnlimitDrivers: t.Switch,
tbxInsAgeDrivers: t.Number,
tbxInsExpDrivers: t.Number,
cbxLastPaymentRedemption: t.Switch,
cbxPriceWithDiscount: t.Switch,
cbxFullPriceWithDiscount: t.Switch,
cbxCostIncrease: t.Switch,
cbxInsurance: t.Switch,
cbxRegistrationQuote: t.Switch,
cbxTechnicalCardQuote: t.Switch,
cbxNSIB: t.Switch,
tbxQuoteName: t.Value,
cbxQuoteRedemptionGraph: t.Value,
cbxShowFinGAP: t.Value,
tbxCreditRate: t.Value,
tbxMaxPriceChange: t.Value,
tbxImporterRewardPerc: t.Value,
tbxImporterRewardRub: t.Value,
cbxDisableChecks: t.Value,
tbxMileage: t.Value,
tbxTotalPayments: t.Value,
tbxVehicleTaxInYear: t.Value,
tbxVehicleTaxInLeasingPeriod: t.Value,
tbxMinPriceChange: t.Value,
cbxQuoteRedemptionGraph: t.Switch,
cbxShowFinGAP: t.Switch,
tbxCreditRate: t.Number,
tbxMaxPriceChange: t.Number,
tbxImporterRewardPerc: t.Number,
tbxImporterRewardRub: t.Number,
cbxDisableChecks: t.Switch,
tbxMileage: t.Number,
tbxTotalPayments: t.Number,
tbxVehicleTaxInYear: t.Number,
tbxVehicleTaxInLeasingPeriod: t.Number,
tbxMinPriceChange: t.Number,
selectProduct: t.Options,
selectClientRisk: t.Options,
@ -155,8 +181,6 @@ const types = wrapElementsTypes({
radioLastPaymentRule: t.Options,
radioGraphType: t.Options,
radioDeliveryTime: t.Options,
radioInsKaskoType: t.Options,
radioInfuranceOPF: t.Options,
selectRequirementTelematic: t.Options,
radioQuoteContactGender: t.Options,
radioCalcType: t.Options,
@ -165,6 +189,13 @@ const types = wrapElementsTypes({
tbxBonusCoefficient: t.Value,
selectLeasingWithoutKasko: t.Options,
tbxVIN: t.Value,
selectUser: t.Options,
cbxSupplierFinancing: t.Switch,
tbxPi: t.Number,
cbxPartialVAT: t.Switch,
cbxFloatingRate: t.Switch,
cbxQuotePriceWithFullVAT: t.Switch,
cbxQuoteShowAcceptLimit: t.Switch,
labelLeaseObjectRisk: t.Readonly,
tbxInsKaskoPriceLeasePeriod: t.Readonly,
@ -174,12 +205,13 @@ const types = wrapElementsTypes({
tbxSubsidySum: t.Readonly,
btnCreateKP: t.Action,
btnCreateKPMini: t.Action,
btnCalculate: t.Action,
linkDownloadKp: t.Readonly,
linkLeadUrl: t.Readonly,
linkOpportunityUrl: t.Readonly,
linkQuoteUrl: t.Readonly,
linkDownloadKp: t.Link,
linkLeadUrl: t.Link,
linkOpportunityUrl: t.Link,
linkQuoteUrl: t.Link,
});
export default types;

Some files were not shown because too many files have changed in this diff Show More