Compare commits
339 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aeb39fb5b | ||
|
|
aacab8840f | ||
|
|
a32ef4e872 | ||
|
|
ce4603be07 | ||
|
|
f9141f3be0 | ||
|
|
59c1c455b2 | ||
|
|
5fcde1ec97 | ||
|
|
d665abe5ec | ||
|
|
d6ba8e006f | ||
|
|
c8d7f2d0b8 | ||
|
|
4360177c97 | ||
|
|
6e0cd06e32 | ||
|
|
39610223a3 | ||
|
|
f4d1b0afbe | ||
|
|
3d39a415eb | ||
|
|
89de3de54f | ||
|
|
e0169ab7a4 | ||
|
|
f87f9f0603 | ||
|
|
3cf60c54f9 | ||
|
|
56f04c558f | ||
|
|
9b3780c0a3 | ||
|
|
4ebd271af6 | ||
|
|
576d5c2257 | ||
|
|
c057f58e56 | ||
|
|
16cbc8edaa | ||
|
|
d21a75e8f7 | ||
|
|
05e371e22a | ||
|
|
f19f195980 | ||
|
|
6752a3e58d | ||
|
|
ff350bb922 | ||
|
|
adf6f69baf | ||
|
|
039761a68e | ||
|
|
3179dcc16c | ||
|
|
6563e4075a | ||
|
|
b89c380a41 | ||
|
|
180c59b338 | ||
|
|
204c39445e | ||
|
|
cbcfa7893a | ||
|
|
e39efc5300 | ||
|
|
bac0040672 | ||
|
|
5337656005 | ||
|
|
58ba6c3f58 | ||
|
|
e84937270e | ||
|
|
0431800f61 | ||
|
|
d74c4d7af8 | ||
|
|
a0d2b6d0c5 | ||
|
|
972df3f0fc | ||
|
|
da1d851093 | ||
|
|
d7b9264546 | ||
|
|
acd4bfd30d | ||
|
|
7d7e7a387a | ||
|
|
c06893c41e | ||
|
|
d63ac8d129 | ||
|
|
eeb374e963 | ||
|
|
042366258f | ||
|
|
19f2ba6976 | ||
|
|
3eba00f525 | ||
|
|
b5c43f8d48 | ||
|
|
3680b4f573 | ||
|
|
2585b055d1 | ||
|
|
4f1cfe623e | ||
|
|
8f47f65cb2 | ||
|
|
51ec0a6a22 | ||
|
|
3b9ffb15bc | ||
|
|
5f6b606d19 | ||
|
|
4c72a2de31 | ||
|
|
41a01b4251 | ||
|
|
d376aaa6c1 | ||
|
|
035a294a64 | ||
|
|
5af320c328 | ||
|
|
e26f443791 | ||
|
|
2a67e69d06 | ||
|
|
2cce3e9256 | ||
|
|
cf2bb89e4c | ||
|
|
32ec86b3b4 | ||
|
|
1878e8696e | ||
|
|
451a8f3bd5 | ||
|
|
ab71bf879e | ||
|
|
2df1230a3e | ||
|
|
94ecaa27eb | ||
|
|
5dbd4a0358 | ||
|
|
a899cfd7a8 | ||
|
|
754a976cf8 | ||
|
|
587b262d24 | ||
|
|
b7ae230209 | ||
|
|
4445a65881 | ||
|
|
109ab308f9 | ||
|
|
c403e3921a | ||
|
|
8cd8dda764 | ||
|
|
4aea353b75 | ||
|
|
fe1542b540 | ||
|
|
db8218de21 | ||
|
|
25898a7864 | ||
|
|
333ffac4e6 | ||
|
|
53c0b760e1 | ||
|
|
e35ff6bda7 | ||
|
|
0ffae42547 | ||
|
|
1b61d2327a | ||
|
|
64354fee80 | ||
|
|
369f4c9c0b | ||
|
|
9e2229fd86 | ||
|
|
e4e3b83a8c | ||
|
|
fb5b13fb27 | ||
|
|
5606c96ab2 | ||
|
|
8f442780ed | ||
|
|
9573e72919 | ||
|
|
e96475c81d | ||
|
|
94d14d548e | ||
|
|
3f8f3275bd | ||
|
|
1cd005c804 | ||
|
|
8a298b76f1 | ||
|
|
a9df29200a | ||
|
|
50eb701490 | ||
|
|
8dca5505d8 | ||
|
|
c113f003df | ||
|
|
f65d07f42b | ||
|
|
b9056b1127 | ||
|
|
1760bdc42f | ||
|
|
e47902a42f | ||
|
|
668196a696 | ||
|
|
e2358b26e5 | ||
|
|
3f5d339d38 | ||
|
|
5d49e8aa16 | ||
|
|
fb7806d612 | ||
|
|
75a71235a7 | ||
|
|
2e3b76d33f | ||
|
|
2905bafd46 | ||
|
|
71a5a424bf | ||
|
|
9a62257ba4 | ||
|
|
6a18bce7f3 | ||
|
|
bc532c8c65 | ||
|
|
338a1e5c02 | ||
|
|
5ff4bcfaba | ||
|
|
8ae2cce79e | ||
|
|
512bdff708 | ||
|
|
e9b231939c | ||
|
|
c76aef126e | ||
|
|
8178993dc0 | ||
|
|
ea990187b3 | ||
|
|
d49d5b5e47 | ||
|
|
8db5b3e4d4 | ||
|
|
541ec24c3f | ||
|
|
62038128db | ||
|
|
f826361ba0 | ||
|
|
6abc010eb3 | ||
|
|
b7eaae1fe9 | ||
|
|
eca74810ae | ||
|
|
f0f72052d5 | ||
|
|
876b30b107 | ||
|
|
3d9bf5bea6 | ||
|
|
4c441d4418 | ||
|
|
506129b592 | ||
|
|
dd513a44e2 | ||
|
|
9510bd2c72 | ||
|
|
65d99c4e2d | ||
|
|
7a430052c2 | ||
|
|
ee20fa6ce3 | ||
|
|
c451702706 | ||
|
|
49cad981e6 | ||
|
|
9f79b30b06 | ||
|
|
93f603afe5 | ||
|
|
502ba13775 | ||
|
|
52daf9513b | ||
|
|
ffa08162d6 | ||
|
|
defc845148 | ||
|
|
c8f88a0b2d | ||
|
|
85853c2e7d | ||
|
|
2769b3cc37 | ||
|
|
6bd866c53c | ||
|
|
4afc34ace8 | ||
|
|
912cf864de | ||
|
|
3f8bf3c391 | ||
|
|
a2f23fb601 | ||
|
|
b3845d8d74 | ||
|
|
07a5489771 | ||
|
|
3f2b86be57 | ||
|
|
713cb61d4e | ||
|
|
b5e87151cf | ||
|
|
ed9e02d25c | ||
|
|
9d83ccd7ed | ||
|
|
700a92710f | ||
|
|
f5cebc4f37 | ||
|
|
73112a90de | ||
|
|
0c2d73f892 | ||
|
|
f49a446c71 | ||
|
|
cac1baeac1 | ||
|
|
c60b3ef985 | ||
|
|
f2f66754b6 | ||
|
|
6d8f4f44c6 | ||
|
|
1f254464aa | ||
|
|
30b0f5f50a | ||
|
|
7c51b47a53 | ||
|
|
021bc7ed83 | ||
|
|
5d231b6047 | ||
|
|
5c845917df | ||
|
|
01fac3110e | ||
|
|
31f894d0ff | ||
|
|
29e8dea345 | ||
|
|
3722c37d76 | ||
|
|
c7595f6904 | ||
|
|
70d39f531f | ||
|
|
e13e8d0153 | ||
|
|
81f4f4aa1f | ||
|
|
b8decb3a60 | ||
|
|
9870f2d208 | ||
|
|
96a3be9f3c | ||
|
|
d0901e6589 | ||
|
|
d9dc4ea25b | ||
|
|
3ece1c00c1 | ||
|
|
61a4d1c4ba | ||
|
|
bbdfd9abcf | ||
|
|
21b5879a78 | ||
|
|
22a54480a4 | ||
|
|
21b868c06f | ||
|
|
ec6d3dd172 | ||
|
|
af8d212b3e | ||
|
|
54ffafc188 | ||
|
|
22b1d37a1a | ||
|
|
686c4eee8b | ||
|
|
1932bc316a | ||
|
|
2afc2cc533 | ||
|
|
9b4f343f77 | ||
|
|
b6b2875e0d | ||
|
|
ffd6394430 | ||
|
|
be9e415dcd | ||
|
|
8184b13d9f | ||
|
|
82d85f8152 | ||
|
|
9efc8aef40 | ||
|
|
fe543fad5a | ||
|
|
a542613841 | ||
|
|
0899595781 | ||
|
|
f11aca5c10 | ||
|
|
3b465c5aa4 | ||
|
|
d7f2a9a1c8 | ||
|
|
44c3fd5869 | ||
|
|
544b04a21e | ||
|
|
fd6b9ab0c2 | ||
|
|
e5c7df9d75 | ||
|
|
eee3cd5064 | ||
|
|
7ba54bfeb2 | ||
|
|
33d8f50ca2 | ||
|
|
0c460b507e | ||
|
|
4cda8468fc | ||
|
|
e82aa0950f | ||
|
|
f2c583fe01 | ||
|
|
667d62ebaa | ||
|
|
7ba7f00641 | ||
|
|
2c8d3b871e | ||
|
|
22b5873670 | ||
|
|
d8e5533481 | ||
|
|
ffefa6ff67 | ||
|
|
5dfda150f2 | ||
|
|
95d8c5b5d0 | ||
|
|
f79e4dc950 | ||
|
|
1b64dec1f7 | ||
|
|
c97e3479fa | ||
|
|
808f716019 | ||
|
|
0875c81da6 | ||
|
|
876ea5ffe6 | ||
|
|
06f47a63dc | ||
|
|
772c7deb66 | ||
|
|
1dc728d1e8 | ||
|
|
3543d3def0 | ||
|
|
f74999177a | ||
|
|
b78d7d38c7 | ||
|
|
cf7dca6f8e | ||
|
|
1fae231fc1 | ||
|
|
0364f69014 | ||
|
|
f5411621f8 | ||
|
|
6ba604ea1b | ||
|
|
e1c5db8122 | ||
|
|
0a856b05f7 | ||
|
|
377cc531f6 | ||
|
|
aa8be776bc | ||
|
|
50e989f8e1 | ||
|
|
c7dfe2ed5d | ||
|
|
e726773658 | ||
|
|
c66942db74 | ||
|
|
d635dcf5de | ||
|
|
61d291e6c6 | ||
|
|
df9bd37f87 | ||
|
|
9bdd8ca18a | ||
|
|
66ab98d259 | ||
|
|
1f0af73ade | ||
|
|
f385bf5d65 | ||
|
|
e01017123c | ||
|
|
c691bfea94 | ||
|
|
e66aa6b213 | ||
|
|
ceef207b19 | ||
|
|
2f1dc3c8a6 | ||
|
|
896654b016 | ||
|
|
cbe514f3b3 | ||
|
|
8a592f6214 | ||
|
|
640a9cd457 | ||
|
|
7893b18c6b | ||
|
|
20211ec837 | ||
|
|
5ae183b33d | ||
|
|
7146abe9f9 | ||
|
|
af564d71bf | ||
|
|
4ef38fcfe1 | ||
|
|
2e1120765c | ||
|
|
32cf95f701 | ||
|
|
0142a0f3e1 | ||
|
|
8ee0ab6a75 | ||
|
|
f2114dbfff | ||
|
|
96425f7484 | ||
|
|
57cd337570 | ||
|
|
07a74b2af7 | ||
|
|
de3956977a | ||
|
|
8a03766a8c | ||
|
|
c3a0b94082 | ||
|
|
a324998c4c | ||
|
|
5ff390e2d7 | ||
|
|
5a6111d464 | ||
|
|
11de0f01fe | ||
|
|
5be46f6858 | ||
|
|
fbebccd00a | ||
|
|
729ea67660 | ||
|
|
1823bfec61 | ||
|
|
17bdb625ea | ||
|
|
7d224b19fa | ||
|
|
507bb0bc12 | ||
|
|
9193e0f9dc | ||
|
|
64a5c55ab6 | ||
|
|
2120e6ac98 | ||
|
|
532f54b730 | ||
|
|
9be6825a19 | ||
|
|
72f7ddf8bb | ||
|
|
653e9d0230 | ||
|
|
5791533170 | ||
|
|
fa60bc69bc | ||
|
|
d994fcb16a | ||
|
|
5cf6aafdcc | ||
|
|
a76d84f042 | ||
|
|
c37c36582e | ||
|
|
d7ea523b11 | ||
|
|
f4f54801db | ||
|
|
2fd5d685e2 | ||
|
|
9a74eb2253 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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/
|
||||||
|
|||||||
@@ -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',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
11
config/firebase.py
Normal 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)
|
||||||
13
config/firebase/ibapp-firebase-key.json
Normal file
13
config/firebase/ibapp-firebase-key.json
Normal 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"
|
||||||
|
}
|
||||||
@@ -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 *
|
|
||||||
|
|||||||
@@ -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')),
|
||||||
]
|
]
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
28
core/apps/accounts/management/commands/import_users.py
Normal file
28
core/apps/accounts/management/commands/import_users.py
Normal 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")
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|
||||||
12895
core/apps/counterparty/data/counterparty.json
Normal file
12895
core/apps/counterparty/data/counterparty.json
Normal file
File diff suppressed because it is too large
Load Diff
52
core/apps/counterparty/data/counterparty_folder.json
Normal file
52
core/apps/counterparty/data/counterparty_folder.json
Normal 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!"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
|
|
||||||
@@ -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'
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
28
core/apps/counterparty/serializers/counterparty_balance.py
Normal file
28
core/apps/counterparty/serializers/counterparty_balance.py
Normal 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'
|
||||||
|
]
|
||||||
@@ -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(
|
||||||
|
|||||||
77
core/apps/counterparty/utils/counterparty.py
Normal file
77
core/apps/counterparty/utils/counterparty.py
Normal 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'
|
||||||
|
])
|
||||||
@@ -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 BO‘LMAYDI
|
||||||
|
"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
|
||||||
@@ -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']
|
||||||
|
|
||||||
@@ -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']
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
|
||||||
128
core/apps/finance/data/cash_transaction.json
Normal file
128
core/apps/finance/data/cash_transaction.json
Normal 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!"
|
||||||
|
}
|
||||||
|
}
|
||||||
155
core/apps/finance/data/expence_contract.json
Normal file
155
core/apps/finance/data/expence_contract.json
Normal 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ı!"
|
||||||
|
}
|
||||||
|
}
|
||||||
230
core/apps/finance/data/income_contract.json
Normal file
230
core/apps/finance/data/income_contract.json
Normal 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ı!"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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")
|
||||||
82
core/apps/finance/management/commands/import_data_by_id.py
Normal file
82
core/apps/finance/management/commands/import_data_by_id.py
Normal 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"))
|
||||||
84
core/apps/finance/management/commands/import_expence.py
Normal file
84
core/apps/finance/management/commands/import_expence.py
Normal 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"))
|
||||||
@@ -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"))
|
||||||
68
core/apps/finance/management/commands/import_income.py
Normal file
68
core/apps/finance/management/commands/import_income.py
Normal 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"))
|
||||||
@@ -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"))
|
||||||
@@ -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")
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
21
core/apps/finance/migrations/0033_deletedexpence_user.py
Normal file
21
core/apps/finance/migrations/0033_deletedexpence_user.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/apps/finance/migrations/0034_expence_number.py
Normal file
18
core/apps/finance/migrations/0034_expence_number.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
26
core/apps/finance/serializers/deleted_expence.py
Normal file
26
core/apps/finance/serializers/deleted_expence.py
Normal 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 {}
|
||||||
@@ -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 validate_price(self, value):
|
||||||
|
if value and value < 0:
|
||||||
|
raise serializers.ValidationError("Narxi manfiy bo'lishi mumkin emas")
|
||||||
|
return value
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
with transaction.atomic():
|
||||||
old_price = instance.price
|
old_price = instance.price
|
||||||
|
old_counterparty = instance.counterparty
|
||||||
|
new_price = validated_data.get('price', instance.price)
|
||||||
|
new_counterparty = validated_data.get('counterparty', instance.counterparty)
|
||||||
|
currency = instance.currency.lower()
|
||||||
|
|
||||||
instance.project_folder = validated_data.get('project_folder', instance.project_folder)
|
instance.project_folder = validated_data.get('project_folder', instance.project_folder)
|
||||||
instance.project = validated_data.get('project', instance.project)
|
instance.project = validated_data.get('project', instance.project)
|
||||||
instance.price = validated_data.get('price', instance.price)
|
instance.price = new_price
|
||||||
instance.expence_type = validated_data.get('expence_type', instance.expence_type)
|
instance.expence_type = validated_data.get('expence_type', instance.expence_type)
|
||||||
instance.counterparty = validated_data.get('counterparty', instance.counterparty)
|
instance.counterparty = new_counterparty
|
||||||
instance.date = validated_data.get('date', instance.date)
|
instance.date = validated_data.get('date', instance.date)
|
||||||
instance.comment = validated_data.get('comment', instance.comment)
|
instance.comment = validated_data.get('comment', instance.comment)
|
||||||
instance.audit = validated_data.get('audit', instance.audit)
|
instance.audit = validated_data.get('audit', instance.audit)
|
||||||
instance.file = validated_data.get('file', instance.file)
|
instance.file = validated_data.get('file', instance.file)
|
||||||
if validated_data.get('price'):
|
|
||||||
|
if validated_data.get('price') and old_price != new_price:
|
||||||
|
price_difference = new_price - old_price
|
||||||
cash_transaction = instance.cash_transaction
|
cash_transaction = instance.cash_transaction
|
||||||
payment_type = instance.payment_type
|
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'):
|
if currency == 'uzs':
|
||||||
if instance.currency == 'uzs':
|
cash_transaction.expence_balance_uzs += price_difference
|
||||||
cash_transaction.expence_balance_uzs += (old_price - validated_data.get('price'))
|
cash_transaction.total_balance_uzs = (
|
||||||
cash_transaction.total_balance_uzs += (old_price - validated_data.get('price'))
|
cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs
|
||||||
payment_type.total_uzs += (old_price - validated_data.get('price'))
|
)
|
||||||
else:
|
|
||||||
cash_transaction.expence_balance_usd += (old_price - validated_data.get('price'))
|
payment_type.total_uzs -= price_difference
|
||||||
cash_transaction.total_balance_usd += (old_price - validated_data.get('price'))
|
|
||||||
payment_type.total_usd += (old_price - validated_data.get('price'))
|
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()
|
cash_transaction.save()
|
||||||
payment_type.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()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
@@ -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)
|
||||||
@@ -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 validate_price(self, value):
|
||||||
|
"""Narxi manfiy bo'lmasligi tekshiruvi"""
|
||||||
|
if value and value < 0:
|
||||||
|
raise serializers.ValidationError("Narxi manfiy bo'lishi mumkin emas")
|
||||||
|
return value
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
with transaction.atomic():
|
||||||
old_price = instance.price
|
old_price = instance.price
|
||||||
instance.project_folder = validated_data.get('project_folder', instance.project_folder)
|
old_counterparty = instance.counterparty
|
||||||
instance.project = validated_data.get('project', instance.project)
|
new_price = validated_data.get("price", instance.price)
|
||||||
instance.price = validated_data.get('price', instance.price)
|
new_counterparty = validated_data.get("counterparty", instance.counterparty)
|
||||||
instance.type_income = validated_data.get('type_income', instance.type_income)
|
currency = instance.currency.lower()
|
||||||
instance.counterparty = validated_data.get('counterparty', instance.counterparty)
|
|
||||||
instance.date = validated_data.get('date', instance.date)
|
instance.project_folder = validated_data.get(
|
||||||
instance.comment = validated_data.get('comment', instance.comment)
|
"project_folder", instance.project_folder
|
||||||
instance.audit = validated_data.get('audit', instance.audit)
|
)
|
||||||
instance.file = validated_data.get('file', instance.file)
|
instance.project = validated_data.get("project", instance.project)
|
||||||
if validated_data.get('price'):
|
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
|
cash_transaction = instance.cash_transaction
|
||||||
payment_type = instance.payment_type
|
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'):
|
if currency == "uzs":
|
||||||
if instance.currency == 'uzs':
|
cash_transaction.income_balance_uzs += price_difference
|
||||||
cash_transaction.income_balance_uzs += (old_price - validated_data.get('price'))
|
cash_transaction.total_balance_uzs = (
|
||||||
cash_transaction.total_balance_uzs += (old_price - validated_data.get('price'))
|
cash_transaction.income_balance_uzs
|
||||||
payment_type.total_uzs += (old_price - validated_data.get('price'))
|
- cash_transaction.expence_balance_uzs
|
||||||
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_uzs += price_difference
|
||||||
payment_type.total_usd += (old_price - validated_data.get('price'))
|
|
||||||
|
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()
|
cash_transaction.save()
|
||||||
payment_type.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()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
@@ -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)
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
from .expence import *
|
|
||||||
from .income import *
|
|
||||||
@@ -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()),
|
||||||
|
],
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
@@ -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'),
|
||||||
|
|||||||
20
core/apps/finance/views/deleted_expence.py
Normal file
20
core/apps/finance/views/deleted_expence.py
Normal 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)
|
||||||
@@ -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,32 +117,63 @@ 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):
|
if serializer.is_valid(raise_exception=True):
|
||||||
|
with transaction.atomic():
|
||||||
comment = serializer.validated_data.get('comment')
|
comment = serializer.validated_data.get('comment')
|
||||||
|
currency = expence.currency.lower()
|
||||||
|
|
||||||
DeletedExpence.objects.create(
|
DeletedExpence.objects.create(
|
||||||
expence=expence,
|
expence=expence,
|
||||||
comment=comment
|
comment=comment,
|
||||||
|
user=request.user,
|
||||||
)
|
)
|
||||||
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()
|
cash_transaction = expence.cash_transaction
|
||||||
expence.payment_type.save()
|
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()
|
expence.save()
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': 'Expence deleted',
|
'message': "Expence o'chirildi",
|
||||||
}, status=200
|
'data': {
|
||||||
|
'expence_id': expence.id,
|
||||||
|
'price': expence.price,
|
||||||
|
'currency': expence.currency
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status=200
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,35 +85,84 @@ 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):
|
if serializer.is_valid(raise_exception=True):
|
||||||
|
with transaction.atomic():
|
||||||
comment = serializer.validated_data.get('comment')
|
comment = serializer.validated_data.get('comment')
|
||||||
|
currency = income.currency.lower()
|
||||||
|
|
||||||
DeletedIncome.objects.create(
|
DeletedIncome.objects.create(
|
||||||
income=income,
|
income=income,
|
||||||
comment=comment
|
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()
|
cash_transaction = income.cash_transaction
|
||||||
income.payment_type.save()
|
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()
|
income.save()
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': 'Income deleted',
|
'message': 'Income o\'chirildi',
|
||||||
}, status=200
|
'data': {
|
||||||
|
'income_id': income.id,
|
||||||
|
'price': income.price,
|
||||||
|
'currency': income.currency
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status=200
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IncomeUpdateApiView(generics.GenericAPIView):
|
class IncomeUpdateApiView(generics.GenericAPIView):
|
||||||
serializer_class = serializers.IncomeUpdateSerializer
|
serializer_class = serializers.IncomeUpdateSerializer
|
||||||
queryset = Income.objects.all()
|
queryset = Income.objects.all()
|
||||||
|
|||||||
2
core/apps/notifications/admin/__init__.py
Normal file
2
core/apps/notifications/admin/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .notification import *
|
||||||
|
from .notification_history import *
|
||||||
5
core/apps/notifications/admin/notification.py
Normal file
5
core/apps/notifications/admin/notification.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from core.apps.notifications.models import Notification
|
||||||
|
|
||||||
|
admin.site.register(Notification)
|
||||||
6
core/apps/notifications/admin/notification_history.py
Normal file
6
core/apps/notifications/admin/notification_history.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from core.apps.notifications.models.notification_history import NotificationHistory
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(NotificationHistory)
|
||||||
11
core/apps/notifications/apps.py
Normal file
11
core/apps/notifications/apps.py
Normal 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()
|
||||||
31
core/apps/notifications/migrations/0001_initial.py
Normal file
31
core/apps/notifications/migrations/0001_initial.py
Normal 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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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')},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
2
core/apps/notifications/models/__init__.py
Normal file
2
core/apps/notifications/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .notification import *
|
||||||
|
from .notification_history import *
|
||||||
17
core/apps/notifications/models/notification.py
Normal file
17
core/apps/notifications/models/notification.py
Normal 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')
|
||||||
17
core/apps/notifications/models/notification_history.py
Normal file
17
core/apps/notifications/models/notification_history.py
Normal 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}'
|
||||||
11
core/apps/notifications/serializers/notification.py
Normal file
11
core/apps/notifications/serializers/notification.py
Normal 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'
|
||||||
|
]
|
||||||
11
core/apps/notifications/serializers/notification_history.py
Normal file
11
core/apps/notifications/serializers/notification_history.py
Normal 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'
|
||||||
|
]
|
||||||
22
core/apps/notifications/tasks/create_notification_history.py
Normal file
22
core/apps/notifications/tasks/create_notification_history.py
Normal 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)
|
||||||
11
core/apps/notifications/urls.py
Normal file
11
core/apps/notifications/urls.py
Normal 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()),
|
||||||
|
]
|
||||||
11
core/apps/notifications/utils/notify_user.py
Normal file
11
core/apps/notifications/utils/notify_user.py
Normal 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)
|
||||||
57
core/apps/notifications/utils/send_notification.py
Normal file
57
core/apps/notifications/utils/send_notification.py
Normal 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)
|
||||||
25
core/apps/notifications/views/notification.py
Normal file
25
core/apps/notifications/views/notification.py
Normal 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)
|
||||||
43
core/apps/notifications/views/notification_history.py
Normal file
43
core/apps/notifications/views/notification_history.py
Normal 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)
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
|
|||||||
5476
core/apps/orders/data/party.json
Normal file
5476
core/apps/orders/data/party.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
39
core/apps/orders/management/commands/find_party.py
Normal file
39
core/apps/orders/management/commands/find_party.py
Normal 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)
|
||||||
60
core/apps/orders/management/commands/import_orders.py
Normal file
60
core/apps/orders/management/commands/import_orders.py
Normal 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 ✅"))
|
||||||
120
core/apps/orders/management/commands/import_party.py
Normal file
120
core/apps/orders/management/commands/import_party.py
Normal 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")
|
||||||
121
core/apps/orders/management/commands/import_party_by_number.py
Normal file
121
core/apps/orders/management/commands/import_party_by_number.py
Normal 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")
|
||||||
11
core/apps/orders/management/commands/update_party.py
Normal file
11
core/apps/orders/management/commands/update_party.py
Normal 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")
|
||||||
@@ -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')
|
||||||
|
)
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/apps/orders/migrations/0028_alter_order_quantity.py
Normal file
18
core/apps/orders/migrations/0028_alter_order_quantity.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/apps/orders/migrations/0029_alter_order_status.py
Normal file
18
core/apps/orders/migrations/0029_alter_order_status.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/apps/orders/migrations/0031_order_type.py
Normal file
18
core/apps/orders/migrations/0031_order_type.py
Normal 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
Reference in New Issue
Block a user