Merge pull request #25 from xoliqberdiyev/test

Test
This commit is contained in:
xolikberdiyev
2025-10-30 15:32:48 +05:00
committed by GitHub
25 changed files with 5859 additions and 289 deletions

11
config/firebase.py Normal file
View File

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

View File

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

View File

@@ -1,6 +1,14 @@
from datetime import timedelta
from 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
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -11,44 +19,44 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# 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!
DEBUG = env.bool('DEBUG')
DEBUG = env.bool("DEBUG")
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
# Application definition
APPS = [
'core.apps.accounts',
'core.apps.shared',
'core.apps.company',
'core.apps.wherehouse',
'core.apps.products',
'core.apps.projects',
'core.apps.orders',
'core.apps.finance',
'core.apps.counterparty',
'core.apps.notifications',
"core.apps.accounts",
"core.apps.shared",
"core.apps.company",
"core.apps.wherehouse",
"core.apps.products",
"core.apps.projects",
"core.apps.orders",
"core.apps.finance",
"core.apps.counterparty",
"core.apps.notifications",
]
PACKAGES = [
'drf_yasg',
'rest_framework',
'rest_framework_simplejwt',
'corsheaders',
'cacheops',
"drf_yasg",
"rest_framework",
"rest_framework_simplejwt",
"corsheaders",
"cacheops",
]
DJANGO_APPS = [
'jazzmin',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"jazzmin",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
INSTALLED_APPS = []
@@ -58,48 +66,48 @@ INSTALLED_APPS += APPS
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
# 'silk.middleware.SilkyMiddleware',
]
ROOT_URLCONF = 'config.urls'
ROOT_URLCONF = "config.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'config.wsgi.application'
WSGI_APPLICATION = "config.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env.str('POSTGRES_DB'),
'USER': env.str('POSTGRES_USER'),
'PASSWORD': env.str('POSTGRES_PASSWORD'),
'HOST': env.str('POSTGRES_HOST'),
'PORT': env.str('POSTGRES_PORT'),
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": env.str("POSTGRES_DB"),
"USER": env.str("POSTGRES_USER"),
"PASSWORD": env.str("POSTGRES_PASSWORD"),
"HOST": env.str("POSTGRES_HOST"),
"PORT": env.str("POSTGRES_PORT"),
}
}
@@ -109,16 +117,16 @@ DATABASES = {
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",
},
]
@@ -126,9 +134,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# 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
@@ -138,25 +146,19 @@ USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'resources/static'
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'resources/media'
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "resources/static"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "resources/media"
# Default primary key field type
# 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'))
from config.conf.rest_framework import *
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 *
SECURE_PROXY_SSL_HEADER = (
"HTTP_X_FORWARDED_PROTO",
env.str("SWAGGER_PROTOCOL", "https"),
)

View File

@@ -1,4 +1,5 @@
import json
from django.core.management import BaseCommand
from core.apps.counterparty.models import Counterparty
@@ -9,24 +10,27 @@ class Command(BaseCommand):
parser.add_argument("file_path", type=str)
def handle(self, *args, **options):
file_path = options['file_path']
file_path = options["file_path"]
with open(file_path, 'r') as f:
with open(file_path, "r") as f:
data = json.load(f)
for item in data['data']['data']:
for item in data["data"]["data"]:
Counterparty.objects.get_or_create(
name=item['name'],
name=item["name"],
defaults={
'phone': item['person']['phone'],
'inn': item['person']['tin'],
'balance_currency': item['balances'][0]['currency']['symbol'].lower() if item['balances'] else 'uzs',
'is_archived': item['is_archived'],
'balance': item['total_amount'],
'total_debit': item['debt_amount'],
'total_kredit': item['credit_amount'],
"phone": item["person"]["phone"],
"inn": item["person"]["tin"],
"balance_currency": (
item["balances"][0]["currency"]["symbol"].lower()
if item["balances"]
else "uzs"
),
"is_archived": item["is_archived"],
"balance": item["total_amount"],
"total_debit": item["debt_amount"],
"total_kredit": item["credit_amount"],
},
)
self.stdout.write("Counterparties added")

View File

@@ -3,6 +3,7 @@ from django.db import transaction
from rest_framework import serializers
from core.apps.finance.models import Expence
from core.apps.notifications.utils.notify_user import notify_user
class ExpenceCreateSerializer(serializers.ModelSerializer):
@@ -33,7 +34,7 @@ class ExpenceCreateSerializer(serializers.ModelSerializer):
)
cash_transaction = expence.cash_transaction
payment_type = expence.payment_type
user = self.context.get('user')
if validated_data.get('currency') == 'uzs':
cash_transaction.expence_balance_uzs += expence.price
cash_transaction.total_balance_uzs = cash_transaction.income_balance_uzs - cash_transaction.expence_balance_uzs
@@ -69,7 +70,11 @@ class ExpenceCreateSerializer(serializers.ModelSerializer):
expence.counterparty.debit_usd += validated_data.get('price')
expence.counterparty.total_debit += expence.price
expence.counterparty.save()
body = f"""{user.full_name} {expence.price} {expence.currency.upper()}... \n screen: /monitoring"""
data = {
"screen": "/monitoring"
}
notify_user(user=user, title="Tasdiqlang yoki rad eting", body=body, data=data)
cash_transaction.save()
payment_type.save()
return expence
@@ -89,7 +94,7 @@ class ExpenceListSerializer(serializers.ModelSerializer):
fields = [
'id', 'cash_transaction', 'payment_type', 'project_folder', 'project', 'expence_type',
'counterparty', 'price', 'exchange_rate', 'currency', 'date', 'comment', 'audit', 'file',
'user', 'expence_chats',
'user', 'expence_chats', 'created_at'
]
def get_user(self, obj):

View File

@@ -1,103 +1,133 @@
from django.db import transaction
from rest_framework import serializers
from core.apps.finance.models import Income
class IncomeListSerializer(serializers.ModelSerializer):
cash_transaction = serializers.SerializerMethodField(method_name='get_cash_transaction')
payment_type = serializers.SerializerMethodField(method_name='get_payment_type')
project_folder = serializers.SerializerMethodField(method_name='get_project_folder')
project = serializers.SerializerMethodField(method_name='get_project')
counterparty = serializers.SerializerMethodField(method_name='get_counterparty')
type_income = serializers.SerializerMethodField(method_name='get_type_income')
user = serializers.SerializerMethodField(method_name='get_user')
cash_transaction = serializers.SerializerMethodField(
method_name="get_cash_transaction"
)
payment_type = serializers.SerializerMethodField(method_name="get_payment_type")
project_folder = serializers.SerializerMethodField(method_name="get_project_folder")
project = serializers.SerializerMethodField(method_name="get_project")
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:
model = Income
fields = [
'id', 'cash_transaction', 'payment_type', 'project_folder', 'project',
'counterparty', 'type_income', 'currency', 'price', 'exchange_rate', 'date',
'comment', 'file', 'audit', 'user', 'income_chat'
"id",
"cash_transaction",
"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):
return {
'id': obj.cash_transaction.id,
'name': obj.cash_transaction.name
}
return {"id": obj.cash_transaction.id, "name": obj.cash_transaction.name}
def get_payment_type(self, obj):
return {
'id': obj.payment_type.id,
'name': obj.payment_type.name
}
return {"id": obj.payment_type.id, "name": obj.payment_type.name}
def get_project_folder(self, obj):
return {
'id': obj.project_folder.id,
'name': obj.project_folder.name
} if obj.project_folder else None
return (
{"id": obj.project_folder.id, "name": obj.project_folder.name}
if obj.project_folder
else None
)
def get_project(self, obj):
return {
'id': obj.project.id,
'name': obj.project.name
} if obj.project else None
return {"id": obj.project.id, "name": obj.project.name} if obj.project else None
def get_counterparty(self, obj):
return {
'id': obj.counterparty.id,
'name': obj.counterparty.name
} if obj.counterparty else None
return (
{"id": obj.counterparty.id, "name": obj.counterparty.name}
if obj.counterparty
else None
)
def get_type_income(self, obj):
return {
'id': obj.type_income.id,
'name': obj.type_income.name
} if obj.type_income else None
return (
{"id": obj.type_income.id, "name": obj.type_income.name}
if obj.type_income
else None
)
def get_user(self, obj):
return {
'id': obj.user.id,
'full_name': obj.user.full_name,
} if obj.user else None
return (
{
"id": obj.user.id,
"full_name": obj.user.full_name,
}
if obj.user
else None
)
class IncomeCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Income
fields = [
'cash_transaction', 'payment_type', 'project_folder', 'project',
'counterparty', 'type_income', 'currency', 'price', 'exchange_rate', 'date',
'comment', 'file', 'audit'
"cash_transaction",
"payment_type",
"project_folder",
"project",
"counterparty",
"type_income",
"currency",
"price",
"exchange_rate",
"date",
"comment",
"file",
"audit",
]
def create(self, validated_data):
with transaction.atomic():
income = Income.objects.create(
user=self.context.get('user'),
cash_transaction=validated_data['cash_transaction'],
payment_type=validated_data['payment_type'],
project_folder=validated_data.get('project_folder'),
project=validated_data.get('project'),
counterparty=validated_data.get('counterparty'),
type_income=validated_data.get('type_income'),
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'),
exchange_rate=validated_data.get('exchange_rate'),
date=validated_data.get('date'),
comment=validated_data.get('comment'),
file=validated_data.get('file'),
audit=validated_data.get('audit')
user=self.context.get("user"),
cash_transaction=validated_data["cash_transaction"],
payment_type=validated_data["payment_type"],
project_folder=validated_data.get("project_folder"),
project=validated_data.get("project"),
counterparty=validated_data.get("counterparty"),
type_income=validated_data.get("type_income"),
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")
),
exchange_rate=validated_data.get("exchange_rate"),
date=validated_data.get("date"),
comment=validated_data.get("comment"),
file=validated_data.get("file"),
audit=validated_data.get("audit"),
)
cash_transaction = income.cash_transaction
payment_type = income.payment_type
if validated_data.get('currency') == 'uzs':
if validated_data.get("currency") == "uzs":
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
if income.counterparty:
@@ -105,26 +135,33 @@ class IncomeCreateSerializer(serializers.ModelSerializer):
income.counterparty.debit_uzs -= income.price
income.counterparty.total_debit -= income.price
income.counterparty.kredit_uzs += income.counterparty.debit_uzs - income.price
income.counterparty.kredit_uzs += (
income.counterparty.debit_uzs - income.price
)
income.counterparty.total_kredit += income.price
else:
income.counterparty.kredit_uzs += income.price
income.counterparty.total_kredit += income.price
income.counterparty.save()
elif validated_data.get('currency') == 'usd':
elif validated_data.get("currency") == "usd":
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
if income.counterparty:
if income.counterparty.debit_usd != 0:
income.counterparty.debit_usd -= validated_data.get('price')
income.counterparty.debit_usd -= validated_data.get("price")
income.counterparty.total_debit -= income.price
income.counterparty.kredit_usd += income.counterparty.debit_usd - validated_data.get('price')
income.counterparty.kredit_usd += (
income.counterparty.debit_usd - validated_data.get("price")
)
income.counterparty.total_kredit += income.price
else:
income.counterparty.kredit_usd += validated_data.get('price')
income.counterparty.kredit_usd += validated_data.get("price")
income.counterparty.total_kredit += income.price
income.counterparty.save()
@@ -141,46 +178,83 @@ class IncomeUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Income
fields = [
'project_folder', 'project', 'price', 'type_income', 'counterparty',
'date', 'comment', 'audit', 'file',
"project_folder",
"project",
"price",
"type_income",
"counterparty",
"date",
"comment",
"audit",
"file",
]
extra_kwargs = {
'price': {'required': False},
"price": {"required": False},
}
def update(self, instance, validated_data):
old_price = instance.price
instance.project_folder = validated_data.get('project_folder', instance.project_folder)
instance.project = validated_data.get('project', instance.project)
instance.price = validated_data.get('price', instance.price)
instance.type_income = validated_data.get('type_income', instance.type_income)
instance.counterparty = validated_data.get('counterparty', instance.counterparty)
instance.date = validated_data.get('date', instance.date)
instance.comment = validated_data.get('comment', instance.comment)
instance.audit = validated_data.get('audit', instance.audit)
instance.file = validated_data.get('file', instance.file)
if validated_data.get('price'):
instance.project_folder = validated_data.get(
"project_folder", instance.project_folder
)
instance.project = validated_data.get("project", instance.project)
instance.price = validated_data.get("price", instance.price)
instance.type_income = validated_data.get("type_income", instance.type_income)
instance.counterparty = validated_data.get(
"counterparty", instance.counterparty
)
instance.date = validated_data.get("date", instance.date)
instance.comment = validated_data.get("comment", instance.comment)
instance.audit = validated_data.get("audit", instance.audit)
instance.file = validated_data.get("file", instance.file)
if validated_data.get("price"):
cash_transaction = instance.cash_transaction
payment_type = instance.payment_type
if old_price > validated_data.get('price'):
if instance.currency == 'uzs':
cash_transaction.income_balance_uzs -= (old_price - validated_data.get('price'))
cash_transaction.total_balance_uzs -= (old_price - validated_data.get('price'))
payment_type.total_uzs -= (old_price - validated_data.get('price')) if payment_type.total_uzs > (old_price - validated_data.get('price')) else 0
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
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 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'))
elif 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")
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'))
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")
cash_transaction.save()
payment_type.save()
instance.save()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,5 +7,5 @@ class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = [
'token'
'type', 'token'
]

View File

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

View File

@@ -1,16 +1,53 @@
import requests
from firebase_admin import messaging
from django.conf import settings
from core.apps.notifications.models import Notification
EXPO_API_URL = "https://exp.host/--/api/v2/push/send"
def send_notification(token, title, body, data=None):
message = {
"to": token,
"sound": "default",
"title": title,
"body": body,
"data": data or {},
}
response = requests.post(
"https://exp.host/--/api/v2/push/send",
json=message,
headers={"Content-Type": "application/json"}
tokens = list(Notification.objects.exclude(token=token).values_list("token", flat=True))
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))
if not tokens:
return
message = messaging.MulticastMessage(
notification=messaging.Notification(
title=title,
body=body
),
data=data or {},
tokens=tokens,
)
return response.json()
response = messaging.send_each_for_multicast(message)

View File

@@ -3,18 +3,23 @@ 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.update_or_create(
Notification.objects.get_or_create(
user=request.user,
token=serializer.validated_data['token']
token=serializer.validated_data['token'],
type=serializer.validated_data.get('type') \
if serializer.validated_data.get('type') \
else 'mobile',
)
return Response({"message": "Token saqlandi"}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

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

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from core.apps.orders.models import Party, PartyAmount, DeletedParty
from core.apps.orders.models import DeletedParty, Party, PartyAmount
class PartyAmountInline(admin.StackedInline):
@@ -11,17 +11,28 @@ class PartyAmountInline(admin.StackedInline):
@admin.register(Party)
class PartyAdmin(admin.ModelAdmin):
list_display = ['id','number','mediator', 'delivery_date', 'payment_date', 'is_deleted']
list_display = [
"id",
"number",
"mediator",
"delivery_date",
"payment_date",
"is_deleted",
]
inlines = [PartyAmountInline]
search_fields = [
"number",
]
@admin.register(PartyAmount)
class PartyAmountAdmin(admin.ModelAdmin):
list_display = ['id', 'total_price', 'cost_amount']
list_display = ["id", "total_price", "cost_amount"]
def has_module_permission(self, request):
return False
@admin.register(DeletedParty)
class DeletedPartyAdmin(admin.ModelAdmin):
list_display = ['id', 'deleted_date', 'party']
list_display = ["id", "deleted_date", "party"]

View File

@@ -20,6 +20,7 @@ class Command(BaseCommand):
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']
@@ -53,6 +54,6 @@ class Command(BaseCommand):
created_at=created_at,
date=delivery_date
)
break
# break
self.stdout.write(self.style.SUCCESS("Orders imported successfully ✅"))

View File

@@ -1,86 +1,109 @@
import json, requests
import json
import requests
from django.core.management import BaseCommand
from core.apps.orders.models import Order, Party, PartyAmount
from core.apps.accounts.models import User
from core.apps.products.models import Product, Unity
from core.apps.counterparty.models import Counterparty
from core.apps.wherehouse.models import WhereHouse
from core.apps.projects.models import Project, ProjectFolder
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.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MTczMTI4MSwiZXhwIjoxNzYxODE3NjgxLCJuYmYiOjE3NjE3MzEyODEsImp0aSI6ImszNVRQUDBiblpSU3ZFS24iLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.G5rjiIV-roIgEZzH9s8XgLYnS9tunnMgnCa2U6S3vd0"
token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2JhY2tlbmQuYXBwLnV5cXVyLnV6L21haW4vYXV0aC9sb2dpbiIsImlhdCI6MTc2MTI4NjcwMywiZXhwIjoxNzYxNjQ2NzAzLCJuYmYiOjE3NjEyODY3MDMsImp0aSI6IjRBNkh4aHI5WkRqOGxVMzUiLCJzdWIiOiIxMDQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3In0.O4NGZL_a3WIrjko5W2sEOBAbM5lv0miVgVa9tfYuyhM'
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('file_path', type=str)
parser.add_argument("file_path", type=str)
def handle(self, *args, **options):
file_path = options['file_path']
headers = {
"Authorization": f"Bearer {token}"
}
file_path = options["file_path"]
headers = {"Authorization": f"Bearer {token}"}
with open(file_path, 'r') as f:
with open(file_path, "r") as f:
data = json.load(f)
for item in data['data']['data']:
url = f'https://backend.app.uyqur.uz/main/supply/order-view?id={item['id']}'
statuses = {
"open": "NEW",
"ordered": "PARTY_IS_MADE",
"pending": "EXPECTED",
"passive": "DRAFT",
"recieved": "RECIEVED",
"rejected": "CANCELLED",
"buying": "PURCHASED",
"partially_recieved": "PROCESS"
}
for item in data["data"]["data"]:
url = f"https://backend.app.uyqur.uz/main/supply/order-view?id={item['id']}"
res = requests.get(url, headers=headers)
data = res.json()['data']
data = res.json()["data"]
user = None
if data.get('agent'):
user = User.objects.filter(full_name=data['agent']['full_name']).first()
if data.get("agent"):
user = User.objects.filter(full_name=data["agent"]["full_name"]).first()
if not user:
continue
party, created = Party.objects.get_or_create(
number=data['id'],
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': data['status'].upper(),
'payment_percentage': data['payment_percent'],
'process': data['percent'],
}
"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
for i in data['warehouse_products']:
product = Product.objects.filter(name__icontains=i['product']['name']['uz']).first()
for i in data["warehouse_products"]:
product = Product.objects.filter(
name__icontains=i["product"]["name"]["uz"]
).first()
if not product:
continue
unit = Unity.objects.filter(value=i['unit']['name']['uz']).first()
counterparty = Counterparty.objects.filter(name=i['company_person']['name']).first()
unit = Unity.objects.filter(value=i["unit"]["name"]["uz"]).first()
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()
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()
if i.get("project"):
project_folder = ProjectFolder.objects.filter(
name=i["project"]["name"]
).first()
order, created = Order.objects.get_or_create(
order = Order.objects.create(
product=product,
amount=i["amount"],
total_price=i["total_amount"],
quantity=i["quantity"],
unity=unit,
counterparty=counterparty,
currency=i['currency']['symbol'].lower(),
wherehouse=wherehouse,
currency=i["currency"]["symbol"].lower(),
project_folder=project_folder,
amount=i['amount'],
total_price=i['total_amount'],
quantity=i['quantity'],
wherehouse=wherehouse,
counterparty=counterparty,
)
total_price += i['total_amount']
paid_amount += i['paid_amount']
calculated_amount += i['calculated_amount']
must_pay_amount += i['must_pay_amount']
total_price += i["total_amount"]
paid_amount += i["paid_amount"]
calculated_amount += i["calculated_amount"]
must_pay_amount += i["must_pay_amount"]
orders.append(order)
party.orders.set(orders)
PartyAmount.objects.get_or_create(
@@ -90,6 +113,6 @@ class Command(BaseCommand):
"calculated_amount": calculated_amount,
"paid_amount": paid_amount,
"payment_amount": must_pay_amount,
}
},
)
self.stdout.write("Parties added")

View File

@@ -71,6 +71,7 @@ class MultipleOrderCreateSerializer(serializers.Serializer):
resources = validated_data.pop('resources')
common_date = validated_data.get('date')
orders = []
user = self.context.get('user')
for resource in resources:
order = Order(
@@ -84,14 +85,14 @@ class MultipleOrderCreateSerializer(serializers.Serializer):
employee=self.context.get('user'),
)
orders.append(order)
body = f"""
{user.full_name} {order.project_folder.name} uchun {order.wherehouse.name} ombor ga {order.quantity} {order.unity.value} {order.product.name} ga buyurtma berdi. Buyurtma
yetkazish sanasi {common_date}
body = f"""{user.full_name} {order.project_folder.name} uchun {order.wherehouse.name} ombor ga {order.quantity} {order.unity.value} {order.product.name} ga buyurtma berdi. Buyurtma yetkazish sanasi {common_date}
"""
notify_user(user=self.context.get("user"), title="Ta'minot",body=body)
data = {
"screen": "/supply"
}
notify_user(user=user, title="Ta'minot",body=body, data=data)
created_orders = Order.objects.bulk_create(orders)
user = self.context.get('user')
return created_orders

View File

@@ -1,11 +1,10 @@
from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework.response import Response
from core.apps.accounts.permissions.permissions import HasRolePermission
from core.apps.shared.models import UsdCourse
from core.apps.shared.serializers.usd_course import UsdCourseSerializer
from core.apps.accounts.permissions.permissions import HasRolePermission
class UsdCourseApiView(generics.GenericAPIView):
@@ -20,10 +19,15 @@ class UsdCourseApiView(generics.GenericAPIView):
def patch(self, request):
usd_cource = UsdCourse.objects.first()
if not usd_cource:
usd_cource = UsdCourse.objects.create(value=0)
old_value = usd_cource.value
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=400)
value = serializer.validated_data.get('value')
value = serializer.validated_data.get("value")
usd_cource.value = value
usd_cource.save()
return Response({'new_value': usd_cource.value, 'old_value': value}, status=200)
return Response(
{"new_value": usd_cource.value, "old_value": old_value}, status=200
)

5279
data/counterparty/cp1.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,22 @@
aiohappyeyeballs==2.6.1
aiohttp==3.13.2
aiosignal==1.4.0
amqp==5.3.1
anyio==4.11.0
asgiref==3.9.1
attrs==25.4.0
billiard==4.2.1
CacheControl==0.14.3
cachetools==6.2.1
celery==5.5.3
certifi==2025.10.5
cffi==2.0.0
charset-normalizer==3.4.4
click==8.2.1
click-didyoumean==0.3.1
click-plugins==1.1.1.2
click-repl==0.3.0
cryptography==46.0.3
Django==5.2.4
django-cacheops==7.2
django-cors-headers==4.7.0
@@ -18,26 +29,59 @@ django-silk==5.4.0
djangorestframework==3.16.0
djangorestframework_simplejwt==5.5.1
drf-yasg==1.21.10
exponent_server_sdk==2.2.0
firebase_admin==7.1.0
frozenlist==1.8.0
funcy==2.0
google-api-core==2.28.1
google-auth==2.42.0
google-cloud-core==2.5.0
google-cloud-firestore==2.21.0
google-cloud-storage==3.4.1
google-crc32c==1.7.1
google-resumable-media==2.7.2
googleapis-common-protos==1.71.0
gprof2dot==2025.4.14
grpcio==1.76.0
grpcio-status==1.76.0
gunicorn==23.0.0
h11==0.16.0
h2==4.3.0
hpack==4.1.0
httpcore==1.0.9
httpx==0.28.1
hyperframe==6.1.0
idna==3.11
inflection==0.5.1
kombu==5.5.4
msgpack==1.1.2
multidict==6.7.0
packaging==25.0
pillow==11.3.0
prompt_toolkit==3.0.51
propcache==0.4.1
proto-plus==1.26.1
protobuf==6.33.0
psycopg2-binary==2.9.10
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.23
pyfcm==2.1.0
PyJWT==2.10.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.2
redis==6.2.0
requests==2.32.5
rsa==4.9.1
six==1.17.0
sniffio==1.3.1
sqlparse==0.5.3
typing_extensions==4.15.0
tzdata==2025.2
uritemplate==4.2.0
urllib3==2.5.0
uvicorn==0.35.0
vine==5.1.0
wcwidth==0.2.13
requests
yarl==1.22.0