139 Commits

Author SHA1 Message Date
komoliddin
3a08c81ff3 Add is_archive field to EvaluationRequest model. Write apis for update is_archive and list archived requests 2026-04-23 12:23:55 +05:00
github-actions[bot]
320f490d23 🔄 Update image to 107 [CI SKIP] 2026-04-22 10:03:44 +00:00
d4e6d80c86 Merge pull request 'deploy.yaml fixess' (#88) from fix/cicd-env into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #88
2026-04-22 10:02:12 +00:00
1f0e942be8 deploy.yaml fixess 2026-04-22 15:01:52 +05:00
github-actions[bot]
2af67333e2 🔄 Update image to 106 [CI SKIP] 2026-04-22 09:21:04 +00:00
81c689e7e9 Merge pull request 'feat: add 404 response for missing company or person in Didox API' (#86) from didox-api into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m56s
Reviewed-on: #86
2026-04-22 09:19:30 +00:00
github-actions[bot]
09d2e0954c 🔄 Update image to 105 [CI SKIP] 2026-04-22 09:17:53 +00:00
84a5afb2ee Merge pull request 'add request status change api' (#87) from shaxob into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m57s
Reviewed-on: #87
Reviewed-by: xoliqberdiyev <behruz@felixits.uz>
2026-04-22 09:16:19 +00:00
github-actions[bot]
cc8fc345d9 🔄 Update image to 104 [CI SKIP] 2026-04-22 09:11:31 +00:00
03e4387e4d Merge pull request 'fix: handle response errors and improve data return in TechPassportService' (#85) from tech-passport into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m16s
Reviewed-on: #85
Reviewed-by: xoliqberdiyev <behruz@felixits.uz>
2026-04-22 09:09:38 +00:00
Shaxobff
64993805af add request status change api 2026-04-22 13:52:39 +05:00
komoliddin
01711b5927 feat: add 404 response for missing company or person in Didox API 2026-04-22 11:28:35 +05:00
komoliddin
33aa06f80b fix: handle response errors and improve data return in TechPassportService 2026-04-22 11:17:09 +05:00
github-actions[bot]
cb2d83b00d 🔄 Update image to 103 [CI SKIP] 2026-04-21 12:36:45 +00:00
46c9621457 Merge pull request 'sort yangilandi' (#84) from fix/evaluation-request-api into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #84
2026-04-21 12:35:14 +00:00
github-actions[bot]
8d73fa09a7 🔄 Update image to 102 [CI SKIP] 2026-04-21 11:43:22 +00:00
3367063707 Merge pull request 'write swagger docs' (#82) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m56s
Reviewed-on: #82
2026-04-21 11:41:48 +00:00
xoliqberdiyev
ae926914a5 write swagger docs 2026-04-21 16:41:37 +05:00
github-actions[bot]
229676be5c 🔄 Update image to 101 [CI SKIP] 2026-04-21 11:27:41 +00:00
d2f517baf4 Merge pull request 'feat: uncomment significant code for ws' (#81) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #81
2026-04-21 11:26:08 +00:00
xoliqberdiyev
6df20f35c2 feat: uncomment significant code for ws 2026-04-21 16:25:51 +05:00
github-actions[bot]
fa676bfa96 🔄 Update image to 100 [CI SKIP] 2026-04-21 11:13:53 +00:00
ca9f12ae32 Merge pull request 'fix' (#80) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m57s
Reviewed-on: #80
2026-04-21 11:12:18 +00:00
xoliqberdiyev
56f693abfd fix 2026-04-21 16:12:06 +05:00
github-actions[bot]
5049919865 🔄 Update image to 99 [CI SKIP] 2026-04-21 10:29:59 +00:00
ca3c9bfe4a Merge pull request 'feat: removing location fields from auto-evalutaion model, adding new tex_passport_file field for auto-evalutaion model' (#79) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m0s
Reviewed-on: #79
2026-04-21 10:28:22 +00:00
xoliqberdiyev
8d8744731a feat: removing location fields from auto-evalutaion model, adding new tex_passport_file field for auto-evalutaion model 2026-04-21 15:28:03 +05:00
github-actions[bot]
7ab1e0b1e0 🔄 Update image to 98 [CI SKIP] 2026-04-21 10:23:04 +00:00
f66a35ec80 Merge pull request 'remote: tex_passport_file field removed from quick evaluation serializers' (#78) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m56s
Reviewed-on: #78
2026-04-21 10:21:29 +00:00
xoliqberdiyev
f71dfa50c1 remote: tex_passport_file field removed from quick evaluation serializers 2026-04-21 15:21:14 +05:00
github-actions[bot]
f68f34178e 🔄 Update image to 97 [CI SKIP] 2026-04-21 10:20:39 +00:00
ab68a6a463 Merge pull request 'feat: add Tech Passport API integration for vehicle information retrieval' (#76) from tech-passport into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m0s
Reviewed-on: #76
Reviewed-by: xoliqberdiyev <behruz@felixits.uz>
2026-04-21 10:19:01 +00:00
github-actions[bot]
d246406384 🔄 Update image to 96 [CI SKIP] 2026-04-21 10:15:49 +00:00
0a74f71efa Merge pull request 'fix: change the evaluation-request when creating auto evaluation to this evaluation-request' (#77) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m2s
Reviewed-on: #77
2026-04-21 10:14:13 +00:00
xoliqberdiyev
575364e0ef fix: change the evaluation-request when creating auto evaluation to this evaluation-request 2026-04-21 15:13:41 +05:00
komoliddin
406477fc33 feat: add Tech Passport API integration for vehicle information retrieval 2026-04-21 15:11:46 +05:00
github-actions[bot]
2aa73e15a0 🔄 Update image to 95 [CI SKIP] 2026-04-21 09:52:02 +00:00
372a289447 Merge pull request 'change region models name field from unique to typical' (#75) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m58s
Reviewed-on: #75
2026-04-21 09:50:28 +00:00
xoliqberdiyev
7343f6191d change region models name field from unique to typical 2026-04-21 14:48:41 +05:00
github-actions[bot]
5c4d5fbb71 🔄 Update image to 94 [CI SKIP] 2026-04-21 09:47:17 +00:00
20043db5b3 Merge pull request 'change ws response' (#74) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m1s
Reviewed-on: #74
2026-04-21 09:45:41 +00:00
xoliqberdiyev
7d6618155f change ws response 2026-04-21 14:45:01 +05:00
github-actions[bot]
11605e6e6c 🔄 Update image to 93 [CI SKIP] 2026-04-21 08:07:30 +00:00
komoliddin
8a1a66a05d feat: add Didox service integration for company info retrieval
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m9s
2026-04-21 13:05:45 +05:00
github-actions[bot]
70555fa93a 🔄 Update image to 92 [CI SKIP] 2026-04-20 13:12:55 +00:00
674eafe65b Merge pull request 'fix' (#73) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m59s
Reviewed-on: #73
2026-04-20 13:11:20 +00:00
xoliqberdiyev
47833176e2 fix 2026-04-20 18:10:51 +05:00
b03d2e1c5e Merge pull request 'add new fields' (#72) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Failing after 31s
Reviewed-on: #72
2026-04-20 13:06:21 +00:00
xoliqberdiyev
7be8999a39 add new fields 2026-04-20 18:06:06 +05:00
github-actions[bot]
c5624e361d 🔄 Update image to 90 [CI SKIP] 2026-04-20 13:02:55 +00:00
c44b08bb28 Merge pull request 'fix' (#71) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m59s
Reviewed-on: #71
2026-04-20 13:01:19 +00:00
xoliqberdiyev
d448324f89 fix 2026-04-20 18:01:00 +05:00
github-actions[bot]
0508a49c1d 🔄 Update image to 89 [CI SKIP] 2026-04-20 10:38:57 +00:00
8b97ca53bb Merge pull request 'fix' (#70) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m56s
Reviewed-on: #70
2026-04-20 10:37:24 +00:00
xoliqberdiyev
2d64777041 fix 2026-04-20 15:37:06 +05:00
github-actions[bot]
9f7b29fa13 🔄 Update image to 88 [CI SKIP] 2026-04-20 10:35:28 +00:00
b4a3243e9c Merge pull request 'gi' (#69) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #69
2026-04-20 10:33:56 +00:00
xoliqberdiyev
63359e69f7 gi 2026-04-20 15:33:04 +05:00
github-actions[bot]
3c9438aff6 🔄 Update image to 87 [CI SKIP] 2026-04-20 10:31:42 +00:00
a3c67c5bfb Merge pull request 'remove name_ru and name_en fields' (#68) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #68
2026-04-20 10:30:11 +00:00
xoliqberdiyev
e768c73d54 remove name_ru and name_en fields 2026-04-20 15:29:59 +05:00
github-actions[bot]
a2684ff749 🔄 Update image to 86 [CI SKIP] 2026-04-20 10:27:16 +00:00
91bbb6b6e5 Merge pull request 'change file name' (#67) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m56s
Reviewed-on: #67
2026-04-20 10:25:44 +00:00
xoliqberdiyev
01a70debaf change file name 2026-04-20 15:25:28 +05:00
github-actions[bot]
ae9f10aa4d 🔄 Update image to 85 [CI SKIP] 2026-04-20 10:16:13 +00:00
f2dc2e1d52 Merge pull request 'add migrations files in shared app' (#66) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m56s
Reviewed-on: #66
2026-04-20 10:14:39 +00:00
xoliqberdiyev
7081a03a98 add migrations files in shared app 2026-04-20 15:14:26 +05:00
github-actions[bot]
10646e2ebf 🔄 Update image to 84 [CI SKIP] 2026-04-20 10:09:49 +00:00
93f6e79f58 Merge pull request 'fix' (#65) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m57s
Reviewed-on: #65
2026-04-20 10:08:16 +00:00
xoliqberdiyev
3b95734ea4 fix 2026-04-20 15:08:04 +05:00
d1aad04ab8 Merge pull request 'registring region,district and village models' (#64) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Failing after 51s
Reviewed-on: #64
2026-04-20 10:06:37 +00:00
xoliqberdiyev
adf3c9fff9 registring region,district and village models 2026-04-20 15:06:06 +05:00
github-actions[bot]
7e4a90fe2a 🔄 Update image to 82 [CI SKIP] 2026-04-20 05:46:19 +00:00
503c16bdb8 Merge pull request 'swagger to'g'irlandi' (#63) from fix/swagger into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m57s
Reviewed-on: #63
2026-04-20 05:44:45 +00:00
701a375e3c swagger to'g'irlandi 2026-04-20 10:44:23 +05:00
github-actions[bot]
e64d7d037f 🔄 Update image to 81 [CI SKIP] 2026-04-18 11:56:52 +00:00
b7ecf0e47c Merge pull request 'fix' (#62) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m54s
Reviewed-on: #62
2026-04-18 11:55:21 +00:00
xoliqberdiyev
c7c76e9c79 fix 2026-04-18 16:55:02 +05:00
github-actions[bot]
afdc6aa0ac 🔄 Update image to 80 [CI SKIP] 2026-04-18 11:52:43 +00:00
f991e3e4ec Merge pull request 'behruz' (#61) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m57s
Reviewed-on: #61
2026-04-18 11:51:08 +00:00
xoliqberdiyev
0f142be77b fix 2026-04-18 16:50:48 +05:00
xoliqberdiyev
182b2483bf fix 2026-04-18 16:48:32 +05:00
469659baa8 Merge pull request 'fix' (#60) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Failing after 1m17s
Reviewed-on: #60
2026-04-18 11:32:05 +00:00
xoliqberdiyev
965328edd4 fix 2026-04-18 16:31:53 +05:00
github-actions[bot]
11aa88a2b0 🔄 Update image to 78 [CI SKIP] 2026-04-18 11:25:47 +00:00
225565bc41 Merge pull request 'add extra user fields for chat' (#59) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #59
2026-04-18 11:24:13 +00:00
xoliqberdiyev
75365f8e7e add extra user fields for chat 2026-04-18 16:24:02 +05:00
github-actions[bot]
3e9675a4be 🔄 Update image to 77 [CI SKIP] 2026-04-18 11:22:09 +00:00
c3cf7ac9d5 Merge pull request 'add all fields to ordering_fields filter' (#58) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #58
2026-04-18 11:20:36 +00:00
xoliqberdiyev
5dcafb9cc1 add all fields to ordering_fields filter 2026-04-18 16:20:19 +05:00
github-actions[bot]
62bc6c17b8 🔄 Update image to 76 [CI SKIP] 2026-04-17 13:46:12 +00:00
22284f228f Merge pull request 'fi' (#57) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m56s
Reviewed-on: #57
2026-04-17 13:44:40 +00:00
xoliqberdiyev
aa487c1557 fi 2026-04-17 18:44:18 +05:00
github-actions[bot]
73d5222a5b 🔄 Update image to 75 [CI SKIP] 2026-04-17 13:35:44 +00:00
707ea1e1fd Merge pull request 'add celery to docker-compose file' (#56) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m52s
Reviewed-on: #56
2026-04-17 13:34:12 +00:00
xoliqberdiyev
bfbc14db82 add celery to docker-compose file 2026-04-17 18:33:58 +05:00
github-actions[bot]
4c1f1d7104 🔄 Update image to 74 [CI SKIP] 2026-04-17 13:26:45 +00:00
38e7aac505 Merge pull request 'fix' (#55) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m50s
Reviewed-on: #55
2026-04-17 13:25:14 +00:00
xoliqberdiyev
a973da91f5 fix 2026-04-17 18:24:59 +05:00
github-actions[bot]
ec94e4cdd0 🔄 Update image to 73 [CI SKIP] 2026-04-17 13:21:05 +00:00
87701c31f1 Merge pull request 'fix' (#54) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m51s
Reviewed-on: #54
2026-04-17 13:19:34 +00:00
xoliqberdiyev
35739ddc27 fix 2026-04-17 18:19:12 +05:00
e8feafca0b Merge pull request 'add new task for sending new messages to the chat' (#53) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Failing after 48s
Reviewed-on: #53
2026-04-17 13:15:06 +00:00
xoliqberdiyev
505c33a554 add new task for sending new messages to the chat 2026-04-17 18:14:24 +05:00
github-actions[bot]
f24afaf55c 🔄 Update image to 71 [CI SKIP] 2026-04-17 10:15:24 +00:00
665db58bbc Merge pull request 'fix' (#52) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m50s
Reviewed-on: #52
2026-04-17 10:13:52 +00:00
xoliqberdiyev
57e22e04db fix 2026-04-17 15:13:36 +05:00
github-actions[bot]
528dc57ea2 🔄 Update image to 70 [CI SKIP] 2026-04-17 10:13:21 +00:00
774be87f45 Merge pull request 'feat: add new extra fields for auto-evalution list serializer' (#51) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m50s
Reviewed-on: #51
2026-04-17 10:11:48 +00:00
xoliqberdiyev
3d0141197f feat: add new extra fields for auto-evalution list serializer 2026-04-17 15:11:26 +05:00
9dc093e244 Merge pull request 'feat: auto-evaluation update api serializer class changed' (#50) from behruz into main
Some checks failed
Deploy to Production / build-and-deploy (push) Failing after 1m19s
Reviewed-on: #50
2026-04-17 10:01:57 +00:00
xoliqberdiyev
aa1e4ca6fc feat: auto-evaluation update api serializer class changed 2026-04-17 15:01:25 +05:00
github-actions[bot]
04ef567d5c 🔄 Update image to 68 [CI SKIP] 2026-04-16 10:33:44 +00:00
b604fff1c2 Merge pull request 'fi' (#49) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m51s
Reviewed-on: #49
2026-04-16 10:32:13 +00:00
xoliqberdiyev
277bc2874e fi 2026-04-16 15:31:49 +05:00
github-actions[bot]
37459e8485 🔄 Update image to 67 [CI SKIP] 2026-04-16 10:28:48 +00:00
f231d889a5 Merge pull request 'add base url' (#48) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m49s
Reviewed-on: #48
2026-04-16 10:27:19 +00:00
xoliqberdiyev
3fa70d9436 add base url 2026-04-16 15:26:47 +05:00
github-actions[bot]
6b1a9439ce 🔄 Update image to 66 [CI SKIP] 2026-04-16 10:15:07 +00:00
d26e2b0147 Merge pull request 'fix' (#47) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m49s
Reviewed-on: #47
2026-04-16 10:13:37 +00:00
xoliqberdiyev
da71ce6d28 fix 2026-04-16 15:13:19 +05:00
github-actions[bot]
bc157da00d 🔄 Update image to 65 [CI SKIP] 2026-04-16 10:09:53 +00:00
9c40908cc7 Merge pull request 'some changes' (#46) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m54s
Reviewed-on: #46
2026-04-16 10:08:16 +00:00
xoliqberdiyev
7ee2eee437 some changes 2026-04-16 15:07:52 +05:00
github-actions[bot]
31a08c810d 🔄 Update image to 64 [CI SKIP] 2026-04-03 12:34:09 +00:00
264548c3b1 Merge pull request 'feat: add appraisers for auto-evaluation and add curd for this' (#45) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m59s
Reviewed-on: #45
2026-04-03 12:32:29 +00:00
xoliqberdiyev
9a18dda657 feat: add appraisers for auto-evaluation and add curd for this 2026-04-03 17:32:05 +05:00
github-actions[bot]
c7612221b8 🔄 Update image to 63 [CI SKIP] 2026-04-03 11:45:10 +00:00
a89dbafb62 Merge pull request 'feat: add filter and pagination to document api' (#44) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #44
2026-04-03 11:42:02 +00:00
xoliqberdiyev
be0c96b28a feat: add filter and pagination to document api 2026-04-03 16:41:39 +05:00
github-actions[bot]
9d39f0b339 🔄 Update image to 62 [CI SKIP] 2026-04-03 11:32:26 +00:00
797286bcdb Merge pull request 'behruz' (#43) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m54s
Reviewed-on: #43
2026-04-03 11:30:50 +00:00
xoliqberdiyev
091ddb39ed feat: add new api 2026-04-03 16:28:29 +05:00
xoliqberdiyev
12b19290d6 feat: add new document and documentcategory model for auto evaluation detail 2026-04-03 16:19:53 +05:00
github-actions[bot]
36ac4af4cd 🔄 Update image to 61 [CI SKIP] 2026-04-03 10:30:10 +00:00
4784b58abc Merge pull request 'feat: add new evaluation-request crud apis for admin' (#42) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 1m55s
Reviewed-on: #42
2026-04-03 10:28:32 +00:00
xoliqberdiyev
3664f8f66d feat: add new evaluation-request crud apis for admin 2026-04-03 15:27:43 +05:00
github-actions[bot]
17e63ee2e4 🔄 Update image to 60 [CI SKIP] 2026-04-02 16:16:23 +00:00
Husanjonazamov
8207b750b8 sort yangilandi 2026-03-12 17:29:10 +05:00
93 changed files with 25208 additions and 228 deletions

View File

@@ -73,6 +73,9 @@ STORAGE_BUCKET_STATIC=name
STORAGE_PATH=127.0.0.1:8081/bucket/ STORAGE_PATH=127.0.0.1:8081/bucket/
STORAGE_PROTOCOL=http: STORAGE_PROTOCOL=http:
# Didox configs
DIDOX_PARTNER_TOKEN=...
# Celery configs # Celery configs

View File

@@ -153,7 +153,7 @@ jobs:
update_env \ update_env \
"DB_HOST=postgres" \ "DB_HOST=postgres" \
"DB_NAME=sifatbahodb" \ "DB_NAME=sifatbahodb" \
"DB_PORT=5432" "DB_PORT=5432" \
"DIDOX_TOKEN=${{ secrets.DIDOX_TOKEN }}"
export PORT=8085 export PORT=8085
docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth

View File

@@ -150,4 +150,41 @@ PAGES = [
}, },
], ],
}, },
{
"title": _("Auto baholash"),
"separator": True,
"items": [
{
"title": _("Hujjatlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:evaluation_documentmodel_changelist"),
},
{
"title": _("Kategoriyalar"),
"icon": "category",
"link": reverse_lazy("admin:evaluation_documentcategorymodel_changelist"),
}
]
},
{
"title": _("Joylashuvlar"),
"separator": True,
"items": [
{
"title": _("Shaharlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:shared_regionmodel_changelist"),
},
{
"title": _("Tumanlar"),
"icon": "attach_file",
"link": reverse_lazy("admin:shared_districtmodel_changelist"),
},
{
"title": _("Mahallalar"),
"icon": "attach_file",
"link": reverse_lazy("admin:shared_villagemodel_changelist"),
},
]
}
] ]

View File

@@ -9,7 +9,7 @@ import environ
environ.Env.read_env(os.path.join(".env")) environ.Env.read_env(os.path.join(".env"))
env = environ.Env( env = environ.Env(
DEBUG=(bool, False), DEBUG=(bool, True),
CACHE_TIME=(int, 180), CACHE_TIME=(int, 180),
OTP_EXPIRE_TIME=(int, 2), OTP_EXPIRE_TIME=(int, 2),
VITE_LIVE=(bool, False), VITE_LIVE=(bool, False),
@@ -26,4 +26,5 @@ env = environ.Env(
OTP_SERVICE="EskizService", OTP_SERVICE="EskizService",
PROJECT_ENV=(str, "prod"), PROJECT_ENV=(str, "prod"),
SILK_ENABLED=(bool, False), SILK_ENABLED=(bool, False),
DIDOX_PARTNER_TOKEN=(str),
) )

View File

@@ -49,6 +49,7 @@ INSTALLED_APPS = [
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.contrib.postgres",
] + APPS ] + APPS
MODULES = [app for app in MODULES if isinstance(app, str)] MODULES = [app for app in MODULES if isinstance(app, str)]
@@ -165,6 +166,8 @@ SITE_URL = env.str("SITE_URL", default="http://localhost:8055")
MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en") MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en")
MODELTRANSLATION_DEFAULT_LANGUAGE = "uz" MODELTRANSLATION_DEFAULT_LANGUAGE = "uz"
DIDOX_PARTNER_TOKEN = env.str("DIDOX_PARTNER_TOKEN")
JST_LANGUAGES = [ JST_LANGUAGES = [

View File

@@ -13,7 +13,7 @@ from config.env import env
def home(request): def home(request):
return HttpResponse("OK: #ea3f14058e4a7edbb94999d6287f396cd60e7305") return HttpResponse("OK: #d4e6d80c86fcf4422f71238c6552dfa4b42f9737")
urlpatterns = [ urlpatterns = [

View File

@@ -3,16 +3,24 @@ from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
avatar = serializers.SerializerMethodField(method_name='get_avatar')
class Meta: class Meta:
exclude = [ exclude = [
"created_at", "created_at",
"updated_at", "updated_at",
"password", "password",
"groups", "groups",
"user_permissions" "user_permissions",
] ]
model = get_user_model() model = get_user_model()
def get_avatar(self, obj):
request = self.context.get('request')
if obj.avatar:
return request.build_absolute_uri(obj.avatar.url)
return None
class UserUpdateSerializer(serializers.ModelSerializer): class UserUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:

View File

@@ -4,7 +4,7 @@ Accounts app urls
from django.urls import path, include from django.urls import path, include
from rest_framework_simplejwt import views as jwt_views from rest_framework_simplejwt import views as jwt_views
from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView from .views import RegisterView, ResetPasswordView, MeView, ChangePasswordView, UserListApiView, AdminUserListApiView
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
router = DefaultRouter() router = DefaultRouter()
@@ -23,4 +23,6 @@ urlpatterns = [
jwt_views.TokenRefreshView.as_view(), jwt_views.TokenRefreshView.as_view(),
name="token_refresh", name="token_refresh",
), ),
path("user/list/", UserListApiView.as_view(), name="user-list"),
path("admin-user/list/", AdminUserListApiView.as_view(), name="admin-user-list"),
] ]

View File

@@ -1 +1,2 @@
from .auth import * # noqa from .auth import * # noqa
from .user import * # noqa

View File

@@ -177,11 +177,11 @@ class MeView(BaseViewSetMixin, GenericViewSet, UserService):
@action(methods=["GET", "OPTIONS"], detail=False, url_path="me") @action(methods=["GET", "OPTIONS"], detail=False, url_path="me")
def me(self, request): def me(self, request):
return Response(self.get_serializer(request.user).data) return Response(UserSerializer(request.user, context={"request": request}).data)
@action(methods=["PATCH", "PUT"], detail=False, url_path="user-update") @action(methods=["PATCH", "PUT"], detail=False, url_path="user-update")
def user_update(self, request): def user_update(self, request):
ser = self.get_serializer(instance=request.user, data=request.data, partial=True) ser = self.get_serializer(instance=request.user, data=request.data, partial=True, context={"request": request})
ser.is_valid(raise_exception=True) ser.is_valid(raise_exception=True)
data = ser.save() data = ser.save()
return Response(UserSerializer(data).data) return Response(UserSerializer(data).data)

View File

@@ -0,0 +1,31 @@
from django.contrib.auth import get_user_model
from rest_framework import generics, filters
from rest_framework.permissions import IsAuthenticated
from drf_spectacular.utils import extend_schema
from core.apps.accounts.serializers.user import UserSerializer
from core.apps.accounts.choices.user import RoleChoice
User = get_user_model()
@extend_schema(tags=['User'])
class UserListApiView(generics.ListAPIView):
queryset = User.objects.filter(role=RoleChoice.USER)
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
filter_backends = [filters.SearchFilter]
search_fields = ['phone', 'first_name', 'last_name']
def serializer_context(self):
return self.serializer_class(context={"request": self.request})
@extend_schema(tags=['User'])
class AdminUserListApiView(generics.ListAPIView):
queryset = User.objects.exclude(role=RoleChoice.USER)
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
filter_backends = [filters.SearchFilter]
search_fields = ['phone', 'first_name', 'last_name']

View File

@@ -4,6 +4,11 @@ from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer from channels.generic.websocket import AsyncWebsocketConsumer
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
def get_base_url(scope):
headers = dict(scope["headers"])
host = headers.get(b"host", b"").decode()
scheme = "https" if scope.get("scheme") == "https" else "http"
return f"{scheme}://{host}"
class ChatConsumer(AsyncWebsocketConsumer): class ChatConsumer(AsyncWebsocketConsumer):
""" """
@@ -36,7 +41,7 @@ class ChatConsumer(AsyncWebsocketConsumer):
async def receive(self, text_data): async def receive(self, text_data):
user = self.scope.get("user") user = self.scope.get("user")
if not user or isinstance(user, AnonymousUser): if not user or isinstance(user, AnonymousUser):
await self.close(code=4001) await self.close(code=401)
return return
try: try:
@@ -48,13 +53,10 @@ class ChatConsumer(AsyncWebsocketConsumer):
message_type = data.get("message_type", "text") message_type = data.get("message_type", "text")
text = (data.get("text") or "").strip() text = (data.get("text") or "").strip()
# Matn xabari uchun text majburiy
if message_type == "text" and not text: if message_type == "text" and not text:
await self.send(text_data=json.dumps({"error": "Matn bo'sh bo'lishi mumkin emas."})) await self.send(text_data=json.dumps({"error": "Matn bo'sh bo'lishi mumkin emas."}))
return return
# WS orqali faqat matn + caption saqlanadi.
# Fayl yuklash uchun REST /chat/messages/ POST ishlatiladi.
if message_type != "text": if message_type != "text":
await self.send( await self.send(
text_data=json.dumps( text_data=json.dumps(
@@ -63,9 +65,9 @@ class ChatConsumer(AsyncWebsocketConsumer):
) )
return return
# DB ga saqlash — post_save signal WS ga broadcast qiladi
await self._save_message(user, text) await self._save_message(user, text)
async def chat_message(self, event): async def chat_message(self, event):
await self.send( await self.send(
text_data=json.dumps( text_data=json.dumps(
@@ -91,6 +93,12 @@ class ChatConsumer(AsyncWebsocketConsumer):
text=text, text=text,
) )
full_name = user.get_full_name().strip() or str(user.phone) full_name = user.get_full_name().strip() or str(user.phone)
base_url = get_base_url(self.scope)
avatar_url = (
base_url + user.avatar.url
if user.avatar else None
)
return { return {
"id": msg.id, "id": msg.id,
"message_type": msg.message_type, "message_type": msg.message_type,
@@ -100,6 +108,8 @@ class ChatConsumer(AsyncWebsocketConsumer):
"id": user.id, "id": user.id,
"full_name": full_name, "full_name": full_name,
"role": user.role, "role": user.role,
"phone": user.phone,
"avatar": avatar_url,
}, },
"created_at": msg.created_at.isoformat(), "created_at": msg.created_at.isoformat(),
} }

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.7 on 2026-04-03 10:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('chat', '0002_chatroom_and_message_media'),
]
operations = [
migrations.AlterModelOptions(
name='chatmessagemodel',
options={'ordering': ['created_at'], 'verbose_name': 'Chat Xabar', 'verbose_name_plural': 'Chat Xabarlar'},
),
migrations.AlterField(
model_name='chatroommodel',
name='type',
field=models.CharField(choices=[('auto_evaluation', 'AutoEvaluation xonasi'), ('direct', "To'g'ridan-to'g'ri")], db_index=True, default='auto_evaluation', max_length=20, verbose_name='type'),
),
]

View File

@@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from core.apps.chat.models import ChatmessageModel from core.apps.chat.models import ChatmessageModel
from core.apps.chat.tasks.message import send_message_to_chat
class BaseChatmessageSerializer(serializers.ModelSerializer): class BaseChatmessageSerializer(serializers.ModelSerializer):
sender = serializers.SerializerMethodField() sender = serializers.SerializerMethodField()
@@ -13,10 +13,13 @@ class BaseChatmessageSerializer(serializers.ModelSerializer):
full_name = obj.sender.get_full_name().strip() full_name = obj.sender.get_full_name().strip()
if not full_name: if not full_name:
full_name = str(obj.sender.phone) full_name = str(obj.sender.phone)
request = self.context.get("request")
return { return {
"id": obj.sender.id, "id": obj.sender.id,
"full_name": full_name, "full_name": full_name,
"role": obj.sender.role, "role": obj.sender.role,
"phone": obj.sender.phone,
"avatar": request.build_absolute_uri(obj.sender.avatar.url) if obj.sender.avatar else None,
} }
def get_file_url(self, obj): def get_file_url(self, obj):
@@ -72,4 +75,9 @@ class CreateChatmessageSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
validated_data["sender"] = self.context["request"].user validated_data["sender"] = self.context["request"].user
return super().create(validated_data) request = self.context["request"]
message = super().create(validated_data)
file_url = request.build_absolute_uri(message.file.url) if message.file else None
avatar_url = request.build_absolute_uri(self.context['request'].user.avatar.url) if self.context['request'].user.avatar else None
send_message_to_chat.delay(message.id, file_url, avatar_url)
return message

View File

@@ -7,42 +7,42 @@ from django.dispatch import receiver
from core.apps.chat.models import ChatmessageModel, ChatroomModel from core.apps.chat.models import ChatmessageModel, ChatroomModel
@receiver(post_save, sender=ChatmessageModel) # @receiver(post_save, sender=ChatmessageModel)
def broadcast_new_message(sender, instance, created, **kwargs): # def broadcast_new_message(sender, instance, created, **kwargs):
"""Yangi xabar saqlanganda xonadagi barcha WS ulanishlariga yuboradi.""" # """Yangi xabar saqlanganda xonadagi barcha WS ulanishlariga yuboradi."""
if not created: # if not created:
return # return
channel_layer = get_channel_layer() # channel_layer = get_channel_layer()
if channel_layer is None: # if channel_layer is None:
return # return
sender_obj = instance.sender # sender_obj = instance.sender
if sender_obj: # if sender_obj:
full_name = sender_obj.get_full_name().strip() or str(sender_obj.phone) # full_name = sender_obj.get_full_name().strip() or str(sender_obj.phone)
sender_data = { # sender_data = {
"id": sender_obj.id, # "id": sender_obj.id,
"full_name": full_name, # "full_name": full_name,
"role": sender_obj.role, # "role": sender_obj.role,
} # }
else: # else:
sender_data = None # sender_data = None
site_url = getattr(settings, "SITE_URL", "").rstrip("/") # site_url = getattr(settings, "SITE_URL", "").rstrip("/")
file_url = (site_url + instance.file.url) if instance.file else None # file_url = (site_url + instance.file.url) if instance.file else None
async_to_sync(channel_layer.group_send)( # async_to_sync(channel_layer.group_send)(
f"chat_room_{instance.room_id}", # f"chat_room_{instance.room_id}",
{ # {
"type": "chat_message", # "type": "chat_message",
"id": instance.id, # "id": instance.id,
"message_type": instance.message_type, # "message_type": instance.message_type,
"text": instance.text, # "text": instance.text,
"file_url": file_url, # "file_url": file_url,
"sender": sender_data, # "sender": sender_data,
"created_at": instance.created_at.isoformat(), # "created_at": instance.created_at.isoformat(),
}, # },
) # )
@receiver(post_save, sender="evaluation.AutoEvaluationModel") @receiver(post_save, sender="evaluation.AutoEvaluationModel")

View File

@@ -0,0 +1 @@
from .message import send_message_to_chat

View File

@@ -0,0 +1,44 @@
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from celery import shared_task
from core.apps.chat.models import ChatmessageModel
@shared_task
def send_message_to_chat(message_id, file_url, avatar_url):
try:
message = ChatmessageModel.objects.get(id=message_id)
except ChatmessageModel.DoesNotExist:
return "Not found"
channel_layer = get_channel_layer()
if channel_layer is None:
return
sender_obj = message.sender
if sender_obj:
full_name = sender_obj.get_full_name().strip() or str(sender_obj.phone)
sender_data = {
"id": sender_obj.id,
"full_name": full_name,
"role": sender_obj.role,
"phone": sender_obj.phone,
"avatar": avatar_url,
}
else:
sender_data = None
async_to_sync(channel_layer.group_send)(
f"chat_room_{message.room_id}",
{
"type": "chat_message",
"id": message.id,
"message_type": message.message_type,
"text": message.text,
"file_url": file_url,
"sender": sender_data,
"created_at": message.created_at.isoformat(),
},
)

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -50,15 +50,8 @@ class AutoEvaluationAdmin(ModelAdmin):
("value_determined", "rate_type"), ("value_determined", "rate_type"),
), ),
}), }),
("Step 3 — Manzil ma'lumotlari", {
"fields": ( ("Step 3 — Avtomobil ma'lumotlari", {
("object_location_province", "object_location_district"),
("object_location_city", "object_location_neighborhood"),
("object_location_street", "object_location_home"),
("object_location_highways", "object_location_covenience"),
),
}),
("Step 4 — Avtomobil ma'lumotlari", {
"fields": ( "fields": (
"tex_passport_serie_num", "tex_passport_serie_num",
("tex_passport_gived_date", "tex_passport_gived_location"), ("tex_passport_gived_date", "tex_passport_gived_location"),

View File

@@ -1,7 +1,7 @@
from django.contrib import admin from django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.evaluation.models import ValuationDocumentModel from core.apps.evaluation.models import DocumentModel, ValuationDocumentModel
@admin.register(ValuationDocumentModel) @admin.register(ValuationDocumentModel)
@@ -23,20 +23,37 @@ class ValuationDocumentAdmin(ModelAdmin):
readonly_fields = ("created_at", "updated_at") readonly_fields = ("created_at", "updated_at")
autocomplete_fields = ("valuation", "uploaded_by") autocomplete_fields = ("valuation", "uploaded_by")
fieldsets = ( fieldsets = (
("Hujjat", { (
"fields": ( "Hujjat",
"valuation", {
"document_type", "fields": (
"title", "valuation",
"file", "document_type",
"uploaded_by", "title",
), "file",
}), "uploaded_by",
("Qo'shimcha", { ),
"fields": ("description",), },
}), ),
("Tizim", { (
"classes": ("collapse",), "Qo'shimcha",
"fields": ("created_at", "updated_at"), {
}), "fields": ("description",),
},
),
(
"Tizim",
{
"classes": ("collapse",),
"fields": ("created_at", "updated_at"),
},
),
)
@admin.register(DocumentModel)
class DocumentAdmin(ModelAdmin):
list_display = (
"id",
"__str__",
) )

View File

@@ -0,0 +1,57 @@
from django.contrib import admin
from unfold.admin import ModelAdmin
from core.apps.evaluation.models import DocumentcategoryModel
@admin.register(DocumentcategoryModel)
class DocumentcategoryAdmin(ModelAdmin):
list_display = (
"id",
"label",
"value",
"created_at",
"updated_at"
)
search_fields = (
"label",
"value",
)
list_filter = (
"created_at",
"updated_at",
)
readonly_fields = (
"created_at",
"updated_at",
)
fieldsets = (
(
("Uzbekcha"),
{
"fields": (
"label_uz",
"value_uz",
)
}
),
(
("Ruscha"),
{
"fields": (
"label_ru",
"value_ru",
)
}
),
(
("Inglizcha"),
{
"fields": (
"label_en",
"value_en",
)
}
),
)
list_display_links =list_display

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -1,11 +1,18 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from core.apps.evaluation.models import ValuationDocumentModel from core.apps.evaluation.models import DocumentModel, ValuationDocumentModel
class ValuationdocumentFilter(filters.FilterSet): class ValuationdocumentFilter(filters.FilterSet):
# name = filters.CharFilter(field_name="name", lookup_expr="icontains")
class Meta: class Meta:
model = ValuationDocumentModel model = ValuationDocumentModel
fields = [] fields = []
class DocumentFilter(filters.FilterSet):
class Meta:
model = DocumentModel
fields = [
]

View File

@@ -0,0 +1,10 @@
from django_filters import rest_framework as filters
from core.apps.evaluation.models import DocumentcategoryModel
class DocumentcategoryFilter(filters.FilterSet):
class Meta:
model = DocumentcategoryModel
fields = [
]

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -1,6 +1,6 @@
from django import forms from django import forms
from core.apps.evaluation.models import ValuationDocumentModel from core.apps.evaluation.models import DocumentModel, ValuationDocumentModel
class ValuationdocumentForm(forms.ModelForm): class ValuationdocumentForm(forms.ModelForm):
@@ -8,3 +8,10 @@ class ValuationdocumentForm(forms.ModelForm):
class Meta: class Meta:
model = ValuationDocumentModel model = ValuationDocumentModel
fields = "__all__" fields = "__all__"
class DocumentForm(forms.ModelForm):
class Meta:
model = DocumentModel
fields = "__all__"

View File

@@ -0,0 +1,10 @@
from django import forms
from core.apps.evaluation.models import DocumentcategoryModel
class DocumentcategoryForm(forms.ModelForm):
class Meta:
model = DocumentcategoryModel
fields = "__all__"

View File

@@ -0,0 +1,52 @@
# Generated by Django 5.2.7 on 2026-04-03 10:58
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0026_alter_autoevaluationmodel_form_ownership_and_more'),
]
operations = [
migrations.CreateModel(
name='DocumentcategoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('label', models.CharField(max_length=255, verbose_name='label')),
('label_uz', models.CharField(max_length=255, null=True, verbose_name='label')),
('label_ru', models.CharField(max_length=255, null=True, verbose_name='label')),
('label_en', models.CharField(max_length=255, null=True, verbose_name='label')),
('value', models.CharField(max_length=255, verbose_name='value')),
('value_uz', models.CharField(max_length=255, null=True, verbose_name='value')),
('value_ru', models.CharField(max_length=255, null=True, verbose_name='value')),
('value_en', models.CharField(max_length=255, null=True, verbose_name='value')),
],
options={
'verbose_name': 'DocumentcategoryModel',
'verbose_name_plural': 'DocumentcategoryModels',
'db_table': 'DocumentCategory',
},
),
migrations.CreateModel(
name='DocumentModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=255, verbose_name='title')),
('document', models.FileField(upload_to='evaluation/documents/%Y/%m/', verbose_name='document')),
('auto_evaluation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='evaluation.autoevaluationmodel', verbose_name='auto evaluation')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='evaluation.documentcategorymodel', verbose_name='category')),
],
options={
'verbose_name': 'DocumentModel',
'verbose_name_plural': 'DocumentModels',
'db_table': 'Document',
},
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.7 on 2026-04-03 11:51
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0027_documentcategorymodel_documentmodel'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='autoevaluationmodel',
name='appraisers',
field=models.ManyToManyField(blank=True, null=True, related_name='auto_evaluations', to=settings.AUTH_USER_MODEL, verbose_name='appraisers'),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.7 on 2026-04-18 11:28
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0028_autoevaluationmodel_appraisers'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='autoevaluationmodel',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='auto_evaluations_user', to=settings.AUTH_USER_MODEL, verbose_name='user'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.7 on 2026-04-20 13:08
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0029_autoevaluationmodel_user'),
]
operations = [
migrations.AddField(
model_name='autoevaluationmodel',
name='evaluation_request',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='auto_evaluations_request', to='evaluation.evaluationrequestmodel', verbose_name='evaluation request'),
),
]

View File

@@ -0,0 +1,54 @@
# Generated by Django 5.2.7 on 2026-04-21 10:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0030_autoevaluationmodel_evaluation_request'),
]
operations = [
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_city',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_covenience',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_district',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_highways',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_home',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_neighborhood',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_province',
),
migrations.RemoveField(
model_name='autoevaluationmodel',
name='object_location_street',
),
migrations.RemoveField(
model_name='quickevaluationmodel',
name='tex_passport_file',
),
migrations.AddField(
model_name='autoevaluationmodel',
name='tex_passport_file',
field=models.FileField(blank=True, null=True, upload_to='quick_evaluation/tech_passports/%Y/%m/', verbose_name='tech passport file'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.4 on 2026-04-23 07:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0031_remove_autoevaluationmodel_object_location_city_and_more'),
]
operations = [
migrations.AddField(
model_name='evaluationrequestmodel',
name='is_archive',
field=models.BooleanField(default=False, verbose_name='is archive'),
),
]

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -22,6 +22,22 @@ from .vehicle import VehicleModel
class AutoEvaluationModel(AbstractBaseModel): class AutoEvaluationModel(AbstractBaseModel):
user = models.ForeignKey(
"accounts.User",
on_delete=models.SET_NULL,
related_name="auto_evaluations_user",
verbose_name=_("user"),
null=True,
blank=True,
)
evaluation_request = models.ForeignKey(
"evaluation.EvaluationRequestModel",
on_delete=models.SET_NULL,
related_name="auto_evaluations_request",
verbose_name=_("evaluation request"),
null=True,
blank=True,
)
valuation = models.OneToOneField( valuation = models.OneToOneField(
ValuationModel, ValuationModel,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -38,6 +54,20 @@ class AutoEvaluationModel(AbstractBaseModel):
null=True, null=True,
blank=True, blank=True,
) )
appraisers = models.ManyToManyField(
"accounts.User",
verbose_name=_("appraisers"),
related_name="auto_evaluations",
blank=True,
null=True,
)
tex_passport_file = models.FileField(
verbose_name=_("tech passport file"),
upload_to="quick_evaluation/tech_passports/%Y/%m/",
blank=True,
null=True,
)
# ── Step 1 — Umumiy ma'lumotlar ────────────────────────────────── # ── Step 1 — Umumiy ma'lumotlar ──────────────────────────────────
registration_number = models.CharField( registration_number = models.CharField(
@@ -157,56 +187,6 @@ class AutoEvaluationModel(AbstractBaseModel):
related_name='evaluation_auto_rate_type' related_name='evaluation_auto_rate_type'
) )
# ── Step 3 — Manzil ma'lumotlari ────────────────────────────────
object_location_province = models.CharField(
verbose_name=_("object location province"),
max_length=100,
blank=True,
null=True,
)
object_location_district = models.CharField(
verbose_name=_("object location district"),
max_length=100,
blank=True,
null=True,
)
object_location_city = models.CharField(
verbose_name=_("object location city"),
max_length=100,
blank=True,
null=True,
)
object_location_neighborhood = models.CharField(
verbose_name=_("object location neighborhood"),
max_length=100,
blank=True,
null=True,
)
object_location_street = models.CharField(
verbose_name=_("object location street"),
max_length=100,
blank=True,
null=True,
)
object_location_home = models.CharField(
verbose_name=_("object location home"),
max_length=50,
blank=True,
null=True,
)
object_location_highways = models.IntegerField(
verbose_name=_("location highways"),
choices=LocationHighways.choices,
blank=True,
null=True,
)
object_location_covenience = models.IntegerField(
verbose_name=_("location convenience"),
choices=LocationConvenience.choices,
blank=True,
null=True,
)
# ── Step 4 — Avtomobil ma'lumotlari ───────────────────────────── # ── Step 4 — Avtomobil ma'lumotlari ─────────────────────────────
tex_passport_serie_num = models.CharField( tex_passport_serie_num = models.CharField(
verbose_name=_("tech passport series and number"), verbose_name=_("tech passport series and number"),

View File

@@ -3,9 +3,10 @@ from django.utils.translation import gettext_lazy as _
from django_core.models import AbstractBaseModel from django_core.models import AbstractBaseModel
from model_bakery import baker from model_bakery import baker
from .valuation import ValuationModel
from core.apps.evaluation.choices.document import DocumentType from core.apps.evaluation.choices.document import DocumentType
from .valuation import ValuationModel
class ValuationDocumentModel(AbstractBaseModel): class ValuationDocumentModel(AbstractBaseModel):
valuation = models.ForeignKey( valuation = models.ForeignKey(
@@ -54,3 +55,35 @@ class ValuationDocumentModel(AbstractBaseModel):
verbose_name = _("Valuation Document") verbose_name = _("Valuation Document")
verbose_name_plural = _("Valuation Documents") verbose_name_plural = _("Valuation Documents")
ordering = ["-created_at"] ordering = ["-created_at"]
class DocumentModel(AbstractBaseModel):
auto_evaluation = models.ForeignKey(
"evaluation.AutoEvaluationModel",
on_delete=models.CASCADE,
related_name="documents",
verbose_name=_("auto evaluation"),
)
title = models.CharField(verbose_name=_("title"), max_length=255)
document = models.FileField(
verbose_name=_("document"),
upload_to="evaluation/documents/%Y/%m/",
)
category = models.ForeignKey(
"evaluation.DocumentCategoryModel",
on_delete=models.CASCADE,
related_name="documents",
verbose_name=_("category"),
)
def __str__(self):
return str(self.pk)
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "Document"
verbose_name = _("DocumentModel")
verbose_name_plural = _("DocumentModels")

View File

@@ -0,0 +1,21 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_core.models import AbstractBaseModel
from model_bakery import baker
class DocumentcategoryModel(AbstractBaseModel):
label = models.CharField(verbose_name=_("label"), max_length=255)
value = models.CharField(verbose_name=_("value"), max_length=255)
def __str__(self):
return str(self.pk)
@classmethod
def _baker(cls):
return baker.make(cls)
class Meta:
db_table = "DocumentCategory"
verbose_name = _("DocumentcategoryModel")
verbose_name_plural = _("DocumentcategoryModels")

View File

@@ -34,12 +34,6 @@ class QuickEvaluationModel(AbstractBaseModel):
blank=True, blank=True,
null=True, null=True,
) )
tex_passport_file = models.FileField(
verbose_name=_("tech passport file"),
upload_to="quick_evaluation/tech_passports/%Y/%m/",
blank=True,
null=True,
)
# Car info # Car info
car_type = models.CharField( car_type = models.CharField(

View File

@@ -118,9 +118,12 @@ class EvaluationrequestModel(AbstractBaseModel):
choices=RequestStatus.choices, choices=RequestStatus.choices,
default=RequestStatus.PENDING, default=RequestStatus.PENDING,
) )
is_archive = models.BooleanField(
verbose_name=_("is archive"),
default=False,
)
def __str__(self): def __str__(self):
return f"Request #{self.pk}{self.get_rate_type_display()}" return f"Requests #{self.pk}{self.get_rate_type_display()}"
@classmethod @classmethod
def _baker(cls): def _baker(cls):

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -10,3 +10,14 @@ class ValuationdocumentPermission(permissions.BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
return True return True
class DocumentPermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True

View File

@@ -0,0 +1,12 @@
from rest_framework import permissions
class DocumentcategoryPermission(permissions.BasePermission):
def __init__(self) -> None: ...
def __call__(self, *args, **kwargs):
return self
def has_permission(self, request, view):
return True

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa
@@ -10,3 +11,4 @@ from .report import * # noqa
from .request import * # noqa from .request import * # noqa
from .valuation import * # noqa from .valuation import * # noqa
from .vehicle import * # noqa from .vehicle import * # noqa
from .tech_passport import * # noqa

View File

@@ -1,10 +1,13 @@
import re import re
from django.contrib.auth import get_user_model
from rest_framework import serializers from rest_framework import serializers
from core.apps.evaluation.models import AutoEvaluationModel,ReferenceitemModel from core.apps.evaluation.models import AutoEvaluationModel,ReferenceitemModel, EvaluationrequestModel
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
from core.apps.evaluation.choices.request import RequestStatus
User = get_user_model()
class BaseAutoevaluationSerializer(serializers.ModelSerializer): class BaseAutoevaluationSerializer(serializers.ModelSerializer):
status_display = serializers.CharField(source="get_status_display", read_only=True) status_display = serializers.CharField(source="get_status_display", read_only=True)
@@ -14,11 +17,23 @@ class BaseAutoevaluationSerializer(serializers.ModelSerializer):
value_determined = ListReferenceitemSerializer(read_only=True) value_determined = ListReferenceitemSerializer(read_only=True)
property_rights = ListReferenceitemSerializer(read_only=True) property_rights = ListReferenceitemSerializer(read_only=True)
form_ownership = ListReferenceitemSerializer(read_only=True) form_ownership = ListReferenceitemSerializer(read_only=True)
user = serializers.SerializerMethodField(method_name="get_user", read_only=True)
class Meta: class Meta:
model = AutoEvaluationModel model = AutoEvaluationModel
fields = [ fields = [
"id", "id",
"contract_date",
"object_inspection_date",
"object_owner_individual_person_passport_num",
"object_owner_type",
"object_owner_individual_person_f_name",
"object_owner_individual_person_l_name",
"object_owner_individual_person_p_name",
"object_owner_legal_entity",
"object_owner_legal_inn",
"tex_passport_serie_num",
"rating_goal",
"registration_number", "registration_number",
"object_type", "object_type",
"object_type_display", "object_type_display",
@@ -34,8 +49,20 @@ class BaseAutoevaluationSerializer(serializers.ModelSerializer):
"rate_type", "rate_type",
"property_rights", "property_rights",
"form_ownership", "form_ownership",
"user",
"evaluation_request",
] ]
def get_user(self, obj):
request = self.context.get('request')
return {
"id": obj.user.id,
"phone": obj.user.phone,
"first_name": obj.user.first_name,
"last_name": obj.user.last_name,
"avatar": request.build_absolute_uri(obj.user.avatar.url) if obj.user.avatar else None
} if obj.user else None
class ListAutoevaluationSerializer(BaseAutoevaluationSerializer): class ListAutoevaluationSerializer(BaseAutoevaluationSerializer):
class Meta(BaseAutoevaluationSerializer.Meta): class Meta(BaseAutoevaluationSerializer.Meta):
@@ -45,12 +72,12 @@ class ListAutoevaluationSerializer(BaseAutoevaluationSerializer):
class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer): class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
car_type_display = serializers.CharField(source="get_car_type_display", read_only=True, default=None) car_type_display = serializers.CharField(source="get_car_type_display", read_only=True, default=None)
car_wheel_display = serializers.CharField(source="get_car_wheel_display", read_only=True, default=None) car_wheel_display = serializers.CharField(source="get_car_wheel_display", read_only=True, default=None)
object_location_highways_display = serializers.CharField( # object_location_highways_display = serializers.CharField(
source="get_object_location_highways_display", read_only=True, default=None # source="get_object_location_highways_display", read_only=True, default=None
) # )
object_location_covenience_display = serializers.CharField( # object_location_covenience_display = serializers.CharField(
source="get_object_location_covenience_display", read_only=True, default=None # source="get_object_location_covenience_display", read_only=True, default=None
) # )
class Meta(BaseAutoevaluationSerializer.Meta): class Meta(BaseAutoevaluationSerializer.Meta):
fields = BaseAutoevaluationSerializer.Meta.fields + [ fields = BaseAutoevaluationSerializer.Meta.fields + [
@@ -69,19 +96,9 @@ class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
"object_owner_individual_person_passport_num", "object_owner_individual_person_passport_num",
"object_owner_legal_entity", "object_owner_legal_entity",
"object_owner_legal_inn", "object_owner_legal_inn",
# Step 3
"object_location_province",
"object_location_district",
"object_location_city",
"object_location_neighborhood",
"object_location_street",
"object_location_home",
"object_location_highways",
"object_location_highways_display",
"object_location_covenience",
"object_location_covenience_display",
# Step 4 # Step 4
"tex_passport_serie_num", "tex_passport_serie_num",
"tex_passport_file",
"tex_passport_gived_date", "tex_passport_gived_date",
"tex_passport_gived_location", "tex_passport_gived_location",
"car_type", "car_type",
@@ -97,7 +114,7 @@ class RetrieveAutoevaluationSerializer(BaseAutoevaluationSerializer):
] ]
class CreateAutoevaluationSerializer(serializers.ModelSerializer): class UpdateAutoevaluationSerializer(serializers.ModelSerializer):
property_rights = serializers.PrimaryKeyRelatedField( property_rights = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(), queryset=ReferenceitemModel.objects.all(),
required=False, required=False,
@@ -148,16 +165,8 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
"form_ownership", "form_ownership",
"value_determined", "value_determined",
"rate_type", "rate_type",
# Step 3
"object_location_province",
"object_location_district",
"object_location_city",
"object_location_neighborhood",
"object_location_street",
"object_location_home",
"object_location_highways",
"object_location_covenience",
# Step 4 # Step 4
"tex_passport_file",
"tex_passport_serie_num", "tex_passport_serie_num",
"tex_passport_gived_date", "tex_passport_gived_date",
"tex_passport_gived_location", "tex_passport_gived_location",
@@ -212,3 +221,138 @@ class CreateAutoevaluationSerializer(serializers.ModelSerializer):
) )
return attrs return attrs
class CreateAutoevaluationSerializer(serializers.ModelSerializer):
property_rights = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
form_ownership = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
value_determined = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
rate_type = serializers.PrimaryKeyRelatedField(
queryset=ReferenceitemModel.objects.all(),
required=False,
allow_null=True,
)
evaluation_request = serializers.PrimaryKeyRelatedField(
queryset=EvaluationrequestModel.objects.all(),
required=False,
allow_null=True,
)
class Meta:
model = AutoEvaluationModel
fields = [
# Step 1
"registration_number",
"evaluation_request",
"contract_date",
"object_inspection_date",
"rate_date",
"rate_report_date",
"rate_object_name",
"object_type",
# Step 2
"object_owner_type",
"object_owner_individual_person_f_name",
"object_owner_individual_person_l_name",
"object_owner_individual_person_p_name",
"object_owner_individual_person_passport_num",
"object_owner_legal_entity",
"object_owner_legal_inn",
"property_rights",
"form_ownership",
"value_determined",
"rate_type",
# Step 4
"tex_passport_serie_num",
"tex_passport_file",
"tex_passport_gived_date",
"tex_passport_gived_location",
"car_type",
"car_wheel",
"car_brand",
"car_model",
"car_number",
"manufacture_year",
"car_dvigatel_number",
"car_color",
]
def validate_tex_passport_serie_num(self, value):
if value and not re.match(r"^[A-Z]{3}\s?\d{7}$", value):
raise serializers.ValidationError(
"Format: AAA 1234567 (3 harf + 7 raqam)"
)
return value
def validate_object_owner_individual_person_passport_num(self, value):
if value and not re.match(r"^[A-Z]{2}\s?\d{7}$", value):
raise serializers.ValidationError(
"Format: AA 1234567 (2 harf + 7 raqam)"
)
return value
def validate(self, attrs):
owner_type = attrs.get("object_owner_type")
if owner_type == 1:
required_fields = {
"object_owner_individual_person_f_name": "Ismi",
"object_owner_individual_person_l_name": "Familiyasi",
"object_owner_individual_person_p_name": "Sharifi",
"object_owner_individual_person_passport_num": "Passport raqami",
}
for field, label in required_fields.items():
if not attrs.get(field):
raise serializers.ValidationError(
{field: f"Jismoniy shaxs uchun {label} majburiy."}
)
elif owner_type == 2:
if not attrs.get("object_owner_legal_entity"):
raise serializers.ValidationError(
{"object_owner_legal_entity": "Yuridik shaxs nomi majburiy."}
)
if not attrs.get("object_owner_legal_inn"):
raise serializers.ValidationError(
{"object_owner_legal_inn": "INN raqami majburiy."}
)
return attrs
def create(self, validated_data):
user = self.context.get('request').user
validated_data['user'] = user
evaluation_req = validated_data.get("evaluation_request")
if evaluation_req:
evaluation_req.status = RequestStatus.IN_PROGRESS
evaluation_req.save()
return super().create(validated_data)
class AutoEvaluationAppraisersSerializer(serializers.Serializer):
ids = serializers.ListField(child=serializers.IntegerField())
def validate(self, data):
if not data.get("ids"):
raise serializers.ValidationError("Appraisers IDs are required.")
users = User.objects.filter(id__in=data["ids"])
if not users:
raise serializers.ValidationError("Invalid appraisers IDs.")
data['users'] = users
return data

View File

@@ -0,0 +1,57 @@
from rest_framework import serializers
from core.apps.evaluation.models import DocumentModel, AutoEvaluationModel, DocumentcategoryModel
from core.apps.evaluation.serializers.documentcategory import ListDocumentcategorySerializer
class BaseDocumentSerializer(serializers.ModelSerializer):
category = ListDocumentcategorySerializer(read_only=True)
class Meta:
model = DocumentModel
fields = [
"id",
"title",
"document",
"category",
"created_at",
"updated_at"
]
class ListDocumentSerializer(BaseDocumentSerializer):
class Meta(BaseDocumentSerializer.Meta): ...
def get_document(self, obj):
request = self.context.get("request")
if obj.document:
if request:
return request.build_absolute_uri(obj.document.url)
return obj.document.url
return None
class RetrieveDocumentSerializer(BaseDocumentSerializer):
class Meta(BaseDocumentSerializer.Meta): ...
def get_document(self, obj):
request = self.context.get("request")
if obj.document:
if request:
return request.build_absolute_uri(obj.document.url)
return obj.document.url
return None
class CreateDocumentSerializer(BaseDocumentSerializer):
auto_evaluation = serializers.PrimaryKeyRelatedField(queryset=AutoEvaluationModel.objects.all())
category = serializers.PrimaryKeyRelatedField(queryset=DocumentcategoryModel.objects.all())
class Meta(BaseDocumentSerializer.Meta):
fields = [
"id",
"title",
"auto_evaluation",
"document",
"category",
"created_at",
"updated_at"
]

View File

@@ -1 +1,2 @@
from .Document import * # noqa
from .ValuationDocument import * # noqa from .ValuationDocument import * # noqa

View File

@@ -0,0 +1,34 @@
from rest_framework import serializers
from core.apps.evaluation.models import DocumentcategoryModel
class BaseDocumentcategorySerializer(serializers.ModelSerializer):
class Meta:
model = DocumentcategoryModel
fields = [
"id",
"label",
"value",
"created_at",
"updated_at"
]
class ListDocumentcategorySerializer(BaseDocumentcategorySerializer):
class Meta(BaseDocumentcategorySerializer.Meta): ...
class RetrieveDocumentcategorySerializer(BaseDocumentcategorySerializer):
class Meta(BaseDocumentcategorySerializer.Meta): ...
# class CreateDocumentcategorySerializer(BaseDocumentcategorySerializer):
# class Meta(BaseDocumentcategorySerializer.Meta):
# fields = [
# "id",
# "label",
# "value",
# "created_at",
# "updated_at"
# ]

View File

@@ -0,0 +1 @@
from .DocumentCategory import * # noqa

View File

@@ -37,6 +37,8 @@ class BaseQuickevaluationSerializer(serializers.ModelSerializer):
"state_car", "state_car",
"state_car_name", "state_car_name",
"created_at", "created_at",
"distance_covered",
"tex_passport_serie_num"
] ]
@@ -51,7 +53,6 @@ class RetrieveQuickevaluationSerializer(BaseQuickevaluationSerializer):
"tex_passport_serie_num", "tex_passport_serie_num",
"tech_passport_issued_date", "tech_passport_issued_date",
"tech_passport_issued_place", "tech_passport_issued_place",
"tex_passport_file",
"car_position", "car_position",
"car_position_name", "car_position_name",
"distance_covered", "distance_covered",
@@ -74,7 +75,6 @@ class CreateQuickevaluationSerializer(serializers.ModelSerializer):
"tex_passport_serie_num", "tex_passport_serie_num",
"tech_passport_issued_date", "tech_passport_issued_date",
"tech_passport_issued_place", "tech_passport_issued_place",
"tex_passport_file",
"car_type", "car_type",
"brand", "brand",
"marka", "marka",

View File

@@ -1,11 +1,16 @@
import re import re
from django.contrib.auth import get_user_model
from rest_framework import serializers from rest_framework import serializers
from core.apps.evaluation.models import EvaluationrequestModel, ReferenceitemModel from core.apps.evaluation.models import EvaluationrequestModel, ReferenceitemModel
from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer from core.apps.evaluation.serializers.reference import ListReferenceitemSerializer
User = get_user_model()
class BaseEvaluationrequestSerializer(serializers.ModelSerializer): class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
rate_type_display = serializers.CharField( rate_type_display = serializers.CharField(
source="get_rate_type_display", read_only=True source="get_rate_type_display", read_only=True
@@ -23,6 +28,7 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
rate_goal = ListReferenceitemSerializer(read_only=True) rate_goal = ListReferenceitemSerializer(read_only=True)
property_rights = ListReferenceitemSerializer(read_only=True) property_rights = ListReferenceitemSerializer(read_only=True)
form_ownership = ListReferenceitemSerializer(read_only=True) form_ownership = ListReferenceitemSerializer(read_only=True)
user = serializers.SerializerMethodField(method_name="get_user")
class Meta: class Meta:
model = EvaluationrequestModel model = EvaluationrequestModel
@@ -46,8 +52,10 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
"location_name", "location_name",
"status", "status",
"status_display", "status_display",
"user",
"created_at", "created_at",
"updated_at", "updated_at",
"is_archive",
] ]
def get_location(self, obj): def get_location(self, obj):
@@ -59,6 +67,17 @@ class BaseEvaluationrequestSerializer(serializers.ModelSerializer):
} }
return None return None
def get_user(self, obj):
request = self.context.get('request')
return {
"id": obj.user.id,
"phone": obj.user.phone,
"first_name": obj.user.first_name,
"last_name": obj.user.last_name,
"role": obj.user.role,
"avatar": request.build_absolute_uri(obj.user.avatar.url) if obj.user.avatar else None
}
class ListEvaluationrequestSerializer(BaseEvaluationrequestSerializer): class ListEvaluationrequestSerializer(BaseEvaluationrequestSerializer):
class Meta(BaseEvaluationrequestSerializer.Meta): class Meta(BaseEvaluationrequestSerializer.Meta):
@@ -93,10 +112,10 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
allow_blank=True, allow_blank=True,
) )
value_determined = serializers.IntegerField(required=False, allow_null=True) value_determined = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
rate_goal = serializers.IntegerField(required=False, allow_null=True) rate_goal = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
property_rights = serializers.IntegerField(required=False, allow_null=True) property_rights = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
form_ownership = serializers.IntegerField(required=False, allow_null=True) form_ownership = serializers.PrimaryKeyRelatedField(required=False, queryset=ReferenceitemModel.objects.all())
class Meta: class Meta:
model = EvaluationrequestModel model = EvaluationrequestModel
@@ -149,30 +168,6 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
{"chassi": "Yuk avtomobil uchun shassi raqami majburiy."} {"chassi": "Yuk avtomobil uchun shassi raqami majburiy."}
) )
if attrs.get("value_determined"):
if attrs.get("value_determined") not in ReferenceitemModel.objects.values_list("id", flat=True):
raise serializers.ValidationError(
{"value_determined": "Noto'g'ri qiymat aniqlandi."}
)
if attrs.get("rate_goal"):
if attrs.get("rate_goal") not in ReferenceitemModel.objects.values_list("id", flat=True):
raise serializers.ValidationError(
{"rate_goal": "Noto'g'ri qiymat aniqlandi."}
)
if attrs.get("property_rights"):
if attrs.get("property_rights") not in ReferenceitemModel.objects.values_list("id", flat=True):
raise serializers.ValidationError(
{"property_rights": "Noto'g'ri qiymat aniqlandi."}
)
if attrs.get("form_ownership"):
if attrs.get("form_ownership") not in ReferenceitemModel.objects.values_list("id", flat=True):
raise serializers.ValidationError(
{"form_ownership": "Noto'g'ri qiymat aniqlandi."}
)
return attrs return attrs
def create(self, validated_data): def create(self, validated_data):
@@ -187,6 +182,9 @@ class CreateEvaluationrequestSerializer(serializers.ModelSerializer):
if location_name: if location_name:
validated_data["location_name"] = str(location_name) validated_data["location_name"] = str(location_name)
validated_data["user"] = self.context["request"].user validated_data["user"] = self.context["request"].user
return super().create(validated_data) return super().create(validated_data)
class ArchiveEvaluationrequestSerializer(serializers.Serializer):
id = serializers.IntegerField(required=True)
is_archive = serializers.BooleanField(required=True)

View File

@@ -0,0 +1 @@
from .tech_passport import * # noqa

View File

@@ -0,0 +1,6 @@
from rest_framework import serializers
class TechPassportSerializer(serializers.Serializer):
autonumber = serializers.CharField(required=True, max_length=20)
tech_pass_number = serializers.CharField(required=True, max_length=20)
tech_pass_series = serializers.CharField(required=True, max_length=20)

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -1,8 +1,12 @@
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from core.apps.evaluation.models import ValuationDocumentModel from core.apps.evaluation.models import DocumentModel, ValuationDocumentModel
@receiver(post_save, sender=ValuationDocumentModel) @receiver(post_save, sender=ValuationDocumentModel)
def ValuationdocumentSignal(sender, instance, created, **kwargs): ... def ValuationdocumentSignal(sender, instance, created, **kwargs): ...
@receiver(post_save, sender=DocumentModel)
def DocumentSignal(sender, instance, created, **kwargs): ...

View File

@@ -0,0 +1,8 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from core.apps.evaluation.models import DocumentcategoryModel
@receiver(post_save, sender=DocumentcategoryModel)
def DocumentcategorySignal(sender, instance, created, **kwargs): ...

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -1 +1,2 @@
from .test_Document import * # noqa
from .test_ValuationDocument import * # noqa from .test_ValuationDocument import * # noqa

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.evaluation.models import DocumentModel
@pytest.fixture
def instance(db):
return DocumentModel._baker()
@pytest.fixture
def api_client(instance):
client = APIClient()
##client.force_authenticate(user=instance.user)
return client, instance
@pytest.fixture
def data(api_client):
client, instance = api_client
return (
{
"list": reverse("Document-list"),
"retrieve": reverse("Document-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("Document-detail", kwargs={"pk": 1000}),
},
client,
instance,
)
@pytest.mark.django_db
def test_list(data):
urls, client, _ = data
response = client.get(urls["list"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True
@pytest.mark.django_db
def test_retrieve(data):
urls, client, _ = data
response = client.get(urls["retrieve"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True
@pytest.mark.django_db
def test_retrieve_not_found(data):
urls, client, _ = data
response = client.get(urls["retrieve-not-found"])
data_resp = response.json()
assert response.status_code == 404
assert data_resp["status"] is False
# @pytest.mark.django_db
# def test_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -0,0 +1 @@
from .test_DocumentCategory import * # noqa

View File

@@ -0,0 +1,101 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.evaluation.models import DocumentcategoryModel
@pytest.fixture
def instance(db):
return DocumentcategoryModel._baker()
@pytest.fixture
def api_client(instance):
client = APIClient()
##client.force_authenticate(user=instance.user)
return client, instance
@pytest.fixture
def data(api_client):
client, instance = api_client
return (
{
"list": reverse("DocumentCategory-list"),
"retrieve": reverse("DocumentCategory-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("DocumentCategory-detail", kwargs={"pk": 1000}),
},
client,
instance,
)
@pytest.mark.django_db
def test_list(data):
urls, client, _ = data
response = client.get(urls["list"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True
@pytest.mark.django_db
def test_retrieve(data):
urls, client, _ = data
response = client.get(urls["retrieve"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True
@pytest.mark.django_db
def test_retrieve_not_found(data):
urls, client, _ = data
response = client.get(urls["retrieve-not-found"])
data_resp = response.json()
assert response.status_code == 404
assert data_resp["status"] is False
# @pytest.mark.django_db
# def test_create(data):
# urls, client, _ = data
# response = client.post(urls["list"], data={"name": "test"})
# assert response.json()["status"] is True
# assert response.status_code == 201
# @pytest.mark.django_db
# def test_update(data):
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_partial_update():
# urls, client, _ = data
# response = client.patch(urls["retrieve"], data={"name": "updated"})
# assert response.json()["status"] is True
# assert response.status_code == 200
#
# # verify updated value
# response = client.get(urls["retrieve"])
# assert response.json()["status"] is True
# assert response.status_code == 200
# assert response.json()["data"]["name"] == "updated"
# @pytest.mark.django_db
# def test_destroy(data):
# urls, client, _ = data
# response = client.delete(urls["retrieve"])
# assert response.status_code == 204

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -1,8 +1,13 @@
from modeltranslation.translator import TranslationOptions, register from modeltranslation.translator import TranslationOptions, register
from core.apps.evaluation.models import ValuationDocumentModel from core.apps.evaluation.models import DocumentModel, ValuationDocumentModel
@register(ValuationDocumentModel) @register(ValuationDocumentModel)
class ValuationdocumentTranslation(TranslationOptions): class ValuationdocumentTranslation(TranslationOptions):
fields = [] fields = []
@register(DocumentModel)
class DocumentTranslation(TranslationOptions):
fields = []

View File

@@ -0,0 +1,8 @@
from modeltranslation.translator import TranslationOptions, register
from core.apps.evaluation.models import DocumentcategoryModel
@register(DocumentcategoryModel)
class DocumentcategoryTranslation(TranslationOptions):
fields = ["label", "value"]

View File

@@ -2,10 +2,13 @@ from django.urls import include, path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .views import ( from .views import (
AdminEvaluationrequestView,
AutoEvaluationHistoryView, AutoEvaluationHistoryView,
AutoEvaluationView, AutoEvaluationView,
CustomerView, CustomerView,
DeterminedValueView, DeterminedValueView,
DocumentCategoryView,
DocumentView,
EvaluationPurposeView, EvaluationPurposeView,
EvaluationReportView, EvaluationReportView,
EvaluationrequestView, EvaluationrequestView,
@@ -20,9 +23,18 @@ from .views import (
ValuationDocumentView, ValuationDocumentView,
ValuationView, ValuationView,
VehicleView, VehicleView,
AutoEvaluationListAppraisersView,
AutoEvaluationSetAppraisersView,
AutoEvaluationRemoveAppraisersView,
DidoxCompanyInfoAPIView,
TechPassportAPIView,
EvaluationStatusChange,
ArchiveEvaluationrequestView,
) )
router = DefaultRouter() router = DefaultRouter()
router.register("document-category", DocumentCategoryView, basename="DocumentCategory")
router.register("document", DocumentView, basename="Document")
router.register("auto-evaluation-history", AutoEvaluationHistoryView, basename="auto-evaluation-history") router.register("auto-evaluation-history", AutoEvaluationHistoryView, basename="auto-evaluation-history")
router.register("quick-evaluation-history", QuickEvaluationHistoryView, basename="quick-evaluation-history") router.register("quick-evaluation-history", QuickEvaluationHistoryView, basename="quick-evaluation-history")
router.register("determined-value", DeterminedValueView, basename="determined-value") router.register("determined-value", DeterminedValueView, basename="determined-value")
@@ -30,6 +42,7 @@ router.register("evaluation-purpose", EvaluationPurposeView, basename="evaluatio
router.register("property-rights", PropertyRightsView, basename="property-rights") router.register("property-rights", PropertyRightsView, basename="property-rights")
router.register("ownership-form", OwnershipFormView, basename="ownership-form") router.register("ownership-form", OwnershipFormView, basename="ownership-form")
router.register("evaluation-request", EvaluationrequestView, basename="evaluation-request") router.register("evaluation-request", EvaluationrequestView, basename="evaluation-request")
router.register("admin-evaluation-request", AdminEvaluationrequestView, basename="admin-evaluation-request")
router.register("reference-item", ReferenceitemView, basename="reference-item") router.register("reference-item", ReferenceitemView, basename="reference-item")
router.register("valuation-document", ValuationDocumentView, basename="valuation-document") router.register("valuation-document", ValuationDocumentView, basename="valuation-document")
router.register("evaluation-report", EvaluationReportView, basename="evaluation-report") router.register("evaluation-report", EvaluationReportView, basename="evaluation-report")
@@ -41,4 +54,26 @@ router.register("vehicle", VehicleView, basename="vehicle")
router.register("valuation", ValuationView, basename="valuation") router.register("valuation", ValuationView, basename="valuation")
router.register("property-owner", PropertyOwnerView, basename="property-owner") router.register("property-owner", PropertyOwnerView, basename="property-owner")
router.register("customer", CustomerView, basename="customer") router.register("customer", CustomerView, basename="customer")
urlpatterns = [path("", include(router.urls))] urlpatterns = [
path("", include(router.urls)),
path("auto-evaluation/appraisers/", include(
[
path("<int:id>/list/", AutoEvaluationListAppraisersView.as_view(), name="auto-evaluation-list-appraisers"),
path("<int:id>/set/", AutoEvaluationSetAppraisersView.as_view(), name="auto-evaluation-set-appraisers"),
path("<int:id>/remove/", AutoEvaluationRemoveAppraisersView.as_view(), name="auto-evaluation-remove-appraisers"),
]
)),
path(
"didox/info/<int:tin>/",
DidoxCompanyInfoAPIView.as_view(),
name="didox-info"
),
path(
"tech-passport/",
TechPassportAPIView.as_view(),
name="tech-passport"
),
path("evaluation-request/<int:pk>/change-status/", EvaluationStatusChange.as_view(),
name="evaluation-change-status"),
path("evaluation-request/archive/", ArchiveEvaluationrequestView.as_view(), name="evaluation-request-archive"),
]

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa

View File

@@ -6,3 +6,10 @@ class ValuationdocumentValidator:
def __call__(self): def __call__(self):
return True return True
class DocumentValidator:
def __init__(self): ...
def __call__(self):
return True

View File

@@ -0,0 +1,8 @@
# from django.core.exceptions import ValidationError
class DocumentcategoryValidator:
def __init__(self): ...
def __call__(self):
return True

View File

@@ -1,6 +1,7 @@
from .auto import * # noqa from .auto import * # noqa
from .customer import * # noqa from .customer import * # noqa
from .document import * # noqa from .document import * # noqa
from .documentcategory import * # noqa
from .history import * # noqa from .history import * # noqa
from .movable import * # noqa from .movable import * # noqa
from .quick import * # noqa from .quick import * # noqa
@@ -10,3 +11,5 @@ from .report import * # noqa
from .request import * # noqa from .request import * # noqa
from .valuation import * # noqa from .valuation import * # noqa
from .vehicle import * # noqa from .vehicle import * # noqa
from .didox import * # noqa
from .tech_passport import * # noqa

View File

@@ -1,16 +1,23 @@
from django.db.models import Q
from django_core.mixins import BaseViewSetMixin from django_core.mixins import BaseViewSetMixin
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from core.apps.accounts.serializers.user import UserSerializer
from core.apps.evaluation.filters.auto import AutoevaluationFilter from core.apps.evaluation.filters.auto import AutoevaluationFilter
from core.apps.evaluation.models import AutoEvaluationModel from core.apps.evaluation.models import AutoEvaluationModel
from core.apps.evaluation.serializers.auto import ( from core.apps.evaluation.serializers.auto import (
CreateAutoevaluationSerializer, CreateAutoevaluationSerializer,
ListAutoevaluationSerializer, ListAutoevaluationSerializer,
RetrieveAutoevaluationSerializer, RetrieveAutoevaluationSerializer,
AutoEvaluationAppraisersSerializer,
UpdateAutoevaluationSerializer
) )
@@ -33,31 +40,34 @@ class AutoEvaluationView(BaseViewSetMixin, ModelViewSet):
"car_number", "car_number",
] ]
ordering_fields = [ ordering_fields = [
"registration_number", "id",
"contract_date", "contract_date",
"object_inspection_date", "object_inspection_date",
"rate_date", "object_owner_individual_person_passport_num",
"rate_report_date",
"rate_object_name",
"object_type",
"object_owner_type", "object_owner_type",
"object_location_province", "object_owner_individual_person_f_name",
"object_location_district", "object_owner_individual_person_l_name",
"object_location_city", "object_owner_individual_person_p_name",
"object_owner_legal_entity",
"object_owner_legal_inn",
"tex_passport_serie_num", "tex_passport_serie_num",
"tex_passport_gived_date", "rating_goal",
"object_location_province",
"registration_number",
"object_type",
"object_type_display",
"car_brand", "car_brand",
"car_model", "car_model",
"car_number", "car_number",
"manufacture_year", "manufacture_year",
"car_color", "car_color",
"property_rights", "status",
"form_ownership", "status_display",
"created_at",
"value_determined", "value_determined",
"rate_type", "rate_type",
"status", "property_rights",
"created_at", "form_ownership",
"updated_at",
] ]
ordering = ["-created_at"] ordering = ["-created_at"]
@@ -66,4 +76,81 @@ class AutoEvaluationView(BaseViewSetMixin, ModelViewSet):
"list": ListAutoevaluationSerializer, "list": ListAutoevaluationSerializer,
"retrieve": RetrieveAutoevaluationSerializer, "retrieve": RetrieveAutoevaluationSerializer,
"create": CreateAutoevaluationSerializer, "create": CreateAutoevaluationSerializer,
"update": UpdateAutoevaluationSerializer,
"partial_update": UpdateAutoevaluationSerializer,
} }
def serializer_context(self):
return self.serializer_class(context={'request': self.request})
@extend_schema(tags=["AutoEvaluation"])
class AutoEvaluationSetAppraisersView(GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = AutoEvaluationModel.objects.all()
serializer_class = AutoEvaluationAppraisersSerializer
def post(self, request, id):
try:
auto_evaluation = get_object_or_404(AutoEvaluationModel, id=id)
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
users = serializer.validated_data.get("users")
auto_evaluation.appraisers.set(users)
auto_evaluation.save()
return Response({"success": True, "data": "Foydalanuvchilar muvaffaqiyatli qo'shildi"})
except Exception as e:
return Response({"error": str(e)}, status=500)
@extend_schema(tags=["AutoEvaluation"])
class AutoEvaluationRemoveAppraisersView(GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = AutoEvaluationModel.objects.all()
serializer_class = AutoEvaluationAppraisersSerializer
def post(self, request, id):
try:
auto_evaluation = get_object_or_404(AutoEvaluationModel, id=id)
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
users = serializer.validated_data.get("users")
auto_evaluation.appraisers.remove(*users)
auto_evaluation.save()
return Response({"success": True, "data": "Foydalanuvchilar muvaffaqiyatli o'chirildi"})
except Exception as e:
return Response({"error": str(e)}, status=500)
@extend_schema(tags=["AutoEvaluation"])
class AutoEvaluationListAppraisersView(GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = AutoEvaluationModel.objects.all()
serializer_class = UserSerializer
@extend_schema(
parameters=[
OpenApiParameter(
name="search",
type=str,
description="Search query",
required=False,
)
]
)
def get(self, request, id):
try:
search_query = request.query_params.get("search", "")
auto_evaluation = get_object_or_404(AutoEvaluationModel, id=id)
query = auto_evaluation.appraisers.all()
if search_query:
query = query.filter(
Q(phone__icontains=search_query) |
Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query)
)
page = self.paginate_queryset(query)
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
except Exception as e:
return Response({"error": str(e)}, status=500)

View File

@@ -0,0 +1,60 @@
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework.generics import GenericAPIView
from drf_spectacular.utils import extend_schema, OpenApiParameter
from core.services.didox import DidoxService
class DidoxCompanyInfoAPIView(GenericAPIView):
authentication_classes = []
permission_classes = [AllowAny]
@extend_schema(
tags=["Didox"],
summary="Get company info by TIN",
description="TIN/JSHSHIR orqali Didoxdan ma'lumot olish",
parameters=[
OpenApiParameter(
name="tin",
type=int,
location=OpenApiParameter.PATH,
required=True,
description="TIN / STIR / INN / JSHSHIR"
)
],
responses={200: dict},
)
def get(self, request, *args, **kwargs):
tin = kwargs.get("tin")
try:
tin = int(tin)
except (TypeError, ValueError):
return Response(
{"detail": "TIN must be a valid integer"},
status=status.HTTP_400_BAD_REQUEST
)
data = DidoxService.get_company_info(tin)
if not data:
return Response(
{"detail": "Didox service unavailable"},
status=status.HTTP_502_BAD_GATEWAY
)
# if both name and personalNum are null/empty -> 404
name = data.get("name")
personal_num = data.get("personalNum")
if not name and not personal_num:
return Response(
{"detail": "Company or person not found"},
status=status.HTTP_404_NOT_FOUND
)
return Response(data, status=status.HTTP_200_OK)

View File

@@ -1,12 +1,21 @@
from django.shortcuts import get_object_or_404
from django_core.mixins import BaseViewSetMixin from django_core.mixins import BaseViewSetMixin
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework.exceptions import NotFound, PermissionDenied
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.viewsets import ReadOnlyModelViewSet from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from core.apps.evaluation.models import ValuationDocumentModel from core.apps.evaluation.models import DocumentModel, ValuationDocumentModel, AutoEvaluationModel
from core.apps.evaluation.serializers.document import ( from core.apps.evaluation.serializers.document import (
CreateDocumentSerializer,
CreateValuationdocumentSerializer, CreateValuationdocumentSerializer,
ListDocumentSerializer,
ListValuationdocumentSerializer, ListValuationdocumentSerializer,
RetrieveDocumentSerializer,
RetrieveValuationdocumentSerializer, RetrieveValuationdocumentSerializer,
) )
@@ -23,3 +32,62 @@ class ValuationDocumentView(BaseViewSetMixin, ReadOnlyModelViewSet):
"retrieve": RetrieveValuationdocumentSerializer, "retrieve": RetrieveValuationdocumentSerializer,
"create": CreateValuationdocumentSerializer, "create": CreateValuationdocumentSerializer,
} }
@extend_schema(tags=["Document"])
class DocumentView(BaseViewSetMixin, ModelViewSet):
queryset = DocumentModel.objects.all()
serializer_class = ListDocumentSerializer
permission_classes = [AllowAny]
parser_classes = [FormParser, MultiPartParser]
filter_backends = [DjangoFilterBackend]
action_permission_classes = {}
action_serializer_class = {
"list": ListDocumentSerializer,
"retrieve": RetrieveDocumentSerializer,
"create": CreateDocumentSerializer,
}
@extend_schema(
summary="Auto evaluation documents.",
description="get auto evaluation documents.",
parameters=[
OpenApiParameter(
name="page",
type=int, description="Page number"
),
OpenApiParameter(
name="page_size",
type=int, description="Page size"
),
OpenApiParameter(
name="search",
type=str, description="Search query"
),
OpenApiParameter(
name="category_id",
type=str, description="Filter by category ID"
),
]
)
@action(methods=["GET"], detail=False, url_path=r"auto_evaluation/(?P<auto_evaluation_id>\d+)")
def auto_evaluation(self, request, auto_evaluation_id=None):
try:
search = request.query_params.get("search", "")
category_id = request.query_params.get("category_id", "")
auto_evaluation = get_object_or_404(AutoEvaluationModel, id=auto_evaluation_id)
documents = DocumentModel.objects.filter(auto_evaluation=auto_evaluation)
if search:
documents = documents.filter(title__icontains=search)
if category_id:
documents = documents.filter(category_id=category_id)
page = self.paginate_queryset(documents)
if page is not None:
serializer = ListDocumentSerializer(page, many=True, context={"request": request})
return self.get_paginated_response(serializer.data)
serializer = ListDocumentSerializer(documents, many=True, context={"request": request})
return Response(serializer.data)
except AutoEvaluationModel.DoesNotExist:
raise NotFound("Auto evaluation not found")
except Exception as e:
raise PermissionDenied(e)

View File

@@ -0,0 +1,24 @@
from django_core.mixins import BaseViewSetMixin
from drf_spectacular.utils import extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import ReadOnlyModelViewSet
from core.apps.evaluation.models import DocumentcategoryModel
from core.apps.evaluation.serializers.documentcategory import (
ListDocumentcategorySerializer,
RetrieveDocumentcategorySerializer,
)
@extend_schema(tags=["DocumentCategory"])
class DocumentCategoryView(BaseViewSetMixin, ReadOnlyModelViewSet):
queryset = DocumentcategoryModel.objects.all()
serializer_class = ListDocumentcategorySerializer
permission_classes = [AllowAny]
action_permission_classes = {}
action_serializer_class = {
"list": ListDocumentcategorySerializer,
"retrieve": RetrieveDocumentcategorySerializer,
# "create": CreateDocumentcategorySerializer,
}

View File

@@ -43,7 +43,14 @@ class AutoEvaluationHistoryView(BaseViewSetMixin, ReadOnlyModelViewSet):
filter_backends = [DjangoFilterBackend, OrderingFilter] filter_backends = [DjangoFilterBackend, OrderingFilter]
filterset_class = AutoevaluationhistoryFilter filterset_class = AutoevaluationhistoryFilter
ordering_fields = ["created_at"] ordering_fields = [
"id",
"event_type",
"event_type_display",
"actor",
"meta",
"created_at",
]
ordering = ["created_at"] ordering = ["created_at"]
action_permission_classes = {} action_permission_classes = {}

View File

@@ -1,3 +1,5 @@
from django.shortcuts import get_object_or_404
from django_core.mixins import BaseViewSetMixin from django_core.mixins import BaseViewSetMixin
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
@@ -5,6 +7,9 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from core.apps.evaluation.filters.request import EvaluationrequestFilter from core.apps.evaluation.filters.request import EvaluationrequestFilter
from core.apps.evaluation.models import EvaluationrequestModel from core.apps.evaluation.models import EvaluationrequestModel
@@ -12,7 +17,11 @@ from core.apps.evaluation.serializers.request import (
CreateEvaluationrequestSerializer, CreateEvaluationrequestSerializer,
ListEvaluationrequestSerializer, ListEvaluationrequestSerializer,
RetrieveEvaluationrequestSerializer, RetrieveEvaluationrequestSerializer,
ArchiveEvaluationrequestSerializer,
) )
from core.apps.evaluation.choices.request import RequestStatus
from rest_framework.generics import GenericAPIView
from drf_spectacular.utils import OpenApiResponse
# class RequestPagination(PageNumberPagination): # class RequestPagination(PageNumberPagination):
@@ -35,11 +44,84 @@ class EvaluationrequestView(BaseViewSetMixin, ModelViewSet):
"tex_passport", "tex_passport",
] ]
ordering_fields = [ ordering_fields = [
"id",
"rate_type",
"rate_type_display",
"object_type",
"object_type_display",
"customer_inn_number",
"owner_inn_number",
"tex_passport",
"value_determined",
"rate_goal",
"property_rights",
"form_ownership",
"worked_hours",
"chassi",
"need_delivering",
"location",
"location_name",
"status",
"status_display",
"user",
"created_at", "created_at",
"updated_at", "updated_at",
]
ordering = ["-created_at"]
action_permission_classes = {}
action_serializer_class = {
"list": ListEvaluationrequestSerializer,
"retrieve": RetrieveEvaluationrequestSerializer,
"create": CreateEvaluationrequestSerializer,
}
def serializer_context(self):
return self.serializer_class(context={"request": self.request})
def get_queryset(self):
return EvaluationrequestModel.objects.filter(
user=self.request.user
).order_by("-created_at")
@extend_schema(tags=["EvaluationRequest"])
class AdminEvaluationrequestView(BaseViewSetMixin, ModelViewSet):
serializer_class = ListEvaluationrequestSerializer
permission_classes = [IsAuthenticated]
# pagination_class = RequestPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_class = EvaluationrequestFilter
search_fields = [
"customer_inn_number",
"owner_inn_number",
"tex_passport",
]
ordering_fields = [
"id",
"rate_type", "rate_type",
"rate_type_display",
"object_type", "object_type",
"object_type_display",
"customer_inn_number",
"owner_inn_number",
"tex_passport",
"value_determined",
"rate_goal",
"property_rights",
"form_ownership",
"worked_hours",
"chassi",
"need_delivering",
"location",
"location_name",
"status", "status",
"status_display",
"user",
"created_at",
"updated_at",
] ]
ordering = ["-created_at"] ordering = ["-created_at"]
@@ -51,6 +133,118 @@ class EvaluationrequestView(BaseViewSetMixin, ModelViewSet):
} }
def get_queryset(self): def get_queryset(self):
return EvaluationrequestModel.objects.filter( return EvaluationrequestModel.objects.select_related("value_determined", "rate_goal", "property_rights", "form_ownership", "user").order_by("-created_at")
user=self.request.user
def serializer_context(self):
return self.serializer_class(context={"request": self.request})
@extend_schema(tags=["EvaluationRequest"])
class EvaluationStatusChange(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, pk):
if request.user.role not in [RoleChoice.ADMIN, RoleChoice.SUPERUSER]:
return Response({'detail': 'Forbidden'}, status=status.HTTP_403_FORBIDDEN)
evaluation = get_object_or_404(EvaluationrequestModel, pk=pk)
status_value = request.data.get('status')
if not status_value:
return Response({'detail': 'Status is required'}, status=status.HTTP_400_BAD_REQUEST)
valid_statuses = [
RequestStatus.PENDING,
RequestStatus.IN_PROGRESS,
RequestStatus.COMPLETED,
RequestStatus.REJECTED
]
if status_value not in valid_statuses:
return Response(
{'detail': f'Invalid status. Must be one of: {valid_statuses}'},
status=status.HTTP_400_BAD_REQUEST
)
evaluation.status = status_value
evaluation.save()
return Response({
'success': True,
'status': evaluation.status,
'id': evaluation.pk
})
@extend_schema(tags=["EvaluationRequest"])
class ArchiveEvaluationrequestView(GenericAPIView):
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.request.method == "GET":
return ListEvaluationrequestSerializer
return ArchiveEvaluationrequestSerializer
@extend_schema(
tags=["EvaluationRequest"],
summary="Get archived evaluation requests list",
description="""
Returns only archived evaluation requests.
This endpoint works like evaluation-request/,
but only records with is_archive=True are returned.
""",
responses={200: ListEvaluationrequestSerializer(many=True)},
)
def get(self, request, *args, **kwargs):
queryset = EvaluationrequestModel.objects.filter(
is_archive=True
).order_by("-created_at") ).order_by("-created_at")
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@extend_schema(
tags=["EvaluationRequest"],
summary="Archive or unarchive evaluation request",
description="""
Update archive status for evaluation request.
- is_archive=true → archive
- is_archive=false → remove from archive
""",
request=ArchiveEvaluationrequestSerializer,
responses={
200: OpenApiResponse(
description="Archive status updated successfully"
),
400: OpenApiResponse(
description="Validation error"
),
404: OpenApiResponse(
description="Evaluation request not found"
),
},
)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
obj = get_object_or_404(
EvaluationrequestModel,
id=validated_data["id"]
)
obj.is_archive = validated_data["is_archive"]
obj.save(update_fields=["is_archive"])
return Response(
{
"success": True,
"message": "Archive status updated successfully"
},
status=status.HTTP_200_OK
)

View File

@@ -0,0 +1,59 @@
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework.generics import GenericAPIView
from drf_spectacular.utils import (
extend_schema,
OpenApiExample,
)
from core.services.tech_passport import TechPassportService
from ..serializers import TechPassportSerializer
class TechPassportAPIView(GenericAPIView):
authentication_classes = []
permission_classes = [AllowAny]
@extend_schema(
tags=["Tech Passport"],
summary="Get vehicle information by technical passport",
description=(
"This endpoint retrieves vehicle information using "
"autonumber, technical passport number, and technical passport series "
"via Gross Insurance API integration."
),
request=TechPassportSerializer,
responses={
200: dict,
400: dict,
},
)
def post(self, request, *args, **kwargs):
serializer = TechPassportSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
result = TechPassportService.get_auto_info(
autonumber=data["autonumber"],
tech_pass_number=data["tech_pass_number"],
tech_pass_series=data["tech_pass_series"],
)
response_data = result["data"]
status_code = result["status_code"]
# success bolsa faqat data ichidagi data qaytariladi
if status_code == 200:
return Response(
response_data.get("data", {}),
status=status.HTTP_200_OK
)
# error bolsa original response qaytariladi
return Response(
response_data,
status=status_code
)

View File

@@ -1 +1,2 @@
from .settings import * # noqa from .settings import * # noqa
from .region import * # noqa

View File

@@ -0,0 +1,21 @@
from django.contrib import admin
from core.apps.shared.models import RegionModel, DistrictModel, VillageModel
@admin.register(RegionModel)
class RegionAdmin(admin.ModelAdmin):
list_display = ('id', 'name',)
search_fields = ('name',)
@admin.register(DistrictModel)
class DistrictAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'region')
search_fields = ('name', 'region')
@admin.register(VillageModel)
class VillageAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'district')
search_fields = ('name', 'district')

View File

@@ -0,0 +1,140 @@
import json
import os
from django.core.management.base import BaseCommand
from django.db import transaction
from core.apps.shared.models import RegionModel, DistrictModel, VillageModel
class Command(BaseCommand):
help = "Import regions, districts, and neighborhoods from JSON files."
def add_arguments(self, parser):
parser.add_argument('--path', type=str, default='JSON/', help='Path to JSON files directory')
def transliterate_uz_to_en(self, text):
"""Simple transliteration for English names"""
if not text:
return ""
mapping = {
"o'": "o", "O'": "O",
"g'": "g", "G'": "G",
"sh": "sh", "Sh": "Sh",
"ch": "ch", "Ch": "Ch",
"ng": "ng", "Ng": "Ng",
"q": "q", "Q": "Q",
"yo": "yo", "Yo": "Yo",
"yu": "yu", "Yu": "Yu",
"ya": "ya", "Ya": "Ya",
"ye": "ye", "Ye": "Ye",
"o": "o", "O": "O",
"g": "g", "G": "G",
}
res = text
for k, v in mapping.items():
res = res.replace(k, v)
replacements = {
"viloyati": "Region",
"viloyat": "Region",
"tumani": "District",
"tuman": "District",
"shahri": "City",
"shahar": "City",
"Respublikasi": "Republic",
}
for k, v in replacements.items():
res = res.replace(k, v)
res = res.replace(k.capitalize(), v)
return res.strip()
def handle(self, *args, **options):
path = options['path']
regions_file = os.path.join(path, 'region.json')
districts_file = os.path.join(path, 'district.json')
villages_file = os.path.join(path, 'village.json')
try:
with transaction.atomic():
# 1. Import Regions
self.stdout.write("Importing Regions...")
if os.path.exists(regions_file):
with open(regions_file, 'r', encoding='utf-8-sig') as f:
regions_data = json.load(f)
for item in regions_data:
name_uz = item.get('name_uz', '')
# name_ru = item.get('name_ru', '') or name_uz
# name_en = self.transliterate_uz_to_en(name_uz)
# Use get_or_create to avoid duplicates if ID matches or name matches
# Actually, it's better to use our own internal IDs if we want to link them
# But since many JSON files use these IDs, we'll store them as a reference or use them directly if we reset the DB
region, created = RegionModel.objects.update_or_create(
id=item['id'],
defaults={
'name': name_uz,
# 'name_ru': name_ru,
# 'name_en': name_en,
}
)
# 2. Import Districts
self.stdout.write("Importing Districts...")
if os.path.exists(districts_file):
with open(districts_file, 'r', encoding='utf-8-sig') as f:
districts_data = json.load(f)
for item in districts_data:
name = item.get('name_uz', '')
region_id = item.get('region_id')
if not RegionModel.objects.filter(id=region_id).exists():
self.stdout.write(self.style.WARNING(f"Region {region_id} not found for district {name_uz}"))
continue
DistrictModel.objects.update_or_create(
id=item['id'],
defaults={
'region_id': region_id,
'name': name,
# 'name_ru': name_ru,
# 'name_en': name_en,
}
)
# 3. Import Neighborhoods (Villages)
self.stdout.write("Importing Neighborhoods (Villages)...")
if os.path.exists(villages_file):
with open(villages_file, 'r', encoding='utf-8-sig') as f:
villages_data = json.load(f)
count = 0
total = len(villages_data)
for item in villages_data:
name_uz = item.get('name_uz', '')
name_ru = item.get('name_ru', '') or name_uz
name_en = self.transliterate_uz_to_en(name_uz)
district_id = item.get('district_id')
if not DistrictModel.objects.filter(id=district_id).exists():
continue
VillageModel.objects.update_or_create(
id=item['id'],
defaults={
'district_id': district_id,
'name': name_uz,
}
)
count += 1
if count % 1000 == 0:
self.stdout.write(f"Processed {count}/{total} neighborhoods...")
self.stdout.write(self.style.SUCCESS("Import completed successfully!"))
except Exception as e:
self.stdout.write(self.style.ERROR(f"Error: {str(e)}"))

View File

@@ -0,0 +1,55 @@
# Generated by Django 5.2.7 on 2026-04-20 10:14
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shared', '0002_settingsmodel_created_at_settingsmodel_description_and_more'),
]
operations = [
migrations.CreateModel(
name='RegionModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, unique=True)),
],
options={
'verbose_name': 'Region',
'verbose_name_plural': 'Regions',
},
),
migrations.CreateModel(
name='DistrictModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, unique=True)),
('region', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='districts', to='shared.regionmodel')),
],
options={
'verbose_name': 'District',
'verbose_name_plural': 'Districts',
},
),
migrations.CreateModel(
name='VillageModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255, unique=True)),
('district', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='villages', to='shared.districtmodel')),
],
options={
'verbose_name': 'Village',
'verbose_name_plural': 'Villages',
},
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-04-21 09:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shared', '0003_regionmodel_districtmodel_villagemodel'),
]
operations = [
migrations.AlterField(
model_name='districtmodel',
name='name',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='regionmodel',
name='name',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='villagemodel',
name='name',
field=models.CharField(max_length=255),
),
]

View File

@@ -1 +1,2 @@
from .settings import * # noqa from .settings import * # noqa
from .region import * # noqa

View File

@@ -0,0 +1,28 @@
from django.db import models
from django_core.models import AbstractBaseModel
class RegionModel(AbstractBaseModel):
name = models.CharField(max_length=255)
class Meta:
verbose_name = "Region"
verbose_name_plural = "Regions"
class DistrictModel(AbstractBaseModel):
name = models.CharField(max_length=255)
region = models.ForeignKey(RegionModel, on_delete=models.CASCADE, related_name='districts')
class Meta:
verbose_name = "District"
verbose_name_plural = "Districts"
class VillageModel(AbstractBaseModel):
name = models.CharField(max_length=255)
district = models.ForeignKey(DistrictModel, on_delete=models.CASCADE, related_name='villages')
class Meta:
verbose_name = "Village"
verbose_name_plural = "Villages"

View File

@@ -0,0 +1,21 @@
from rest_framework import serializers
from core.apps.shared.models import RegionModel, VillageModel, DistrictModel
class DistrictSerializer(serializers.ModelSerializer):
class Meta:
model = DistrictModel
fields = ('id', 'name')
class VillageSerializer(serializers.ModelSerializer):
class Meta:
model = VillageModel
fields = ('id', 'name')
class RegionSerializer(serializers.ModelSerializer):
class Meta:
model = RegionModel
fields = ('id', 'name')

View File

@@ -1,6 +1,7 @@
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .views import SettingsView from .views import SettingsView
from .views.region.region import RegionListCreateView, DistrictListCreateView, VillageListCreateView
router = DefaultRouter() router = DefaultRouter()
router.register("settings", SettingsView, basename="settings") router.register("settings", SettingsView, basename="settings")
@@ -8,4 +9,7 @@ router.register("settings", SettingsView, basename="settings")
urlpatterns = [ urlpatterns = [
path("", include(router.urls)), path("", include(router.urls)),
path("region/", RegionListCreateView.as_view(), name="region-list-create"),
path("district/", DistrictListCreateView.as_view(), name="district-list-create"),
path("village/", VillageListCreateView.as_view(), name="village-list-create"),
] ]

View File

@@ -0,0 +1,74 @@
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
from core.apps.shared.serializers.region.district import DistrictSerializer, VillageSerializer, RegionSerializer
from core.apps.shared.models import RegionModel, VillageModel, DistrictModel
@extend_schema(
parameters=[
OpenApiParameter(
name="region",
type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY,
description="Filter by region ID"
),
OpenApiParameter(
name="name",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
description="Filter by district name"
),
]
)
class DistrictListCreateView(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = DistrictSerializer
queryset = DistrictModel.objects.all()
def get_queryset(self):
region = self.request.query_params.get('region')
name = self.request.query_params.get('name')
if name:
return self.queryset.filter(name__icontains=name)
if region:
return self.queryset.filter(region=region)
return super().get_queryset()
@extend_schema(
parameters=[
OpenApiParameter(
name="district",
type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY,
description="Filter by district ID"
),
OpenApiParameter(
name="name",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
description="Filter by village name"
),
]
)
class VillageListCreateView(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = VillageSerializer
queryset = VillageModel.objects.all()
def get_queryset(self):
district = self.request.query_params.get('district')
name = self.request.query_params.get('name')
if district:
return self.queryset.filter(district=district)
if name:
return self.queryset.filter(name__icontains=name)
return super().get_queryset()
class RegionListCreateView(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = RegionSerializer
queryset = RegionModel.objects.all()

View File

@@ -1,3 +1,5 @@
from .otp import * # noqa from .otp import * # noqa
from .sms import * # noqa from .sms import * # noqa
from .user import * # noqa from .user import * # noqa
from .didox import * # noqa
from .tech_passport import * # noqa

27
core/services/didox.py Normal file
View File

@@ -0,0 +1,27 @@
import requests
from django.conf import settings
import logging
logger = logging.getLogger(__name__)
class DidoxService:
BASE_URL = "https://testapi3.didox.uz/v1"
@classmethod
def get_company_info(cls, tin: int) -> dict:
url = f"{cls.BASE_URL}/utils/info/{tin}"
headers = {"Partner-Authorization": settings.DIDOX_PARTNER_TOKEN}
try:
response = requests.get(url=url, headers=headers, timeout=15,verify=False)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
logger.exception(f"Didox API error: {str(e)}")
return {}

View File

@@ -0,0 +1,71 @@
import requests
import logging
logger = logging.getLogger(__name__)
class TechPassportService:
BASE_URL = "https://api-test.gross.uz/api/v1/osago/check-tech-data"
@classmethod
def get_auto_info(
cls,
autonumber: str,
tech_pass_number: str,
tech_pass_series: str
):
payload = {
"tech_data": {
"autonumber": autonumber,
"tech_pass_number": tech_pass_number,
"tech_pass_series": tech_pass_series,
},
"payload": {
"promo": "",
"autotype": 1,
"citizen": 1,
"number": 1,
"period": 1,
"region": 1,
"coeff": 1
}
}
headers = {
"Content-Type": "application/json"
}
try:
response = requests.post(
cls.BASE_URL,
json=payload,
headers=headers,
timeout=30,
verify=False
)
logger.info(
f"Tech passport response status: {response.status_code}"
)
try:
response_data = response.json()
except ValueError:
response_data = {
"detail": "Invalid response from external service"
}
return {
"status_code": response.status_code,
"data": response_data
}
except requests.exceptions.RequestException as e:
logger.error(str(e))
return {
"status_code": 500,
"data": {
"detail": str(e)
}
}

View File

@@ -56,3 +56,20 @@ services:
image: redis image: redis
celery:
build:
context: .
dockerfile: ./docker/Dockerfile.web
command: celery -A config worker --loglevel=info
volumes:
- "./:/code"
depends_on:
- redis
- web
networks:
- sifatbaho
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
restart: always

1682
resources/json/district.json Normal file

File diff suppressed because it is too large Load Diff

100
resources/json/region.json Normal file
View File

@@ -0,0 +1,100 @@
[
{
"id": 2,
"soato_id": 1703,
"name_uz": "Andijon viloyati",
"name_oz": "Андижон вилояти",
"name_ru": "Андижанская область"
},
{
"id": 3,
"soato_id": 1706,
"name_uz": "Buxoro viloyati",
"name_oz": "Бухоро вилояти",
"name_ru": "Бухарская область"
},
{
"id": 4,
"soato_id": 1708,
"name_uz": "Jizzax viloyati",
"name_oz": "Жиззах вилояти",
"name_ru": "Джизакская область"
},
{
"id": 5,
"soato_id": 1710,
"name_uz": "Qashqadaryo viloyati",
"name_oz": "Қашқадарё вилояти",
"name_ru": "Кашкадарьинская область"
},
{
"id": 6,
"soato_id": 1712,
"name_uz": "Navoiy viloyati",
"name_oz": "Навоий вилояти",
"name_ru": "Навоийская область"
},
{
"id": 7,
"soato_id": 1714,
"name_uz": "Namangan viloyati",
"name_oz": "Наманган вилояти",
"name_ru": "Наманганская область"
},
{
"id": 8,
"soato_id": 1718,
"name_uz": "Samarqand viloyati",
"name_oz": "Самарқанд вилояти",
"name_ru": "Самаркандская область"
},
{
"id": 10,
"soato_id": 1724,
"name_uz": "Sirdaryo viloyati",
"name_oz": "Сирдарё вилояти",
"name_ru": "Сырдарьинская область"
},
{
"id": 11,
"soato_id": 1726,
"name_uz": "Toshkent shahri",
"name_oz": "Тошкент шаҳри",
"name_ru": "город Ташкент"
},
{
"id": 12,
"soato_id": 1727,
"name_uz": "Toshkent viloyati",
"name_oz": "Тошкент вилояти",
"name_ru": "Ташкентская область"
},
{
"id": 13,
"soato_id": 1730,
"name_uz": "Farg'ona viloyati",
"name_oz": "Фарғона вилояти",
"name_ru": "Ферганская область"
},
{
"id": 14,
"soato_id": 1733,
"name_uz": "Xorazm viloyati",
"name_oz": "Хоразм вилояти",
"name_ru": "Хорезмская область"
},
{
"id": 15,
"soato_id": 1735,
"name_uz": "Qoraqalpog'iston Respublikasi",
"name_oz": "Қорақалпоғистон Республикаси",
"name_ru": "Республика Каракалпакстан"
},
{
"id": 5723,
"soato_id": 1722,
"name_uz": "Surxondaryo viloyati",
"name_oz": "Сурхондарё вилояти",
"name_ru": "Сурхандарьинская область"
}
]

21130
resources/json/village.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,7 @@ services:
max_failure_ratio: 0.2 max_failure_ratio: 0.2
resources: resources:
limits: limits:
cpus: '1.00' cpus: "1.00"
memory: 512M memory: 512M
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d sifatbaho"] test: ["CMD-SHELL", "pg_isready -U postgres -d sifatbaho"]
@@ -69,7 +69,7 @@ services:
max_failure_ratio: 0.2 max_failure_ratio: 0.2
resources: resources:
limits: limits:
cpus: '0.50' cpus: "0.50"
memory: 256M memory: 256M
healthcheck: healthcheck:
test: ["CMD", "redis-cli", "ping"] test: ["CMD", "redis-cli", "ping"]
@@ -84,7 +84,7 @@ services:
max-file: "5" max-file: "5"
web: web:
image: husanjon/sifatbaho:59 image: husanjon/sifatbaho:107
env_file: env_file:
- .env - .env
environment: environment:
@@ -114,7 +114,7 @@ services:
max_failure_ratio: 0.2 max_failure_ratio: 0.2
resources: resources:
limits: limits:
cpus: '1.50' cpus: "1.50"
memory: 1024M memory: 1024M
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8000/health/ || exit 1"] test: ["CMD-SHELL", "curl -f http://localhost:8000/health/ || exit 1"]
@@ -128,13 +128,47 @@ services:
max-size: "50m" max-size: "50m"
max-file: "5" max-file: "5"
celery:
image: husanjon/sifatbaho:107
env_file:
- .env
environment:
- DJANGO_SETTINGS_MODULE=config.settings.production
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
command: >
sh -c "
echo 'Waiting for redis...' &&
sleep 5 &&
celery -A config worker --loglevel=info
"
networks:
- sifatbaho
volumes:
- logs:/code/resources/logs/:rw
- pycache:/var/cache/pycache:rw
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: any
update_config:
parallelism: 1
order: start-first
failure_action: rollback
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
nginx: nginx:
image: nginx:alpine image: nginx:alpine
ports: ports:
- mode: ingress - mode: ingress
target: 80 target: 80
published: ${PORT:?enviromentda PORT topilmadi} published: ${PORT:?enviromentda PORT topilmadi}
protocol: tcp protocol: tcp
volumes: volumes:
- type: bind - type: bind
@@ -161,7 +195,7 @@ services:
max_failure_ratio: 0.2 max_failure_ratio: 0.2
resources: resources:
limits: limits:
cpus: '0.50' cpus: "0.50"
memory: 256M memory: 256M
tmpfs: tmpfs:
- /var/cache/nginx - /var/cache/nginx