Compare commits

339 Commits
main ... test

Author SHA1 Message Date
behruz-dev
4aeb39fb5b fix 2025-12-05 14:43:35 +05:00
behruz-dev
aacab8840f fix 2025-12-05 14:40:11 +05:00
behruz-dev
a32ef4e872 kontranget qoshish toriglandi 2025-12-05 14:38:58 +05:00
behruz-dev
ce4603be07 kontranget qoshish toriglandi 2025-12-05 14:36:05 +05:00
behruz-dev
f9141f3be0 jwt tokenni life time 1 yil qilib qoyildi 2025-12-01 17:04:23 +05:00
behruz-dev
59c1c455b2 fix 2025-11-21 16:27:04 +05:00
behruz-dev
5fcde1ec97 fix 2025-11-18 17:01:39 +05:00
behruz-dev
d665abe5ec fix 2025-11-18 16:55:39 +05:00
behruz-dev
d6ba8e006f fix 2025-11-18 16:54:20 +05:00
behruz-dev
c8d7f2d0b8 fix 2025-11-18 16:53:23 +05:00
behruz-dev
4360177c97 fix 2025-11-18 16:52:03 +05:00
behruz-dev
6e0cd06e32 fix 2025-11-18 16:17:18 +05:00
behruz-dev
39610223a3 fix 2025-11-18 16:05:32 +05:00
behruz-dev
f4d1b0afbe fix 2025-11-18 16:02:55 +05:00
behruz-dev
3d39a415eb fix 2025-11-18 15:21:25 +05:00
behruz-dev
89de3de54f fox 2025-11-15 19:17:58 +05:00
behruz-dev
e0169ab7a4 fox 2025-11-15 18:53:41 +05:00
behruz-dev
f87f9f0603 fox 2025-11-15 18:52:51 +05:00
behruz-dev
3cf60c54f9 fox 2025-11-15 18:41:36 +05:00
behruz-dev
56f04c558f fox 2025-11-15 17:15:00 +05:00
behruz-dev
9b3780c0a3 fox 2025-11-15 17:12:54 +05:00
behruz-dev
4ebd271af6 fox 2025-11-15 17:12:12 +05:00
behruz-dev
576d5c2257 fucking changes 2025-11-15 16:06:28 +05:00
behruz-dev
c057f58e56 change token 2025-11-15 15:34:53 +05:00
behruz-dev
16cbc8edaa fix 2025-11-15 15:20:45 +05:00
behruz-dev
d21a75e8f7 fix 2025-11-14 19:42:31 +05:00
behruz-dev
05e371e22a fix 2025-11-14 19:36:41 +05:00
behruz-dev
f19f195980 fix 2025-11-14 18:59:33 +05:00
behruz-dev
6752a3e58d fix 2025-11-14 18:59:02 +05:00
behruz-dev
ff350bb922 fix 2025-11-14 18:58:11 +05:00
behruz-dev
adf6f69baf fix 2025-11-14 18:15:40 +05:00
behruz-dev
039761a68e fix 2025-11-14 18:01:48 +05:00
behruz-dev
3179dcc16c fix 2025-11-14 17:13:48 +05:00
behruz-dev
6563e4075a fix 2025-11-14 17:13:08 +05:00
behruz-dev
b89c380a41 fix 2025-11-14 17:12:13 +05:00
behruz-dev
180c59b338 change alot of things 2025-11-14 17:06:57 +05:00
behruz-dev
204c39445e change alot of things 2025-11-14 16:28:49 +05:00
behruz-dev
cbcfa7893a fi 2025-11-14 16:09:09 +05:00
behruz-dev
e39efc5300 fix 2025-11-14 15:41:52 +05:00
behruz-dev
bac0040672 fi 2025-11-14 15:33:43 +05:00
behruz-dev
5337656005 fix 2025-11-14 15:28:44 +05:00
behruz-dev
58ba6c3f58 fix 2025-11-14 15:28:00 +05:00
behruz-dev
e84937270e fix bug 2025-11-14 15:22:54 +05:00
behruz-dev
0431800f61 change counterparty akt statistics api 2025-11-14 15:05:55 +05:00
behruz-dev
d74c4d7af8 fix 2025-11-13 16:45:58 +05:00
behruz-dev
a0d2b6d0c5 fix 2025-11-13 16:45:06 +05:00
behruz-dev
972df3f0fc fix 2025-11-13 16:39:13 +05:00
behruz-dev
da1d851093 fix 2025-11-13 16:37:36 +05:00
behruz-dev
d7b9264546 counterparty: counterparty akt api added 2025-11-13 16:28:00 +05:00
behruz-dev
acd4bfd30d fix 2025-11-12 19:03:16 +05:00
behruz-dev
7d7e7a387a fix 2025-11-12 18:59:31 +05:00
behruz-dev
c06893c41e fix 2025-11-12 18:50:27 +05:00
behruz-dev
d63ac8d129 fix 2025-11-12 18:45:20 +05:00
behruz-dev
eeb374e963 fix 2025-11-12 18:14:50 +05:00
behruz-dev
042366258f fix 2025-11-12 18:00:40 +05:00
behruz-dev
19f2ba6976 fix 2025-11-12 17:44:09 +05:00
behruz-dev
3eba00f525 fix 2025-11-12 16:15:32 +05:00
behruz-dev
b5c43f8d48 fix 2025-11-12 15:49:02 +05:00
behruz-dev
3680b4f573 fix 2025-11-12 15:41:04 +05:00
behruz-dev
2585b055d1 fi 2025-11-12 15:39:21 +05:00
behruz-dev
4f1cfe623e fix 2025-11-12 15:23:06 +05:00
behruz-dev
8f47f65cb2 fix 2025-11-12 15:21:02 +05:00
behruz-dev
51ec0a6a22 fix 2025-11-12 15:09:34 +05:00
behruz-dev
3b9ffb15bc fix 2025-11-12 15:03:36 +05:00
behruz-dev
5f6b606d19 fi 2025-11-12 14:51:56 +05:00
behruz-dev
4c72a2de31 fi 2025-11-12 14:47:51 +05:00
behruz-dev
41a01b4251 fix 2025-11-12 14:45:33 +05:00
behruz-dev
d376aaa6c1 fix 2025-11-12 14:39:19 +05:00
behruz-dev
035a294a64 fi 2025-11-12 14:33:42 +05:00
behruz-dev
5af320c328 fix 2025-11-12 14:27:43 +05:00
behruz-dev
e26f443791 fix 2025-11-12 14:23:38 +05:00
behruz-dev
2a67e69d06 fix 2025-11-12 14:04:00 +05:00
behruz-dev
2cce3e9256 fix 2025-11-12 13:59:57 +05:00
behruz-dev
cf2bb89e4c fix 2025-11-12 13:56:25 +05:00
behruz-dev
32ec86b3b4 fix 2025-11-12 13:54:21 +05:00
behruz-dev
1878e8696e fix 2025-11-12 13:40:33 +05:00
behruz-dev
451a8f3bd5 fix 2025-11-12 13:34:45 +05:00
behruz-dev
ab71bf879e add search to party list api 2025-11-12 13:27:53 +05:00
behruz-dev
2df1230a3e add search to party list api 2025-11-12 13:25:37 +05:00
behruz-dev
94ecaa27eb fix 2025-11-11 18:48:04 +05:00
behruz-dev
5dbd4a0358 fix 2025-11-11 18:45:52 +05:00
behruz-dev
a899cfd7a8 fix 2025-11-11 18:38:48 +05:00
behruz-dev
754a976cf8 fix 2025-11-11 18:36:27 +05:00
behruz-dev
587b262d24 fic 2025-11-11 18:32:21 +05:00
behruz-dev
b7ae230209 fic 2025-11-11 18:27:52 +05:00
behruz-dev
4445a65881 fic 2025-11-11 18:24:18 +05:00
behruz-dev
109ab308f9 fix 2025-11-11 18:23:08 +05:00
behruz-dev
c403e3921a fi 2025-11-11 18:08:37 +05:00
behruz-dev
8cd8dda764 fix 2025-11-11 17:26:13 +05:00
behruz-dev
4aea353b75 orders: fixed old bugs 2025-11-11 16:56:35 +05:00
behruz-dev
fe1542b540 fix 2025-11-11 16:48:07 +05:00
behruz-dev
db8218de21 finance, orders and counterparty: fix all bugs 2025-11-11 16:26:07 +05:00
behruz-dev
25898a7864 finance: expence delete api fixed 2025-11-11 16:17:47 +05:00
behruz-dev
333ffac4e6 orders: add user field to deleted party model, deleted party list serializer, deleted party api 2025-11-11 15:38:02 +05:00
behruz-dev
53c0b760e1 orders: add search filter to deleted party list api 2025-11-11 15:33:44 +05:00
behruz-dev
e35ff6bda7 finance: add user field to expence delete model 2025-11-11 15:24:39 +05:00
behruz-dev
0ffae42547 orders: fixing party percentage while it creating 2025-11-11 15:12:27 +05:00
behruz-dev
1b61d2327a orders: forgotten value is putting 2025-11-11 15:09:48 +05:00
behruz-dev
64354fee80 fuck 2025-11-10 17:27:28 +05:00
behruz-dev
369f4c9c0b fucking changes 2025-11-10 17:24:42 +05:00
behruz-dev
9e2229fd86 fucking counterparty statistics not done yet! fuck 2025-11-10 17:22:17 +05:00
behruz-dev
e4e3b83a8c fix 2025-11-10 16:29:22 +05:00
behruz-dev
fb5b13fb27 orders: fix is made apig 2025-11-10 16:21:13 +05:00
behruz-dev
5606c96ab2 finance: add new api 2025-11-10 16:10:42 +05:00
behruz-dev
8f442780ed orders: deleted party list serializer changed 2025-11-10 16:01:07 +05:00
behruz-dev
9573e72919 wherehouse: add new field to inventory list serilaizer 2025-11-10 15:55:21 +05:00
behruz-dev
e96475c81d wherehouse: change inventory list serializer 2025-11-10 15:54:30 +05:00
behruz-dev
94d14d548e orders: change party payment history response 2025-11-10 15:45:41 +05:00
behruz-dev
3f8f3275bd orders: add new field to party detail serializer 2025-11-10 15:38:14 +05:00
behruz-dev
1cd005c804 orders: add new fields to order model 2025-11-10 15:28:31 +05:00
behruz-dev
8a298b76f1 change a lot of code 2025-11-07 21:21:03 +05:00
behruz-dev
a9df29200a fix 2025-11-07 17:28:35 +05:00
behruz-dev
50eb701490 fix 2025-11-07 17:25:19 +05:00
behruz-dev
8dca5505d8 change price type int -> decimal field 2025-11-07 12:17:57 +05:00
behruz-dev
c113f003df fi 2025-11-06 23:32:22 +05:00
behruz-dev
f65d07f42b fix part 2025-11-06 16:33:03 +05:00
behruz-dev
b9056b1127 fix part 2025-11-06 16:30:27 +05:00
behruz-dev
1760bdc42f fix part 2025-11-06 16:26:44 +05:00
behruz-dev
e47902a42f fix part 2025-11-06 16:24:44 +05:00
behruz-dev
668196a696 fix part 2025-11-06 16:21:50 +05:00
behruz-dev
e2358b26e5 fix part 2025-11-06 16:15:09 +05:00
behruz-dev
3f5d339d38 fix 2025-11-06 16:10:57 +05:00
behruz-dev
5d49e8aa16 fix 2025-11-06 16:09:12 +05:00
behruz-dev
fb7806d612 fix 2025-11-06 10:27:13 +05:00
behruz-dev
75a71235a7 fix 2025-11-06 10:21:44 +05:00
behruz-dev
2e3b76d33f fix 2025-11-06 10:07:08 +05:00
behruz-dev
2905bafd46 fix 2025-11-05 18:57:51 +05:00
behruz-dev
71a5a424bf fix 2025-11-05 18:56:26 +05:00
behruz-dev
9a62257ba4 fix 2025-11-05 18:52:28 +05:00
behruz-dev
6a18bce7f3 fix 2025-11-05 18:51:27 +05:00
behruz-dev
bc532c8c65 fix 2025-11-05 18:46:34 +05:00
behruz-dev
338a1e5c02 fix 2025-11-05 18:35:03 +05:00
behruz-dev
5ff4bcfaba fix 2025-11-05 18:34:15 +05:00
behruz-dev
8ae2cce79e fix 2025-11-05 18:33:02 +05:00
behruz-dev
512bdff708 change 2025-11-05 18:17:02 +05:00
behruz-dev
e9b231939c fix 2025-11-05 18:15:58 +05:00
behruz-dev
c76aef126e fix 2025-11-05 18:15:17 +05:00
behruz-dev
8178993dc0 fix 2025-11-05 18:08:13 +05:00
behruz-dev
ea990187b3 fix 2025-11-05 18:02:35 +05:00
behruz-dev
d49d5b5e47 add new command 2025-11-05 17:54:12 +05:00
behruz-dev
8db5b3e4d4 add new command 2025-11-05 17:47:29 +05:00
behruz-dev
541ec24c3f add new command 2025-11-05 17:47:04 +05:00
behruz-dev
62038128db add new command 2025-11-05 17:45:45 +05:00
behruz-dev
f826361ba0 add new field 2025-11-05 16:58:05 +05:00
behruz-dev
6abc010eb3 add new field 2025-11-05 16:52:33 +05:00
behruz-dev
b7eaae1fe9 add new field 2025-11-05 16:51:38 +05:00
behruz-dev
eca74810ae fix party addd 2025-11-05 16:50:29 +05:00
behruz-dev
f0f72052d5 fix 2025-11-05 16:27:36 +05:00
behruz-dev
876b30b107 fix 2025-11-05 16:25:39 +05:00
behruz-dev
3d9bf5bea6 fix 2025-11-05 16:17:41 +05:00
behruz-dev
4c441d4418 fix 2025-11-05 15:22:12 +05:00
behruz-dev
506129b592 fix 2025-11-05 15:21:26 +05:00
behruz-dev
dd513a44e2 fix 2025-11-05 14:51:33 +05:00
behruz-dev
9510bd2c72 fix 2025-11-05 14:49:29 +05:00
behruz-dev
65d99c4e2d fix 2025-11-05 14:47:18 +05:00
behruz-dev
7a430052c2 fix 2025-11-05 14:38:45 +05:00
behruz-dev
ee20fa6ce3 fix 2025-11-05 14:36:49 +05:00
behruz-dev
c451702706 fix 2025-11-05 14:22:54 +05:00
behruz-dev
49cad981e6 fi 2025-11-05 14:07:19 +05:00
behruz-dev
9f79b30b06 fix 2025-11-05 13:56:31 +05:00
behruz-dev
93f603afe5 orders: party list filter fixed 2025-11-04 15:11:13 +05:00
behruz-dev
502ba13775 products: fix product update api 2025-11-04 14:24:43 +05:00
behruz-dev
52daf9513b fix import party command 2025-11-03 18:25:43 +05:00
behruz-dev
ffa08162d6 fox 2025-11-03 15:25:58 +05:00
behruz-dev
defc845148 fix 2025-11-03 15:22:24 +05:00
behruz-dev
c8f88a0b2d fix function 2025-11-03 13:49:19 +05:00
behruz-dev
85853c2e7d counterparty: update counterparty balance fixed 2025-11-03 13:42:27 +05:00
behruz-dev
2769b3cc37 finance: change import income and expence commands 2025-11-03 13:32:48 +05:00
behruz-dev
6bd866c53c fix 2025-11-01 17:02:37 +05:00
behruz-dev
4afc34ace8 fix 2025-11-01 17:01:01 +05:00
behruz-dev
912cf864de fix 2025-11-01 16:16:46 +05:00
behruz-dev
3f8bf3c391 fix 2025-11-01 16:12:26 +05:00
behruz-dev
a2f23fb601 wherehouse: fix stock movemend model field number which is auto agrement feature 2025-11-01 15:20:12 +05:00
behruz-dev
b3845d8d74 wherehouse: fix stock movemend create serializer 2025-11-01 15:15:11 +05:00
behruz-dev
07a5489771 finance: add income and expence contract import commands 2025-11-01 09:41:59 +05:00
behruz-dev
3f2b86be57 finance: import income and expence commands added 2025-11-01 08:45:57 +05:00
behruz-dev
713cb61d4e ficnance: fucking is not done 2025-10-31 23:05:02 +05:00
behruz-dev
b5e87151cf fix 2025-10-31 23:01:22 +05:00
behruz-dev
ed9e02d25c fix 2025-10-31 22:52:07 +05:00
behruz-dev
9d83ccd7ed finance: fucking income expence 2025-10-31 22:49:14 +05:00
behruz-dev
700a92710f finance: back old version 2025-10-31 22:41:28 +05:00
behruz-dev
f5cebc4f37 finance: fix income and expence logic 2025-10-31 22:36:04 +05:00
behruz-dev
73112a90de finance: expence and income type import command added 2025-10-31 22:14:41 +05:00
behruz-dev
0c2d73f892 wherehouse: add sender field to stock movemend model 2025-10-31 21:49:47 +05:00
behruz-dev
f49a446c71 fix 2025-10-31 19:20:07 +05:00
behruz-dev
cac1baeac1 wherehouse: fix bug 2025-10-31 18:42:09 +05:00
behruz-dev
c60b3ef985 wherehouse: add import invalid product command 2025-10-31 18:39:44 +05:00
behruz-dev
f2f66754b6 orders: add new filter 2025-10-31 17:55:07 +05:00
behruz-dev
6d8f4f44c6 fix 2025-10-31 17:51:42 +05:00
behruz-dev
1f254464aa fix 2025-10-31 17:44:31 +05:00
behruz-dev
30b0f5f50a fix 2025-10-31 17:40:21 +05:00
behruz-dev
7c51b47a53 wherehouse: change 2025-10-31 17:29:40 +05:00
behruz-dev
021bc7ed83 orders: change order list api 2025-10-31 16:34:45 +05:00
behruz-dev
5d231b6047 fix 2025-10-31 16:23:32 +05:00
behruz-dev
5c845917df orders: add order type 2025-10-31 16:15:36 +05:00
behruz-dev
01fac3110e counterparty: add counterpart 2025-10-31 16:02:08 +05:00
behruz-dev
31f894d0ff products: add product import 2025-10-31 15:35:07 +05:00
behruz-dev
29e8dea345 projects: fix error bug 2025-10-30 23:52:24 +05:00
behruz-dev
3722c37d76 counterparty: added counterparty 2025-10-30 23:47:05 +05:00
behruz-dev
c7595f6904 wherehouse: change command 2025-10-30 23:43:04 +05:00
behruz-dev
70d39f531f Merge branch 'main' of https://github.com/XoliqberdiyevBehruz/UyQur into test 2025-10-30 23:38:23 +05:00
behruz-dev
e13e8d0153 finance: import_cash-transaction command added 2025-10-30 23:37:50 +05:00
xolikberdiyev
81f4f4aa1f Merge pull request #29 from xoliqberdiyev/test
product: product folders import is done
2025-10-30 23:34:48 +05:00
behruz-dev
b8decb3a60 product: product folders import is done 2025-10-30 23:32:17 +05:00
xolikberdiyev
9870f2d208 Merge pull request #28 from xoliqberdiyev/test
fix
2025-10-30 19:49:40 +05:00
behruz-dev
96a3be9f3c fix 2025-10-30 19:49:13 +05:00
xolikberdiyev
d0901e6589 Merge pull request #27 from xoliqberdiyev/test
fix
2025-10-30 19:27:43 +05:00
behruz-dev
d9dc4ea25b fix 2025-10-30 19:26:27 +05:00
xolikberdiyev
3ece1c00c1 Merge pull request #26 from xoliqberdiyev/test
Test
2025-10-30 18:48:31 +05:00
behruz-dev
61a4d1c4ba fix 2025-10-30 17:44:03 +05:00
behruz-dev
bbdfd9abcf fix 2025-10-30 17:40:04 +05:00
behruz-dev
21b5879a78 fix 2025-10-30 17:38:02 +05:00
behruz-dev
22a54480a4 notification: change notification data 2025-10-30 16:41:30 +05:00
behruz-dev
21b868c06f git 2025-10-30 16:34:12 +05:00
behruz-dev
ec6d3dd172 wherehouse: fix bug 2025-10-30 16:21:26 +05:00
behruz-dev
af8d212b3e notification: add notification history 2025-10-30 16:17:25 +05:00
xolikberdiyev
54ffafc188 Merge pull request #25 from xoliqberdiyev/test
Test
2025-10-30 15:32:48 +05:00
behruz-dev
22b1d37a1a fix 2025-10-30 15:27:56 +05:00
behruz-dev
686c4eee8b fix 2025-10-30 15:24:33 +05:00
behruz-dev
1932bc316a notification: change send notification 2025-10-30 15:18:07 +05:00
behruz-dev
2afc2cc533 config: remove fsm token 2025-10-30 15:03:06 +05:00
behruz-dev
9b4f343f77 notification: add firebase notification to web 2025-10-30 14:59:59 +05:00
behruz-dev
b6b2875e0d shared: fix bug 2025-10-30 14:36:59 +05:00
behruz-dev
ffd6394430 add new field 2025-10-29 18:36:25 +05:00
behruz-dev
be9e415dcd add new field 2025-10-29 17:20:39 +05:00
behruz-dev
8184b13d9f fix 2025-10-29 16:28:16 +05:00
behruz-dev
82d85f8152 change party command 2025-10-29 16:24:58 +05:00
behruz-dev
9efc8aef40 fix 2025-10-28 17:03:04 +05:00
behruz-dev
fe543fad5a add notification for expence create api 2025-10-28 16:53:01 +05:00
behruz-dev
a542613841 fix 2025-10-28 16:44:29 +05:00
behruz-dev
0899595781 fix 2025-10-28 16:30:08 +05:00
behruz-dev
f11aca5c10 fi 2025-10-28 16:24:48 +05:00
behruz-dev
3b465c5aa4 fix 2025-10-28 16:20:04 +05:00
behruz-dev
d7f2a9a1c8 fi 2025-10-28 16:19:34 +05:00
xolikberdiyev
44c3fd5869 Merge pull request #24 from xoliqberdiyev/test
Test
2025-10-28 16:04:24 +05:00
behruz-dev
544b04a21e add notification and send notification 2025-10-28 16:03:54 +05:00
behruz-dev
fd6b9ab0c2 fix 500 error 2025-10-27 17:23:19 +05:00
xolikberdiyev
e5c7df9d75 Merge pull request #23 from xoliqberdiyev/test
Test
2025-10-25 18:26:50 +05:00
behruz-dev
eee3cd5064 fix 2025-10-25 16:41:09 +05:00
behruz-dev
7ba54bfeb2 add new management commands for finance and counterparty 2025-10-25 15:01:46 +05:00
behruz-dev
33d8f50ca2 fi 2025-10-24 16:40:31 +05:00
behruz-dev
0c460b507e added wherehouse json 2025-10-24 16:24:11 +05:00
behruz-dev
4cda8468fc add management command for products and orders app 2025-10-24 15:17:59 +05:00
behruz-dev
e82aa0950f add search filter for product list api 2025-10-16 14:34:14 +05:00
xolikberdiyev
f2c583fe01 Merge pull request #22 from xoliqberdiyev/test
Test
2025-10-15 14:56:43 +05:00
behruz-dev
667d62ebaa fix 2025-10-15 14:54:38 +05:00
behruz-dev
7ba7f00641 fix 2025-10-15 14:52:33 +05:00
xolikberdiyev
2c8d3b871e Merge pull request #21 from xoliqberdiyev/test
Test
2025-10-15 14:30:16 +05:00
behruz-dev
22b5873670 fix 2025-10-15 14:27:47 +05:00
behruz-dev
d8e5533481 fix 2025-10-15 14:25:10 +05:00
behruz-dev
ffefa6ff67 fix 2025-10-15 14:23:28 +05:00
behruz-dev
5dfda150f2 fix project folder 2025-10-15 14:16:47 +05:00
behruz-dev
95d8c5b5d0 add new cors 2025-10-11 16:34:17 +05:00
xolikberdiyev
f79e4dc950 Merge pull request #20 from xoliqberdiyev/test
Test
2025-10-10 16:41:54 +05:00
behruz-dev
1b64dec1f7 change order list filter 2025-10-10 16:26:49 +05:00
behruz-dev
c97e3479fa fix 2025-10-10 16:20:49 +05:00
behruz-dev
808f716019 fix 2025-10-10 16:18:50 +05:00
behruz-dev
0875c81da6 fix payment api 2025-10-10 15:22:01 +05:00
xolikberdiyev
876ea5ffe6 Merge pull request #19 from xoliqberdiyev/test
add user to party pay api
2025-10-09 18:19:41 +05:00
behruz-dev
06f47a63dc add user to party pay api 2025-10-09 15:01:10 +05:00
xolikberdiyev
772c7deb66 Merge pull request #18 from xoliqberdiyev/test
change type
2025-10-08 14:30:46 +05:00
behruz-dev
1dc728d1e8 change type 2025-10-08 14:30:23 +05:00
xolikberdiyev
3543d3def0 Merge pull request #17 from xoliqberdiyev/test
Test
2025-10-08 14:30:03 +05:00
behruz-dev
f74999177a change type 2025-10-08 14:28:28 +05:00
behruz-dev
b78d7d38c7 party payment history api added 2025-10-08 14:03:42 +05:00
xoliqberdiyev
cf7dca6f8e Merge pull request #16 from xoliqberdiyev/test
Test
2025-10-07 18:18:13 +05:00
behruz-dev
1fae231fc1 fxi 2025-10-07 18:13:19 +05:00
behruz-dev
0364f69014 fix 2025-10-07 18:09:25 +05:00
xoliqberdiyev
f5411621f8 Merge pull request #15 from xoliqberdiyev/test
Test
2025-10-07 17:35:50 +05:00
behruz-dev
6ba604ea1b change task 2025-10-07 17:27:59 +05:00
behruz-dev
e1c5db8122 fix 2025-10-07 16:48:10 +05:00
behruz-dev
0a856b05f7 fix 2025-10-07 16:36:21 +05:00
behruz-dev
377cc531f6 change type 2025-10-07 16:32:56 +05:00
behruz-dev
aa8be776bc change type 2025-10-07 16:28:28 +05:00
behruz-dev
50e989f8e1 fix 2025-10-07 16:17:17 +05:00
behruz-dev
c7dfe2ed5d commit 2025-10-07 16:10:14 +05:00
behruz-dev
e726773658 fix payment 2025-10-07 16:06:09 +05:00
behruz-dev
c66942db74 fix payment 2025-10-07 16:00:29 +05:00
behruz-dev
d635dcf5de add api for usd course for Aziz aka 2025-10-07 15:55:41 +05:00
behruz-dev
61d291e6c6 fucking calculate party payment is done! 2025-10-07 15:44:35 +05:00
behruz-dev
df9bd37f87 fix 2025-10-07 15:39:50 +05:00
behruz-dev
9bdd8ca18a fix 2025-10-07 15:37:45 +05:00
behruz-dev
66ab98d259 fiux 2025-10-07 15:36:32 +05:00
behruz-dev
1f0af73ade change party payment 2025-10-07 15:19:39 +05:00
behruz-dev
f385bf5d65 change party payment 2025-10-07 15:14:13 +05:00
behruz-dev
e01017123c fix payment 2025-10-07 14:57:29 +05:00
behruz-dev
c691bfea94 fix save method 2025-10-07 14:40:37 +05:00
behruz-dev
e66aa6b213 calculate party process and payment_percentag 2025-10-07 14:17:40 +05:00
behruz-dev
ceef207b19 change first_name and last_name to full_name on cash_transaction list 2025-10-07 13:56:28 +05:00
xoliqberdiyev
2f1dc3c8a6 Merge pull request #14 from xoliqberdiyev/test
order party list api by party_number
2025-10-07 13:51:41 +05:00
behruz-dev
896654b016 order party list api by party_number 2025-10-07 13:51:07 +05:00
xoliqberdiyev
cbe514f3b3 Merge pull request #13 from xoliqberdiyev/test
add error handling for product crearte
2025-10-07 13:48:06 +05:00
behruz-dev
8a592f6214 add error handling for product crearte 2025-10-07 12:50:27 +05:00
xoliqberdiyev
640a9cd457 Merge pull request #12 from xoliqberdiyev/test
fix
2025-10-06 18:16:56 +05:00
behruz-dev
7893b18c6b fix 2025-10-06 18:15:58 +05:00
xoliqberdiyev
20211ec837 Merge pull request #11 from xoliqberdiyev/test
fix
2025-10-06 17:09:59 +05:00
behruz-dev
5ae183b33d fix 2025-10-06 17:09:18 +05:00
xoliqberdiyev
7146abe9f9 Merge pull request #10 from xoliqberdiyev/test
Test
2025-10-06 17:04:07 +05:00
behruz-dev
af564d71bf fix 2025-10-06 16:58:28 +05:00
behruz-dev
4ef38fcfe1 fix 2025-10-06 16:57:44 +05:00
behruz-dev
2e1120765c fix party update 2025-10-06 16:54:19 +05:00
xoliqberdiyev
32cf95f701 Merge pull request #9 from xoliqberdiyev/test
Test
2025-10-06 16:29:53 +05:00
behruz-dev
0142a0f3e1 add: add new field 2025-10-06 16:17:18 +05:00
behruz-dev
8ee0ab6a75 fix 2025-10-06 16:02:12 +05:00
xoliqberdiyev
f2114dbfff Merge pull request #8 from xoliqberdiyev/test
Test
2025-10-06 14:59:04 +05:00
behruz-dev
96425f7484 fix: fix filter error 2025-10-06 14:42:47 +05:00
behruz-dev
57cd337570 fix error 2025-10-06 14:29:29 +05:00
behruz-dev
07a74b2af7 add: add new filter 2025-10-06 14:20:43 +05:00
xoliqberdiyev
de3956977a Merge pull request #7 from xoliqberdiyev/test
Test
2025-10-04 17:05:01 +05:00
behruz-dev
8a03766a8c fix 2025-10-04 16:15:16 +05:00
behruz-dev
c3a0b94082 fix 2025-10-04 15:18:57 +05:00
behruz-dev
a324998c4c fix 2025-10-04 14:59:50 +05:00
behruz-dev
5ff390e2d7 change 2025-10-04 14:57:02 +05:00
xoliqberdiyev
5a6111d464 Merge pull request #6 from xoliqberdiyev/test
add: add new api
2025-10-03 18:15:26 +05:00
behruz-dev
11de0f01fe add: add new api 2025-10-03 18:12:23 +05:00
xoliqberdiyev
5be46f6858 Merge pull request #5 from xoliqberdiyev/test
Test
2025-10-02 15:39:39 +05:00
behruz-dev
fbebccd00a fix 2025-10-02 15:28:27 +05:00
behruz-dev
729ea67660 add: add filters 2025-10-01 17:54:49 +05:00
xoliqberdiyev
1823bfec61 Merge pull request #4 from xoliqberdiyev/test
fix
2025-10-01 15:55:00 +05:00
behruz-dev
17bdb625ea fix 2025-10-01 15:51:32 +05:00
xoliqberdiyev
7d224b19fa Merge pull request #3 from xoliqberdiyev/test
Test
2025-10-01 15:32:59 +05:00
behruz-dev
507bb0bc12 fxi 2025-10-01 15:31:26 +05:00
behruz-dev
9193e0f9dc fix 2025-10-01 15:25:02 +05:00
behruz-dev
64a5c55ab6 fix: fix error 2025-10-01 15:06:55 +05:00
behruz-dev
2120e6ac98 add: add project_ids field for warehouse and cash_transaction update api 2025-10-01 15:01:46 +05:00
behruz-dev
532f54b730 fix: fix 500 server error 2025-10-01 14:15:44 +05:00
behruz-dev
9be6825a19 add: add new field for list api 2025-10-01 13:40:23 +05:00
xoliqberdiyev
72f7ddf8bb Merge pull request #2 from xoliqberdiyev/test
Test
2025-10-01 13:33:20 +05:00
behruz-dev
653e9d0230 add: add new field for warehouse create api 2025-10-01 13:30:36 +05:00
behruz-dev
5791533170 add: add filter for stock movemend 2025-09-29 17:56:00 +05:00
behruz-dev
fa60bc69bc add: add filter for inventory list 2025-09-29 17:54:42 +05:00
behruz-dev
d994fcb16a change: change filter name 2025-09-29 17:52:26 +05:00
behruz-dev
5cf6aafdcc add: add new filter field for invalid product 2025-09-29 17:49:48 +05:00
behruz-dev
a76d84f042 add: add new field for cash transaction create api 2025-09-29 16:46:12 +05:00
behruz-dev
c37c36582e add: add date filter 2025-09-29 16:13:28 +05:00
xoliqberdiyev
d7ea523b11 Merge pull request #1 from xoliqberdiyev/test
Test
2025-09-29 15:39:57 +05:00
behruz-dev
f4f54801db add: add new field for expence income, and add to filter new fields 2025-09-29 15:31:06 +05:00
behruz-dev
2fd5d685e2 fix: fix party filter 2025-09-29 15:24:50 +05:00
behruz-dev
9a74eb2253 add: add new field for filter 2025-09-29 14:52:50 +05:00
183 changed files with 386905 additions and 852 deletions

2
.gitignore vendored
View File

@@ -267,3 +267,5 @@ poetry.toml
pyrightconfig.json pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python,django # End of https://www.toptal.com/developers/gitignore/api/python,django
.zed/

View File

@@ -7,6 +7,8 @@ CORS_ALLOWED_ORIGINS = [
"https://ibapp.uz", "https://ibapp.uz",
"https://www.ibapp.uz", "https://www.ibapp.uz",
'https://test.ibapp.uz', 'https://test.ibapp.uz',
'http://192.168.1.104:8081',
'https://ikkita.felixits.uz',
] ]
CSRF_TRUSTED_ORIGINS = [ CSRF_TRUSTED_ORIGINS = [
@@ -14,4 +16,5 @@ CSRF_TRUSTED_ORIGINS = [
'http://127.0.0.1:8001', 'http://127.0.0.1:8001',
'https://test-uyqur.felixits.uz', 'https://test-uyqur.felixits.uz',
"https://test-api.ibapp.uz", "https://test-api.ibapp.uz",
'https://bitta.felixits.uz',
] ]

View File

@@ -1,9 +1,8 @@
from datetime import timedelta from datetime import timedelta
SIMPLE_JWT = { SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(days=1), "ACCESS_TOKEN_LIFETIME": timedelta(days=365),
"REFRESH_TOKEN_LIFETIME": timedelta(days=30), "REFRESH_TOKEN_LIFETIME": timedelta(days=3000),
"ROTATE_REFRESH_TOKENS": True, "ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True, "BLACKLIST_AFTER_ROTATION": True,
"UPDATE_LAST_LOGIN": True, "UPDATE_LAST_LOGIN": True,

11
config/firebase.py Normal file
View File

@@ -0,0 +1,11 @@
import os
import firebase_admin
from firebase_admin import credentials
from django.conf import settings
def initialize_firebase():
if not firebase_admin._apps:
cred_path = os.path.join(settings.BASE_DIR, 'config/firebase/ibapp-firebase-key.json')
cred = credentials.Certificate(cred_path)
firebase_admin.initialize_app(cred)

View File

@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "ibapp-5458d",
"private_key_id": "0eba3cee419c4ce5c75ebb27e150dc8e8491de8f",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/8FhTrfpCD1kO\nB1j6Xw/FpNIk06w28JP5EydBsUi7fNs7b4MsGu3+gNuWZDFAHvXrijdptMBOs0A+\nPG/hDIrPccVybPXkXFxQGnt7SH1SmbB5r99EYVMvbqMqBru9vByX7TGKxFHec0++\nD2RmuQjOLmP1TKDS/YavmXtrmTtdTlGSzAtKGV7ep3kDUk3ocx+XoO0zex5f61C9\nANJEe94bFX6ptGYTJ2yMf43cA7LnLz8omxrTzgTZUDVyGA1tyCeWiLfg+reCdm3G\nySVCojqVmmGImZpYv7cLsEIOH6RxqR9FNbQKA+sh5WasE7NJFPTkXgd2uMwuoBzx\nvMJZSuIBAgMBAAECggEAAySQkT40Q/Lem/1Oz3OYXWhZRd/EFz3DchIxGwhEVivN\n36Bh0M30hlhQB0YqfoGfuFhK0G8td9VRx8FTXW5y693DfTxjEJNMXP75H/mojlc/\nO9e/YpjPJsd0VA8sKnmd1j4gQcTD1txqSL3AOY9C9sKl7FxVEyZ5XlR+mRX+WUwU\nW2oueKrYZs0k8ivwkvnzC0IaZl1JToMiz6MewC3fJf6vjiFpwxLk8iF3c6XXElaW\nANtnYSU4SJ5I5UxnQ+kWniIYxHaRdnziImyupkyvqNOQ36F3elRfNDLwRb5L2VyH\nm1f8kirsuvdQi4NJtKLi6LXK/XBEoJrBQuZPDKdJ4QKBgQD57WMrMISQv7htJj8/\nFwgZ7y+fk01zRAET5dWUPqhEqpjeDvKhXHJxFDdAnz2BFJEbQ9UvLFJtfdjvBw5e\n+QMYGKqdwJoRaGFRA5q0S4LA/Q8a4QOhzigMWx+orsnXr2adUWrqhuBv1s+39HoE\n9UtPnoZlzI3Fvxwr+wOM0WgOhQKBgQDEmkEkjJ+hQAQnHc3bY45gf9N1fGqbGBk1\nqIiqLXAYYmzHG/vX5O/SdVJDAU2u8ai232a0p5rYmhkHHSt5r7BdjbUDnxQCdrg+\nfBuu9+rlVCFEuctNdr+XQbvMXgh/fpPqrutN9H6pcNG6+lSh4yccK9uFHW+23e4N\naHxqUHW0TQKBgQDcaCGAwTTngmxetdApD2KxnGJfVESFVn+s0I/eQLOceuZ9Tqli\n7GhwmOdxMf3HjB+778Jd67R1ovphMdPWpbu40GgG3wgAOhE4pPkV71HMaF+d0Lqn\nQ2vGGcZ9uEeA0sqoCllDotbjSom9LPk+ziQwfxj/Rbnxnmx1zNSAp1whEQKBgQCU\nLDHmivmQPUAj/wl0TL80qYJErKVoCKyPTVra723pYtza35Nabpf1BSkfiO70Z/uh\nABRXGW7RyqoMO4bVqn0AtVl0xkL9bF6F8WGn5/+oIVUFiAXadyIErK0OZNyAwnRW\nzshXM2r25ymagyqf7CRAzikfVUUqDG9quZHzSnq7jQKBgBWFCBly2BKAsoeiYZow\n6we7j167UasDsqypvFRAR5txMG1CSQgcy9bs8Zk6pPl0YZ/15tdabb05jUeoOVBB\nHHkpC+VN4uBUhcbvKusPawvdF/4SdjYMQRt5WhF4tQLEyc9rJea0XARwsfVDcNeo\n1qUYaaXno/dXsquLjwc9BdSb\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@ibapp-5458d.iam.gserviceaccount.com",
"client_id": "114063251328249657406",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40ibapp-5458d.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View File

@@ -1,6 +1,14 @@
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
from config.conf.celery import *
from config.conf.cors_headers import *
from config.conf.drf_yasg import *
from config.conf.jazzmin import *
from config.conf.logs import *
from config.conf.redis import *
from config.conf.rest_framework import *
from config.conf.rest_framework_simplejwt import *
from config.env import env from config.env import env
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -11,43 +19,44 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env.str('SECRET_KEY') SECRET_KEY = env.str("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool('DEBUG') DEBUG = env.bool("DEBUG")
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
# Application definition # Application definition
APPS = [ APPS = [
'core.apps.accounts', "core.apps.accounts",
'core.apps.shared', "core.apps.shared",
'core.apps.company', "core.apps.company",
'core.apps.wherehouse', "core.apps.wherehouse",
'core.apps.products', "core.apps.products",
'core.apps.projects', "core.apps.projects",
'core.apps.orders', "core.apps.orders",
'core.apps.finance', "core.apps.finance",
'core.apps.counterparty', "core.apps.counterparty",
"core.apps.notifications",
] ]
PACKAGES = [ PACKAGES = [
'drf_yasg', "drf_yasg",
'rest_framework', "rest_framework",
'rest_framework_simplejwt', "rest_framework_simplejwt",
'corsheaders', "corsheaders",
'cacheops', "cacheops",
] ]
DJANGO_APPS = [ DJANGO_APPS = [
'jazzmin', "jazzmin",
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
] ]
INSTALLED_APPS = [] INSTALLED_APPS = []
@@ -57,48 +66,48 @@ INSTALLED_APPS += APPS
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'corsheaders.middleware.CorsMiddleware', "corsheaders.middleware.CorsMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
# 'silk.middleware.SilkyMiddleware', # 'silk.middleware.SilkyMiddleware',
] ]
ROOT_URLCONF = 'config.urls' ROOT_URLCONF = "config.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'config.wsgi.application' WSGI_APPLICATION = "config.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases # https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.postgresql', "ENGINE": "django.db.backends.postgresql",
'NAME': env.str('POSTGRES_DB'), "NAME": env.str("POSTGRES_DB"),
'USER': env.str('POSTGRES_USER'), "USER": env.str("POSTGRES_USER"),
'PASSWORD': env.str('POSTGRES_PASSWORD'), "PASSWORD": env.str("POSTGRES_PASSWORD"),
'HOST': env.str('POSTGRES_HOST'), "HOST": env.str("POSTGRES_HOST"),
'PORT': env.str('POSTGRES_PORT'), "PORT": env.str("POSTGRES_PORT"),
} }
} }
@@ -108,16 +117,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
@@ -125,9 +134,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/ # https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'uz' LANGUAGE_CODE = "uz"
TIME_ZONE = 'Asia/Tashkent' TIME_ZONE = "Asia/Tashkent"
USE_I18N = True USE_I18N = True
@@ -137,25 +146,19 @@ USE_TZ = False
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/ # https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/' STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / 'resources/static' STATIC_ROOT = BASE_DIR / "resources/static"
MEDIA_URL = 'media/' MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / 'resources/media' MEDIA_ROOT = BASE_DIR / "resources/media"
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
AUTH_USER_MODEL = 'accounts.User' AUTH_USER_MODEL = "accounts.User"
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', env.str("SWAGGER_PROTOCOL", 'https')) SECURE_PROXY_SSL_HEADER = (
"HTTP_X_FORWARDED_PROTO",
from config.conf.rest_framework import * env.str("SWAGGER_PROTOCOL", "https"),
from config.conf.rest_framework_simplejwt import * )
from config.conf.logs import *
from config.conf.cors_headers import *
from config.conf.drf_yasg import *
from config.conf.jazzmin import *
from config.conf.celery import *
from config.conf.redis import *

View File

@@ -35,6 +35,7 @@ urlpatterns = [
path('orders/', include('core.apps.orders.urls')), path('orders/', include('core.apps.orders.urls')),
path('finance/', include('core.apps.finance.urls')), path('finance/', include('core.apps.finance.urls')),
path('counterparties/', include('core.apps.counterparty.urls')), path('counterparties/', include('core.apps.counterparty.urls')),
path('notifications/', include('core.apps.notifications.urls')),
] ]
)), )),

View File

@@ -1,19 +1,24 @@
from django.contrib import admin from django.contrib import admin
from core.apps.accounts.models.permission import Permission, PermissionToTab, PermissionToAction from core.apps.accounts.models.permission import (
Permission,
PermissionToTab,
PermissionToAction,
)
@admin.register(Permission) @admin.register(Permission)
class PermissionAdmin(admin.ModelAdmin): class PermissionAdmin(admin.ModelAdmin):
list_display = ['name', 'code'] list_display = ["name", "code"]
filter_horizontal = ['permission_tab'] filter_horizontal = ["permission_tab"]
@admin.register(PermissionToTab) @admin.register(PermissionToTab)
class PermissionToTabAdmin(admin.ModelAdmin): class PermissionToTabAdmin(admin.ModelAdmin):
list_display = ['name', 'code'] list_display = ["name", "code"]
filter_horizontal = ['permission_to_actions'] filter_horizontal = ["permission_to_actions"]
@admin.register(PermissionToAction) @admin.register(PermissionToAction)
class PermissionToActionAdmin(admin.ModelAdmin): class PermissionToActionAdmin(admin.ModelAdmin):
list_display = ['name', 'code'] list_display = ["name", "code"]

View File

@@ -0,0 +1,28 @@
import json
from django.core.management import BaseCommand
from core.apps.accounts.models import User
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('file_path', type=str)
def handle(self, *args, **options):
file_path = options['file_path']
with open(file_path, 'r') as f:
data = json.load(f)
for item in data['data']['data']:
User.objects.get_or_create(
username=item['login'],
defaults={
'full_name': item['full_name'],
'phone_number': item['phone'],
'password': '12345678a0'
}
)
self.stdout.write("Users added")

View File

@@ -73,7 +73,7 @@ class UserCreateApiView(generics.GenericAPIView):
class UserListApiView(generics.ListAPIView): class UserListApiView(generics.ListAPIView):
serializer_class = serializers.UserListSerializer serializer_class = serializers.UserListSerializer
queryset = User.objects.select_related('role').exclude(is_staff=True) queryset = User.objects.select_related('role').exclude(is_staff=True).distinct()
permission_classes = [HasRolePermission] permission_classes = [HasRolePermission]
pagination_class = CustomPageNumberPagination pagination_class = CustomPageNumberPagination

View File

@@ -1,15 +1,27 @@
from django.contrib import admin from django.contrib import admin
from core.apps.counterparty.models import Counterparty, CounterpartyFolder from core.apps.counterparty.models import Counterparty, CounterpartyFolder, CounterpartyBalance
class CounterpartyBalanceInline(admin.StackedInline):
model = CounterpartyBalance
can_delete = False
verbose_name_plural = 'Balance'
fk_name = 'counterparty'
extra = 0
@admin.register(Counterparty) @admin.register(Counterparty)
class CounterpartyAdmin(admin.ModelAdmin): class CounterpartyAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'phone', 'type', 'inn'] list_display = ['id', 'name', 'inn', 'balance__kredit_uzs', 'balance__kredit_usd', 'balance__debit_uzs', 'balance__debit_usd']
inlines = [CounterpartyBalanceInline]
search_fields = ['name']
def get_queryset(self, request):
return super().get_queryset(request).select_related('balance')
@admin.register(CounterpartyFolder) @admin.register(CounterpartyFolder)
class CounterpartyFolderAdmin(admin.ModelAdmin): class CounterpartyFolderAdmin(admin.ModelAdmin):
list_display = ['id', 'name'] list_display = ['id', 'name']
list_filter = ['name'] list_filter = ['name']

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
{
"data": [
{
"id": 15,
"name": "marketing",
"color": null,
"company_persons_count": 103
},
{
"id": 16,
"name": "Qurilish bozorlik",
"color": null,
"company_persons_count": 0
},
{
"id": 23,
"name": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u044b",
"color": null,
"company_persons_count": 15
},
{
"id": 32,
"name": "Mavrid \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u044b",
"color": null,
"company_persons_count": 1
},
{
"id": 34,
"name": "Buxgalter",
"color": null,
"company_persons_count": 1
},
{
"id": 36,
"name": "dur",
"color": null,
"company_persons_count": 0
},
{
"id": 44,
"name": "A",
"color": null,
"company_persons_count": 0
}
],
"message": {
"uz": "Muvaffaqiyatli!",
"ru": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e!",
"en": "Success!",
"tr": "Ba\u015far\u0131l\u0131!"
}
}

View File

@@ -0,0 +1,44 @@
import json
from datetime import datetime
from django.core.management import BaseCommand
from core.apps.counterparty.models import Counterparty, CounterpartyBalance
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("file_path", type=str)
def handle(self, *args, **options):
file_path = options["file_path"]
with open(file_path, "r") as f:
data = json.load(f)
for item in data["data"]["data"]:
counterparty, created = Counterparty.objects.update_or_create(
name=item["name"],
defaults={
"phone": item["person"]["phone"],
"inn": item["person"]["tin"],
"is_archived": item["is_archived"],
},
)
if item.get("balances"):
balance, created = CounterpartyBalance.objects.get_or_create(
counterparty=counterparty,
defaults=dict(
balance_date=datetime.strptime(item['balances'][0]['start_date'], "%d.%m.%Y"),
kredit_uzs=item['credit_amount'],
debit_uzs=item['debt_amount'],
)
)
for b in item["balances"]:
if b["currency"]["symbol"].lower() == 'uzs':
balance.balance_uzs = b['amount']
elif item["balances"][0]["currency"]["symbol"].lower() == 'usd':
balance.balance_usd = b['amount']
balance.save()
self.stdout.write("Counterparties added")

View File

@@ -0,0 +1,63 @@
import json, requests
from datetime import datetime
from django.core.management import BaseCommand
from core.apps.counterparty.models import CounterpartyFolder, Counterparty, CounterpartyBalance
headers = {
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MjUxMDQyMywiZXhwIjoxNzYyNTk2ODIzLCJuYmYiOjE3NjI1MTA0MjMsImp0aSI6IlNPMmx5VjJ3Mllmb3BlSXEiLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.3DemwyRz2FMzMm-JRBOqGPSgu_m4s4ndWz56e_ROp8A"
}
def get_counterparty(folder_id):
url = f"https://backend.app.uyqur.uz/main/company-person-folder/view?company_person_folder_id={folder_id}&size=500"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
return None
def create_counterparty(item, folder):
counterparty, created = Counterparty.objects.update_or_create(
name=item["name"],
defaults={
"phone": item["person"]["phone"],
"inn": item["person"]["tin"],
"is_archived": item["is_archived"],
},
)
if item.get("balances"):
balance = CounterpartyBalance.objects.create(
counterparty=counterparty,
balance_date=datetime.strptime(item['balances'][0]['start_date'], "%d.%m.%Y"),
kredit_uzs=item['credit_amount'],
debit_uzs=item['debt_amount'],
)
for b in item["balances"]:
if b["currency"]["symbol"].lower() == 'uzs':
balance.balance_uzs = b['amount']
elif item["balances"][0]["currency"]["symbol"].lower() == 'usd':
balance.balance_usd = b['amount']
balance.save()
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("file_path", type=str)
def handle(self, *args, **options):
file_path = options['file_path']
with open(file_path, 'r') as f:
data = json.load(f)
for item in data['data']:
folder, created = CounterpartyFolder.objects.get_or_create(
name=item['name'],
)
data = get_counterparty(item['id'])
for counterparty in data['data']['data']:
create_counterparty(counterparty, folder)
self.stdout.write("Counterparty Folders added")

View File

@@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand
from core.apps.finance.models import Counterparty
from core.apps.counterparty.utils.counterparty import update_counterparty_balance
class Command(BaseCommand):
help = 'Barcha counterpartylardagi debit va kredit balanslarini yangilash'
def handle(self, *args, **options):
counterparties = Counterparty.objects.all()
for counterparty in counterparties:
update_counterparty_balance(counterparty)
self.stdout.write(
self.style.SUCCESS(
f'{counterparties.count()} ta counterparty yangilandi'
)
)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-10-25 14:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('counterparty', '0006_rename_debit_counterparty_debit_usd_and_more'),
]
operations = [
migrations.AlterField(
model_name='counterparty',
name='balance',
field=models.BigIntegerField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,71 @@
# Generated by Django 5.2.4 on 2025-11-07 11:09
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('counterparty', '0007_alter_counterparty_balance'),
]
operations = [
migrations.RemoveField(
model_name='counterparty',
name='balance',
),
migrations.RemoveField(
model_name='counterparty',
name='balance_currency',
),
migrations.RemoveField(
model_name='counterparty',
name='balance_date',
),
migrations.RemoveField(
model_name='counterparty',
name='debit_usd',
),
migrations.RemoveField(
model_name='counterparty',
name='debit_uzs',
),
migrations.RemoveField(
model_name='counterparty',
name='kredit_usd',
),
migrations.RemoveField(
model_name='counterparty',
name='kredit_uzs',
),
migrations.RemoveField(
model_name='counterparty',
name='total_debit',
),
migrations.RemoveField(
model_name='counterparty',
name='total_kredit',
),
migrations.CreateModel(
name='CounterpartyBalance',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('balance_uzs', models.DecimalField(decimal_places=2, default=0.0, max_digits=15)),
('balance_usd', models.DecimalField(decimal_places=2, default=0.0, max_digits=15)),
('balance_date', models.DateField(blank=True, null=True)),
('kredit_usd', models.DecimalField(decimal_places=2, default=0.0, max_digits=15)),
('kredit_uzs', models.DecimalField(decimal_places=2, default=0.0, max_digits=15)),
('debit_usd', models.DecimalField(decimal_places=2, default=0.0, max_digits=15)),
('debit_uzs', models.DecimalField(decimal_places=2, default=0.0, max_digits=15)),
('counterparty', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='balance', to='counterparty.counterparty')),
],
options={
'verbose_name': 'Kontragent Balansi',
'verbose_name_plural': 'Kontragent Balanslari',
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-11-18 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('counterparty', '0008_remove_counterparty_balance_and_more'),
]
operations = [
migrations.AddField(
model_name='counterpartybalance',
name='total_balance',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.4 on 2025-12-05 14:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('counterparty', '0009_counterpartybalance_total_balance'),
]
operations = [
migrations.RemoveField(
model_name='counterpartybalance',
name='total_balance',
),
]

View File

@@ -1,7 +1,9 @@
from django.db import models from decimal import Decimal
from core.apps.shared.models import BaseModel, Region, District from django.db import models
from core.apps.accounts.models import User from django.apps import apps
from core.apps.shared.models import BaseModel, Region, District, UsdCourse
class CounterpartyFolder(BaseModel): class CounterpartyFolder(BaseModel):
@@ -41,24 +43,104 @@ class Counterparty(BaseModel):
district = models.ForeignKey( district = models.ForeignKey(
District, on_delete=models.SET_NULL, null=True, blank=True, related_name='counterparties' District, on_delete=models.SET_NULL, null=True, blank=True, related_name='counterparties'
) )
balance = models.PositiveBigIntegerField(null=True, blank=True)
balance_currency = models.CharField(
max_length=3, choices=[('usd', 'usd'), ('uzs', 'uzs')], null=True, blank=True
)
balance_date = models.DateField(null=True, blank=True)
comment = models.TextField(null=True, blank=True) comment = models.TextField(null=True, blank=True)
is_archived = models.BooleanField(default=False) is_archived = models.BooleanField(default=False)
debit_usd = models.BigIntegerField(default=0, null=True, blank=True)
debit_uzs = models.BigIntegerField(default=0, null=True, blank=True)
total_debit = models.BigIntegerField(default=0, null=True, blank=True)
kredit_usd = models.BigIntegerField(default=0, null=True, blank=True)
kredit_uzs = models.BigIntegerField(default=0, null=True, blank=True)
total_kredit = models.BigIntegerField(default=0, null=True, blank=True)
def __str__(self): def __str__(self):
return self.name return self.name
class Meta: class Meta:
verbose_name = 'Kontragent' verbose_name = 'Kontragent'
verbose_name_plural = 'Kontragentlar' verbose_name_plural = 'Kontragentlar'
class CounterpartyBalance(BaseModel):
counterparty = models.OneToOneField(Counterparty, on_delete=models.CASCADE, related_name='balance')
# balance
balance_uzs = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
balance_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
# balance date
balance_date = models.DateField(null=True, blank=True)
# kreditor -> qazrdorlik
kredit_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
kredit_uzs = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
# debitor -> xaqdorlik
debit_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
debit_uzs = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
def save(self, *args, **kwargs):
if self.total_balance_usd >= 0:
self.kredit_usd = self.total_balance_usd
elif self.total_balance_usd <= 0:
self.debit_usd = self.total_balance_usd
if self.total_balance_uzs >= 0:
self.kredit_uzs = self.total_balance_uzs
elif self.total_balance_uzs <= 0:
self.debit_uzs = self.total_balance_uzs
super().save(*args, **kwargs)
total_usd = Decimal(self.total_balance_usd)
total_uzs = Decimal(self.total_balance_uzs)
if total_usd < 0 or total_uzs < 0:
self.counterparty.status = 'CREDITOR'
elif total_usd > 0 or total_uzs > 0:
self.counterparty.status = 'DEBITOR'
else:
self.counterparty.status = 'CREDITOR'
self.counterparty.save(update_fields=['status'])
@property
def total_balance_usd(self):
Party = apps.get_model('orders', 'Party')
Income = apps.get_model('finance', 'Income')
Expence = apps.get_model('finance', 'Expence')
parties = Party.objects.filter(
orders__counterparty=self.counterparty,
is_deleted=False,
currency='usd'
).distinct()
total_amount = parties.aggregate(total_price=models.Sum('party_amount__calculated_amount'))['total_price'] or 0
income = Income.objects.filter(currency='usd', counterparty=self.counterparty).aggregate(
total_price=models.Sum("price")
)['total_price'] or 0
expence = Expence.objects.filter(currency='usd', counterparty=self.counterparty).aggregate(
total_price=models.Sum("price")
)['total_price'] or 0
return (total_amount + income) - expence
@property
def total_balance_uzs(self):
Party = apps.get_model('orders', 'Party')
Income = apps.get_model('finance', 'Income')
Expence = apps.get_model('finance', 'Expence')
parties = Party.objects.filter(
orders__counterparty=self.counterparty,
is_deleted=False,
currency='uzs'
).distinct()
total_amount = parties.aggregate(total_price=models.Sum('party_amount__calculated_amount'))['total_price'] or 0
income = Income.objects.filter(currency='uzs', counterparty=self.counterparty).aggregate(
total_price=models.Sum("price")
)['total_price'] or 0
expence = Expence.objects.filter(currency='uzs', counterparty=self.counterparty).aggregate(
total_price=models.Sum("price")
)['total_price'] or 0
return (total_amount + income) - expence
@property
def total_balance(self):
usd_course = Decimal(UsdCourse.objects.first().value) if UsdCourse.objects.exists() else Decimal(12000)
return self.total_balance_uzs + (self.total_balance_usd * usd_course)
def __str__(self):
return f"{self.counterparty.name}"
class Meta:
verbose_name = "Kontragent Balansi"
verbose_name_plural = "Kontragent Balanslari"

View File

@@ -2,17 +2,23 @@ from django.db import transaction
from rest_framework import serializers from rest_framework import serializers
from core.apps.counterparty.models import Counterparty, CounterpartyFolder from core.apps.counterparty.models import Counterparty, CounterpartyFolder, CounterpartyBalance
from core.apps.shared.models import Region, District from core.apps.shared.models import Region, District
from core.apps.counterparty.serializers.counterparty_balance import (
CounterpartyBalanceSerializer,
CounterpartyBalanceCreateSerializer,
CounterpartyBalanceUpdateSerializer
)
class CounterpartyListSerializer(serializers.ModelSerializer): class CounterpartyListSerializer(serializers.ModelSerializer):
balance = CounterpartyBalanceSerializer()
class Meta: class Meta:
model = Counterparty model = Counterparty
fields = [ fields = [
'id', 'inn', 'name', 'phone', 'type', 'folder', 'type', 'region', 'district', 'id', 'inn', 'name', 'phone', 'type', 'folder', 'type', 'region', 'district',
'balance', 'balance_currency', 'balance_date', 'comment', 'is_archived', 'comment', 'is_archived', 'balance',
'kredit_usd', 'kredit_uzs', 'total_kredit', 'debit_usd', 'debit_uzs', 'total_debit',
] ]
@@ -32,9 +38,8 @@ class CounterpartyCreateSerializer(serializers.Serializer):
folder_id = serializers.UUIDField(required=False) folder_id = serializers.UUIDField(required=False)
region_id = serializers.UUIDField(required=False) region_id = serializers.UUIDField(required=False)
district_id = serializers.UUIDField(required=False) district_id = serializers.UUIDField(required=False)
balance = serializers.IntegerField(required=False)
balance_date = serializers.DateField(required=False)
comment = serializers.CharField(required=False) comment = serializers.CharField(required=False)
balance = CounterpartyBalanceCreateSerializer(required=False)
def validate(self, data): def validate(self, data):
if data.get('folder_id'): if data.get('folder_id'):
@@ -54,8 +59,10 @@ class CounterpartyCreateSerializer(serializers.Serializer):
return data return data
def create(self, validated_data): def create(self, validated_data):
balance_data = validated_data.pop('balance', {}) or {}
with transaction.atomic(): with transaction.atomic():
return Counterparty.objects.create( counterparty = Counterparty.objects.create(
inn=validated_data.get('inn'), inn=validated_data.get('inn'),
name=validated_data.get('name'), name=validated_data.get('name'),
phone=validated_data.get('phone'), phone=validated_data.get('phone'),
@@ -63,18 +70,29 @@ class CounterpartyCreateSerializer(serializers.Serializer):
folder=validated_data.get('folder'), folder=validated_data.get('folder'),
region=validated_data.get('region'), region=validated_data.get('region'),
district=validated_data.get('district'), district=validated_data.get('district'),
balance=validated_data.get('balance'),
balance_date=validated_data.get('balance_date'),
comment=validated_data.get('comment'), comment=validated_data.get('comment'),
) )
CounterpartyBalance.objects.create(
counterparty=counterparty,
balance_uzs=balance_data.get('balance_uzs', 0),
balance_usd=balance_data.get('balance_usd', 0),
balance_date=balance_data.get('balance_date'),
kredit_usd=0,
kredit_uzs=0,
debit_usd=0,
debit_uzs=0,
)
return counterparty
class CounterpartyUpdateSerializer(serializers.ModelSerializer): class CounterpartyUpdateSerializer(serializers.ModelSerializer):
balance = CounterpartyBalanceUpdateSerializer(required=False)
class Meta: class Meta:
model = Counterparty model = Counterparty
fields = [ fields = [
'inn', 'name', 'phone', 'type', 'folder', 'region', 'district', 'balance', 'inn', 'name', 'phone', 'type', 'folder', 'region', 'district',
'balance_currency', 'balance_date', 'comment' 'balance', 'comment'
] ]
extra_kwargs = { extra_kwargs = {
'name': {'required': False}, 'name': {'required': False},
@@ -82,8 +100,24 @@ class CounterpartyUpdateSerializer(serializers.ModelSerializer):
'folder': {'required': False}, 'folder': {'required': False},
'region': {'required': False}, 'region': {'required': False},
'district': {'required': False}, 'district': {'required': False},
'balance': {'required': False},
'balance_currency': {'required': False},
'balance_date': {'required': False},
'comment': {'required': False} 'comment': {'required': False}
} }
def update(self, instance, validated_data):
instance.inn = validated_data.get('inn', instance.inn)
instance.name = validated_data.get('name', instance.name)
instance.phone = validated_data.get('phone', instance.phone)
instance.type = validated_data.get('type', instance.type)
instance.folder = validated_data.get('folder', instance.folder)
instance.region = validated_data.get('region', instance.region)
instance.district = validated_data.get('district', instance.district)
instance.comment = validated_data.get('district', instance.comment)
# balance
balance_data = validated_data.get('balance')
instance.balance.balance_uzs = balance_data.get('balance_uzs', instance.balance.balance_uzs)
instance.balance.balance_usd = balance_data.get('balance_usd', instance.balance.balance_usd)
instance.balance.balance_date = balance_data.get('balance_date', instance.balance.balance_date)
instance.balance.save()
instance.save()
return instance

View File

@@ -0,0 +1,28 @@
from rest_framework import serializers
from core.apps.counterparty.models import CounterpartyBalance
class CounterpartyBalanceSerializer(serializers.ModelSerializer):
class Meta:
model = CounterpartyBalance
fields = [
'id', 'balance_uzs', 'balance_usd', 'balance_date', 'kredit_usd', 'kredit_uzs',
'debit_usd', 'debit_uzs', 'total_balance_usd', 'total_balance_uzs',
]
class CounterpartyBalanceCreateSerializer(serializers.ModelSerializer):
class Meta:
model = CounterpartyBalance
fields = [
'balance_uzs', 'balance_usd', 'balance_date'
]
class CounterpartyBalanceUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = CounterpartyBalance
fields = [
'balance_uzs', 'balance_usd', 'balance_date'
]

View File

@@ -17,7 +17,8 @@ urlpatterns = [
path('all/', cp_views.CounterpartiesApiView.as_view()), path('all/', cp_views.CounterpartiesApiView.as_view()),
path("<uuid:id>/", cp_views.CounterpartyDetailApiView.as_view()), path("<uuid:id>/", cp_views.CounterpartyDetailApiView.as_view()),
path('<uuid:id>/un_archive/', cp_views.UnArchiveCounterpartyApiView.as_view()), path('<uuid:id>/un_archive/', cp_views.UnArchiveCounterpartyApiView.as_view()),
path('all/', cp_views.AllCounterpartyListApiView.as_view()), path("<uuid:id>/statistics/", cp_views.CounterPartyIncomeExpenceStatisticsApiView.as_view()),
path('<uuid:id>/akt_statistics/', cp_views.CounterpartyAKTApiView.as_view()),
] ]
)), )),
path('counterparty_folder/', include( path('counterparty_folder/', include(

View File

@@ -0,0 +1,77 @@
from django.db.models import Sum, F
from core.apps.finance.models import Expence, Income
from core.apps.orders.models import Party
def update_counterparty_balance(counterparty):
if not counterparty:
return
expences = Expence.objects.filter(
counterparty=counterparty,
is_deleted=False,
# status='CONFIRMED'
)
debit_usd = expences.filter(currency='usd').aggregate(
total=Sum('price')
)['total'] or 0
debit_uzs = expences.filter(currency='uzs').aggregate(
total=Sum('price')
)['total'] or 0
parties = Party.objects.filter(
orders__counterparty=counterparty,
is_deleted=False
).distinct()
party_payment_usd = parties.filter(currency='usd').aggregate(
total=Sum('party_amount__paid_amount')
)['total'] or 0
party_payment_uzs = parties.filter(currency='uzs').aggregate(
total=Sum('party_amount__paid_amount')
)['total'] or 0
party_payment_amount_usd = parties.filter(currency='usd').aggregate(
total=Sum('party_amount__payment_amount')
)['total'] or 0
party_payment_amount_uzs = parties.filter(currency='uzs').aggregate(
total=Sum('party_amount__payment_amount')
)['total'] or 0
debit_usd = debit_usd - party_payment_usd + party_payment_amount_usd
debit_uzs = debit_uzs - party_payment_uzs + party_payment_amount_uzs
incomes = Income.objects.filter(
counterparty=counterparty,
is_deleted=False
)
kredit_usd = incomes.filter(currency='usd').aggregate(
total=Sum('price')
)['total'] or 0
kredit_uzs = incomes.filter(currency='uzs').aggregate(
total=Sum('price')
)['total'] or 0
total_debit = debit_usd + debit_uzs
total_kredit = kredit_usd + kredit_uzs
counterparty.debit_usd = debit_usd
counterparty.debit_uzs = debit_uzs
counterparty.total_debit = total_debit
counterparty.kredit_usd = kredit_usd
counterparty.kredit_uzs = kredit_uzs
counterparty.total_kredit = total_kredit
counterparty.save(update_fields=[
'debit_usd', 'debit_uzs', 'total_debit',
'kredit_usd', 'kredit_uzs', 'total_kredit'
])

View File

@@ -1,20 +1,38 @@
from django.db.models import Sum from decimal import Decimal
from collections import defaultdict
from django.db.models import Sum, Count, Q
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics, views, filters from rest_framework import generics, views, filters
from rest_framework.response import Response from rest_framework.response import Response
from django_filters.rest_framework.backends import DjangoFilterBackend from django_filters.rest_framework.backends import DjangoFilterBackend
# accounts
from core.apps.accounts.permissions.permissions import HasRolePermission from core.apps.accounts.permissions.permissions import HasRolePermission
# shared
from core.apps.shared.paginations.custom import CustomPageNumberPagination from core.apps.shared.paginations.custom import CustomPageNumberPagination
from core.apps.counterparty.models import Counterparty, CounterpartyFolder # counterparty
from core.apps.counterparty.models import Counterparty, CounterpartyFolder, CounterpartyBalance
from core.apps.counterparty.serializers import counterparty as serializers from core.apps.counterparty.serializers import counterparty as serializers
from core.apps.counterparty.filters.counterparty import CounterpartyFilter from core.apps.counterparty.filters.counterparty import CounterpartyFilter
# finance
from core.apps.finance.models import Expence, Income
from core.apps.finance.serializers.income import IncomeListSerializer
from core.apps.finance.serializers.expence import ExpenceListSerializer
# orders
from core.apps.orders.models import Party
from core.apps.orders.serializers.party import PartyAKTSerializer
class CounterpartyListApiView(generics.ListAPIView): class CounterpartyListApiView(generics.ListAPIView):
serializer_class = serializers.CounterpartyListSerializer serializer_class = serializers.CounterpartyListSerializer
queryset = Counterparty.objects.exclude(is_archived=True).exclude(folder__isnull=False) queryset = Counterparty.objects\
.select_related('balance')\
.exclude(is_archived=True)\
.exclude(folder__isnull=False)\
.order_by('-created_at')
pagination_class = [HasRolePermission] pagination_class = [HasRolePermission]
pagination_class = CustomPageNumberPagination pagination_class = CustomPageNumberPagination
filter_backends = [DjangoFilterBackend, filters.SearchFilter] filter_backends = [DjangoFilterBackend, filters.SearchFilter]
@@ -32,9 +50,13 @@ class CounterpartyCreateApiView(generics.GenericAPIView):
def post(self, request): def post(self, request):
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True): if serializer.is_valid(raise_exception=True):
serializer.save() data = serializer.save()
return Response( return Response(
{'success': True, 'message': 'Conterparty Created'}, {
'success': True,
'message': 'Conterparty Created',
'data': serializers.CounterpartyListSerializer(data).data
},
status=201 status=201
) )
return Response( return Response(
@@ -58,7 +80,7 @@ class ArchiveCounterpartyApiView(views.APIView):
class ArchivedCounterpartyListApiView(generics.ListAPIView): class ArchivedCounterpartyListApiView(generics.ListAPIView):
serializer_class = serializers.CounterpartyListSerializer serializer_class = serializers.CounterpartyListSerializer
queryset = Counterparty.objects.exclude(is_archived=False) queryset = Counterparty.objects.exclude(is_archived=False).select_related('balance').order_by('-created_at')
pagination_class = [HasRolePermission] pagination_class = [HasRolePermission]
pagination_class = CustomPageNumberPagination pagination_class = CustomPageNumberPagination
@@ -84,7 +106,7 @@ class CounterpartyUpdateApiView(generics.UpdateAPIView):
class FolderCounterpartyListApiView(generics.GenericAPIView): class FolderCounterpartyListApiView(generics.GenericAPIView):
serializer_class = serializers.CounterpartyListSerializer serializer_class = serializers.CounterpartyListSerializer
queryset = Counterparty.objects.exclude(is_archived=True) queryset = Counterparty.objects.exclude(is_archived=True).select_related('balance').order_by('-created_at')
permission_classes = [HasRolePermission] permission_classes = [HasRolePermission]
filter_backends = [filters.SearchFilter] filter_backends = [filters.SearchFilter]
search_fields = [ search_fields = [
@@ -105,25 +127,47 @@ class CounterpartyStatisticsApiView(views.APIView):
def get(self, request): def get(self, request):
counterparty_ids = request.query_params.getlist('counterparty') counterparty_ids = request.query_params.getlist('counterparty')
if counterparty_ids: if counterparty_ids:
queryset = Counterparty.objects.filter(id__in=counterparty_ids) queryset = Counterparty.objects.filter(id__in=counterparty_ids)
else: else:
queryset = Counterparty.objects.all() queryset = Counterparty.objects.all()
res = queryset.aggregate( balance_qs = CounterpartyBalance.objects.filter(counterparty__in=queryset)
kredit_usd=Sum('kredit_usd'),
kredit_uzs=Sum('kredit_uzs'), stats = balance_qs.aggregate(
total_kredit=Sum('total_kredit'), total_balance_uzs=Sum('balance_uzs'),
debit_usd=Sum('debit_usd'), total_balance_usd=Sum('balance_usd'),
debit_uzs=Sum('debit_uzs'), total_debit_uzs=Sum('debit_uzs'),
total_debut=Sum('total_debit'), total_kredit_uzs=Sum('kredit_uzs'),
total_debit_usd=Sum('debit_usd'),
total_kredit_usd=Sum('kredit_usd'),
) )
return Response(res)
counterparty_stats = queryset.aggregate(
total_counterparties=Count('id'),
total_creditors=Count('id', filter=Q(status='CREDITOR')),
total_debtors=Count('id', filter=Q(status='DEBITOR')),
)
result = {
**counterparty_stats,
**stats,
}
for key, value in result.items():
if value is None:
result[key] = 0
result['total_debit_uzs'] = abs(Decimal(result['total_debit_uzs']))
result['total_debit_usd'] = abs(Decimal(result['total_debit_usd']))
return Response(result)
class CounterpartiesApiView(generics.GenericAPIView): class CounterpartiesApiView(generics.GenericAPIView):
serializer_class = serializers.CounterpartyListSerializer serializer_class = serializers.CounterpartyListSerializer
queryset = Counterparty.objects.all() queryset = Counterparty.objects.order_by('-created_at').select_related('balance')
permission_classes = [HasRolePermission] permission_classes = [HasRolePermission]
filter_backends = [filters.SearchFilter] filter_backends = [filters.SearchFilter]
search_fields = [ search_fields = [
@@ -158,17 +202,198 @@ class UnArchiveCounterpartyApiView(views.APIView):
) )
class AllCounterpartyListApiView(generics.GenericAPIView): class CounterPartyIncomeExpenceStatisticsApiView(views.APIView):
serializer_class = serializers.CounterpartyListSerializer
queryset = Counterparty.objects.all()
permission_classes = [HasRolePermission] permission_classes = [HasRolePermission]
filter_backends = [filters.SearchFilter]
search_fields = [
'name'
]
def get(self, request): def get(self, request, id):
page = self.paginate_queryset(self.queryset) counterparty = get_object_or_404(Counterparty, id=id)
if page is not None:
serializer = self.serializer_class(page, many=True) incomes = Income.objects.filter(counterparty=counterparty, is_deleted=False)
return self.get_paginated_response(serializer.data) expences = Expence.objects.filter(counterparty=counterparty, is_deleted=False)
income_by_currency = {'uzs': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)},
'usd': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)}}
for income in incomes:
currency = income.currency
amount = Decimal(income.price or 0)
rate = Decimal(income.exchange_rate or 1)
income_by_currency[currency]['total'] += amount
income_by_currency[currency]['count'] += 1
income_by_currency[currency]['amount_uzs'] += amount * rate if currency == 'usd' else amount
expence_by_currency = {'uzs': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)},
'usd': {'total': Decimal(0), 'count': 0, 'amount_uzs': Decimal(0)}}
for expence in expences:
currency = expence.currency
amount = Decimal(expence.price or 0)
rate = Decimal(expence.exchange_rate or 1)
expence_by_currency[currency]['total'] += amount
expence_by_currency[currency]['count'] += 1
expence_by_currency[currency]['amount_uzs'] += amount * rate if currency == 'usd' else amount
total_income_uzs = sum(v['amount_uzs'] for v in income_by_currency.values())
total_expence_uzs = sum(v['amount_uzs'] for v in expence_by_currency.values())
total_income_usd = income_by_currency['usd']['total']
total_expence_usd = expence_by_currency['usd']['total']
balance_uzs = counterparty.balance.total_balance_uzs
balance_usd = counterparty.balance.total_balance_usd
if balance_uzs > 0:
status = 'positive'
elif balance_uzs < 0:
status = 'negative'
else:
status = 'zero'
data = {
'counterparty': {
'id': counterparty.id,
'name': counterparty.name
},
'income': {
'by_currency': income_by_currency,
'total_uzs': total_income_uzs,
'total_usd': total_income_usd,
'total_count': sum(v['count'] for v in income_by_currency.values())
},
'expence': {
'by_currency': expence_by_currency,
'total_uzs': total_expence_uzs,
'total_usd': total_expence_usd,
'total_count': sum(v['count'] for v in expence_by_currency.values())
},
'balance': {
'uzs': balance_uzs,
'usd': balance_usd,
'status': status,
'total_balance': counterparty.balance.total_balance
}
}
return Response(data, status=200)
class CounterpartyAKTApiView(views.APIView):
permission_classes = [HasRolePermission]
def get(self, request, id):
# TODO: filterlar
date = request.query_params.get('date', None)
end_date = request.query_params.get('end_date', None)
project_folder = request.query_params.getlist('folder', None)
project = request.query_params.getlist('project', None)
currency = request.query_params.get('currency', 'uzs')
counterparty = get_object_or_404(Counterparty, id=id)
parties = Party.objects.prefetch_related('orders', 'orders__product').filter(
orders__counterparty=counterparty, is_deleted=False, process=100, payment_percentage=100
).distinct().order_by('-created_at')
expences = Expence.objects.select_related().filter(counterparty=counterparty, is_deleted=False).distinct().order_by('-created_at')
incomes = Income.objects.filter(counterparty=counterparty, is_deleted=False).distinct().order_by('-created_at')
# TODO: date va end date boyicha filter
if date:
parties = parties.filter(close_date__gte=date)
expences = expences.filter(created_at__gte=date)
incomes = incomes.filter(created_at__gte=date)
if end_date:
parties = parties.filter(close_date__lte=end_date)
expences = expences.filter(created_at__lte=end_date)
incomes = incomes.filter(created_at__lte=end_date)
# TODO: project folder va project boyicha filter
if project_folder:
parties = parties.filter(orders__project_folder=project_folder).distinct()
expences = expences.filter(project_folder=project_folder)
incomes = incomes.filter(project_folder=project_folder)
if project:
parties = parties.filter(orders__project=project).distinct()
expences = expences.filter(project=project)
incomes = incomes.filter(project=project)
# TODO: currency boyicha filter
if currency:
parties = parties.filter(currency=currency)
expences = expences.filter(currency=currency)
incomes = incomes.filter(currency=currency)
# TODO: date boyicha guruhlash
daily_breakdown = self._group_by_date(parties, incomes, expences)
# TODO: total kreditni hisoblash kerak: Sum(party total_price) + Sum(income total_price)
parties_total_price = parties.aggregate(total_price=Sum('party_amount__total_price'))['total_price'] or 0
income_total_price = incomes.aggregate(total_price=Sum('price'))['total_price'] or 0
total_kredit = Decimal(parties_total_price) + Decimal(income_total_price)
# TODO: total debitni hisoblash kerak: Sum(expence total_price)
expence_total_balance = expences.aggregate(total_price=Sum('price'))['total_price'] or 0
total_debit = expence_total_balance
# TODO: final balanceni hisoblash kerak: total_kredit - total_debit = final balance => negative or positive
final_balance = total_kredit - total_debit
# TODO: final balance typeni topish kerak -> debit or kredit: if negative == debit, positive == kredit
type = 'debit' if final_balance < 0 else 'kredit'
response = {
"daily_breakdown": daily_breakdown,
"total_kredit": str(total_kredit),
"total_debit": str(total_debit),
"final_balance": {
"balance": str(final_balance) if not str(final_balance).startswith('-') else str(final_balance).replace('-', ''),
"type": type,
}
}
return Response(response, status=200)
def _group_by_date(self, parties, incomes, expences):
daily_data = defaultdict(list)
# PARTIYALAR (kirim)
for party in parties:
date_key = party.closed_date.strftime('%Y-%m-%d')
daily_data[date_key].append({
"type": "kirim",
"price": str(getattr(party.party_amount, 'calculated_amount', 0) or 0),
"partiya": PartyAKTSerializer(party).data,
"kirim": None,
"chiqim": None
})
# INCOMES (kirim)
for income in incomes:
date_key = income.created_at.strftime('%Y-%m-%d')
daily_data[date_key].append({
"type": "kirim",
"price": str(income.price or 0),
"partiya": None, # ❗ KIRIM ichida PARTIYA BOLMAYDI
"kirim": IncomeListSerializer(income).data,
"chiqim": None
})
# EXPENCES (chiqim)
for expence in expences:
date_key = expence.created_at.strftime('%Y-%m-%d')
daily_data[date_key].append({
"type": "chiqim",
"price": str(expence.price or 0),
"partiya": None,
"kirim": None,
"chiqim": ExpenceListSerializer(expence).data
})
# Yakuniy format
result = []
for date_key in sorted(daily_data.keys(), reverse=True):
result.append({
"date": date_key,
"items": daily_data[date_key]
})
return result

View File

@@ -5,10 +5,11 @@ from core.apps.finance.models import Expence, DeletedExpence
@admin.register(Expence) @admin.register(Expence)
class ExpenceAdmin(admin.ModelAdmin): class ExpenceAdmin(admin.ModelAdmin):
list_display = ['id', 'price', 'cash_transaction', 'status'] list_display = ['number', 'price', 'cash_transaction', 'currency']
search_fields = ['party__number', 'counterparty__name', 'price', 'number']
list_filter = ['counterparty']
@admin.register(DeletedExpence) @admin.register(DeletedExpence)
class DeletedExpenceAdmin(admin.ModelAdmin): class DeletedExpenceAdmin(admin.ModelAdmin):
list_display = ['id', 'comment', 'expence'] list_display = ['id', 'comment', 'expence']

View File

@@ -2,6 +2,6 @@ from django.contrib import admin
from core.apps.finance.models import ExpenceContract from core.apps.finance.models import ExpenceContract
@admin.register(ExpenceContract)
class ExpenceContractAdmin(admin.ModelAdmin): class ExpenceContractAdmin(admin.ModelAdmin):
list_display = ['id', 'price', 'date', 'currency'] list_display = ['id', 'price', 'date', 'currency']

View File

@@ -5,9 +5,9 @@ from core.apps.finance.models import Income, DeletedIncome
@admin.register(Income) @admin.register(Income)
class IncomeAdmin(admin.ModelAdmin): class IncomeAdmin(admin.ModelAdmin):
list_display = ['id', 'price', 'cash_transaction'] list_display = ['id', 'price', 'cash_transaction', 'currency']
list_filter = ['cash_transaction', 'payment_type'] list_filter = ['cash_transaction', 'payment_type']
search_fields = ['party__number', 'counterparty__name']
@admin.register(DeletedIncome) @admin.register(DeletedIncome)
class DeletedIncomeAdmin(admin.ModelAdmin): class DeletedIncomeAdmin(admin.ModelAdmin):

View File

@@ -4,6 +4,3 @@ from django.apps import AppConfig
class FinanceConfig(AppConfig): class FinanceConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'core.apps.finance' name = 'core.apps.finance'
def ready(self):
from . import signals

View File

@@ -0,0 +1,128 @@
{
"data": {
"cashs": [
{
"id": 24,
"name": "FARM NAQT",
"status": "open",
"users": [
{
"id": 109,
"full_name": "Mirjonov Meronshox",
"image": null
},
{
"id": 104,
"full_name": "SuperAdminIskander",
"image": "https:\/\/backend.app.uyqur.uz\/public\/upload\/image\/neDbGZivNlWAz5nHopbl.webp"
},
{
"id": 138,
"full_name": "Baratova Zilola",
"image": null
},
{
"id": 306,
"full_name": "Uyqur Support",
"image": null
}
]
},
{
"id": 25,
"name": "FARM BUGHALTERIYA",
"status": "open",
"users": [
{
"id": 111,
"full_name": "MARDONOVA DILAFRUZ",
"image": null
},
{
"id": 104,
"full_name": "SuperAdminIskander",
"image": "https:\/\/backend.app.uyqur.uz\/public\/upload\/image\/neDbGZivNlWAz5nHopbl.webp"
},
{
"id": 138,
"full_name": "Baratova Zilola",
"image": null
},
{
"id": 306,
"full_name": "Uyqur Support",
"image": null
}
]
},
{
"id": 82,
"name": "Yunusobod BUHGALTERIYA",
"status": "open",
"users": [
{
"id": 104,
"full_name": "SuperAdminIskander",
"image": "https:\/\/backend.app.uyqur.uz\/public\/upload\/image\/neDbGZivNlWAz5nHopbl.webp"
},
{
"id": 111,
"full_name": "MARDONOVA DILAFRUZ",
"image": null
},
{
"id": 138,
"full_name": "Baratova Zilola",
"image": null
},
{
"id": 306,
"full_name": "Uyqur Support",
"image": null
}
]
},
{
"id": 83,
"name": "Yunusobod NAQT",
"status": "open",
"users": [
{
"id": 109,
"full_name": "Mirjonov Meronshox",
"image": null
},
{
"id": 138,
"full_name": "Baratova Zilola",
"image": null
},
{
"id": 104,
"full_name": "SuperAdminIskander",
"image": "https:\/\/backend.app.uyqur.uz\/public\/upload\/image\/neDbGZivNlWAz5nHopbl.webp"
},
{
"id": 306,
"full_name": "Uyqur Support",
"image": null
}
]
}
],
"cash_folders": [
{
"id": 3,
"name": "MARKETING",
"color": null,
"cashs_count": 0
}
]
},
"message": {
"uz": "Muvaffaqiyatli!",
"ru": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e!",
"en": "Success!",
"tr": "Ba\u015far\u0131l\u0131!"
}
}

View File

@@ -0,0 +1,155 @@
{
"data": {
"current_page": 1,
"total": 3,
"data": [
{
"id": 511,
"date": "13.09.2025",
"amount": 1,
"debt_amount": 1,
"type": "expense",
"project": {
"id": 131,
"name": "Mezbon",
"is_project": false
},
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
},
"user": null,
"financial": null,
"company_person": {
"id": 938,
"type": "supplier",
"name": "1",
"description": null,
"start_date": "02.09.2025",
"status": "active",
"person": {
"id": 854,
"name": "11",
"tin": "11",
"phone": "+998111111111",
"address": null,
"longitude": null,
"latitude": null
},
"balances": [
{
"id": 870,
"start_amount": 0,
"amount": 0,
"start_date": "02.09.2025",
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
}
},
{
"id": 909,
"start_amount": 0,
"amount": 0,
"start_date": "09.09.2025",
"currency": {
"id": 3,
"name": {
"ru": "US dollar",
"uz": "Aqsh dollari"
},
"symbol": "USD",
"icon": "https://backend.app.uyqur.uz/public/upload/image/LIMeAcPrnbN6X9m5uYMH.webp"
}
}
]
},
"creator": {
"id": 111,
"full_name": "MARDONOVA DILAFRUZ",
"image": null
}
},
{
"id": 41,
"date": "27.06.2024",
"amount": 4000000,
"debt_amount": 4000000,
"type": "expense",
"project": {
"id": 62,
"name": "Blok A",
"is_project": true
},
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
},
"user": null,
"financial": {
"id": 42,
"name": "SPECTEXNIKA QURULISHGA ARENDA"
},
"company_person": null,
"creator": {
"id": 109,
"full_name": "Mirjonov Meronshox",
"image": null
}
},
{
"id": 40,
"date": "27.06.2024",
"amount": 8000000,
"debt_amount": 8000000,
"type": "expense",
"project": {
"id": 62,
"name": "Blok A",
"is_project": true
},
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
},
"user": null,
"financial": {
"id": 62,
"name": "PROCHEE RASXODI"
},
"company_person": null,
"creator": {
"id": 109,
"full_name": "Mirjonov Meronshox",
"image": null
}
}
]
},
"message": {
"uz": "Muvaffaqiyatli!",
"ru": "Успешно!",
"en": "Success!",
"tr": "Başarılı!"
}
}

View File

@@ -0,0 +1,230 @@
{
"data": {
"current_page": 1,
"total": 3,
"data": [
{
"id": 512,
"date": "13.09.2025",
"amount": 1,
"debt_amount": 0.9999795545,
"type": "income",
"project": {
"id": 89,
"name": "Mavrid",
"is_project": false
},
"currency": {
"id": 3,
"name": {
"ru": "US dollar",
"uz": "Aqsh dollari"
},
"symbol": "USD",
"icon": "https://backend.app.uyqur.uz/public/upload/image/LIMeAcPrnbN6X9m5uYMH.webp"
},
"user": null,
"financial": null,
"company_person": {
"id": 978,
"type": "supplier",
"name": "OOO\u003C\u003CIBRAT-AFZAL TRADE\u003E\u003E",
"description": null,
"start_date": "11.09.2025",
"status": "active",
"person": {
"id": 891,
"name": "OOO\u003C\u003CIBRAT-AFZAL TRADE\u003E\u003E",
"tin": "308342724",
"phone": "+998974047441",
"address": null,
"longitude": null,
"latitude": null
},
"balances": [
{
"id": 919,
"start_amount": 0,
"amount": 0,
"start_date": "11.09.2025",
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
}
}
]
},
"creator": {
"id": 111,
"full_name": "MARDONOVA DILAFRUZ",
"image": null
}
},
{
"id": 510,
"date": "12.09.2025",
"amount": 1111,
"debt_amount": 1111,
"type": "income",
"project": {
"id": 131,
"name": "Mezbon",
"is_project": false
},
"currency": {
"id": 3,
"name": {
"ru": "US dollar",
"uz": "Aqsh dollari"
},
"symbol": "USD",
"icon": "https://backend.app.uyqur.uz/public/upload/image/LIMeAcPrnbN6X9m5uYMH.webp"
},
"user": null,
"financial": null,
"company_person": {
"id": 938,
"type": "supplier",
"name": "1",
"description": null,
"start_date": "02.09.2025",
"status": "active",
"person": {
"id": 854,
"name": "11",
"tin": "11",
"phone": "+998111111111",
"address": null,
"longitude": null,
"latitude": null
},
"balances": [
{
"id": 870,
"start_amount": 0,
"amount": 0,
"start_date": "02.09.2025",
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
}
},
{
"id": 909,
"start_amount": 0,
"amount": 0,
"start_date": "09.09.2025",
"currency": {
"id": 3,
"name": {
"ru": "US dollar",
"uz": "Aqsh dollari"
},
"symbol": "USD",
"icon": "https://backend.app.uyqur.uz/public/upload/image/LIMeAcPrnbN6X9m5uYMH.webp"
}
}
]
},
"creator": {
"id": 111,
"full_name": "MARDONOVA DILAFRUZ",
"image": null
}
},
{
"id": 508,
"date": "10.09.2025",
"amount": 1,
"debt_amount": 1,
"type": "income",
"project": {
"id": 89,
"name": "Mavrid",
"is_project": false
},
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
},
"user": null,
"financial": null,
"company_person": {
"id": 938,
"type": "supplier",
"name": "1",
"description": null,
"start_date": "02.09.2025",
"status": "active",
"person": {
"id": 854,
"name": "11",
"tin": "11",
"phone": "+998111111111",
"address": null,
"longitude": null,
"latitude": null
},
"balances": [
{
"id": 870,
"start_amount": 0,
"amount": 0,
"start_date": "02.09.2025",
"currency": {
"id": 1,
"name": {
"ru": "Узбекский сум",
"uz": "O'zbek so'mi"
},
"symbol": "UZS",
"icon": "https://backend.app.uyqur.uz/public/upload/image/YZfoihq4VHMSHaZziQlp.webp"
}
},
{
"id": 909,
"start_amount": 0,
"amount": 0,
"start_date": "09.09.2025",
"currency": {
"id": 3,
"name": {
"ru": "US dollar",
"uz": "Aqsh dollari"
},
"symbol": "USD",
"icon": "https://backend.app.uyqur.uz/public/upload/image/LIMeAcPrnbN6X9m5uYMH.webp"
}
}
]
},
"creator": {
"id": 111,
"full_name": "MARDONOVA DILAFRUZ",
"image": null
}
}
]
},
"message": {
"uz": "Muvaffaqiyatli!",
"ru": "Успешно!",
"en": "Success!",
"tr": "Başarılı!"
}
}

View File

@@ -8,12 +8,15 @@ from core.apps.finance.models import Expence
class ExpenceFilter(django_filters.FilterSet): class ExpenceFilter(django_filters.FilterSet):
start_date = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
end_date = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')
date = django_filters.CharFilter(method='filter_by_created_at') date = django_filters.CharFilter(method='filter_by_created_at')
class Meta: class Meta:
model = Expence model = Expence
fields = [ fields = [
'payment_type', 'project_folder', 'project', 'user', 'expence_type', 'cash_transaction', 'date' 'payment_type', 'project_folder', 'project', 'user', 'expence_type', 'cash_transaction', 'date',
'audit', 'counterparty', 'start_date', 'end_date'
] ]
def filter_by_created_at(self, queryset, name, value): def filter_by_created_at(self, queryset, name, value):

View File

@@ -8,12 +8,15 @@ from core.apps.finance.models import Income
class IncomeFilter(django_filters.FilterSet): class IncomeFilter(django_filters.FilterSet):
start_date = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
end_date = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')
date = django_filters.CharFilter(method='filter_by_created_at') date = django_filters.CharFilter(method='filter_by_created_at')
class Meta: class Meta:
model = Income model = Income
fields = [ fields = [
'payment_type', 'project_folder', 'project', 'user', 'type_income', 'date', 'cash_transaction' 'payment_type', 'project_folder', 'project', 'user', 'type_income', 'date', 'cash_transaction',
'audit', 'counterparty', 'start_date', 'end_date'
] ]
def filter_by_created_at(self, queryset, name, value): def filter_by_created_at(self, queryset, name, value):

View File

@@ -0,0 +1,31 @@
import json
from django.core.management import BaseCommand
from core.apps.finance.models import CashTransaction
from core.apps.accounts.models import User
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('file_path', type=str)
def handle(self, *args, **options):
file_path = options['file_path']
with open(file_path, 'r') as f:
data = json.load(f)
for item in data['data']['cashs']:
users_data = item['users']
users_full_name = []
for user_data in users_data:
users_full_name.append(user_data['full_name'])
users = User.objects.filter(full_name__in=users_full_name)
cash_tx, created = CashTransaction.objects.get_or_create(
name=item['name']
)
cash_tx.employees.set(users)
cash_tx.save()
self.stdout.write("Cash Transactions added")

View File

@@ -0,0 +1,82 @@
import requests
from datetime import datetime
from django.core.management import BaseCommand
from core.apps.finance.models import (
Income,
Expence,
TypeIncome,
ExpenceType,
CashTransaction,
PaymentType
)
from core.apps.projects.models import ProjectFolder
from core.apps.accounts.models import User
from core.apps.wherehouse.models import WhereHouse
from core.apps.counterparty.models import Counterparty
from core.apps.orders.models import Party
headers = {
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MzIwMTY2OSwiZXhwIjoxNzYzMjg4MDY5LCJuYmYiOjE3NjMyMDE2NjksImp0aSI6IlBGZkVycXlzb0t2UzFKbXoiLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.YK-JSixgyK_cQVixR9rpEGSRbHmgpTd8vInkgtKT3Zk"
}
def get_data(id):
url = f"https://backend.app.uyqur.uz/main/payment/view?id={id}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
return response.json()
def create_expense(data):
statuses = {
"passive": "PENDING",
"active": "CONFIRMED",
"cancel": "CANCELLED"
}
user = User.objects.filter(full_name=data['creator']['full_name']).first()
cash_transaction = CashTransaction.objects.filter(name=data['cash']['name']).first()
expence_type = None
if data.get('financila'):
expence_type = ExpenceType.objects.filter(name=data['financial']['name']).first()
project_folder = None
if data.get('project'):
project_folder = ProjectFolder.objects.filter(name=data['project']['name']).first()
payment_type, created = PaymentType.objects.get_or_create(name=data['payment_type']['name'])
counterparty = None
if data.get('company_person'):
counterparty = Counterparty.objects.filter(name=data['company_person']['name']).first()
party = None
if data.get('order_ids'):
party_number = data.get('order_ids')[0]
party = Party.objects.filter(number=party_number).first()
Expence.objects.update_or_create(
user=user,
cash_transaction=cash_transaction,
expence_type=expence_type,
price=data['amount'],
currency=data['currency']['symbol'].lower(),
payment_type=payment_type,
project_folder= project_folder,
counterparty= counterparty,
exchange_rate= data['currency_amount'],
date=datetime.strptime(data['date'], "%d.%m.%Y").date(),
comment=data['description'],
status=statuses.get(data['status']),
party=party,
)
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('ids', type=int, nargs='+')
def handle(self, *args, **options):
ids = options['ids']
for id in ids:
data = get_data(id)
create_expense(data['data'])
self.stdout.write(self.style.SUCCESS("Expense qo'shildi"))

View File

@@ -0,0 +1,84 @@
import requests
from datetime import datetime
from django.core.management import BaseCommand
from core.apps.finance.models import (
Income,
Expence,
TypeIncome,
ExpenceType,
CashTransaction,
PaymentType
)
from core.apps.projects.models import ProjectFolder
from core.apps.accounts.models import User
from core.apps.wherehouse.models import WhereHouse
from core.apps.counterparty.models import Counterparty
from core.apps.orders.models import Party
headers = {
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MzIwMTY2OSwiZXhwIjoxNzYzMjg4MDY5LCJuYmYiOjE3NjMyMDE2NjksImp0aSI6IlBGZkVycXlzb0t2UzFKbXoiLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.YK-JSixgyK_cQVixR9rpEGSRbHmgpTd8vInkgtKT3Zk"
}
def get_data(page):
url = f"https://backend.app.uyqur.uz/main/payment/view?type=expense&size=100&page={page}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
return response.json()
def create_expense(data):
statuses = {
"passive": "PENDING",
"active": "CONFIRMED",
"cancel": "CANCELLED"
}
user = User.objects.filter(full_name=data['creator']['full_name']).first()
cash_transaction = CashTransaction.objects.filter(name=data['cash']['name']).first()
expence_type = None
if data.get('financila'):
expence_type = ExpenceType.objects.filter(name=data['financial']['name']).first()
project_folder = None
if data.get('project'):
project_folder = ProjectFolder.objects.filter(name=data['project']['name']).first()
payment_type, created = PaymentType.objects.get_or_create(name=data['payment_type']['name'])
counterparty = None
if data.get('company_person'):
counterparty = Counterparty.objects.filter(name=data['company_person']['name']).first()
party = None
if data.get('order_ids'):
party_number = data.get('order_ids')[0]
party = Party.objects.filter(number=party_number).first()
Expence.objects.update_or_create(
number=data.get('id'),
defaults=dict(
user=user,
cash_transaction=cash_transaction,
expence_type=expence_type,
price=data['amount'],
currency=data['currency']['symbol'].lower(),
payment_type=payment_type,
project_folder= project_folder,
counterparty= counterparty,
exchange_rate= data['currency_amount'],
date=datetime.strptime(data['date'], "%d.%m.%Y").date(),
comment=data['description'],
status=statuses.get(data['status']),
party=party,
)
)
class Command(BaseCommand):
def handle(self, *args, **options):
for page in range(1,235):
data = get_data(page)
for item in data['data']['data']:
create_expense(item)
print(page)
self.stdout.write(self.style.SUCCESS("Expenselar qo'shildi"))

View File

@@ -0,0 +1,42 @@
import json
from datetime import datetime
from django.core.management import BaseCommand
from core.apps.finance.models import ExpenceContract, ExpenceType
from core.apps.accounts.models import User
from core.apps.projects.models import Project, ProjectFolder
from core.apps.counterparty.models import Counterparty
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("file_path", type=str)
def handle(self, *args, **options):
file_path = options['file_path']
with open(file_path, 'r') as f:
data = json.load(f)
for item in data['data']['data']:
user = User.objects.filter(full_name=item['creator']['full_name']).first()
project_folder = ProjectFolder.objects.filter(name=item['project']['name']).first()
counterparty = None
if item.get('counterparty'):
counterparty = Counterparty.objects.filter(name=item['company_person']['name']).first()
expence_type = None
if item.get('financial'):
expence_type = ExpenceType.objects.filter(name=item['financial']['name']).first()
ExpenceContract.objects.get_or_create(
user=user,
project_folder=project_folder,
price=item['amount'],
currency=item['currency']['symbol'].lower(),
defaults={
"counterparty": counterparty,
"expence_type": expence_type,
"date": datetime.strptime(item['date'], "%d.%m.%Y").date(),
}
)
self.stdout.write(self.style.SUCCESS("Expence Contractlar qoshildi"))

View File

@@ -0,0 +1,68 @@
import requests
from datetime import datetime
from django.core.management import BaseCommand
from core.apps.finance.models import (
Income,
Expence,
TypeIncome,
ExpenceType,
CashTransaction,
PaymentType
)
from core.apps.projects.models import ProjectFolder
from core.apps.accounts.models import User
from core.apps.wherehouse.models import WhereHouse
from core.apps.counterparty.models import Counterparty
from core.apps.orders.models import Party
headers = {
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MzIwMTY2OSwiZXhwIjoxNzYzMjg4MDY5LCJuYmYiOjE3NjMyMDE2NjksImp0aSI6IlBGZkVycXlzb0t2UzFKbXoiLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.YK-JSixgyK_cQVixR9rpEGSRbHmgpTd8vInkgtKT3Zk"
}
def get_data():
url = f"https://backend.app.uyqur.uz/main/payment/view?type=income&size=1509"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
return response.json()
def create_income(data):
user = User.objects.filter(full_name=data['creator']['full_name']).first()
cash_transaction = CashTransaction.objects.filter(name=data['cash']['name']).first()
payment_type, created = PaymentType.objects.get_or_create(name=data['payment_type']['name'])
income_type = None
if data.get('financiel'):
income_type = TypeIncome.objects.filter(name=data['financial']['name']).first()
project_folder = None
if data.get('project'):
project_folder = ProjectFolder.objects.filter(name=data['project']['name']).first()
counterparty = None
if data.get('company_person'):
counterparty = Counterparty.objects.filter(name=data['company_person']['name']).first()
income, created = Income.objects.get_or_create(
price=data['amount'],
currency=data['currency']['symbol'].lower(),
cash_transaction=cash_transaction,
user=user,
type_income= income_type,
payment_type= payment_type,
project_folder= project_folder,
counterparty= counterparty,
exchange_rate= data['currency_amount'],
date= datetime.strptime(data['date'], "%d.%m.%Y").date(),
comment= data['description'],
)
class Command(BaseCommand):
def handle(self, *args, **options):
data = get_data()
for item in data['data']['data']:
create_income(item)
self.stdout.write(self.style.SUCCESS("Income qo'shildi"))

View File

@@ -0,0 +1,42 @@
import json
from datetime import datetime
from django.core.management import BaseCommand
from core.apps.finance.models import IncomeContract, TypeIncome
from core.apps.accounts.models import User
from core.apps.projects.models import Project, ProjectFolder
from core.apps.counterparty.models import Counterparty
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("file_path", type=str)
def handle(self, *args, **options):
file_path = options['file_path']
with open(file_path, 'r') as f:
data = json.load(f)
for item in data['data']['data']:
user = User.objects.filter(full_name=item['creator']['full_name']).first()
project_folder = ProjectFolder.objects.filter(name=item['project']['name']).first()
counterparty = None
if item.get('counterparty'):
counterparty = Counterparty.objects.filter(name=item['company_person']['name']).first()
income_type = None
if item.get('financial'):
income_type = TypeIncome.objects.filter(name=item['financial']['name']).first()
IncomeContract.objects.get_or_create(
user=user,
project_folder=project_folder,
price=item['amount'],
currency=item['currency']['symbol'].lower(),
defaults={
"counterparty": counterparty,
"income_type": income_type,
"date": datetime.strptime(item['date'], "%d.%m.%Y").date(),
}
)
self.stdout.write(self.style.SUCCESS("Expence Contractlar qoshildi"))

View File

@@ -0,0 +1,56 @@
import requests
from django.core.management import BaseCommand
from core.apps.finance.models import ExpenceType, TypeIncome
headers = {
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MTkyMDM2MSwiZXhwIjoxNzYyMDA2NzYxLCJuYmYiOjE3NjE5MjAzNjEsImp0aSI6Inhqak81azJLc2pSaEJJOGUiLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.ZcREfvT21qpd9eK_-zBumKBtaKKJ-l9QoudSLZ3IpP4"
}
def get_data():
url = "https://backend.app.uyqur.uz/main/financial/view"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
return response.json()
class Command(BaseCommand):
def handle(self, *args, **options):
data = get_data()
income_categories = {
"Boshqalar": "OTHERS",
"Doimiy daromad": "CONTANT_INCOME"
}
expence_categories = {
"Boshqalar": "OTHERS",
"Doimiy xarajatlar": "FIXED_COST",
}
activities = {
"Moliyaviy": "FINANCIAL",
"Sarmoya": "CAPITAL"
}
for income in data['data']['income']:
TypeIncome.objects.get_or_create(
name=income['name'],
defaults={
"comment": income['description'],
"category": income_categories.get(income['financial_category']['name']['uz']),
"activity": activities.get(income['activity_type']['name']['uz']),
}
)
for expence in data['data']['expense']:
ExpenceType.objects.get_or_create(
name=expence['name'],
defaults={
"comment": expence.get('description') if expence.get('description') else "Comment",
"category": expence_categories.get(expence['financial_category']['name']['uz']),
"activity": activities.get(expence['activity_type']['name']['uz']),
}
)
self.stdout.write("IncomeType and ExpenceType qo'shildi")

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.4 on 2025-09-29 15:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0030_incomechat_incomemessage'),
]
operations = [
migrations.AlterField(
model_name='expence',
name='audit',
field=models.CharField(blank=True, choices=[('CHECKED', 'tekshirildi'), ('PROCESS', 'jarayonda')], max_length=20, null=True),
),
migrations.AlterField(
model_name='income',
name='audit',
field=models.CharField(blank=True, choices=[('CHECKED', 'tekshirildi'), ('PROCESS', 'jarayonda')], max_length=20, null=True),
),
]

View File

@@ -0,0 +1,83 @@
# Generated by Django 5.2.4 on 2025-11-07 12:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0031_alter_expence_audit_alter_income_audit'),
]
operations = [
migrations.AlterField(
model_name='cashtransaction',
name='expence_balance_usd',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='cashtransaction',
name='expence_balance_uzs',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='cashtransaction',
name='income_balance_usd',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='cashtransaction',
name='income_balance_uzs',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='cashtransaction',
name='total_balance_usd',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='cashtransaction',
name='total_balance_uzs',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='expence',
name='price',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='expencecontract',
name='paid_price',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='expencecontract',
name='price',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='income',
name='price',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='incomecontract',
name='paid_price',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='incomecontract',
name='price',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='paymenttype',
name='total_usd',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
migrations.AlterField(
model_name='paymenttype',
name='total_uzs',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=15),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.4 on 2025-11-11 15:21
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0032_alter_cashtransaction_expence_balance_usd_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='deletedexpence',
name='user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_expences', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-11-12 15:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('finance', '0033_deletedexpence_user'),
]
operations = [
migrations.AddField(
model_name='expence',
name='number',
field=models.BigIntegerField(default=0),
),
]

View File

@@ -28,13 +28,13 @@ class CashTransaction(BaseModel):
CashTransactionFolder, on_delete=models.SET_NULL, related_name='cash_transactions', CashTransactionFolder, on_delete=models.SET_NULL, related_name='cash_transactions',
null=True, blank=True null=True, blank=True
) )
total_balance_usd = models.BigIntegerField(default=0) total_balance_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
income_balance_usd = models.BigIntegerField(default=0) income_balance_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
expence_balance_usd = models.BigIntegerField(default=0) expence_balance_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
total_balance_uzs = models.BigIntegerField(default=0) total_balance_uzs = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
income_balance_uzs = models.BigIntegerField(default=0) income_balance_uzs = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
expence_balance_uzs = models.BigIntegerField(default=0) expence_balance_uzs = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@@ -12,6 +12,7 @@ class Expence(BaseModel):
('PENDING', 'kutilmoqda'), ('PENDING', 'kutilmoqda'),
('CONFIRMED', 'tasdiqlangan'), ('CONFIRMED', 'tasdiqlangan'),
) )
number = models.BigIntegerField(default=0)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='expences', null=True) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='expences', null=True)
cash_transaction = models.ForeignKey(CashTransaction, on_delete=models.CASCADE, related_name='expences') cash_transaction = models.ForeignKey(CashTransaction, on_delete=models.CASCADE, related_name='expences')
payment_type = models.ForeignKey(PaymentType, on_delete=models.CASCADE, related_name='expences') payment_type = models.ForeignKey(PaymentType, on_delete=models.CASCADE, related_name='expences')
@@ -31,14 +32,17 @@ class Expence(BaseModel):
'orders.Party', on_delete=models.SET_NULL, null=True, blank=True, related_name='expences' 'orders.Party', on_delete=models.SET_NULL, null=True, blank=True, related_name='expences'
) )
price = models.PositiveBigIntegerField() price = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
exchange_rate = models.PositiveBigIntegerField(default=0, null=True, blank=True) exchange_rate = models.PositiveBigIntegerField(default=0, null=True, blank=True)
currency = models.CharField( currency = models.CharField(
max_length=3, choices=[('usd','usd'), ('uzs', 'uzs')] max_length=3, choices=[('usd','usd'), ('uzs', 'uzs')]
) )
date = models.DateField(null=True, blank=True) date = models.DateField(null=True, blank=True)
comment = models.TextField(null=True, blank=True) comment = models.TextField(null=True, blank=True)
audit = models.CharField(max_length=200, null=True, blank=True) audit = models.CharField(
max_length=20, choices=[('CHECKED', 'tekshirildi'),('PROCESS', 'jarayonda')],
null=True, blank=True
)
file = models.FileField(null=True, blank=True, upload_to='finance/expence/files/') file = models.FileField(null=True, blank=True, upload_to='finance/expence/files/')
status = models.CharField(max_length=20, choices=STATUS, default='PENDING', null=True, blank=True) status = models.CharField(max_length=20, choices=STATUS, default='PENDING', null=True, blank=True)
is_deleted = models.BooleanField(default=False) is_deleted = models.BooleanField(default=False)
@@ -54,6 +58,7 @@ class Expence(BaseModel):
class DeletedExpence(BaseModel): class DeletedExpence(BaseModel):
expence = models.ForeignKey(Expence, on_delete=models.CASCADE, related_name='deleted_expences') expence = models.ForeignKey(Expence, on_delete=models.CASCADE, related_name='deleted_expences')
comment = models.CharField(max_length=200) comment = models.CharField(max_length=200)
user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name='deleted_expences', null=True)
def __str__(self): def __str__(self):
return f'{self.expence} is deleted' return f'{self.expence} is deleted'

View File

@@ -21,9 +21,9 @@ class ExpenceContract(BaseModel):
Counterparty, on_delete=models.SET_NULL, related_name='expence_contracts', null=True, blank=True Counterparty, on_delete=models.SET_NULL, related_name='expence_contracts', null=True, blank=True
) )
price = models.PositiveBigIntegerField() price = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
currency = models.CharField(max_length=3, choices=[('uzs', 'uzs'), ('usd', 'usd')]) currency = models.CharField(max_length=3, choices=[('uzs', 'uzs'), ('usd', 'usd')])
paid_price = models.PositiveBigIntegerField(default=0, null=True, blank=True) paid_price = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
date = models.DateField() date = models.DateField()
comment = models.TextField(null=True, blank=True) comment = models.TextField(null=True, blank=True)

View File

@@ -26,12 +26,15 @@ class Income(BaseModel):
) )
currency = models.CharField(choices=[('uzs', 'uzs'),('usd', 'usd')], max_length=3) currency = models.CharField(choices=[('uzs', 'uzs'),('usd', 'usd')], max_length=3)
price = models.PositiveBigIntegerField() price = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
exchange_rate = models.PositiveBigIntegerField(default=0, null=True, blank=True) exchange_rate = models.PositiveBigIntegerField(default=0, null=True, blank=True)
date = models.DateField(null=True, blank=True) date = models.DateField(null=True, blank=True)
comment = models.TextField(null=True, blank=True) comment = models.TextField(null=True, blank=True)
file = models.FileField(upload_to='finance/income/file/', null=True, blank=True) file = models.FileField(upload_to='finance/income/file/', null=True, blank=True)
audit = models.CharField(max_length=200, null=True, blank=True) audit = models.CharField(
max_length=20, choices=[('CHECKED', 'tekshirildi'),('PROCESS', 'jarayonda')],
null=True, blank=True
)
is_deleted = models.BooleanField(default=False) is_deleted = models.BooleanField(default=False)
def __str__(self): def __str__(self):

View File

@@ -21,9 +21,9 @@ class IncomeContract(BaseModel):
) )
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='income_contracts') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='income_contracts')
price = models.PositiveBigIntegerField() price = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
currency = models.CharField(max_length=3, choices=[('uzs', 'uzs'), ('usd', 'usd')]) currency = models.CharField(max_length=3, choices=[('uzs', 'uzs'), ('usd', 'usd')])
paid_price = models.PositiveBigIntegerField(default=0, null=True, blank=True) paid_price = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
date = models.DateField() date = models.DateField()
comment = models.TextField(null=True, blank=True) comment = models.TextField(null=True, blank=True)

View File

@@ -6,8 +6,8 @@ from core.apps.shared.models import BaseModel
class PaymentType(BaseModel): class PaymentType(BaseModel):
name = models.CharField(max_length=200, unique=True) name = models.CharField(max_length=200, unique=True)
total_uzs = models.PositiveBigIntegerField(default=0) total_uzs = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
total_usd = models.PositiveBigIntegerField(default=0) total_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@@ -6,36 +6,71 @@ from core.apps.finance.models import CashTransaction, CashTransactionFolder
from core.apps.accounts.models import User from core.apps.accounts.models import User
from core.apps.finance.models import PaymentType from core.apps.finance.models import PaymentType
from core.apps.finance.serializers.payment_type import PaymentTypeSerializer from core.apps.finance.serializers.payment_type import PaymentTypeSerializer
from core.apps.projects.models import Project
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = [
'id', 'name', 'start_date', 'end_date', 'status'
]
class CashTransactionEmployeeListSerializer(serializers.ModelSerializer): class CashTransactionEmployeeListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = [ fields = [
'id', 'profile_image', 'first_name', 'last_name', 'username' 'id', 'profile_image', 'full_name', 'username'
] ]
class CashTransactionListSerializer(serializers.ModelSerializer): class CashTransactionListSerializer(serializers.ModelSerializer):
payment_type = PaymentTypeSerializer(many=True) payment_type = PaymentTypeSerializer(many=True)
employees = CashTransactionEmployeeListSerializer(many=True) employees = CashTransactionEmployeeListSerializer(many=True)
projects = ProjectSerializer(many=True)
class Meta: class Meta:
model = CashTransaction model = CashTransaction
fields = [ fields = [
'id', 'name', 'payment_type', 'employees', 'status', 'total_balance_usd', 'id', 'name', 'payment_type', 'employees', 'status', 'total_balance_usd',
'income_balance_usd', 'expence_balance_usd', 'total_balance_uzs', 'income_balance_usd', 'expence_balance_usd', 'total_balance_uzs',
'income_balance_uzs', 'expence_balance_uzs' 'income_balance_uzs', 'expence_balance_uzs', 'projects'
] ]
class CashTransactionUpdateSerializer(serializers.ModelSerializer): class CashTransactionUpdateSerializer(serializers.ModelSerializer):
project_ids = serializers.ListField(child=serializers.UUIDField(required=False), required=False)
class Meta: class Meta:
model = CashTransaction model = CashTransaction
fields = [ fields = [
'name', 'payment_type', 'employees', 'status', 'folder', 'name', 'payment_type', 'employees', 'status', 'folder', 'project_ids'
] ]
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.status = validated_data.get('status', instance.status)
instance.folder = validated_data.get('folder', instance.folder)
payment_types = validated_data.get('payment_type', None)
if payment_types is not None:
instance.payment_type.set(payment_types)
employees = validated_data.get('employees', None)
if employees is not None:
instance.employees.set(employees)
project_ids = validated_data.pop('project_ids', [])
if project_ids:
projects = Project.objects.filter(id__in=project_ids)
instance.projects.clear()
instance.projects.add(*projects)
instance.save()
return instance
class CashTransactionCreateSerializer(serializers.Serializer): class CashTransactionCreateSerializer(serializers.Serializer):
payment_type_ids = serializers.ListSerializer(child=serializers.UUIDField(), write_only=True) payment_type_ids = serializers.ListSerializer(child=serializers.UUIDField(), write_only=True)
@@ -43,6 +78,7 @@ class CashTransactionCreateSerializer(serializers.Serializer):
name = serializers.CharField() name = serializers.CharField()
status = serializers.BooleanField() status = serializers.BooleanField()
folder_id = serializers.UUIDField(required=False) folder_id = serializers.UUIDField(required=False)
project_ids = serializers.ListField(child=serializers.UUIDField(required=False), required=False)
def validate_name(self, value): def validate_name(self, value):
if CashTransaction.objects.filter(name=value).exists(): if CashTransaction.objects.filter(name=value).exists():
@@ -66,6 +102,11 @@ class CashTransactionCreateSerializer(serializers.Serializer):
status=validated_data.get('status'), status=validated_data.get('status'),
folder=validated_data.get('folder') folder=validated_data.get('folder')
) )
if validated_data.get('project_ids'):
projects = Project.objects.filter(id__in=validated_data.get('project_ids'))
for project in projects:
project.cash_transaction.add(cash_transaction)
project.save()
cash_transaction.employees.set(employee_ids) cash_transaction.employees.set(employee_ids)
cash_transaction.payment_type.set(payment_type_ids) cash_transaction.payment_type.set(payment_type_ids)
cash_transaction.save() cash_transaction.save()

View File

@@ -0,0 +1,26 @@
from rest_framework import serializers
from core.apps.finance.models import DeletedExpence
from core.apps.finance.serializers.expence import ExpenceListSerializer
class DeletedExpenceListSerializer(serializers.ModelSerializer):
expence = ExpenceListSerializer()
user = serializers.SerializerMethodField(method_name='get_user')
class Meta:
model = DeletedExpence
fields = [
'id',
'created_at',
'comment',
'expence',
'user'
]
def get_user(self, obj):
return {
"id": obj.user.id,
"full_name": obj.user.full_name,
} if obj.user else {}

View File

@@ -3,6 +3,7 @@ from django.db import transaction
from rest_framework import serializers from rest_framework import serializers
from core.apps.finance.models import Expence from core.apps.finance.models import Expence
from core.apps.notifications.utils.notify_user import notify_user
class ExpenceCreateSerializer(serializers.ModelSerializer): class ExpenceCreateSerializer(serializers.ModelSerializer):
@@ -13,8 +14,27 @@ class ExpenceCreateSerializer(serializers.ModelSerializer):
'counterparty', 'price', 'exchange_rate', 'currency', 'date', 'comment', 'audit', 'file' 'counterparty', 'price', 'exchange_rate', 'currency', 'date', 'comment', 'audit', 'file'
] ]
def validate(self, data):
cash_transaction = data.get('cash_transaction')
payment_type = data.get('payment_type')
price = data.get('price')
exchange_rate = data.get('exchange_rate', 1)
currency = data.get('currency', 'uzs')
final_price = price
if currency == 'uzs' and payment_type.total_uzs < final_price:
raise serializers.ValidationError(f"Yetarli UZS balansi yo'q. Mavjud: {payment_type.total_uzs}")
elif currency == 'usd' and payment_type.total_usd < final_price:
raise serializers.ValidationError(f"Yetarli USD balansi yo'q. Mavjud: {payment_type.total_usd}")
return data
def create(self, validated_data): def create(self, validated_data):
with transaction.atomic(): with transaction.atomic():
exchange_rate = validated_data.get('exchange_rate') or 1
final_price = validated_data.get('price')
expence = Expence.objects.create( expence = Expence.objects.create(
user=self.context.get('user'), user=self.context.get('user'),
cash_transaction=validated_data.get('cash_transaction'), cash_transaction=validated_data.get('cash_transaction'),
@@ -23,58 +43,72 @@ class ExpenceCreateSerializer(serializers.ModelSerializer):
project=validated_data.get('project'), project=validated_data.get('project'),
expence_type=validated_data.get('expence_type'), expence_type=validated_data.get('expence_type'),
counterparty=validated_data.get('counterparty'), counterparty=validated_data.get('counterparty'),
price=validated_data.get('price') * validated_data.get('exchange_rate') if validated_data.get('exchange_rate') else validated_data.get('price'), price=final_price,
exchange_rate=validated_data.get('exchange_rate'), exchange_rate=exchange_rate,
currency=validated_data.get('currency'), currency=validated_data.get('currency'),
date=validated_data.get('date'), date=validated_data.get('date'),
comment=validated_data.get('comment'), comment=validated_data.get('comment'),
audit=validated_data.get('audit'), audit=validated_data.get('audit'),
file=validated_data.get('file'), file=validated_data.get('file'),
) )
cash_transaction = expence.cash_transaction cash_transaction = expence.cash_transaction
payment_type = expence.payment_type payment_type = expence.payment_type
currency = validated_data.get('currency', 'uzs').lower()
user = self.context.get('user')
if validated_data.get('currency') == 'uzs': if currency == 'uzs':
cash_transaction.expence_balance_uzs += expence.price cash_transaction.expence_balance_uzs += expence.price
cash_transaction.total_balance_uzs = cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs cash_transaction.total_balance_uzs = cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs
if payment_type.total_uzs > expence.price:
payment_type.total_uzs -= expence.price payment_type.total_uzs -= expence.price
if expence.counterparty: if expence.counterparty:
if expence.counterparty.kredit_uzs != 0: if expence.counterparty.kredit_uzs > 0:
expence.counterparty.kredit_uzs -= expence.price expence.counterparty.kredit_uzs -= expence.price
expence.counterparty.total_kredit -= expence.price expence.counterparty.total_kredit -= expence.price
expence.counterparty.debit_uzs += expence.counterparty.kredit_uzs - expence.price expence.counterparty.debit_uzs += expence.price
expence.counterparty.total_debit += expence.price expence.counterparty.total_debit += expence.price
else: else:
expence.counterparty.debit_uzs += expence.price expence.counterparty.debit_uzs += expence.price
expence.counterparty.total_debit += expence.price expence.counterparty.total_debit += expence.price
expence.counterparty.save() expence.counterparty.save()
elif validated_data.get('currency') == 'usd': elif currency == 'usd':
cash_transaction.expence_balance_usd += expence.price cash_transaction.expence_balance_usd += expence.price
cash_transaction.total_balance_usd = cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd cash_transaction.total_balance_usd = cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd
if payment_type.total_usd > expence.price:
payment_type.total_usd -= expence.price payment_type.total_usd -= expence.price
if expence.counterparty: if expence.counterparty:
if expence.counterparty.kredit_usd != 0: if expence.counterparty.kredit_usd > 0:
expence.counterparty.kredit_usd -= validated_data.get('price') expence.counterparty.kredit_usd -= expence.price
expence.counterparty.total_kredit -= expence.price expence.counterparty.total_kredit -= expence.price
expence.counterparty.debit_usd += expence.counterparty.kredit_usd - validated_data.get('price') expence.counterparty.debit_usd += expence.price
expence.counterparty.total_debit += expence.price expence.counterparty.total_debit += expence.price
else: else:
expence.counterparty.debit_usd += validated_data.get('price') expence.counterparty.debit_usd += expence.price
expence.counterparty.total_debit += expence.price expence.counterparty.total_debit += expence.price
expence.counterparty.save() expence.counterparty.save()
body = f"""{user.full_name} {expence.price} {expence.currency.upper()}... \n screen: /monitoring"""
data = {
"screen": "/monitoring",
"type": "expence",
}
notify_user(user=user, title="Tasdiqlang yoki rad eting", body=body, data=data)
cash_transaction.save() cash_transaction.save()
payment_type.save() payment_type.save()
return expence return expence
class ExpenceListSerializer(serializers.ModelSerializer): class ExpenceListSerializer(serializers.ModelSerializer):
cash_transaction = serializers.SerializerMethodField(method_name='get_cash_transaction') cash_transaction = serializers.SerializerMethodField(method_name='get_cash_transaction')
payment_type = serializers.SerializerMethodField(method_name='get_payment_type') payment_type = serializers.SerializerMethodField(method_name='get_payment_type')
@@ -89,7 +123,7 @@ class ExpenceListSerializer(serializers.ModelSerializer):
fields = [ fields = [
'id', 'cash_transaction', 'payment_type', 'project_folder', 'project', 'expence_type', 'id', 'cash_transaction', 'payment_type', 'project_folder', 'project', 'expence_type',
'counterparty', 'price', 'exchange_rate', 'currency', 'date', 'comment', 'audit', 'file', 'counterparty', 'price', 'exchange_rate', 'currency', 'date', 'comment', 'audit', 'file',
'user', 'expence_chats', 'user', 'expence_chats', 'created_at'
] ]
def get_user(self, obj): def get_user(self, obj):
@@ -150,40 +184,93 @@ class ExpenceUpdateSerializer(serializers.ModelSerializer):
'price': {'required': False}, 'price': {'required': False},
} }
def update(self, instance, validated_data): def validate_price(self, value):
old_price = instance.price if value and value < 0:
instance.project_folder = validated_data.get('project_folder', instance.project_folder) raise serializers.ValidationError("Narxi manfiy bo'lishi mumkin emas")
instance.project = validated_data.get('project', instance.project) return value
instance.price = validated_data.get('price', instance.price)
instance.expence_type = validated_data.get('expence_type', instance.expence_type)
instance.counterparty = validated_data.get('counterparty', instance.counterparty)
instance.date = validated_data.get('date', instance.date)
instance.comment = validated_data.get('comment', instance.comment)
instance.audit = validated_data.get('audit', instance.audit)
instance.file = validated_data.get('file', instance.file)
if validated_data.get('price'):
cash_transaction = instance.cash_transaction
payment_type = instance.payment_type
if old_price > validated_data.get('price'):
if instance.currency == 'uzs':
cash_transaction.expence_balance_uzs -= (old_price - validated_data.get('price'))
cash_transaction.total_balance_uzs -= (old_price - validated_data.get('price'))
payment_type.total_uzs -= (old_price - validated_data.get('price')) if payment_type.total_uzs > (old_price - validated_data.get('price')) else 0
else:
cash_transaction.expence_balance_usd -= (old_price - validated_data.get('price'))
cash_transaction.total_balance_usd -= (old_price - validated_data.get('price'))
payment_type.total_usd -= (old_price - validated_data.get('price')) if payment_type.total_usd > (old_price - validated_data.get('price')) else 0
elif old_price < validated_data.get('price'): def update(self, instance, validated_data):
if instance.currency == 'uzs': with transaction.atomic():
cash_transaction.expence_balance_uzs += (old_price - validated_data.get('price')) old_price = instance.price
cash_transaction.total_balance_uzs += (old_price - validated_data.get('price')) old_counterparty = instance.counterparty
payment_type.total_uzs += (old_price - validated_data.get('price')) new_price = validated_data.get('price', instance.price)
else: new_counterparty = validated_data.get('counterparty', instance.counterparty)
cash_transaction.expence_balance_usd += (old_price - validated_data.get('price')) currency = instance.currency.lower()
cash_transaction.total_balance_usd += (old_price - validated_data.get('price'))
payment_type.total_usd += (old_price - validated_data.get('price')) instance.project_folder = validated_data.get('project_folder', instance.project_folder)
cash_transaction.save() instance.project = validated_data.get('project', instance.project)
payment_type.save() instance.price = new_price
instance.save() instance.expence_type = validated_data.get('expence_type', instance.expence_type)
return instance instance.counterparty = new_counterparty
instance.date = validated_data.get('date', instance.date)
instance.comment = validated_data.get('comment', instance.comment)
instance.audit = validated_data.get('audit', instance.audit)
instance.file = validated_data.get('file', instance.file)
if validated_data.get('price') and old_price != new_price:
price_difference = new_price - old_price
cash_transaction = instance.cash_transaction
payment_type = instance.payment_type
if currency == 'uzs':
cash_transaction.expence_balance_uzs += price_difference
cash_transaction.total_balance_uzs = (
cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs
)
payment_type.total_uzs -= price_difference
elif currency == 'usd':
cash_transaction.expence_balance_usd += price_difference
cash_transaction.total_balance_usd = (
cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd
)
payment_type.total_usd -= price_difference
cash_transaction.save()
payment_type.save()
if new_counterparty != old_counterparty:
if old_counterparty:
if currency == 'uzs':
if old_counterparty.debit_uzs > 0:
old_counterparty.debit_uzs -= old_price
old_counterparty.total_debit -= old_price
else:
old_counterparty.kredit_uzs += old_price
old_counterparty.total_kredit += old_price
else:
if old_counterparty.debit_usd > 0:
old_counterparty.debit_usd -= old_price
old_counterparty.total_debit -= old_price
else:
old_counterparty.kredit_usd += old_price
old_counterparty.total_kredit += old_price
old_counterparty.save()
if new_counterparty:
if currency == 'uzs':
if new_counterparty.kredit_uzs > 0:
new_counterparty.kredit_uzs -= new_price
new_counterparty.total_kredit -= new_price
new_counterparty.debit_uzs += new_price
new_counterparty.total_debit += new_price
else:
new_counterparty.debit_uzs += new_price
new_counterparty.total_debit += new_price
else:
if new_counterparty.kredit_usd > 0:
new_counterparty.kredit_usd -= new_price
new_counterparty.total_kredit -= new_price
new_counterparty.debit_usd += new_price
new_counterparty.total_debit += new_price
else:
new_counterparty.debit_usd += new_price
new_counterparty.total_debit += new_price
new_counterparty.save()
instance.save()
return instance

View File

@@ -84,4 +84,4 @@ class ExpenceContractCreateSerializer(serializers.ModelSerializer):
class ExpenceContractCalculatePriceSerializer(serializers.Serializer): class ExpenceContractCalculatePriceSerializer(serializers.Serializer):
price = serializers.IntegerField() price = serializers.DecimalField(max_digits=15, decimal_places=2, default=0.00)

View File

@@ -1,131 +1,182 @@
from django.db import transaction from django.db import transaction
from rest_framework import serializers from rest_framework import serializers
from core.apps.finance.models import Income from core.apps.finance.models import Income
class IncomeListSerializer(serializers.ModelSerializer): class IncomeListSerializer(serializers.ModelSerializer):
cash_transaction = serializers.SerializerMethodField(method_name='get_cash_transaction') cash_transaction = serializers.SerializerMethodField(
payment_type = serializers.SerializerMethodField(method_name='get_payment_type') method_name="get_cash_transaction"
project_folder = serializers.SerializerMethodField(method_name='get_project_folder') )
project = serializers.SerializerMethodField(method_name='get_project') payment_type = serializers.SerializerMethodField(method_name="get_payment_type")
counterparty = serializers.SerializerMethodField(method_name='get_counterparty') project_folder = serializers.SerializerMethodField(method_name="get_project_folder")
type_income = serializers.SerializerMethodField(method_name='get_type_income') project = serializers.SerializerMethodField(method_name="get_project")
user = serializers.SerializerMethodField(method_name='get_user') counterparty = serializers.SerializerMethodField(method_name="get_counterparty")
type_income = serializers.SerializerMethodField(method_name="get_type_income")
user = serializers.SerializerMethodField(method_name="get_user")
class Meta: class Meta:
model = Income model = Income
fields = [ fields = [
'id', 'cash_transaction', 'payment_type', 'project_folder', 'project', "id",
'counterparty', 'type_income', 'currency', 'price', 'exchange_rate', 'date', "cash_transaction",
'comment', 'file', 'audit', 'user', 'income_chat' "payment_type",
"project_folder",
"project",
"counterparty",
"type_income",
"currency",
"price",
"exchange_rate",
"date",
"comment",
"file",
"audit",
"user",
"income_chat",
"created_at",
] ]
def get_cash_transaction(self, obj): def get_cash_transaction(self, obj):
return { return {"id": obj.cash_transaction.id, "name": obj.cash_transaction.name}
'id': obj.cash_transaction.id,
'name': obj.cash_transaction.name
}
def get_payment_type(self, obj): def get_payment_type(self, obj):
return { return {"id": obj.payment_type.id, "name": obj.payment_type.name}
'id': obj.payment_type.id,
'name': obj.payment_type.name
}
def get_project_folder(self, obj): def get_project_folder(self, obj):
return { return (
'id': obj.project_folder.id, {"id": obj.project_folder.id, "name": obj.project_folder.name}
'name': obj.project_folder.name if obj.project_folder
} if obj.project_folder else None else None
)
def get_project(self, obj): def get_project(self, obj):
return { return {"id": obj.project.id, "name": obj.project.name} if obj.project else None
'id': obj.project.id,
'name': obj.project.name
} if obj.project else None
def get_counterparty(self, obj): def get_counterparty(self, obj):
return { return (
'id': obj.counterparty.id, {"id": obj.counterparty.id, "name": obj.counterparty.name}
'name': obj.counterparty.name if obj.counterparty
} if obj.counterparty else None else None
)
def get_type_income(self, obj): def get_type_income(self, obj):
return { return (
'id': obj.type_income.id, {"id": obj.type_income.id, "name": obj.type_income.name}
'name': obj.type_income.name if obj.type_income
} if obj.type_income else None else None
)
def get_user(self, obj): def get_user(self, obj):
return { return (
'id': obj.user.id, {
'full_name': obj.user.full_name, "id": obj.user.id,
} if obj.user else None "full_name": obj.user.full_name,
}
if obj.user
else None
)
class IncomeCreateSerializer(serializers.ModelSerializer): class IncomeCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Income model = Income
fields = [ fields = [
'cash_transaction', 'payment_type', 'project_folder', 'project', "cash_transaction",
'counterparty', 'type_income', 'currency', 'price', 'exchange_rate', 'date', "payment_type",
'comment', 'file', 'audit' "project_folder",
"project",
"counterparty",
"type_income",
"currency",
"price",
"exchange_rate",
"date",
"comment",
"file",
"audit",
] ]
def validate(self, data):
price = data.get('price')
exchange_rate = data.get('exchange_rate')
if price and price < 0:
raise serializers.ValidationError("Narxi manfiy bo'lishi mumkin emas")
if exchange_rate and exchange_rate < 0:
raise serializers.ValidationError("Kurs manfiy bo'lishi mumkin emas")
return data
def create(self, validated_data): def create(self, validated_data):
with transaction.atomic(): with transaction.atomic():
exchange_rate = validated_data.get("exchange_rate") or 1
final_price = validated_data.get("price")
income = Income.objects.create( income = Income.objects.create(
user=self.context.get('user'), user=self.context.get("user"),
cash_transaction=validated_data['cash_transaction'], cash_transaction=validated_data["cash_transaction"],
payment_type=validated_data['payment_type'], payment_type=validated_data["payment_type"],
project_folder=validated_data.get('project_folder'), project_folder=validated_data.get("project_folder"),
project=validated_data.get('project'), project=validated_data.get("project"),
counterparty=validated_data.get('counterparty'), counterparty=validated_data.get("counterparty"),
type_income=validated_data.get('type_income'), type_income=validated_data.get("type_income"),
currency=validated_data.get('currency'), currency=validated_data.get("currency"),
price=validated_data.get('price') * validated_data.get('exchange_rate') if validated_data.get('exchange_rate') else validated_data.get('price'), price=final_price,
exchange_rate=validated_data.get('exchange_rate'), exchange_rate=exchange_rate,
date=validated_data.get('date'), date=validated_data.get("date"),
comment=validated_data.get('comment'), comment=validated_data.get("comment"),
file=validated_data.get('file'), file=validated_data.get("file"),
audit=validated_data.get('audit') audit=validated_data.get("audit"),
) )
cash_transaction = income.cash_transaction cash_transaction = income.cash_transaction
payment_type = income.payment_type payment_type = income.payment_type
currency = validated_data.get("currency", "uzs").lower()
if validated_data.get('currency') == 'uzs': if currency == "uzs":
cash_transaction.income_balance_uzs += income.price cash_transaction.income_balance_uzs += income.price
cash_transaction.total_balance_uzs = cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs cash_transaction.total_balance_uzs = (
cash_transaction.income_balance_uzs
- cash_transaction.expence_balance_uzs
)
payment_type.total_uzs += income.price payment_type.total_uzs += income.price
if income.counterparty: if income.counterparty:
if income.counterparty.debit_uzs != 0: if income.counterparty.debit_uzs > 0:
income.counterparty.debit_uzs -= income.price income.counterparty.debit_uzs -= income.price
income.counterparty.total_debit -= income.price income.counterparty.total_debit -= income.price
income.counterparty.kredit_uzs += income.counterparty.debit_uzs - income.price income.counterparty.kredit_uzs += income.price
income.counterparty.total_kredit += income.price income.counterparty.total_kredit += income.price
else: else:
income.counterparty.kredit_uzs += income.price income.counterparty.kredit_uzs += income.price
income.counterparty.total_kredit += income.price income.counterparty.total_kredit += income.price
income.counterparty.save() income.counterparty.save()
elif validated_data.get('currency') == 'usd': elif currency == "usd":
cash_transaction.income_balance_usd += income.price cash_transaction.income_balance_usd += income.price
cash_transaction.total_balance_usd = cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd cash_transaction.total_balance_usd = (
cash_transaction.income_balance_usd
- cash_transaction.expence_balance_usd
)
payment_type.total_usd += income.price payment_type.total_usd += income.price
if income.counterparty: if income.counterparty:
if income.counterparty.debit_usd != 0: if income.counterparty.debit_usd > 0:
income.counterparty.debit_usd -= validated_data.get('price') income.counterparty.debit_usd -= income.price
income.counterparty.total_debit -= income.price income.counterparty.total_debit -= income.price
income.counterparty.kredit_usd += income.counterparty.debit_usd - validated_data.get('price') income.counterparty.kredit_usd += income.price
income.counterparty.total_kredit += income.price income.counterparty.total_kredit += income.price
else: else:
income.counterparty.kredit_usd += validated_data.get('price') income.counterparty.kredit_usd += income.price
income.counterparty.total_kredit += income.price income.counterparty.total_kredit += income.price
income.counterparty.save() income.counterparty.save()
cash_transaction.save() cash_transaction.save()
@@ -141,47 +192,112 @@ class IncomeUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Income model = Income
fields = [ fields = [
'project_folder', 'project', 'price', 'type_income', 'counterparty', "project_folder",
'date', 'comment', 'audit', 'file', "project",
"price",
"type_income",
"counterparty",
"date",
"comment",
"audit",
"file",
] ]
extra_kwargs = { extra_kwargs = {
'price': {'required': False}, "price": {"required": False},
} }
def update(self, instance, validated_data): def validate_price(self, value):
old_price = instance.price """Narxi manfiy bo'lmasligi tekshiruvi"""
instance.project_folder = validated_data.get('project_folder', instance.project_folder) if value and value < 0:
instance.project = validated_data.get('project', instance.project) raise serializers.ValidationError("Narxi manfiy bo'lishi mumkin emas")
instance.price = validated_data.get('price', instance.price) return value
instance.type_income = validated_data.get('type_income', instance.type_income)
instance.counterparty = validated_data.get('counterparty', instance.counterparty)
instance.date = validated_data.get('date', instance.date)
instance.comment = validated_data.get('comment', instance.comment)
instance.audit = validated_data.get('audit', instance.audit)
instance.file = validated_data.get('file', instance.file)
if validated_data.get('price'):
cash_transaction = instance.cash_transaction
payment_type = instance.payment_type
if old_price > validated_data.get('price'):
if instance.currency == 'uzs':
cash_transaction.income_balance_uzs -= (old_price - validated_data.get('price'))
cash_transaction.total_balance_uzs -= (old_price - validated_data.get('price'))
payment_type.total_uzs -= (old_price - validated_data.get('price')) if payment_type.total_uzs > (old_price - validated_data.get('price')) else 0
else:
cash_transaction.income_balance_usd -= (old_price - validated_data.get('price'))
cash_transaction.total_balance_usd -= (old_price - validated_data.get('price'))
payment_type.total_usd -= (old_price - validated_data.get('price')) if payment_type.total_usd > (old_price - validated_data.get('price')) else 0
elif old_price < validated_data.get('price'): def update(self, instance, validated_data):
if instance.currency == 'uzs': with transaction.atomic():
cash_transaction.income_balance_uzs += (old_price - validated_data.get('price')) old_price = instance.price
cash_transaction.total_balance_uzs += (old_price - validated_data.get('price')) old_counterparty = instance.counterparty
payment_type.total_uzs += (old_price - validated_data.get('price')) new_price = validated_data.get("price", instance.price)
else: new_counterparty = validated_data.get("counterparty", instance.counterparty)
cash_transaction.income_balance_usd += (old_price - validated_data.get('price')) currency = instance.currency.lower()
cash_transaction.total_balance_usd += (old_price - validated_data.get('price'))
payment_type.total_usd += (old_price - validated_data.get('price')) instance.project_folder = validated_data.get(
cash_transaction.save() "project_folder", instance.project_folder
payment_type.save() )
instance.save() instance.project = validated_data.get("project", instance.project)
return instance instance.price = new_price
instance.type_income = validated_data.get("type_income", instance.type_income)
instance.counterparty = new_counterparty
instance.date = validated_data.get("date", instance.date)
instance.comment = validated_data.get("comment", instance.comment)
instance.audit = validated_data.get("audit", instance.audit)
instance.file = validated_data.get("file", instance.file)
if validated_data.get("price") and old_price != new_price:
price_difference = new_price - old_price
cash_transaction = instance.cash_transaction
payment_type = instance.payment_type
if currency == "uzs":
cash_transaction.income_balance_uzs += price_difference
cash_transaction.total_balance_uzs = (
cash_transaction.income_balance_uzs
- cash_transaction.expence_balance_uzs
)
payment_type.total_uzs += price_difference
elif currency == "usd":
cash_transaction.income_balance_usd += price_difference
cash_transaction.total_balance_usd = (
cash_transaction.income_balance_usd
- cash_transaction.expence_balance_usd
)
payment_type.total_usd += price_difference
cash_transaction.save()
payment_type.save()
if new_counterparty != old_counterparty:
if old_counterparty:
if currency == "uzs":
if old_counterparty.kredit_uzs > 0:
old_counterparty.kredit_uzs -= old_price
old_counterparty.total_kredit -= old_price
else:
old_counterparty.debit_uzs += old_price
old_counterparty.total_debit += old_price
else:
if old_counterparty.kredit_usd > 0:
old_counterparty.kredit_usd -= old_price
old_counterparty.total_kredit -= old_price
else:
old_counterparty.debit_usd += old_price
old_counterparty.total_debit += old_price
old_counterparty.save()
if new_counterparty:
if currency == "uzs":
if new_counterparty.debit_uzs > 0:
new_counterparty.debit_uzs -= new_price
new_counterparty.total_debit -= new_price
new_counterparty.kredit_uzs += new_price
new_counterparty.total_kredit += new_price
else:
new_counterparty.kredit_uzs += new_price
new_counterparty.total_kredit += new_price
else:
if new_counterparty.debit_usd > 0:
new_counterparty.debit_usd -= new_price
new_counterparty.total_debit -= new_price
new_counterparty.kredit_usd += new_price
new_counterparty.total_kredit += new_price
else:
new_counterparty.kredit_usd += new_price
new_counterparty.total_kredit += new_price
new_counterparty.save()
instance.save()
return instance

View File

@@ -78,4 +78,4 @@ class IncomeContractCreateSerializer(serializers.ModelSerializer):
class IncomeContractCalculatePriceSerializer(serializers.Serializer): class IncomeContractCalculatePriceSerializer(serializers.Serializer):
price = serializers.IntegerField() price = serializers.DecimalField(max_digits=15, decimal_places=2, default=0.00)

View File

@@ -1,2 +0,0 @@
from .expence import *
from .income import *

View File

@@ -11,6 +11,7 @@ from core.apps.finance.views import income_contract as ic_views
from core.apps.finance.views import expence_contract as ec_views from core.apps.finance.views import expence_contract as ec_views
from core.apps.finance.views import expence_chat as ex_chat_views from core.apps.finance.views import expence_chat as ex_chat_views
from core.apps.finance.views import income_chat as in_chat_views from core.apps.finance.views import income_chat as in_chat_views
from core.apps.finance.views import deleted_expence as deleted_expence_views
urlpatterns = [ urlpatterns = [
@@ -110,4 +111,9 @@ urlpatterns = [
)), )),
] ]
)), )),
path('deleted_expence/', include(
[
path('list/', deleted_expence_views.DeletedExpenceListApiView.as_view()),
],
)),
] ]

View File

@@ -14,7 +14,7 @@ class CashTransactionListApiView(generics.ListAPIView):
permission_classes = [HasRolePermission] permission_classes = [HasRolePermission]
required_permissions = [] required_permissions = []
serializer_class = serializers.CashTransactionListSerializer serializer_class = serializers.CashTransactionListSerializer
queryset = CashTransaction.objects.prefetch_related('employees', 'payment_type') queryset = CashTransaction.objects.prefetch_related('employees', 'payment_type', 'projects')
pagination_class = CustomPageNumberPagination pagination_class = CustomPageNumberPagination
@@ -71,10 +71,18 @@ class CashTransactionStatisticsApiView(views.APIView):
def get(self, request): def get(self, request):
cash_transaction_ids = request.query_params.getlist('cash_transaction') cash_transaction_ids = request.query_params.getlist('cash_transaction')
start_date = request.query_params.get('start_date')
end_date = request.query_params.get('end_date')
project_ids = request.query_params.getlist('project_ids')
queryset = CashTransaction.objects.all()
if cash_transaction_ids: if cash_transaction_ids:
queryset = CashTransaction.objects.filter(id__in=cash_transaction_ids) queryset = CashTransaction.objects.filter(id__in=cash_transaction_ids)
else: if start_date and end_date:
queryset = CashTransaction.objects.all() queryset = CashTransaction.objects.filter(created_at__range=(start_date, end_date))
if project_ids:
queryset = CashTransaction.objects.filter(projects__id__in=project_ids)
res = queryset.aggregate( res = queryset.aggregate(
total_balance_usd=Sum('total_balance_usd'), total_balance_usd=Sum('total_balance_usd'),
income_balance_usd=Sum('income_balance_usd'), income_balance_usd=Sum('income_balance_usd'),

View File

@@ -0,0 +1,20 @@
from rest_framework import generics, response
from rest_framework.response import Response
from core.apps.finance.models import DeletedExpence
from core.apps.finance.serializers.deleted_expence import DeletedExpenceListSerializer
from core.apps.accounts.permissions.permissions import HasRolePermission
class DeletedExpenceListApiView(generics.GenericAPIView):
serializer_class = DeletedExpenceListSerializer
queryset = DeletedExpence.objects.select_related('expence', 'user').order_by('-created_at')
permission_classes = [HasRolePermission]
def get(self, request):
page = self.paginate_queryset(queryset=self.queryset)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.serializer_class(self.queryset, many=True)
return Response(serializer.data)

View File

@@ -1,4 +1,5 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db import transaction
from rest_framework import generics, views, parsers, filters from rest_framework import generics, views, parsers, filters
from rest_framework.response import Response from rest_framework.response import Response
@@ -74,7 +75,7 @@ class CounterpartyExpenceListApiView(generics.GenericAPIView):
queryset = Expence.objects.select_related( queryset = Expence.objects.select_related(
'cash_transaction', 'payment_type', 'project_folder', 'project', 'counterparty', 'expence_type', 'cash_transaction', 'payment_type', 'project_folder', 'project', 'counterparty', 'expence_type',
'user' 'user'
).exclude(counterparty__isnull=True) ).exclude(counterparty__isnull=True).distinct()
serializer_class = serializers.ExpenceListSerializer serializer_class = serializers.ExpenceListSerializer
def get(self, request, counterparty_id): def get(self, request, counterparty_id):
@@ -116,33 +117,64 @@ class ExpenceDeleteApiView(generics.GenericAPIView):
permission_classes = [HasRolePermission] permission_classes = [HasRolePermission]
def post(self, request, id): def post(self, request, id):
expence = get_object_or_404(Expence, id=id) expence = get_object_or_404(Expence, id=id, is_deleted=False)
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
comment = serializer.validated_data.get('comment')
DeletedExpence.objects.create(
expence=expence,
comment=comment
)
expence.is_deleted = True
if expence.currency == 'uzs':
expence.cash_transaction.expence_balance_uzs += expence.price
expence.cash_transaction.total_balance_uzs += expence.price
expence.payment_type.total_uzs += expence.price
else:
expence.cash_transaction.expence_balance_usd += expence.price
expence.cash_transaction.total_balance_usd += expence.price
expence.payment_type.total_usd += expence.price
expence.cash_transaction.save() if serializer.is_valid(raise_exception=True):
expence.payment_type.save() with transaction.atomic():
expence.save() comment = serializer.validated_data.get('comment')
return Response( currency = expence.currency.lower()
{
'success': True, DeletedExpence.objects.create(
'message': 'Expence deleted', expence=expence,
}, status=200 comment=comment,
) user=request.user,
)
cash_transaction = expence.cash_transaction
payment_type = expence.payment_type
counterparty = expence.counterparty
if currency == 'uzs':
cash_transaction.expence_balance_uzs -= expence.price
cash_transaction.total_balance_uzs = (
cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs
)
payment_type.total_uzs += expence.price
if counterparty and hasattr(counterparty, 'balance'):
counterparty.balance.balance_uzs -= expence.price
counterparty.balance.save()
elif currency == 'usd':
cash_transaction.expence_balance_usd -= expence.price
cash_transaction.total_balance_usd = (
cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd
)
payment_type.total_usd += expence.price
if counterparty and hasattr(counterparty, 'balance'):
counterparty.balance.balance_usd -= expence.price
counterparty.balance.save()
expence.is_deleted = True
cash_transaction.save()
payment_type.save()
expence.save()
return Response(
{
'success': True,
'message': "Expence o'chirildi",
'data': {
'expence_id': expence.id,
'price': expence.price,
'currency': expence.currency
}
},
status=200
)
class ExpenceUpdateApiView(generics.GenericAPIView): class ExpenceUpdateApiView(generics.GenericAPIView):

View File

@@ -1,4 +1,5 @@
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db import transaction
from rest_framework import generics, views, parsers, filters from rest_framework import generics, views, parsers, filters
from rest_framework.response import Response from rest_framework.response import Response
@@ -67,7 +68,7 @@ class CounterpartyIncomeListApiView(generics.GenericAPIView):
queryset = Income.objects.select_related( queryset = Income.objects.select_related(
'cash_transaction', 'payment_type', 'project_folder', 'project', 'counterparty', 'type_income', 'cash_transaction', 'payment_type', 'project_folder', 'project', 'counterparty', 'type_income',
'user' 'user'
).exclude(counterparty__isnull=True) ).exclude(counterparty__isnull=True).distinct()
serializer_class = serializers.IncomeListSerializer serializer_class = serializers.IncomeListSerializer
def get(self, request, counterparty_id): def get(self, request, counterparty_id):
@@ -84,33 +85,82 @@ class IncomeDeleteApiView(generics.GenericAPIView):
permission_classes = [HasRolePermission] permission_classes = [HasRolePermission]
def post(self, request, id): def post(self, request, id):
income = get_object_or_404(Income, id=id) income = get_object_or_404(Income, id=id, is_deleted=False)
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
comment = serializer.validated_data.get('comment')
DeletedIncome.objects.create(
income=income,
comment=comment
)
income.is_deleted = True
if income.currency == 'uzs':
income.cash_transaction.expence_balance_uzs -= income.price
income.cash_transaction.total_balance_uzs -= income.price
income.payment_type.total_uzs -= income.price if income.payment_type.total_uzs > income.price else 0
else:
income.cash_transaction.expence_balance_usd -= income.price
income.cash_transaction.total_balance_usd -= income.price
income.payment_type.total_usd -= income.price if income.payment_type.total_usd > income.price else 0
income.cash_transaction.save() if serializer.is_valid(raise_exception=True):
income.payment_type.save() with transaction.atomic():
income.save() comment = serializer.validated_data.get('comment')
return Response( currency = income.currency.lower()
{
'success': True, DeletedIncome.objects.create(
'message': 'Income deleted', income=income,
}, status=200 comment=comment
) )
cash_transaction = income.cash_transaction
payment_type = income.payment_type
counterparty = income.counterparty
if currency == 'uzs':
cash_transaction.income_balance_uzs -= income.price
cash_transaction.total_balance_uzs = (
cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs
)
payment_type.total_uzs -= income.price
if counterparty:
if counterparty.kredit_uzs > 0:
counterparty.kredit_uzs -= income.price
counterparty.total_kredit -= income.price
counterparty.debit_uzs += income.price
counterparty.total_debit += income.price
else:
counterparty.debit_uzs += income.price
counterparty.total_debit += income.price
counterparty.save()
elif currency == 'usd':
cash_transaction.income_balance_usd -= income.price
cash_transaction.total_balance_usd = (
cash_transaction.income_balance_usd - cash_transaction.expence_balance_usd
)
payment_type.total_usd -= income.price
if counterparty:
if counterparty.kredit_usd > 0:
counterparty.kredit_usd -= income.price
counterparty.total_kredit -= income.price
counterparty.debit_usd += income.price
counterparty.total_debit += income.price
else:
counterparty.debit_usd += income.price
counterparty.total_debit += income.price
counterparty.save()
income.is_deleted = True
cash_transaction.save()
payment_type.save()
income.save()
return Response(
{
'success': True,
'message': 'Income o\'chirildi',
'data': {
'income_id': income.id,
'price': income.price,
'currency': income.currency
}
},
status=200
)
class IncomeUpdateApiView(generics.GenericAPIView): class IncomeUpdateApiView(generics.GenericAPIView):

View File

@@ -0,0 +1,2 @@
from .notification import *
from .notification_history import *

View File

@@ -0,0 +1,5 @@
from django.contrib import admin
from core.apps.notifications.models import Notification
admin.site.register(Notification)

View File

@@ -0,0 +1,6 @@
from django.contrib import admin
from core.apps.notifications.models.notification_history import NotificationHistory
admin.site.register(NotificationHistory)

View File

@@ -0,0 +1,11 @@
from django.apps import AppConfig
class NotificationsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'core.apps.notifications'
def ready(self):
import core.apps.notifications.admin
from config.firebase import initialize_firebase
initialize_firebase()

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.2.4 on 2025-10-28 15:45
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('token', models.CharField(max_length=255, unique=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.2.4 on 2025-10-30 14:56
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notifications', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='notification',
name='type',
field=models.CharField(choices=[('web', 'web'), ('mobile', 'mobile')], default='mobile', max_length=6),
),
migrations.AlterUniqueTogether(
name='notification',
unique_together={('type', 'user', 'token')},
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.2.4 on 2025-10-30 16:16
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notifications', '0002_notification_type_alter_notification_unique_together'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='NotificationHistory',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=200)),
('body', models.TextField()),
('is_read', models.BooleanField(default=False)),
('data', models.JSONField(blank=True, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_histories', to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,2 @@
from .notification import *
from .notification_history import *

View File

@@ -0,0 +1,17 @@
from django.db import models
from core.apps.shared.models import BaseModel
from core.apps.accounts.models import User
class Notification(BaseModel):
type = models.CharField(
choices=[('web', 'web'), ('mobile', 'mobile')],
max_length=6,
default='mobile'
)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notifications')
token = models.CharField(max_length=255, unique=True)
class Meta:
unique_together = ('type', 'user', 'token')

View File

@@ -0,0 +1,17 @@
from django.db import models
from core.apps.shared.models import BaseModel
from core.apps.accounts.models import User
class NotificationHistory(BaseModel):
title = models.CharField(max_length=200)
body = models.TextField()
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='notification_histories'
)
is_read = models.BooleanField(default=False)
data = models.JSONField(null=True, blank=True)
def __str__(self):
return f'Notification to {self.user}: {self.title}'

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.notifications.models import Notification
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = [
'type', 'token'
]

View File

@@ -0,0 +1,11 @@
from rest_framework import serializers
from core.apps.notifications.models import NotificationHistory
class NotificationHistorySerializer(serializers.ModelSerializer):
class Meta:
model = NotificationHistory
fields = [
'id', 'title', 'body', 'data', 'is_read', 'created_at'
]

View File

@@ -0,0 +1,22 @@
from celery import shared_task
from django.shortcuts import get_object_or_404
from core.apps.notifications.models import NotificationHistory
from core.apps.accounts.models import User
@shared_task
def create_history(user_ids, title, body, data=None):
histories = []
for user_id in user_ids:
user = get_object_or_404(User, id=user_id)
histories.append(NotificationHistory(
title=title,
user=user,
body=body,
data=data,
is_read=False
))
NotificationHistory.objects.bulk_create(histories)

View File

@@ -0,0 +1,11 @@
from django.urls import path
from core.apps.notifications.views import notification
from core.apps.notifications.views import notification_history
urlpatterns = [
path('device/register/', notification.RegisterExpoPushToken.as_view()),
path('history/', notification_history.NotificationHistoryListApiView.as_view()),
path('history/<uuid:id>/read/', notification_history.NotificationHistoryUpdateApiView.as_view()),
]

View File

@@ -0,0 +1,11 @@
from core.apps.notifications.models import Notification
from core.apps.notifications.utils.send_notification import send_notification, send_web_notification
def notify_user(user, title, body, data):
tokens = Notification.objects.filter(user=user)
for token in tokens:
if token.type == 'mobile':
send_notification(token.token, title, body, data)
if token.type == 'web':
send_web_notification(token.token, title, body, data)

View File

@@ -0,0 +1,57 @@
import requests
from firebase_admin import messaging
from core.apps.notifications.models import Notification
from core.apps.notifications.tasks.create_notification_history import create_history
EXPO_API_URL = "https://exp.host/--/api/v2/push/send"
def send_notification(token, title, body, data=None):
tokens = list(Notification.objects.exclude(token=token).values_list("token", flat=True))
users = list(Notification.objects.exclude(token=token).values_list("user", flat=True))
create_history.delay(users, title, body, data)
if not tokens:
return {"error": "No tokens found"}
messages = [
{
"to": token,
"sound": "default",
"title": title,
"body": body,
"data": data or {},
}
for token in tokens
]
chunk_size = 100
results = []
for i in range(0, len(messages), chunk_size):
chunk = messages[i:i + chunk_size]
response = requests.post(EXPO_API_URL, json=chunk, headers={"Content-Type": "application/json"})
try:
results.append(response.json())
except Exception:
results.append({"error": "Invalid JSON response"})
return results
def send_web_notification(token, title, body, data=None):
tokens = list(Notification.objects.exclude(token=token).values_list('token', flat=True))
users = list(Notification.objects.exclude(token=token).values_list("user", flat=True))
create_history.delay(users, title, body, data)
if not tokens:
return
message = messaging.MulticastMessage(
notification=messaging.Notification(
title=title,
body=body
),
data=data or {},
tokens=tokens,
)
response = messaging.send_each_for_multicast(message)

View File

@@ -0,0 +1,25 @@
from rest_framework import generics, status
from rest_framework.response import Response
from core.apps.notifications.serializers import notification as serializers
from core.apps.notifications.models import Notification
from core.apps.accounts.permissions.permissions import HasRolePermission
class RegisterExpoPushToken(generics.GenericAPIView):
serializer_class = serializers.NotificationSerializer
queryset = Notification.objects.all()
permission_classes = [HasRolePermission]
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
Notification.objects.get_or_create(
user=request.user,
token=serializer.validated_data['token'],
type=serializer.validated_data.get('type') \
if serializer.validated_data.get('type') \
else 'mobile',
)
return Response({"message": "Token saqlandi"}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@@ -0,0 +1,43 @@
from django.shortcuts import get_object_or_404
from rest_framework import generics, views
from rest_framework.response import Response
from core.apps.accounts.permissions.permissions import HasRolePermission
from core.apps.notifications.models import NotificationHistory
from core.apps.notifications.serializers.notification_history import NotificationHistorySerializer
class NotificationHistoryListApiView(generics.GenericAPIView):
serializer_class = NotificationHistorySerializer
permission_classes = [HasRolePermission]
def get_queryset(self):
return NotificationHistory.objects.filter(user=self.request.user).order_by('-created_at')
def get(self, request):
queryset = self.get_queryset()
is_read = request.query_params.get('is_read')
if is_read:
if is_read.lower() == 'true':
queryset = queryset.filter(is_read=True)
if is_read.lower() == 'false':
queryset = queryset.filter(is_read=False)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
return None
class NotificationHistoryUpdateApiView(views.APIView):
permission_classes = [HasRolePermission]
def post(self, request, id):
obj = get_object_or_404(NotificationHistory, id=id)
if obj.is_read:
return Response({'success': False, "message": 'already readed'}, status=400)
obj.is_read = True
obj.save()
return Response({'success': True, "message": 'readed'}, status=200)

View File

@@ -5,4 +5,17 @@ from core.apps.orders.models import Order
@admin.register(Order) @admin.register(Order)
class OrderAdmin(admin.ModelAdmin): class OrderAdmin(admin.ModelAdmin):
list_display = ['id', 'product', 'wherehouse', 'currency'] list_display = ['id', 'product', 'wherehouse', 'currency', 'counterparty']
list_filter = ['counterparty']
search_fields = ['parties__number']
ordering = ('-created_at',)
# # Statuslar: {'ORDERED', 'PARTIALLY_RECIEVED', 'pending', "open", "buying", "rejected", "recieved", "passive"}
# open -> yangi == NEW
# ordered -> partiya qilingan == PARTY_IS_MADE
# pending -> kutilmoqda == EXPECTED
# passive -> qoralama == DRAFT
# recieved -> yopilgan == RECIEVED
# rejected -> bekor qilingan == CANCELLED
# buying -> sotib olinmoqda == PURCHASED
# partially_recieved -> jarayonda == PROCESS

View File

@@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from core.apps.orders.models import Party, PartyAmount, DeletedParty from core.apps.orders.models import DeletedParty, Party, PartyAmount
class PartyAmountInline(admin.StackedInline): class PartyAmountInline(admin.StackedInline):
@@ -11,17 +11,39 @@ class PartyAmountInline(admin.StackedInline):
@admin.register(Party) @admin.register(Party)
class PartyAdmin(admin.ModelAdmin): class PartyAdmin(admin.ModelAdmin):
list_display = ['id','number','mediator', 'delivery_date', 'payment_date', 'is_deleted'] list_display = [
"number",
"party_amount__total_price",
'party_amount__calculated_amount',
'party_amount__paid_amount',
"currency",
'process',
'payment_percentage',
'is_deleted',
]
inlines = [PartyAmountInline] inlines = [PartyAmountInline]
search_fields = [
"number", "orders__counterparty__name"
]
autocomplete_fields = ['orders']
ordering = ['-number']
list_editable = [
"currency",
]
list_filter = ['currency']
def get_queryset(self, request):
return super().get_queryset(request).select_related('party_amount', 'mediator').prefetch_related('orders')
@admin.register(PartyAmount) @admin.register(PartyAmount)
class PartyAmountAdmin(admin.ModelAdmin): class PartyAmountAdmin(admin.ModelAdmin):
list_display = ['id', 'total_price', 'cost_amount'] list_display = ["id", "total_price", "cost_amount"]
def has_module_permission(self, request): def has_module_permission(self, request):
return False return False
@admin.register(DeletedParty) @admin.register(DeletedParty)
class DeletedPartyAdmin(admin.ModelAdmin): class DeletedPartyAdmin(admin.ModelAdmin):
list_display = ['id', 'deleted_date', 'party'] list_display = ["id", "deleted_date", "party"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,47 @@
from django.db.models import Q
import django_filters import django_filters
from core.apps.orders.models.order import Order from core.apps.orders.models.order import Order
from core.apps.wherehouse.models import WhereHouse
from core.apps.projects.models import Project, ProjectFolder
class OrderFilter(django_filters.FilterSet): class OrderFilter(django_filters.FilterSet):
wherehouse = django_filters.ModelMultipleChoiceFilter(
field_name='wherehouse',
queryset=WhereHouse.objects.all()
)
project = django_filters.ModelMultipleChoiceFilter(
field_name='project',
queryset=Project.objects.all(),
method='filter_by_project_and_folder'
)
project_folder = django_filters.ModelMultipleChoiceFilter(
field_name='project_folder',
queryset=ProjectFolder.objects.all(),
method='filter_by_project_and_folder'
)
start_date = django_filters.DateFilter(field_name='date', lookup_expr='gte')
end_date = django_filters.DateFilter(field_name='date', lookup_expr='lte')
class Meta: class Meta:
model = Order model = Order
fields = [ fields = ['wherehouse', 'project', 'project_folder', 'date', ]
'wherehouse', 'project', 'project_folder', 'date',
]
def filter_by_project_and_folder(self, queryset, name, value):
project_ids = self.data.getlist('project')
folder_ids = self.data.getlist('project_folder')
if project_ids and folder_ids:
return queryset.filter(
Q(project__in=project_ids) | Q(project_folder__in=folder_ids)
)
if project_ids:
return queryset.filter(project__in=project_ids)
if folder_ids:
return queryset.filter(project_folder__in=folder_ids)
return queryset

View File

@@ -50,19 +50,23 @@ class PartyFilter(django_filters.FilterSet):
'status', 'payment_status', 'confirmation', 'status', 'payment_status', 'confirmation',
'orders__wherehouse', 'orders__project', 'orders__project_folder', 'orders__wherehouse', 'orders__project', 'orders__project_folder',
'mediator', 'orders__counterparty', 'min_price', 'max_price', 'mediator', 'orders__counterparty', 'min_price', 'max_price',
'qqs', 'discount', 'payment_type' 'qqs', 'discount', 'payment_type', 'audit'
] ]
@property
def qs(self):
return super().qs.distinct()
def filter_by_qqs(self, queryset, name, value): def filter_by_qqs(self, queryset, name, value):
if value == True: if value == True:
queryset = queryset.filter(orders__qqs__isnull=True) queryset = queryset.filter(orders__qqs__isnull=False)
return queryset return queryset
else: else:
return queryset return queryset
def filter_by_discount(self, queryset, name, value): def filter_by_discount(self, queryset, name, value):
if value == True: if value == True:
return queryset.filter(discount__isnull=True) return queryset.filter(discount__isnull=False)
else: else:
return queryset return queryset

View File

@@ -0,0 +1,39 @@
import json
from decimal import Decimal
import requests
from django.core.management import BaseCommand
from core.apps.accounts.models import User
from core.apps.counterparty.models import Counterparty
from core.apps.orders.models import Order, Party, PartyAmount
from core.apps.orders.utils.parse_date import parse_date
from core.apps.products.models import Product, Unity
from core.apps.projects.models import Project, ProjectFolder
from core.apps.wherehouse.models import WhereHouse
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("file_path", type=str)
def handle(self, *args, **options):
file_path = options['file_path']
with open(file_path, 'r') as f:
data = json.load(f)
count = 0
party_not = 0
party_number = []
for item in data['data']['data']:
party = Party.objects.filter(number=item['id']).first()
if party:
if party.party_amount.paid_amount != item['paid_amount']:
print(f"Party number: {party.number}: {party.party_amount.paid_amount} -> {item['paid_amount']}")
party.party_amount.paid_amount = Decimal(item['paid_amount'])
party.party_amount.save()
count += 1
print(count)
print(party_not)
# print(party_number)

View File

@@ -0,0 +1,60 @@
import json
from datetime import datetime
from django.core.management.base import BaseCommand
from core.apps.orders.models import Order
from core.apps.products.models import Unity, Product
from core.apps.projects.models import ProjectFolder
from core.apps.wherehouse.models import WhereHouse
from core.apps.accounts.models import User
class Command(BaseCommand):
help = "Import orders from JSON file"
def add_arguments(self, parser):
parser.add_argument("file_path", type=str, help="Path to JSON file")
def handle(self, *args, **options):
file_path = options['file_path']
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
for item in data['data']['data']:
product_name = item['product']['name']['uz']
unit_name = item['unit']['name']['uz']
warehouse_name = item['warehouse']['name']
project_name = item.get('project').get('name') if item.get('project') else None
creator_name = item['creator']['full_name']
product = Product.objects.filter(name=product_name).first()
unity = Unity.objects.filter(value=unit_name).first()
wherehouse = WhereHouse.objects.filter(name=warehouse_name).first()
project_folder = ProjectFolder.objects.filter(name=project_name).first()
user = User.objects.filter(full_name=creator_name).first()
delivery_date = datetime.strptime(item['delivery_date'], "%d.%m.%Y").date()
created_at = datetime.strptime(item['created_at'], "%d.%m.%Y %H:%M")
if not product:
product = Product.objects.create(
name=product_name,
product_code=item['product']['code'],
type=item['product']['resource']['type'].upper(),
unity=unity
)
Order.objects.update_or_create(
status=item['status'].upper(),
product=product,
unity=unity,
wherehouse=wherehouse,
project_folder=project_folder,
employee=user,
quantity=item['quantity'],
currency=item['currency']['symbol'].lower(),
created_at=created_at,
date=delivery_date,
type='order'
)
# break
self.stdout.write(self.style.SUCCESS("Orders imported successfully ✅"))

View File

@@ -0,0 +1,120 @@
import json
import requests
from django.core.management import BaseCommand
from core.apps.accounts.models import User
from core.apps.counterparty.models import Counterparty
from core.apps.orders.models import Order, Party, PartyAmount
from core.apps.orders.utils.parse_date import parse_date
from core.apps.products.models import Product, Unity
from core.apps.projects.models import Project, ProjectFolder
from core.apps.wherehouse.models import WhereHouse
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MzExMzM1OSwiZXhwIjoxNzYzMTk5NzU5LCJuYmYiOjE3NjMxMTMzNTksImp0aSI6IlZyVU5BVE9IVmJBNnpLbW0iLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.p4ndkWQYfXpftzUhgcxr3V9DQOarSz6Q0xbPhtezw70"
headers = {"Authorization": f"Bearer {token}"}
class Command(BaseCommand):
def handle(self, *args, **options):
for page in range(1,10479):
statuses = {
"open": "NEW",
"ordered": "PARTY_IS_MADE",
"pending": "EXPECTED",
"passive": "DRAFT",
"recieved": "RECIEVED",
"rejected": "CANCELLED",
"buying": "PURCHASED",
"partially_recieved": "PROCESS"
}
url = f"https://backend.app.uyqur.uz/main/supply/order-view?id={page}"
res = requests.get(url, headers=headers)
data = res.json()["data"]
if data:
user = None
if data.get("agent"):
user = User.objects.filter(full_name=data["agent"]["full_name"]).first()
if not user:
continue
party, created = Party.objects.update_or_create(
number=data["id"],
defaults={
"mediator": user,
"delivery_date": parse_date(data["delivery_date"]),
"closed_date": parse_date(data["recieved_date"]),
"order_date": parse_date(data["ordered_date"]),
"payment_date": (
parse_date(data["payment_date"])
if parse_date(data["payment_date"])
else parse_date(data["ordered_date"])
),
"status": statuses.get(data["status"].lower()) or 'NEW',
"payment_percentage": data["payment_percent"],
"process": data["percent"],
},
)
orders = []
total_price = 0
paid_amount = 0
calculated_amount = 0
must_pay_amount = 0
debt_amount = 0
for i in data["warehouse_products"]:
product = Product.objects.filter(
name__icontains=i["product"]["name"]["uz"]
).first()
if not product:
continue
unit, created = Unity.objects.get_or_create(value=i["unit"]["name"]["uz"])
counterparty = Counterparty.objects.filter(
name=i["company_person"]["name"]
).first()
wherehouse = None
if i.get("warehouse"):
wherehouse = WhereHouse.objects.filter(
name=i.get("warehouse").get("name")
).first()
project_folder = None
if i.get("project"):
project_folder = ProjectFolder.objects.filter(
name=i["project"]["name"]
).first()
order = Order.objects.create(
product=product,
amount=i["amount"],
total_price=i["total_amount"],
quantity=i["quantity"],
unity=unit,
currency=i["currency"]["symbol"].lower(),
project_folder=project_folder,
wherehouse=wherehouse,
counterparty=counterparty,
type='party',
)
total_price += i["total_amount"]
paid_amount += i["paid_amount"]
calculated_amount += i["calculated_amount"]
must_pay_amount += i["must_pay_amount"]
debt_amount += i['debt_amount']
orders.append(order)
party.orders.set(orders)
PartyAmount.objects.get_or_create(
party=party,
defaults={
"total_price": total_price,
"calculated_amount": calculated_amount,
"paid_amount": paid_amount,
"payment_amount": must_pay_amount,
"debt_amount": debt_amount,
# "total_expense_amount": item['total_expense_amount'],
},
)
print(page)
self.stdout.write("Parties added")

View File

@@ -0,0 +1,121 @@
import json
import requests
from django.core.management import BaseCommand
from core.apps.accounts.models import User
from core.apps.counterparty.models import Counterparty
from core.apps.orders.models import Order, Party, PartyAmount
from core.apps.orders.utils.parse_date import parse_date
from core.apps.products.models import Product, Unity
from core.apps.projects.models import Project, ProjectFolder
from core.apps.wherehouse.models import WhereHouse
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MzQ2MDcwNSwiZXhwIjoxNzYzNTQ3MTA1LCJuYmYiOjE3NjM0NjA3MDUsImp0aSI6IlRrUDNGS2t3djlaV21OVFIiLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.Ch6Gpqy3BM1qMSRkTMceasjw6qmvqxoQ5E-MPpS-YDQ"
headers = {"Authorization": f"Bearer {token}"}
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("ids", type=int, nargs="+")
def handle(self, *args, **options):
ids = options['ids']
statuses = {
"open": "NEW",
"ordered": "PARTY_IS_MADE",
"pending": "EXPECTED",
"passive": "DRAFT",
"recieved": "RECIEVED",
"rejected": "CANCELLED",
"buying": "PURCHASED",
"partially_recieved": "PROCESS"
}
for id in ids:
url = f"https://backend.app.uyqur.uz/main/supply/order-view?id={id}"
res = requests.get(url, headers=headers)
data = res.json()['data']
user = None
if data.get("agent"):
user = User.objects.filter(full_name=data["agent"]["full_name"]).first()
if not user:
continue
party, created = Party.objects.update_or_create(
number=data["id"],
defaults={
"mediator": user,
"delivery_date": parse_date(data["delivery_date"]),
"closed_date": parse_date(data["recieved_date"]),
"order_date": parse_date(data["ordered_date"]),
"payment_date": (
parse_date(data["payment_date"])
if parse_date(data["payment_date"])
else parse_date(data["ordered_date"])
),
"status": statuses.get(data["status"].lower()),
"payment_percentage": data["payment_percent"],
"process": data["percent"],
},
)
orders = []
total_price = 0
paid_amount = 0
calculated_amount = 0
must_pay_amount = 0
debt_amount = 0
for i in data["warehouse_products"]:
product = Product.objects.filter(
name__icontains=i["product"]["name"]["uz"]
).first()
if not product:
continue
unit, created = Unity.objects.get_or_create(value=i["unit"]["name"]["uz"])
counterparty = Counterparty.objects.filter(
name=i["company_person"]["name"]
).first()
wherehouse = None
if i.get("warehouse"):
wherehouse = WhereHouse.objects.filter(
name=i.get("warehouse").get("name")
).first()
project_folder = None
if i.get("project"):
project_folder = ProjectFolder.objects.filter(
name=i["project"]["name"]
).first()
total_amount = i["total_amount"] or i["calculated_amount"]
order = Order.objects.create(
product=product,
amount=i["amount"],
total_price=total_amount,
quantity=i["quantity"],
unity=unit,
currency=i["currency"]["symbol"].lower(),
project_folder=project_folder,
wherehouse=wherehouse,
counterparty=counterparty,
type='party',
received_count=i['recieved_quantity'],
)
total_price += total_amount
paid_amount += i["paid_amount"]
calculated_amount += i["calculated_amount"]
must_pay_amount += i["must_pay_amount"]
debt_amount += i['debt_amount']
orders.append(order)
party.orders.set(orders)
PartyAmount.objects.update_or_create(
party=party,
defaults={
"total_price": total_price,
"calculated_amount": calculated_amount,
"paid_amount": paid_amount,
"payment_amount": must_pay_amount,
"debt_amount": debt_amount,
# "total_expense_amount": item['total_expense_amount'],
},
)
self.stdout.write("Parties added")

View File

@@ -0,0 +1,11 @@
from django.core.management import BaseCommand
from core.apps.orders.models import Party, PartyAmount
class Command(BaseCommand):
def handle(self, *args, **options):
parties = Party.objects.all()
for party in parties:
party.party_amount.save()
self.stdout.write("Parties updated")

View File

@@ -0,0 +1,55 @@
import json
import requests
from django.core.management import BaseCommand
from core.apps.accounts.models import User
from core.apps.counterparty.models import Counterparty
from core.apps.orders.models import Order, Party, PartyAmount
from core.apps.orders.utils.parse_date import parse_date
from core.apps.products.models import Product, Unity
from core.apps.projects.models import Project, ProjectFolder
from core.apps.wherehouse.models import WhereHouse
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2Mjk1MjUxNiwiZXhwIjoxNzYzMDM4OTE2LCJuYmYiOjE3NjI5NTI1MTYsImp0aSI6IkVlcW1lVVluMUR0VTNvUDciLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.64QPbq6CeJqXubai4nMfH9RlJIJ0YUPFfJ298ar4YGQ"
headers = {"Authorization": f"Bearer {token}"}
def get_data(page):
url = f"https://backend.app.uyqur.uz/main/supply/order-view?size=1000&page={page}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json()
class Command(BaseCommand):
def handle(self, *args, **options):
for page in range(1, 100):
data = get_data(page)
for item in data['data']['data']:
try:
# Party ID orqali topish va yangilash
party = Party.objects.get(number=item["id"])
party.number = item["id"]
party.payment_percentage = item["payment_percent"]
party.process = item["percent"]
party.save()
self.stdout.write(
self.style.SUCCESS(
f'Party {item["id"]} updated successfully'
)
)
except Party.DoesNotExist:
self.stdout.write(
self.style.WARNING(
f'Party {item["id"]} not found'
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f'Error updating Party {item["id"]}: {str(e)}'
)
)
self.stdout.write(
self.style.SUCCESS('All parties processed')
)

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.4 on 2025-10-07 16:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0026_alter_order_qqs_alter_order_qqs_price_and_more'),
]
operations = [
migrations.AlterField(
model_name='partyamount',
name='calculated_amount',
field=models.BigIntegerField(default=0),
),
migrations.AlterField(
model_name='partyamount',
name='cost_amount',
field=models.BigIntegerField(default=0),
),
migrations.AlterField(
model_name='partyamount',
name='paid_amount',
field=models.BigIntegerField(default=0),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-10-08 14:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0027_alter_partyamount_calculated_amount_and_more'),
]
operations = [
migrations.AlterField(
model_name='order',
name='quantity',
field=models.FloatField(default=1),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-10-24 12:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0028_alter_order_quantity'),
]
operations = [
migrations.AlterField(
model_name='order',
name='status',
field=models.CharField(choices=[('NEW', 'yangi'), ('OPEN', 'ochiq'), ('CANCELLED', 'bekor qilindi'), ('ACCEPTED', 'qabul qilindi')], default='NEW', max_length=20),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.2.4 on 2025-10-25 16:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0029_alter_order_status'),
('wherehouse', '0017_wherehouse_users'),
]
operations = [
migrations.AlterField(
model_name='order',
name='wherehouse',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='wherehouse.wherehouse'),
),
migrations.AlterField(
model_name='party',
name='status',
field=models.CharField(choices=[('NEW', 'yangi'), ('PARTY_IS_MADE', 'partiya qilingan'), ('EXPECTED', 'kutilmoqda'), ('DRAFT', 'qoralama'), ('CANCELLED', 'bekor qilingan'), ('PURCHASED', 'sotib olinmoqda'), ('PROCESS', 'jarayonda'), ('RECIEVED', 'qabul qilingan')], default='NEW', max_length=20),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-10-31 15:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0030_alter_order_wherehouse_alter_party_status'),
]
operations = [
migrations.AddField(
model_name='order',
name='type',
field=models.CharField(choices=[('order', 'order'), ('party', 'party')], default='order', max_length=6),
),
]

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