Initial commit

This commit is contained in:
Abdulaziz Axmadaliyev
2026-02-17 19:05:54 +05:00
parent 493cb58222
commit 3691e2d068
56 changed files with 3546 additions and 893 deletions

View File

@@ -10,6 +10,7 @@ from django.views.static import serve
from config.env import env from config.env import env
def home(request): def home(request):
return HttpResponse("OK") return HttpResponse("OK")
@@ -20,17 +21,12 @@ urlpatterns = [
path("", include("core.apps.shared.urls")), path("", include("core.apps.shared.urls")),
path("", include("core.apps.management.urls")), path("", include("core.apps.management.urls")),
] ]
urlpatterns += [ urlpatterns += [path("admin/", admin.site.urls), path("i18n/", include("django.conf.urls.i18n"))]
path("admin/", admin.site.urls),
# path("accounts/", include("django.contrib.auth.urls")),
path("i18n/", include("django.conf.urls.i18n")),
]
if env.bool("SILK_ENABLED", False): if env.bool("SILK_ENABLED", False):
urlpatterns += [] urlpatterns += []
if env.str("PROJECT_ENV") == "debug": if env.str("PROJECT_ENV") == "debug":
urlpatterns += [ urlpatterns += []
]
urlpatterns += [ urlpatterns += [
re_path("static/(?P<path>.*)", serve, {"document_root": settings.STATIC_ROOT}), re_path("static/(?P<path>.*)", serve, {"document_root": settings.STATIC_ROOT}),
re_path("media/(?P<path>.*)", serve, {"document_root": settings.MEDIA_ROOT}), re_path("media/(?P<path>.*)", serve, {"document_root": settings.MEDIA_ROOT}),
] ]

View File

@@ -0,0 +1,10 @@
from django.db import models
class Employee(models.Model):
user = models.ForeignKey("accounts.User", on_delete=models.PROTECT)
device = models.ManyToManyField("management.Device")
def __str__(self):
return self.user.first_name

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.2.7 on 2026-02-17 11:44
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_user_warehouse'),
('management', '0022_alter_district_region'),
]
operations = [
migrations.AlterField(
model_name='user',
name='region',
field=models.ForeignKey(blank=True, help_text='Faqat menejerlar uchun', null=True, on_delete=django.db.models.deletion.SET_NULL, to='management.region'),
),
migrations.AlterField(
model_name='user',
name='warehouse',
field=models.ForeignKey(blank=True, help_text='Faqat xodimlar uchun', null=True, on_delete=django.db.models.deletion.PROTECT, to='management.warehouse'),
),
]

View File

@@ -22,7 +22,7 @@ class User(auth_models.AbstractUser):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
blank=True, blank=True,
help_text="Only for managers" help_text="Faqat menejerlar uchun"
) )
warehouse = models.ForeignKey( warehouse = models.ForeignKey(
@@ -30,7 +30,7 @@ class User(auth_models.AbstractUser):
on_delete=models.PROTECT, on_delete=models.PROTECT,
null=True, null=True,
blank=True, blank=True,
help_text="Only for employees" help_text="Faqat xodimlar uchun"
) )
def get_full_name(self): def get_full_name(self):

View File

@@ -1,19 +1,31 @@
from django import forms from django import forms
from ..models import Device, District from ..models import Device, District
class DeviceForm(forms.ModelForm): class DeviceForm(forms.ModelForm):
class Meta: class Meta:
model = Device model = Device
fields = ["address", "district"] fields = ["address", "district", "amount", "due_date"]
labels = {
"address": "Manzil",
"district": "Tuman",
"amount": "Summa",
"due_date": "Tolov muddati",
}
widgets = {
"due_date": forms.DateInput(
attrs={"type": "date"}
)
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None) # get the user from kwargs user = kwargs.pop("user", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if user is not None: if user:
if user.role == "manager": if user.role == "manager":
# Manager: only districts in the same region self.fields["district"].queryset = District.objects.filter(
self.fields['district'].queryset = District.objects.filter(region=user.region) region=user.region
)
else: else:
# Businessman: show all districts self.fields["district"].queryset = District.objects.all()
self.fields['district'].queryset = District.objects.all()

View File

@@ -0,0 +1,24 @@
from django import forms
from ..models import Device
class DevicePaymentForm(forms.ModelForm):
class Meta:
model = Device
fields = ["is_paid"] # 🔥 Only this field
widgets = {
"is_paid": forms.CheckboxInput(attrs={"class": "form-check-input"})
}
def save(self, commit=True):
instance = super().save(commit=False)
# 🔥 Ensure employee can ONLY mark as True
if not instance.is_paid:
instance.is_paid = True
if commit:
instance.save()
return instance

View File

@@ -2,42 +2,75 @@ from django import forms
from ..models import Expense, Device from ..models import Expense, Device
from core.apps.accounts.models import User from core.apps.accounts.models import User
# Base form
# =========================
# Base Form
# =========================
class BaseExpenseForm(forms.ModelForm): class BaseExpenseForm(forms.ModelForm):
class Meta: class Meta:
model = Expense model = Expense
fields = ["amount", "expense_type", "employee", "device"] fields = ["amount", "expense_type", "employee", "device", "comment"]
labels = {
"amount": "Summa",
"expense_type": "Xarajat turi",
"employee": "Xodim",
"device": "Qurilma",
"comment": "Izoh",
}
widgets = {
"comment": forms.Textarea(attrs={"rows": 3})
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Show all devices # Show all devices
self.fields["device"].queryset = Device.objects.all() self.fields["device"].queryset = Device.objects.all()
# Show all employees
# Show only employees
self.fields["employee"].queryset = User.objects.filter(role="employee") self.fields["employee"].queryset = User.objects.filter(role="employee")
# Comment is optional unless expense_type == other
self.fields["comment"].required = False
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
expense_type = cleaned_data.get("expense_type") expense_type = cleaned_data.get("expense_type")
employee = cleaned_data.get("employee") employee = cleaned_data.get("employee")
device = cleaned_data.get("device") device = cleaned_data.get("device")
comment = cleaned_data.get("comment")
# OTHER requires comment
if expense_type == Expense.ExpenseType.OTHER and not comment:
self.add_error("comment", "Iltimos 'boshqa' tanlovi uchun izoh qoldiring!")
# Salary requires employee # Salary requires employee
if expense_type == Expense.ExpenseType.SALARY and not employee: if expense_type == Expense.ExpenseType.SALARY and not employee:
self.add_error("employee", "Employee must be set for Salary expenses.") self.add_error("employee", "Employee must be set for Salary expenses.")
# Device required for rent/maintenance # Maintenance requires device
if expense_type in [Expense.ExpenseType.RENT, Expense.ExpenseType.MAINTENANCE] and not device: if expense_type == Expense.ExpenseType.MAINTENANCE and not device:
self.add_error("device", "Device must be set for this type of expense.") self.add_error("device", "Device must be set for Maintenance expenses.")
return cleaned_data return cleaned_data
# Employee form: cannot create Salary or Buy Toys # =========================
# Employee Form
# =========================
class ExpenseFormEmployee(BaseExpenseForm): class ExpenseFormEmployee(BaseExpenseForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Remove forbidden types for employee
forbidden = [Expense.ExpenseType.SALARY, Expense.ExpenseType.BUY_TOYS] # Employee cannot create Salary or Buy Toys
forbidden = [
Expense.ExpenseType.SALARY,
Expense.ExpenseType.BUY_TOYS,
]
self.fields["expense_type"].choices = [ self.fields["expense_type"].choices = [
(value, label) (value, label)
for value, label in Expense.ExpenseType.choices for value, label in Expense.ExpenseType.choices
@@ -45,11 +78,19 @@ class ExpenseFormEmployee(BaseExpenseForm):
] ]
# Manager form: cannot create Buy Toys # =========================
# Manager Form
# =========================
class ExpenseFormManager(BaseExpenseForm): class ExpenseFormManager(BaseExpenseForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
forbidden = [Expense.ExpenseType.BUY_TOYS]
# Manager cannot create Buy Toys
forbidden = [
Expense.ExpenseType.BUY_TOYS,
]
self.fields["expense_type"].choices = [ self.fields["expense_type"].choices = [
(value, label) (value, label)
for value, label in Expense.ExpenseType.choices for value, label in Expense.ExpenseType.choices
@@ -57,6 +98,8 @@ class ExpenseFormManager(BaseExpenseForm):
] ]
# Businessman form: full access # =========================
# Businessman Form
# =========================
class ExpenseFormBusinessman(BaseExpenseForm): class ExpenseFormBusinessman(BaseExpenseForm):
pass pass

View File

@@ -1,10 +1,11 @@
from django import forms from django import forms
from ..models import Income, Device from ..models import Income, Device, Warehouse
class IncomeForm(forms.ModelForm): class IncomeForm(forms.ModelForm):
class Meta: class Meta:
model = Income model = Income
fields = ["device", "amount"] fields = ["device", "amount", "warehouse"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None) self.user = kwargs.pop("user", None)
@@ -13,8 +14,10 @@ class IncomeForm(forms.ModelForm):
if self.user is not None: if self.user is not None:
# Filter devices # Filter devices
if self.user.role == "businessman": if self.user.role == "businessman":
self.fields["device"].queryset = Device.objects.all() self.fields["warehouse"].queryset = Warehouse.objects.all()
else: # manager or employee elif self.user.role == "manager":
self.fields["warehouse"].queryset = Warehouse.objects.filter(region=self.user.region)
elif self.user.role == "employee":
self.fields["device"].queryset = Device.objects.filter(district__region=self.user.region) self.fields["device"].queryset = Device.objects.filter(district__region=self.user.region)
# Remove amount for employees # Remove amount for employees

View File

@@ -0,0 +1,61 @@
from django import forms
from core.apps.management.models import Device
class ReportForm(forms.Form):
device = forms.ModelChoiceField(
queryset=Device.objects.all(),
label="Qurilma",
required=True,
widget=forms.Select(attrs={
'class': 'form-select',
}),
error_messages={
'required': 'Qurilmani tanlang',
'invalid_choice': 'Tanlangan qurilma topilmadi'
}
)
quantity = forms.IntegerField(
min_value=1,
label="So'nggi miqdor",
required=True,
widget=forms.NumberInput(attrs={
'class': 'form-input',
'placeholder': 'Miqdorni kiriting',
'min': '1'
}),
error_messages={
'required': 'Miqdorni kiriting',
'invalid': 'Miqdor raqam bo\'lishi kerak',
'min_value': 'Miqdor 1 dan katta bo\'lishi kerak'
}
)
def __init__(self, *args, **kwargs):
user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)
self.user = user
# ✅ SHOW ALL DEVICES - don't filter by warehouse
# Reports need to see all devices, not just warehouse devices
self.fields["device"].queryset = Device.objects.all().order_by('address')
# Show count for debugging
device_count = self.fields["device"].queryset.count()
if device_count == 0:
self.fields["device"].help_text = "⚠️ Hech qanday qurilma topilmadi. Admindan so'rab qurilmalar qo'shing."
def clean_device(self):
device = self.cleaned_data.get('device')
if not device:
raise forms.ValidationError("Qurilmani tanlang")
return device
def clean_quantity(self):
quantity = self.cleaned_data.get('quantity')
if quantity is None:
raise forms.ValidationError("Miqdorni kiriting")
if quantity < 1:
raise forms.ValidationError("Miqdor 1 dan katta bo'lishi kerak")
return quantity

View File

@@ -2,10 +2,18 @@ from django import forms
from core.apps.management.models import ToyMovement, Warehouse, Device from core.apps.management.models import ToyMovement, Warehouse, Device
from core.apps.management.choice import TOY_MOVEMENT_TYPE from core.apps.management.choice import TOY_MOVEMENT_TYPE
class ToyMovementForm(forms.ModelForm): class ToyMovementForm(forms.ModelForm):
class Meta: class Meta:
model = ToyMovement model = ToyMovement
fields = ["movement_type", "from_warehouse", "to_warehouse", "device", "quantity"] fields = ["movement_type", "from_warehouse", "to_warehouse", "device", "quantity"]
labels = {
"movement_type": "Harakat turi",
"from_warehouse": "Qaysi ombordan",
"to_warehouse": "Qaysi omborga",
"device": "Qurilma",
"quantity": "Soni",
}
widgets = { widgets = {
"movement_type": forms.Select(choices=TOY_MOVEMENT_TYPE), "movement_type": forms.Select(choices=TOY_MOVEMENT_TYPE),
} }
@@ -13,18 +21,36 @@ class ToyMovementForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
user = kwargs.pop("user", None) user = kwargs.pop("user", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields["from_warehouse"].queryset = Warehouse.objects.all() self.fields["from_warehouse"].queryset = Warehouse.objects.all()
self.fields["to_warehouse"].queryset = Warehouse.objects.all() self.fields["to_warehouse"].queryset = Warehouse.objects.all()
self.fields["device"].queryset = Device.objects.all() self.fields["device"].queryset = Device.objects.all()
def save(self, commit=True): def clean(self):
instance = super().save(commit=False) cleaned_data = super().clean()
if instance.movement_type == "from_warehouse": movement_type = cleaned_data.get("movement_type")
instance.to_warehouse = None from_wh = cleaned_data.get("from_warehouse")
elif instance.movement_type == "between_warehouses": to_wh = cleaned_data.get("to_warehouse")
instance.device = None device = cleaned_data.get("device")
quantity = cleaned_data.get("quantity")
if commit: if not from_wh:
instance.save() self.add_error("from_warehouse", "Source warehouse is required.")
return instance
if quantity and quantity <= 0:
self.add_error("quantity", "Quantity must be greater than 0.")
if movement_type == "from_warehouse":
if not device:
self.add_error("device", "Device is required for this movement type.")
cleaned_data["to_warehouse"] = None
if movement_type == "between_warehouses":
if not to_wh:
self.add_error("to_warehouse", "Destination warehouse required.")
if from_wh and to_wh and from_wh == to_wh:
self.add_error("to_warehouse", "Cannot move to the same warehouse.")
cleaned_data["device"] = None
return cleaned_data

View File

@@ -4,4 +4,9 @@ from ..models import Warehouse
class WarehouseForm(forms.ModelForm): class WarehouseForm(forms.ModelForm):
class Meta: class Meta:
model = Warehouse model = Warehouse
fields = ["name", "region", "toys_count"] fields = ["name", "region", "toys_count"]
labels = {
"name": "Nomi",
"region": "Hudud",
"toys_count": "Oyinchoqlar soni",
}

View File

@@ -5,4 +5,6 @@ from .WarehouseForm import *
from .UserCreateForm import * from .UserCreateForm import *
from .ToyMovementEmployeeForm import ToyMovementFormEmployee from .ToyMovementEmployeeForm import ToyMovementFormEmployee
from .ToyMovementForm import ToyMovementForm from .ToyMovementForm import ToyMovementForm
from .user import * from .user import *
from .DevicePaymentForm import *
from .ReportForm import *

View File

@@ -27,6 +27,15 @@ class BaseUserForm(forms.ModelForm):
"region", "region",
"warehouse", "warehouse",
] ]
labels = {
"phone": "Telefon raqami",
"password": "Parol",
"role": "Lavozim",
"first_name": "Ism",
"last_name": "Familiya",
"region": "Hudud",
"warehouse": "Ombor",
}
def clean_role(self): def clean_role(self):
role = self.cleaned_data["role"] role = self.cleaned_data["role"]

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.2.7 on 2026-02-12 06:57
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('management', '0015_remove_income_confirmed_by'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='rent',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='created_rents', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='rent',
name='is_paid',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='rent',
name='paid_at',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-02-12 09:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('management', '0016_rent_created_by_rent_is_paid_rent_paid_at'),
]
operations = [
migrations.AddField(
model_name='expense',
name='comment',
field=models.TextField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.7 on 2026-02-12 13:27
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('management', '0017_expense_comment'),
]
operations = [
migrations.AddField(
model_name='income',
name='warehouse',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='warehouse_incomes', to='management.warehouse'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-02-13 06:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('management', '0018_income_warehouse'),
]
operations = [
migrations.AlterField(
model_name='expense',
name='expense_type',
field=models.CharField(choices=[('salary', 'Maosh'), ('utilities', 'Kommunal tolovlar'), ('maintenance', 'Texnik xizmat'), ('food', 'Oziq-ovqat'), ('transport', "Yo'lkira"), ('buy_toys', 'Oʻyinchoqlar sotib olish'), ('other', 'Boshqa')], default='other', max_length=20),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-02-13 09:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('management', '0019_alter_expense_expense_type'),
]
operations = [
migrations.AddField(
model_name='device',
name='amount',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='device',
name='due_date',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='device',
name='is_paid',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,26 @@
# Generated by Django 5.2.7 on 2026-02-13 12:56
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('management', '0020_device_amount_device_due_date_device_is_paid'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('device', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='management.device')),
],
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.7 on 2026-02-17 09:17
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('management', '0021_report'),
]
operations = [
migrations.AlterField(
model_name='district',
name='region',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='districts', to='management.region'),
),
]

View File

@@ -5,4 +5,5 @@ from .district import *
from .toyMovement import * from .toyMovement import *
from .warehouse import * from .warehouse import *
from .expense import * from .expense import *
from .rent import * from .rent import *
from .report import *

View File

@@ -1,10 +1,31 @@
from django.db import models from django.db import models
from .district import District from .district import District
from django.utils import timezone
class Device(models.Model): class Device(models.Model):
address = models.CharField(max_length=100, unique=True) address = models.CharField(max_length=100, unique=True)
district = models.ForeignKey(District, on_delete=models.PROTECT) district = models.ForeignKey(District, on_delete=models.PROTECT)
due_date = models.DateField(null=True, blank=True)
amount = models.IntegerField(null=True, blank=True)
is_paid = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):
return self.address return self.address
@property
def days_until_due(self):
"""Calculate days remaining until payment is due"""
if not self.due_date:
return None
today = timezone.now().date()
delta = self.due_date - today
return delta.days
@property
def is_overdue(self):
"""Check if payment is overdue"""
if not self.due_date or self.is_paid:
return False
today = timezone.now().date()
return self.due_date < today

View File

@@ -4,7 +4,7 @@ from .region import Region
class District(models.Model): class District(models.Model):
region = models.ForeignKey( region = models.ForeignKey(
Region, Region,
on_delete=models.CASCADE, on_delete=models.PROTECT,
related_name="districts" related_name="districts"
) )
name = models.CharField(max_length=100) name = models.CharField(max_length=100)

View File

@@ -3,7 +3,6 @@ from core.apps.management.models import Device
class Expense(models.Model): class Expense(models.Model):
class ExpenseType(models.TextChoices): class ExpenseType(models.TextChoices):
RENT = "rent", "Ijara"
SALARY = "salary", "Maosh" SALARY = "salary", "Maosh"
UTILITIES = "utilities", "Kommunal tolovlar" UTILITIES = "utilities", "Kommunal tolovlar"
MAINTENANCE = "maintenance", "Texnik xizmat" MAINTENANCE = "maintenance", "Texnik xizmat"
@@ -28,7 +27,7 @@ class Expense(models.Model):
"accounts.User", on_delete=models.PROTECT, "accounts.User", on_delete=models.PROTECT,
null=True, blank=True, related_name="confirmed_expenses" null=True, blank=True, related_name="confirmed_expenses"
) )
comment = models.TextField(blank=True, null=True)
is_confirmed = models.BooleanField(default=False) is_confirmed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
@@ -41,7 +40,6 @@ class Expense(models.Model):
# Device required for rent/utilities/maintenance # Device required for rent/utilities/maintenance
if self.expense_type in [ if self.expense_type in [
self.ExpenseType.RENT,
self.ExpenseType.MAINTENANCE self.ExpenseType.MAINTENANCE
] and not self.device: ] and not self.device:
raise ValidationError({"device": "Device must be set for this type of expense."}) raise ValidationError({"device": "Device must be set for this type of expense."})

View File

@@ -3,8 +3,10 @@ from .device import Device
class Income(models.Model): class Income(models.Model):
device = models.ForeignKey(Device, related_name='incomes',on_delete=models.PROTECT) device = models.ForeignKey(Device, related_name='incomes',on_delete=models.PROTECT)
amount = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) warehouse = models.ForeignKey("management.Warehouse", on_delete=models.PROTECT, related_name="warehouse_incomes", null=True, blank=True)
created_by = models.ForeignKey("accounts.User", on_delete=models.PROTECT, related_name="created_incomes")
amount = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
created_by = models.ForeignKey("accounts.User", on_delete=models.PROTECT, related_name="created_incomes")
is_confirmed = models.BooleanField(default=False) is_confirmed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)

View File

@@ -1,6 +1,8 @@
from django.db import models from django.db import models
from .district import District from .district import District
from .device import Device from .device import Device
from django.conf import settings
class Rent(models.Model): class Rent(models.Model):
address = models.CharField(max_length=100, unique=True) address = models.CharField(max_length=100, unique=True)
@@ -9,6 +11,15 @@ class Rent(models.Model):
due_date = models.DateField() due_date = models.DateField()
amount = models.IntegerField() amount = models.IntegerField()
is_paid = models.BooleanField(default=False)
paid_at = models.DateTimeField(null=True, blank=True)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="created_rents",
on_delete=models.PROTECT,
null=True,
blank=True
)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):

View File

@@ -0,0 +1,9 @@
from django.db import models
class Report(models.Model):
quantity = models.PositiveIntegerField(default=0)
device = models.ForeignKey("management.Device", on_delete=models.PROTECT, null=True, blank=True)
created_by = models.ForeignKey("accounts.User", on_delete=models.PROTECT, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)

View File

@@ -1,23 +1,39 @@
from django.db import models from django.db import models
from .device import Device
from core.apps.management.choice import TOY_MOVEMENT_TYPE
from core.apps.management.models import Device
from .warehouse import Warehouse from .warehouse import Warehouse
from ..choice import TOY_MOVEMENT_TYPE
class ToyMovement(models.Model): class ToyMovement(models.Model):
movement_type = models.CharField(max_length=30, choices=TOY_MOVEMENT_TYPE) movement_type = models.CharField(max_length=30, choices=TOY_MOVEMENT_TYPE)
from_warehouse = models.ForeignKey( from_warehouse = models.ForeignKey(
Warehouse, on_delete=models.PROTECT, Warehouse,
on_delete=models.PROTECT,
related_name="outgoing" related_name="outgoing"
) )
to_warehouse = models.ForeignKey( to_warehouse = models.ForeignKey(
Warehouse, on_delete=models.PROTECT, Warehouse,
on_delete=models.PROTECT,
related_name="incoming", related_name="incoming",
null=True, blank=True null=True,
blank=True
) )
device = models.ForeignKey( device = models.ForeignKey(
Device, on_delete=models.PROTECT, Device,
null=True, blank=True on_delete=models.PROTECT,
null=True,
blank=True
) )
quantity = models.PositiveIntegerField() quantity = models.PositiveIntegerField()
created_by = models.ForeignKey("accounts.User", on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey(
"accounts.User",
on_delete=models.PROTECT
)
created_at = models.DateTimeField(auto_now_add=True)

View File

@@ -6,7 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<style> <style>
* { box-sizing: border-box; } * {
box-sizing: border-box;
}
body { body {
margin: 0; margin: 0;
@@ -27,97 +29,79 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative; position: relative;
box-shadow: 0 2px 8px rgba(0,0,0,0.15); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
} }
header .logo { font-size: 22px; font-weight: 700; letter-spacing: 1px; }
#menu-btn { header .logo {
font-size: 22px;
font-weight: 700;
letter-spacing: 1px;
}
/* Home Button */
.home-btn {
position: absolute; position: absolute;
left: 24px; top: 50%; left: 24px;
top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
background: #fff; border: none; padding: 6px 10px; background: rgba(255, 255, 255, 0.2);
border-radius: 8px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#menu-btn svg { stroke: #4f46e5; }
/* Sidebar */
#sidebar {
background: #fff;
border-right: 1px solid #e5e7eb;
padding: 20px;
width: 240px;
position: fixed;
top: 0;
left: -260px;
height: 100vh; /* full viewport height */
overflow: hidden; /* non-scrollable */
transition: left 0.3s ease, box-shadow 0.3s ease;
z-index: 1000;
display: flex;
flex-direction: column;
justify-content: space-between; /* keeps logout at bottom */
box-shadow: 0 0 20px rgba(0,0,0,0.05);
}
#sidebar.active { left: 0; }
#sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
#sidebar ul li { margin-bottom: 16px; }
#sidebar ul li a {
text-decoration: none;
color: #1f2937;
font-weight: 500;
display: block;
padding: 12px 16px;
border-radius: 8px;
transition: all 0.25s ease;
background: #f9fafb;
white-space: nowrap; /* prevent wrapping */
overflow: hidden;
text-overflow: ellipsis;
}
#sidebar ul li a:hover {
background: #4f46e5;
color: #fff;
box-shadow: 0 2px 8px rgba(79,70,229,0.3);
}
/* Logout button fixed at bottom */
.logout-container {
margin-top: auto;
flex-shrink: 0;
}
.logout-btn {
width: 100%;
background: #ef4444;
color: #fff;
border: none; border: none;
padding: 10px 14px; color: #fff;
border-radius: 10px; width: 40px;
font-weight: 600; height: 40px;
font-size: 14px; border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.25s ease; transition: all 0.2s ease;
box-shadow: 0 2px 6px rgba(0,0,0,0.1); text-decoration: none;
} }
.home-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-50%) scale(1.05);
}
.home-btn svg {
width: 20px;
height: 20px;
stroke: #fff;
stroke-width: 2;
}
/* Logout Button */
.logout-btn {
position: absolute;
right: 24px;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.2);
border: none;
color: #fff;
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
font-size: 20px;
}
.logout-btn:hover { .logout-btn:hover {
background: #dc2626; background: #ef4444;
box-shadow: 0 3px 8px rgba(0,0,0,0.15); transform: translateY(-50%) scale(1.05);
} }
main { main {
flex: 1; flex: 1;
padding: 10px; padding: 20px;
transition: margin-left 0.3s ease;
background: #f4f5f7; background: #f4f5f7;
min-height: calc(100vh - 64px); min-height: calc(100vh - 64px);
} }
main.shifted { margin-left: 240px; }
/* Cards */ /* Cards */
.cards-container { .cards-container {
@@ -125,17 +109,34 @@
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px; gap: 16px;
} }
.card { .card {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 12px;
padding: 16px 20px; padding: 16px 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: transform 0.2s, box-shadow 0.2s; transition: transform 0.2s, box-shadow 0.2s;
} }
.card:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,0.08); }
.card-row { margin-bottom: 8px; font-size: 14px; color: #374151; } .card:hover {
.card-row strong { color: #111827; } transform: translateY(-2px);
.card-actions { margin-top: 12px; } box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
}
.card-row {
margin-bottom: 8px;
font-size: 14px;
color: #374151;
}
.card-row strong {
color: #111827;
}
.card-actions {
margin-top: 12px;
}
.card-actions .btn { .card-actions .btn {
display: inline-block; display: inline-block;
padding: 6px 10px; padding: 6px 10px;
@@ -147,93 +148,97 @@
margin-right: 6px; margin-right: 6px;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.card-actions .btn.edit { background: #4f46e5; }
.card-actions .btn.confirm { background: #10b981; } .card-actions .btn.edit {
.card-actions .btn.decline { background: #ef4444; } background: #4f46e5;
.card-actions .btn:hover { opacity: 0.85; transform: translateY(-1px); } }
.card-actions .btn.confirm {
background: #10b981;
}
.card-actions .btn.decline {
background: #ef4444;
}
.card-actions .btn:hover {
opacity: 0.85;
transform: translateY(-1px);
}
/* Responsive */ /* Responsive */
@media (max-width: 1024px) {
#sidebar { width: 220px; }
main.shifted { margin-left: 220px; }
}
@media (max-width: 768px) {
#sidebar { width: 200px; left: -220px; }
#sidebar.active { left: 0; box-shadow: 2px 0 12px rgba(0,0,0,0.2); }
main.shifted { margin-left: 0; } /* overlay on small screens */
}
@media (max-width: 640px) { @media (max-width: 640px) {
.cards-container { grid-template-columns: 1fr; } .cards-container {
.card { padding: 14px 16px; } grid-template-columns: 1fr;
.card-row { font-size: 13px; } }
.card-actions .btn { font-size: 12px; padding: 5px 8px; margin-bottom: 4px; }
.card {
padding: 14px 16px;
}
.card-row {
font-size: 13px;
}
.card-actions .btn {
font-size: 12px;
padding: 5px 8px;
margin-bottom: 4px;
}
} }
@media (max-width: 480px) { @media (max-width: 480px) {
header { padding: 12px 16px; } header {
#menu-btn { left: 16px; padding: 4px 8px; } padding: 12px 16px;
.logo { font-size: 18px; } }
#sidebar { padding: 16px; }
.logout-btn { font-size: 13px; padding: 8px 10px; } .logo {
font-size: 18px;
}
main {
padding: 16px;
}
.home-btn {
left: 16px;
width: 36px;
height: 36px;
}
.home-btn svg {
width: 18px;
height: 18px;
}
.logout-btn {
right: 16px;
width: 36px;
height: 36px;
font-size: 18px;
}
} }
</style> </style>
</head> </head>
<body> <body>
<header> <header>
<button id="menu-btn"> <a href="{% url 'dashboard' %}" class="home-btn" title="Dashboard">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
stroke-linecap="round" stroke-linejoin="round"> <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<path d="M4 5h16"/> <polyline points="9 22 9 12 15 12 15 22"></polyline>
<path d="M4 12h16"/>
<path d="M4 19h16"/>
</svg> </svg>
</button> </a>
<div class="logo">Xvatayka</div> <div class="logo">Xvatayka</div>
<form method="post" action="{% url 'logout' %}" style="margin: 0;">
{% csrf_token %}
<button type="submit" class="logout-btn" title="Chiqish">🚪</button>
</form>
</header> </header>
<nav id="sidebar">
<ul>
<li><a href="{% url 'dashboard' %}">Dashboard</a></li>
<li><a href="{% url 'expense_list' %}">Xarajatlar</a></li>
<li><a href="{% url 'income_list' %}">Kirimlar</a></li>
<li><a href="{% url 'toy_movement_list' %}">Oʻyinchoq harakatlari</a></li>
{% if user.role == "manager" or user.role == "businessman" %}
<li><a href="{% url 'device_list' %}">Aparatlar</a></li>
<li><a href="{% url 'warehouse_list' %}">Omborlar</a></li>
<li><a href="{% url 'user_list' %}">Foydalanuvchilar</a></li>
{% endif %}
</ul>
<div class="logout-container">
{% if user.is_authenticated %}
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit" class="logout-btn">Chiqish</button>
</form>
{% endif %}
</div>
</nav>
<main id="main-content"> <main id="main-content">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
<script>
const menuBtn = document.getElementById('menu-btn');
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('main-content');
menuBtn.addEventListener('click', () => {
sidebar.classList.toggle('active');
mainContent.classList.toggle('shifted');
});
// Optional: click outside to close
document.addEventListener('click', (e) => {
if (!sidebar.contains(e.target) && !menuBtn.contains(e.target)) {
sidebar.classList.remove('active');
mainContent.classList.remove('shifted');
}
});
</script>
</body> </body>
</html> </html>

View File

@@ -1,117 +1,146 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<br> <br>
<h2 style="text-align:center; margin-bottom:24px; color:#111827;">Businessman Paneli</h2> <h2 style="text-align:center; margin-bottom:24px; color:#111827;">Businessman Paneli</h2>
<div class="dashboard-grid"> <div class="dashboard-grid">
<a href="{% url 'create_user' %}" class="dashboard-card"> <!-- View/Manage Actions -->
<div class="icon">👤</div> <a href="{% url 'expense_list' %}" class="dashboard-card">
<div class="label">Foydalanuvchi yaratish</div> <div class="icon">📋</div>
</a> <div class="label">Xarajatlar</div>
<a href="{% url 'create_device' %}" class="dashboard-card"> </a>
<div class="icon">💻</div> <a href="{% url 'toy_movement_list' %}" class="dashboard-card">
<div class="label">Aparat yaratish</div> <div class="icon">📦</div>
</a> <div class="label">Oʻyinchoq harakatlari</div>
<a href="{% url 'create_expense' %}" class="dashboard-card"> </a>
<div class="icon">💸</div> <a href="{% url 'device_list' %}" class="dashboard-card">
<div class="label">Xarajat qo'shish</div> <div class="icon">🖥️</div>
</a> <div class="label">Aparatlar</div>
<a href="{% url 'create_income' %}" class="dashboard-card"> </a>
<div class="icon">📥</div> <a href="{% url 'warehouse_list' %}" class="dashboard-card">
<div class="label">Kirim qo'shish</div> <div class="icon">🏭</div>
</a> <div class="label">Omborlar</div>
<a href="{% url 'create_warehouse' %}" class="dashboard-card"> </a>
<div class="icon">🏬</div> <a href="{% url 'user_list' %}" class="dashboard-card">
<div class="label">Ombor yaratish</div> <div class="icon">👥</div>
</a> <div class="label">Foydalanuvchilar</div>
<a href="{% url 'create_toy_movement' %}" class="dashboard-card"> </a>
<div class="icon">🚚</div> <a href="{% url 'report_list' %}" class="dashboard-card">
<div class="label">Oʻyinchoq harakati yaratish</div> <div class="icon">📊</div>
</a> <div class="label">Kunlik Hisobotlar</div>
</div> </a>
</div>
<style> <style>
.dashboard-grid { .dashboard-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px; gap: 16px;
max-width: 960px; max-width: 960px;
margin: 0 auto; margin: 0 auto;
padding: 0 10px; padding: 0 10px;
} }
.dashboard-card { .dashboard-card {
background: #fff; background: #fff;
border-radius: 16px; border-radius: 16px;
padding: 20px 12px; padding: 20px 12px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
color: #111827; color: #111827;
font-weight: 600; font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,0.06); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
transition: transform 0.2s, box-shadow 0.2s, background 0.2s; transition: transform 0.2s, box-shadow 0.2s, background 0.2s;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} border: none;
cursor: pointer;
}
.dashboard-card .icon { .dashboard-card .icon {
font-size: 32px; font-size: 32px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.dashboard-card:hover { .dashboard-card:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 6px 20px rgba(0,0,0,0.1); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
background: #4f46e5; background: #4f46e5;
color: #fff; color: #fff;
} }
.dashboard-card:hover .icon { .dashboard-card:hover .icon {
color: #fff; color: #fff;
} }
.label { .dashboard-card.secondary {
font-size: 14px; background: #f0f4ff;
text-align: center; color: #4f46e5;
} }
/* Mobile adjustments: keep grid layout, adjust size */ .dashboard-card.secondary:hover {
@media (max-width: 768px) { background: #4f46e5;
.dashboard-grid { color: #fff;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
gap: 12px;
}
.dashboard-card {
padding: 16px 10px;
}
.dashboard-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.label {
font-size: 13px;
}
}
@media (max-width: 480px) { .dashboard-card.logout-card {
.dashboard-grid { background: #fee2e2;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); color: #dc2626;
gap: 10px; }
}
.dashboard-card {
padding: 14px 8px;
}
.dashboard-card .icon {
font-size: 24px;
margin-bottom: 6px;
}
.label {
font-size: 12px;
}
}
</style>
{% endblock %} .dashboard-card.logout-card:hover {
background: #dc2626;
color: #fff;
}
.label {
font-size: 14px;
text-align: center;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
}
.dashboard-card {
padding: 16px 10px;
}
.dashboard-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.label {
font-size: 13px;
}
}
@media (max-width: 480px) {
.dashboard-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.dashboard-card {
padding: 14px 8px;
}
.dashboard-card .icon {
font-size: 24px;
margin-bottom: 6px;
}
.label {
font-size: 12px;
}
}
</style>
{% endblock %}

View File

@@ -1,33 +1,58 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ title|default:"Yaratish" }}{% endblock %} {% block title %}{{ title|default:"Aparat Yaratish" }}{% endblock %}
{% block content %} {% block content %}
<div class="form-container"> <div class="form-container">
<h2>{{ title|default:"Yaratish" }}</h2> <h2>{{ title|default:"Aparat Yaratish" }}</h2>
<a href="{% url 'dashboard' %}" class="back-btn"> <a href="{% url 'device_list' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
stroke-width="2"> fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/> <path stroke-linecap="round" stroke-linejoin="round"
d="M15 18l-6-6 6-6"/>
</svg> </svg>
Orqaga Orqaga
</a> </a>
<form method="post" novalidate> <form method="post" novalidate>
{% csrf_token %} {% csrf_token %}
{% for field in form %}
<!-- Address -->
<div class="form-group"> <div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label> <label for="{{ form.address.id_for_label }}">Manzil</label>
{{ field }} {{ form.address }}
{% if field.help_text %} {% for error in form.address.errors %}
<small class="help-text">{{ field.help_text }}</small> <div class="error">{{ error }}</div>
{% endif %} {% endfor %}
{% for error in field.errors %} </div>
<div class="error">{{ error }}</div>
<!-- District -->
<div class="form-group">
<label for="{{ form.district.id_for_label }}">Tuman</label>
{{ form.district }}
{% for error in form.district.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
<!-- Amount -->
<div class="form-group">
<label for="{{ form.amount.id_for_label }}">Summa</label>
{{ form.amount }}
{% for error in form.amount.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
<!-- Due Date -->
<div class="form-group">
<label for="{{ form.due_date.id_for_label }}">To'lov muddati</label>
{{ form.due_date }}
{% for error in form.due_date.errors %}
<div class="error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% endfor %}
<button type="submit" class="submit-btn">Saqlash</button> <button type="submit" class="submit-btn">Saqlash</button>
</form> </form>
@@ -36,7 +61,7 @@
<style> <style>
.form-container { .form-container {
max-width: 480px; max-width: 480px;
margin: 0 auto; margin: 40px auto;
background: #fff; background: #fff;
padding: 24px 28px; padding: 24px 28px;
border-radius: 12px; border-radius: 12px;
@@ -44,7 +69,7 @@
} }
.form-container h2 { .form-container h2 {
margin-bottom: 16px; margin-bottom: 20px;
font-size: 22px; font-size: 22px;
color: #111827; color: #111827;
text-align: center; text-align: center;
@@ -70,7 +95,6 @@
background: #4f46e5; background: #4f46e5;
color: #fff; color: #fff;
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
} }
.back-btn .icon { .back-btn .icon {
@@ -79,10 +103,9 @@
} }
.form-group { .form-group {
margin-bottom: 16px; margin-bottom: 18px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: all 0.2s ease;
} }
.form-group label { .form-group label {
@@ -107,12 +130,6 @@
box-shadow: 0 0 0 2px rgba(79,70,229,0.2); box-shadow: 0 0 0 2px rgba(79,70,229,0.2);
} }
.help-text {
font-size: 12px;
color: #6b7280;
margin-top: 4px;
}
.error { .error {
font-size: 12px; font-size: 12px;
color: #ef4444; color: #ef4444;
@@ -121,7 +138,7 @@
.submit-btn { .submit-btn {
width: 100%; width: 100%;
padding: 10px 0; padding: 12px 0;
background: #4f46e5; background: #4f46e5;
color: #fff; color: #fff;
font-weight: 600; font-weight: 600;
@@ -137,11 +154,10 @@
transform: translateY(-1px); transform: translateY(-1px);
} }
/* Mobile adjustments */
@media (max-width: 480px) { @media (max-width: 480px) {
.form-container { .form-container {
padding: 20px 16px; padding: 20px 16px;
} }
} }
</style> </style>
{% endblock %} {% endblock %}

View File

@@ -6,7 +6,7 @@
<div class="form-container"> <div class="form-container">
<h2>{{ title|default:"Yaratish" }}</h2> <h2>{{ title|default:"Yaratish" }}</h2>
<a href="{% url 'dashboard' %}" class="back-btn"> <a href="{% url 'expense_list' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2"> stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/> <path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/>
@@ -41,25 +41,42 @@ document.addEventListener("DOMContentLoaded", function() {
const typeSelect = document.getElementById("id_expense_type"); const typeSelect = document.getElementById("id_expense_type");
const employeeGroup = document.getElementById("group-employee"); const employeeGroup = document.getElementById("group-employee");
const deviceGroup = document.getElementById("group-device"); const deviceGroup = document.getElementById("group-device");
const commentGroup = document.getElementById("group-comment");
function hideAllConditional() {
if (employeeGroup) employeeGroup.style.display = "none";
if (deviceGroup) deviceGroup.style.display = "none";
if (commentGroup) commentGroup.style.display = "none";
}
function toggleFields() { function toggleFields() {
if (!typeSelect) return; if (!typeSelect) return;
const value = typeSelect.value; const value = typeSelect.value;
// Hide both initially // Hide all conditional fields first
if (employeeGroup) employeeGroup.style.display = "none"; hideAllConditional();
if (deviceGroup) deviceGroup.style.display = "none";
// Show relevant fields based on expense type
if (value === "salary") { if (value === "salary") {
if (employeeGroup) employeeGroup.style.display = "flex"; if (employeeGroup) employeeGroup.style.display = "flex";
} else if (value === "rent" || value === "maintenance") { } else if (value === "rent" || value === "maintenance") {
if (deviceGroup) deviceGroup.style.display = "flex"; if (deviceGroup) deviceGroup.style.display = "flex";
} else if (value === "other") {
if (commentGroup) commentGroup.style.display = "flex";
} }
} }
// Initialize: hide all conditional fields on page load
hideAllConditional();
// Then show appropriate fields based on current selection
toggleFields(); toggleFields();
if (typeSelect) typeSelect.addEventListener("change", toggleFields);
// Listen for changes
if (typeSelect) {
typeSelect.addEventListener("change", toggleFields);
}
}); });
</script> </script>
@@ -114,7 +131,8 @@ document.addEventListener("DOMContentLoaded", function() {
color: #374151; color: #374151;
} }
.form-group input, .form-group input,
.form-group select { .form-group select,
.form-group textarea {
padding: 10px 14px; padding: 10px 14px;
border: 1px solid #d1d5db; border: 1px solid #d1d5db;
border-radius: 8px; border-radius: 8px;
@@ -123,7 +141,8 @@ document.addEventListener("DOMContentLoaded", function() {
transition: border-color 0.2s, box-shadow 0.2s; transition: border-color 0.2s, box-shadow 0.2s;
} }
.form-group input:focus, .form-group input:focus,
.form-group select:focus { .form-group select:focus,
.form-group textarea:focus {
border-color: #4f46e5; border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79,70,229,0.2); box-shadow: 0 0 0 2px rgba(79,70,229,0.2);
} }
@@ -162,4 +181,4 @@ document.addEventListener("DOMContentLoaded", function() {
.form-container { padding: 20px 16px; } .form-container { padding: 20px 16px; }
} }
</style> </style>
{% endblock %} {% endblock %}

View File

@@ -6,7 +6,7 @@
<div class="form-container"> <div class="form-container">
<h2>{{ title|default:"Yaratish" }}</h2> <h2>{{ title|default:"Yaratish" }}</h2>
<a href="{% url 'dashboard' %}" class="back-btn"> <a href="{% url 'income_list' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2"> stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/> <path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/>

View File

@@ -0,0 +1,148 @@
{% extends "base.html" %}
{% block title %}{{ title|default:"Arenda Yaratish" }}{% endblock %}
{% block content %}
<div class="form-container">
<h2>{{ title|default:"Arenda Yaratish" }}</h2>
<a href="{% url 'dashboard' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/>
</svg>
Orqaga
</a>
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<small class="help-text">{{ field.help_text }}</small>
{% endif %}
{% for error in field.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<button type="submit" class="submit-btn">Saqlash</button>
</form>
</div>
<style>
.form-container {
max-width: 480px;
margin: 0 auto;
background: #fff;
padding: 24px 28px;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
}
.form-container h2 {
margin-bottom: 16px;
font-size: 22px;
color: #111827;
text-align: center;
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 20px;
padding: 8px 16px;
background: #f3f4f6;
color: #4f46e5;
text-decoration: none;
font-weight: 500;
font-size: 14px;
border-radius: 9999px;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: all 0.2s ease;
}
.back-btn:hover {
background: #4f46e5;
color: #fff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.back-btn .icon {
width: 16px;
height: 16px;
}
.form-group {
margin-bottom: 16px;
display: flex;
flex-direction: column;
transition: all 0.2s ease;
}
.form-group label {
margin-bottom: 6px;
font-weight: 600;
color: #374151;
}
.form-group input,
.form-group select {
padding: 10px 14px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus,
.form-group select:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79,70,229,0.2);
}
.help-text {
font-size: 12px;
color: #6b7280;
margin-top: 4px;
}
.error {
font-size: 12px;
color: #ef4444;
margin-top: 4px;
}
.submit-btn {
width: 100%;
padding: 10px 0;
background: #4f46e5;
color: #fff;
font-weight: 600;
font-size: 15px;
border: none;
border-radius: 10px;
cursor: pointer;
transition: background 0.2s, transform 0.2s;
}
.submit-btn:hover {
background: #4338ca;
transform: translateY(-1px);
}
/* Responsive adjustments */
@media (max-width: 480px) {
.form-container {
padding: 20px 16px;
}
}
</style>
{% endblock %}

View File

@@ -0,0 +1,284 @@
{% extends "base.html" %}
{% block title %}Yakuniy Hisobot{% endblock %}
{% block content %}
<div class="form-container">
<div class="form-header">
<h2>Yakuniy Hisobot</h2>
<p class="subtitle">Kunni yakunlash uchun qurilma va oxirgi ko'rsatkichni tanlang</p>
</div>
{% if form.non_field_errors %}
<div class="alert alert-error">
{% for error in form.non_field_errors %}
<p>⚠️ {{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post" id="reportForm" novalidate>
{% csrf_token %}
<!-- Device Field -->
<div class="form-group">
<label for="{{ form.device.id_for_label }}">{{ form.device.label }} <span class="required">*</span></label>
<div class="input-wrapper">
{{ form.device }}
{% if form.device.help_text %}
<small class="help-text">{{ form.device.help_text }}</small>
{% endif %}
{% if form.device.errors %}
<div class="field-error">
{% for error in form.device.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<!-- Quantity Field -->
<div class="form-group">
<label for="{{ form.quantity.id_for_label }}">{{ form.quantity.label }} <span class="required">*</span></label>
<div class="input-wrapper">
{{ form.quantity }}
{% if form.quantity.errors %}
<div class="field-error">
{% for error in form.quantity.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<!-- Submit Button -->
<button type="submit" class="submit-btn">Yakunlash</button>
<!-- Back Link -->
<a href="{% url 'employee_dashboard' %}" class="back-link">Orqaga qaytish</a>
</form>
</div>
<script>
document.getElementById("reportForm").addEventListener("submit", function(e){
const device = document.getElementById("id_device").value;
const quantity = document.getElementById("id_quantity").value;
// Validate before showing confirmation
if (!device) {
e.preventDefault();
alert("Qurilmani tanlang");
return;
}
if (!quantity || quantity < 1) {
e.preventDefault();
alert("Miqdorni to'g'ri kiriting");
return;
}
// Show confirmation dialog
const deviceText = document.getElementById("id_device").options[
document.getElementById("id_device").selectedIndex
].text;
if (!confirm(`Siz kunni yakunlamoqchimisiz?\n\nQurilma: ${deviceText}\nMiqdor: ${quantity}`)) {
e.preventDefault();
}
});
</script>
<style>
.form-container {
max-width: 450px;
margin: 40px auto;
padding: 32px;
background: #fff;
border-radius: 14px;
box-shadow: 0 6px 20px rgba(0,0,0,0.08);
}
.form-header {
text-align: center;
margin-bottom: 28px;
}
.form-header h2 {
margin: 0 0 8px 0;
font-size: 24px;
color: #111827;
font-weight: 700;
}
.subtitle {
margin: 0;
font-size: 14px;
color: #6b7280;
line-height: 1.5;
}
/* Alerts */
.alert {
padding: 12px 14px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
line-height: 1.5;
}
.alert-error {
background: #fee2e2;
border: 1px solid #fecaca;
color: #dc2626;
}
.alert-error p {
margin: 0;
}
/* Form Group */
.form-group {
margin-bottom: 20px;
display: flex;
flex-direction: column;
}
.form-group label {
margin-bottom: 8px;
font-weight: 600;
color: #374151;
font-size: 14px;
}
.required {
color: #ef4444;
}
.input-wrapper {
display: flex;
flex-direction: column;
}
#id_device,
#id_quantity {
padding: 11px 13px;
border: 1.5px solid #d1d5db;
border-radius: 9px;
font-size: 14px;
font-family: inherit;
outline: none;
transition: all 0.25s ease;
background: #fff;
}
#id_device:focus,
#id_quantity:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
background: #f8f7ff;
}
#id_device:disabled {
background: #f3f4f6;
color: #9ca3af;
cursor: not-allowed;
}
#id_quantity::placeholder {
color: #9ca3af;
}
/* Help Text */
.help-text {
font-size: 13px;
color: #f59e0b;
margin-top: 6px;
padding: 8px 10px;
background: #fef3c7;
border-left: 3px solid #f59e0b;
border-radius: 4px;
}
/* Field Errors */
.field-error {
margin-top: 6px;
padding: 8px 10px;
background: #fee2e2;
border-left: 3px solid #ef4444;
border-radius: 4px;
}
.field-error p {
margin: 0;
color: #dc2626;
font-size: 13px;
font-weight: 500;
line-height: 1.4;
}
.field-error p:not(:last-child) {
margin-bottom: 4px;
}
/* Buttons */
.submit-btn {
width: 100%;
padding: 12px 0;
background: linear-gradient(135deg, #16a34a 0%, #15803d 100%);
color: #fff;
font-weight: 600;
font-size: 15px;
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.25s ease;
margin-bottom: 12px;
}
.submit-btn:hover {
background: linear-gradient(135deg, #15803d 0%, #166534 100%);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(22, 163, 74, 0.3);
}
.submit-btn:active {
transform: translateY(0);
}
.back-link {
display: block;
text-align: center;
padding: 10px 0;
color: #4f46e5;
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
border-radius: 8px;
}
.back-link:hover {
background: #f3f4f6;
color: #4338ca;
}
/* Responsive */
@media (max-width: 480px) {
.form-container {
padding: 24px 16px;
margin: 20px 10px;
}
.form-header h2 {
font-size: 20px;
}
.subtitle {
font-size: 13px;
}
}
</style>
{% endblock %}

View File

@@ -4,31 +4,93 @@
{% block content %} {% block content %}
<div class="form-container"> <div class="form-container">
<h2>{{ title|default:"Yaratish" }}</h2> <h2>{{ title|default:"O'yinchoq harakati yaratish" }}</h2>
<a href="{% url 'dashboard' %}" class="back-btn"> <a href="{% url 'toy_movement_list' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/> <path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/>
</svg> </svg>
Orqaga Orqaga
</a> </a>
{% if form.non_field_errors %}
<div class="alert alert-error" style="margin-bottom: 16px;">
{% for error in form.non_field_errors %}
<p>⚠️ {{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post" novalidate> <form method="post" novalidate>
{% csrf_token %} {% csrf_token %}
{% for field in form %} <!-- EMPLOYEE FORM (Device + Quantity only) -->
<div class="form-group"> {% if request.user.role == "employee" %}
<label for="{{ field.id_for_label }}">{{ field.label }}</label> <!-- Device Field -->
{{ field }} <div class="form-group">
{% if field.help_text %} <label for="{{ form.device.id_for_label }}">{{ form.device.label }}</label>
<small class="help-text">{{ field.help_text }}</small> {{ form.device }}
{% endif %} {% if form.device.errors %}
{% for error in field.errors %} <div class="error">{{ form.device.errors.0 }}</div>
<div class="error">{{ error }}</div> {% endif %}
{% endfor %} </div>
</div>
{% endfor %} <!-- Quantity Field -->
<div class="form-group">
<label for="{{ form.quantity.id_for_label }}">{{ form.quantity.label }}</label>
{{ form.quantity }}
{% if form.quantity.errors %}
<div class="error">{{ form.quantity.errors.0 }}</div>
{% endif %}
</div>
<!-- MANAGER/BUSINESSMAN FORM (Full form) -->
{% else %}
<!-- Movement Type Field -->
<div class="form-group">
<label for="{{ form.movement_type.id_for_label }}">{{ form.movement_type.label }}</label>
{{ form.movement_type }}
{% if form.movement_type.errors %}
<div class="error">{{ form.movement_type.errors.0 }}</div>
{% endif %}
</div>
<!-- From Warehouse Field -->
<div class="form-group">
<label for="{{ form.from_warehouse.id_for_label }}">{{ form.from_warehouse.label }}</label>
{{ form.from_warehouse }}
{% if form.from_warehouse.errors %}
<div class="error">{{ form.from_warehouse.errors.0 }}</div>
{% endif %}
</div>
<!-- To Warehouse Field (hidden by default) -->
<div class="form-group" id="to-warehouse-group" style="display: none;">
<label for="{{ form.to_warehouse.id_for_label }}">{{ form.to_warehouse.label }}</label>
{{ form.to_warehouse }}
{% if form.to_warehouse.errors %}
<div class="error">{{ form.to_warehouse.errors.0 }}</div>
{% endif %}
</div>
<!-- Device Field (hidden by default) -->
<div class="form-group" id="device-group" style="display: none;">
<label for="{{ form.device.id_for_label }}">{{ form.device.label }}</label>
{{ form.device }}
{% if form.device.errors %}
<div class="error">{{ form.device.errors.0 }}</div>
{% endif %}
</div>
<!-- Quantity Field -->
<div class="form-group">
<label for="{{ form.quantity.id_for_label }}">{{ form.quantity.label }}</label>
{{ form.quantity }}
{% if form.quantity.errors %}
<div class="error">{{ form.quantity.errors.0 }}</div>
{% endif %}
</div>
{% endif %}
<button type="submit" class="submit-btn">Saqlash</button> <button type="submit" class="submit-btn">Saqlash</button>
</form> </form>
@@ -79,10 +141,29 @@
height: 16px; height: 16px;
} }
.alert {
padding: 12px 14px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
line-height: 1.5;
}
.alert-error {
background: #fee2e2;
border: 1px solid #fecaca;
color: #dc2626;
}
.alert-error p {
margin: 0;
}
.form-group { .form-group {
margin-bottom: 16px; margin-bottom: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%;
} }
.form-group label { .form-group label {
@@ -94,11 +175,14 @@
.form-group input, .form-group input,
.form-group select { .form-group select {
padding: 10px 14px; padding: 10px 14px;
border: 1px solid #d1d5db; border: 1.5px solid #d1d5db;
border-radius: 8px; border-radius: 8px;
font-size: 14px; font-size: 14px;
outline: none; outline: none;
transition: border-color 0.2s, box-shadow 0.2s; transition: border-color 0.2s, box-shadow 0.2s;
font-family: inherit;
width: 100%;
box-sizing: border-box;
} }
.form-group input:focus, .form-group input:focus,
@@ -117,6 +201,10 @@
font-size: 12px; font-size: 12px;
color: #ef4444; color: #ef4444;
margin-top: 4px; margin-top: 4px;
padding: 6px 8px;
background: #fee2e2;
border-left: 3px solid #ef4444;
border-radius: 4px;
} }
.submit-btn { .submit-btn {
@@ -146,49 +234,37 @@
<script> <script>
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const movementType = document.getElementById("id_movement_type"); const userRole = "{{ request.user.role }}";
const toWarehouse = document.getElementById("id_to_warehouse");
const device = document.getElementById("id_device");
function toggleFields() { // Only show field toggling for manager/businessman
const value = movementType.value; if (userRole !== "employee") {
const movementTypeSelect = document.getElementById("id_movement_type");
const toWarehouseGroup = document.getElementById("to-warehouse-group");
const deviceGroup = document.getElementById("device-group");
if (value === "from_warehouse") { // Ombordan -> Aparatga function toggleFields() {
toWarehouse.parentElement.style.display = "none"; const value = movementTypeSelect.value;
device.parentElement.style.display = "";
} else if (value === "between_warehouses") { // Ombordan -> Omborga // Reset all to hidden
device.parentElement.style.display = "none"; toWarehouseGroup.style.display = "none";
toWarehouse.parentElement.style.display = ""; deviceGroup.style.display = "none";
} else {
toWarehouse.parentElement.style.display = ""; // Show relevant fields based on movement type
device.parentElement.style.display = ""; if (value === "from_warehouse") {
// Ombordan -> Aparatga
deviceGroup.style.display = "block";
} else if (value === "between_warehouses") {
// Ombordan -> Omborga
toWarehouseGroup.style.display = "block";
}
} }
}
// Update movement_type when to_warehouse is selected // Listen for changes
if (toWarehouse) { if (movementTypeSelect) {
toWarehouse.addEventListener("change", function() { movementTypeSelect.addEventListener("change", toggleFields);
if (toWarehouse.value) { // Initial toggle on page load
movementType.value = "between_warehouses"; toggleFields();
toggleFields(); }
}
});
}
// Update movement_type when device is selected
if (device) {
device.addEventListener("change", function() {
if (device.value) {
movementType.value = "from_warehouse";
toggleFields();
}
});
}
// Run on page load
if (movementType) {
toggleFields();
movementType.addEventListener("change", toggleFields);
} }
}); });
</script> </script>

View File

@@ -6,7 +6,7 @@
<div class="form-container"> <div class="form-container">
<h2>{{ title|default:"Yaratish" }}</h2> <h2>{{ title|default:"Yaratish" }}</h2>
<a href="{% url 'dashboard' %}" class="back-btn"> <a href="{% url 'user_list' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2"> stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/> <path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/>
@@ -22,10 +22,10 @@
<label for="{{ field.id_for_label }}">{{ field.label }}</label> <label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }} {{ field }}
{% if field.help_text %} {% if field.help_text %}
<small class="help-text">{{ field.help_text }}</small> <p class="help-text">{{ field.help_text }}</p>
{% endif %} {% endif %}
{% for error in field.errors %} {% for error in field.errors %}
<div class="error">{{ error }}</div> <div class="error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
@@ -35,120 +35,121 @@
</div> </div>
<style> <style>
.form-container {
max-width: 480px;
margin: 40px auto;
background: #fff;
padding: 24px 28px;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
}
.form-container h2 {
margin-bottom: 16px;
font-size: 22px;
color: #111827;
text-align: center;
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 20px;
padding: 8px 16px;
background: #f3f4f6;
color: #4f46e5;
text-decoration: none;
font-weight: 500;
font-size: 14px;
border-radius: 9999px;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: all 0.2s ease;
}
.back-btn:hover {
background: #4f46e5;
color: #fff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.back-btn .icon {
width: 16px;
height: 16px;
}
.form-group {
margin-bottom: 16px;
display: flex;
flex-direction: column !important;
}
.form-group label {
margin-bottom: 6px;
font-weight: 600;
color: #374151;
}
.form-group input,
.form-group select,
.form-group textarea {
padding: 10px 14px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
font-family: inherit;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79,70,229,0.2);
}
.help-text {
font-size: 12px;
color: #6b7280;
margin-top: 4px;
}
.error {
font-size: 12px;
color: #ef4444;
margin-top: 4px;
}
.submit-btn {
width: 100%;
padding: 10px 0;
background: #4f46e5;
color: #fff;
font-weight: 600;
font-size: 15px;
border: none;
border-radius: 10px;
cursor: pointer;
transition: background 0.2s, transform 0.2s;
}
.submit-btn:hover {
background: #4338ca;
transform: translateY(-1px);
}
@media (max-width: 480px) {
.form-container { .form-container {
max-width: 480px; padding: 20px 16px;
margin: 0 auto;
background: #fff;
padding: 24px 28px;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
}
.form-container h2 {
margin-bottom: 16px;
font-size: 22px;
color: #111827;
text-align: center;
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 20px;
padding: 8px 16px;
background: #f3f4f6;
color: #4f46e5;
text-decoration: none;
font-weight: 500;
font-size: 14px;
border-radius: 9999px; /* pill shape */
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: all 0.2s ease;
}
.back-btn:hover {
background: #4f46e5;
color: #fff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.back-btn .icon {
width: 16px;
height: 16px;
}
.form-group {
margin-bottom: 16px;
display: flex;
flex-direction: column;
transition:all 0.2s ease;
}
.form-group label {
margin-bottom: 6px;
font-weight: 600;
color: #374151;
}
.form-group input,
.form-group select {
padding: 10px 14px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus,
.form-group select:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79,70,229,0.2);
}
.help-text {
font-size: 12px;
color: #6b7280;
margin-top: 4px;
}
.error {
font-size: 12px;
color: #ef4444;
margin-top: 4px;
}
.submit-btn {
width: 100%;
padding: 10px 0;
background: #4f46e5;
color: #fff;
font-weight: 600;
font-size: 15px;
border: none;
border-radius: 10px;
cursor: pointer;
transition: background 0.2s, transform 0.2s;
}
.submit-btn:hover {
background: #4338ca;
transform: translateY(-1px);
}
/* Responsive adjustments */
@media (max-width: 480px) {
.form-container {
padding: 20px 16px;
}
} }
}
</style> </style>
<script> <script>
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const roleField = document.getElementById("id_role"); const roleField = document.getElementById("id_role");
const regionGroup = document.getElementById("id_region")?.closest(".form-group"); const regionGroup = document.getElementById("id_region")?.closest(".form-group");
const warehouseGroup = document.getElementById("id_warehouse")?.closest(".form-group"); const warehouseGroup = document.getElementById("id_warehouse")?.closest(".form-group");
@@ -158,15 +159,23 @@ document.addEventListener("DOMContentLoaded", function () {
const role = roleField.value; const role = roleField.value;
// Manager // Hide everything first
if (regionGroup) regionGroup.style.display = "none";
if (warehouseGroup) warehouseGroup.style.display = "none";
// Manager - shows region
// Employee - shows warehouse only
if (role === "manager") { if (role === "manager") {
if (regionGroup) regionGroup.style.display = "block"; if (regionGroup) regionGroup.style.display = "flex";
if (warehouseGroup) warehouseGroup.style.display = "none";
} }
// Employee
if (role === "employee") { if (role === "employee") {
if (regionGroup) regionGroup.style.display = "none"; if (warehouseGroup) warehouseGroup.style.display = "flex";
}
// Businessman - shows both region and warehouse
if (role === "businessman") {
if (regionGroup) regionGroup.style.display = "block";
if (warehouseGroup) warehouseGroup.style.display = "block"; if (warehouseGroup) warehouseGroup.style.display = "block";
} }
} }
@@ -179,6 +188,4 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
</script> </script>
{% endblock %}
{% endblock %}

View File

@@ -1,10 +1,120 @@
<!DOCTYPE html> {% extends "base.html" %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body> {% block title %}{{ title }}{% endblock %}
</html>
{% block content %}
<div class="page">
<div class="card">
<a href="{% url 'warehouse_list' %}" class="back">
← Orqaga
</a>
<h1>{{ title }}</h1>
<form method="post">
{% csrf_token %}
<div class="field">
<label for="{{ form.name.id_for_label }}">Nomi</label>
{{ form.name }}
{{ form.name.errors }}
</div>
<div class="field">
<label for="{{ form.region.id_for_label }}">Hudud</label>
{{ form.region }}
{{ form.region.errors }}
</div>
<div class="field">
<label for="{{ form.toys_count.id_for_label }}">O'yinchoqlar Soni</label>
{{ form.toys_count }}
{{ form.toys_count.errors }}
</div>
<button type="submit" class="submit-btn">Saqlash</button>
</form>
</div>
</div>
<style>
body, html {
overflow: hidden;
margin: 0;
padding: 0;
height: 100vh;
}
.page {
height: 100vh;
width: 100vw;
background: #f6f7fb;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 40px 20px;
overflow: hidden;
box-sizing: border-box;
}
.card {
width: 100%;
max-width: 480px;
background: white;
padding: 32px;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0,0,0,.08);
}
.card h1 {
text-align: center;
margin-bottom: 24px;
margin-top: 0;
}
.back {
display: inline-block;
margin-bottom: 20px;
text-decoration: none;
color: #4f46e5;
font-weight: 500;
}
.field {
margin-bottom: 18px;
}
.field label {
display: block;
margin-bottom: 6px;
font-weight: 600;
}
.field input,
.field select {
width: 100%;
padding: 12px;
border-radius: 10px;
border: 1px solid #d1d5db;
box-sizing: border-box;
}
.submit-btn {
width: 100%;
padding: 14px;
background: #4f46e5;
color: white;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
}
.submit-btn:hover {
background: #4338ca;
}
</style>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Create Expense{% endblock %} {% block title %}Edit Expense{% endblock %}
{% block content %} {% block content %}
<style> <style>
@@ -19,6 +19,29 @@
text-align: center; text-align: center;
} }
.back-btn {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 20px;
padding: 8px 16px;
background: #f3f4f6;
color: #4f46e5;
text-decoration: none;
font-weight: 500;
font-size: 14px;
border-radius: 9999px;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: all 0.2s ease;
}
.back-btn:hover {
background: #4f46e5;
color: #fff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.back-btn .icon { width: 16px; height: 16px; }
.field { .field {
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -31,13 +54,25 @@
font-weight: 500; font-weight: 500;
} }
select, input { select, input, textarea {
width: 100%; width: 100%;
padding: 14px; padding: 14px;
font-size: 16px; font-size: 16px;
border-radius: 12px; border-radius: 12px;
border: 1px solid #d1d5db; border: 1px solid #d1d5db;
background: #f9fafb; background: #f9fafb;
font-family: inherit;
}
textarea {
resize: vertical;
min-height: 80px;
}
select:focus, input:focus, textarea:focus {
outline: none;
border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79,70,229,0.2);
} }
button { button {
@@ -66,44 +101,74 @@
margin-bottom: 14px; margin-bottom: 14px;
font-size: 14px; font-size: 14px;
} }
.field-error {
color: #ef4444;
font-size: 12px;
margin-top: 4px;
}
</style> </style>
<div class="card"> <div class="card">
<h1>Create Expense</h1> <a href="{% url 'dashboard' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/>
</svg>
Orqaga
</a>
<h1>{% if title %}{{ title }}{% else %}Edit Expense{% endif %}</h1>
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="error">{{ form.non_field_errors.0 }}</div> <div class="error">{{ form.non_field_errors.0 }}</div>
{% endif %} {% endif %}
<form method="POST"> <form method="POST" novalidate>
{% csrf_token %} {% csrf_token %}
<div class="field" id="field-expense_type"> <div class="field" id="field-expense_type">
{{ form.expense_type.label_tag }} {{ form.expense_type.label_tag }}
{{ form.expense_type }} {{ form.expense_type }}
{{ form.expense_type.errors }} {% for error in form.expense_type.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %}
</div> </div>
<div class="field" id="field-amount"> <div class="field" id="field-amount">
{{ form.amount.label_tag }} {{ form.amount.label_tag }}
{{ form.amount }} {{ form.amount }}
{{ form.amount.errors }} {% for error in form.amount.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %}
</div> </div>
<div class="field" id="field-employee" style="display:none;"> <div class="field" id="field-employee" style="display:none;">
{{ form.employee.label_tag }} {{ form.employee.label_tag }}
{{ form.employee }} {{ form.employee }}
{{ form.employee.errors }} {% for error in form.employee.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %}
</div> </div>
<div class="field" id="field-device" style="display:none;"> <div class="field" id="field-device" style="display:none;">
{{ form.device.label_tag }} {{ form.device.label_tag }}
{{ form.device }} {{ form.device }}
{{ form.device.errors }} {% for error in form.device.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %}
</div>
<div class="field" id="field-comment" style="display:none;">
{{ form.comment.label_tag }}
{{ form.comment }}
{% for error in form.comment.errors %}
<div class="field-error">{{ error }}</div>
{% endfor %}
</div> </div>
<button type="submit"> <button type="submit">
{% if title %}{{ title }}{% else %}Create{% endif %} {% if title %}{{ title }}{% else %}Saqlash{% endif %}
</button> </button>
</form> </form>
</div> </div>
@@ -113,27 +178,38 @@
const typeSelect = document.getElementById("id_expense_type"); const typeSelect = document.getElementById("id_expense_type");
const employeeField = document.getElementById("field-employee"); const employeeField = document.getElementById("field-employee");
const deviceField = document.getElementById("field-device"); const deviceField = document.getElementById("field-device");
const commentField = document.getElementById("field-comment");
function hideAllConditional() {
if (employeeField) employeeField.style.display = "none";
if (deviceField) deviceField.style.display = "none";
if (commentField) commentField.style.display = "none";
}
function toggleFields() { function toggleFields() {
if (!typeSelect) return; if (!typeSelect) return;
const value = typeSelect.value; const value = typeSelect.value;
// Reset both // Hide all conditional fields first
employeeField.style.display = "none"; hideAllConditional();
deviceField.style.display = "none";
// Show relevant fields based on expense type
if (value === "salary") { if (value === "salary") {
employeeField.style.display = "block"; if (employeeField) employeeField.style.display = "block";
} else if (value === "rent" || value === "maintenance") { } else if (value === "rent" || value === "maintenance") {
deviceField.style.display = "block"; if (deviceField) deviceField.style.display = "block";
} else if (value === "other") {
if (commentField) commentField.style.display = "block";
} }
} }
if (typeSelect) { if (typeSelect) {
// Initialize on page load
toggleFields(); toggleFields();
// Listen for changes
typeSelect.addEventListener("change", toggleFields); typeSelect.addEventListener("change", toggleFields);
} }
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Create Income{% endblock %} {% block title %}Edit Income{% endblock %}
{% block content %} {% block content %}
<style> <style>
@@ -40,7 +40,7 @@
background: #f9fafb; background: #f9fafb;
} }
button { .submit-btn{
width: 100%; width: 100%;
margin-top: 12px; margin-top: 12px;
padding: 15px; padding: 15px;
@@ -69,7 +69,7 @@
</style> </style>
<div class="card"> <div class="card">
<h1>Create Income</h1> <h1>Edit Income</h1>
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="error">{{ form.non_field_errors.0 }}</div> <div class="error">{{ form.non_field_errors.0 }}</div>
@@ -90,7 +90,7 @@
{{ form.amount.errors }} {{ form.amount.errors }}
</div> </div>
<button type="submit"> <button type="submit" class="submit-btn">
{% if title %}{{ title }}{% else %}Create{% endif %} {% if title %}{{ title }}{% else %}Create{% endif %}
</button> </button>
</form> </form>

View File

@@ -1,10 +1,153 @@
<!DOCTYPE html> {% extends "base.html" %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body> {% block title %}{{ title }}{% endblock %}
</html>
{% block content %}
<div class="form-container">
<h2>{{ title }}</h2>
<a href="{% url 'businessman_dashboard' %}" class="back-btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 18l-6-6 6-6"/>
</svg>
Orqaga
</a>
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label for="{{ form.name.id_for_label }}">Nomi</label>
{{ form.name }}
{% for error in form.name.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.region.id_for_label }}">Hududi</label>
{{ form.region }}
{% for error in form.region.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="{{ form.toys_count.id_for_label }}">O'yinchoqlar Soni</label>
{{ form.toys_count }}
{% for error in form.toys_count.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
<button type="submit" class="submit-btn">Saqlash</button>
</form>
</div>
<style>
.form-container {
max-width: 480px;
margin: 0 auto;
background: #fff;
padding: 24px 28px;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
}
.form-container h2 {
margin-bottom: 16px;
font-size: 22px;
color: #111827;
text-align: center;
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 20px;
padding: 8px 16px;
background: #f3f4f6;
color: #4f46e5;
text-decoration: none;
font-weight: 500;
font-size: 14px;
border-radius: 9999px;
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transition: all 0.2s ease;
}
.back-btn:hover {
background: #4f46e5;
color: #fff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.back-btn .icon {
width: 16px;
height: 16px;
}
.form-group {
margin-bottom: 16px;
display: flex;
flex-direction: column;
transition: all 0.2s ease;
}
.form-group label {
margin-bottom: 6px;
font-weight: 600;
color: #374151;
}
.form-group input,
.form-group select {
padding: 10px 14px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.form-group input:focus,
.form-group select:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79,70,229,0.2);
}
.error {
font-size: 12px;
color: #ef4444;
margin-top: 4px;
}
.submit-btn {
width: 100%;
padding: 10px 0;
background: #4f46e5;
color: #fff;
font-weight: 600;
font-size: 15px;
border: none;
border-radius: 10px;
cursor: pointer;
transition: background 0.2s, transform 0.2s;
}
.submit-btn:hover {
background: #4338ca;
transform: translateY(-1px);
}
/* Mobile adjustments */
@media (max-width: 480px) {
.form-container {
padding: 20px 16px;
}
}
</style>
{% endblock %}

View File

@@ -3,7 +3,20 @@
{% block title %}Aparatlar{% endblock %} {% block title %}Aparatlar{% endblock %}
{% block content %} {% block content %}
<h2 style="margin-bottom:20px;">{{ title|default:"Aparatlar" }}</h2> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">{{ title|default:"Aparatlar" }}</h2>
{% if role == "manager" or role == "businessman" %}
<a href="{% url 'create_device' %}" class="btn edit" style="padding: 12px 24px; text-decoration: none; background: #4f46e5; color: #fff; font-weight: 600; border-radius: 10px; transition: all 0.2s ease; display: inline-block; font-size: 15px;">+ Yaratish</a>
{% endif %}
</div>
<style>
a.btn.edit:hover {
background: #4338ca;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
}
</style>
<div class="cards-container"> <div class="cards-container">
{% for device in devices %} {% for device in devices %}

View File

@@ -0,0 +1,401 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<br>
<h2 style="text-align:center; margin-bottom:24px;">Arendalar</h2>
<!-- Hidden CSRF token for JavaScript access -->
<input type="hidden" id="csrfToken" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
<div class="table-container">
<table class="styled-table">
<thead>
<tr>
<th>#</th>
<th>Aparat</th>
<th>Hudud</th>
<th>To'lov muddati</th>
<th>Summa</th>
<th>To'landi</th>
</tr>
</thead>
<tbody>
{% for device in devices %}
<tr class="{% if not device.is_paid %}{% if device.is_overdue %}row-overdue{% elif device.days_until_due < 1 %}row-red{% elif device.days_until_due < 3 %}row-yellow{% elif device.days_until_due < 5 %}row-green{% endif %}{% endif %}">
<td>{{ forloop.counter }}</td>
<td>{{ device.address }}</td>
<td>{{ device.district.name }}</td>
<td>{{ device.due_date|date:"d.m.Y" }}</td>
<td class="amount-cell">{{ device.amount }}</td>
<td>
{% if not device.is_paid %}
<input type="checkbox"
class="payment-checkbox"
data-device-id="{{ device.id }}"
data-device-name="{{ device.address }}"
data-amount="{{ device.amount }}">
{% else %}
<span class="paid-badge">✅ To'langan</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" style="text-align:center; padding: 30px; color: #6b7280;">
Hech qanday arenda topilmadi
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Confirmation Modal -->
<div id="confirmModal" class="modal hidden">
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-header">
<h3>To'lovni tasdiqlash</h3>
<button class="modal-close" type="button">&times;</button>
</div>
<div class="modal-body">
<p>Siz haqiqatan ham <strong id="modalDeviceName"></strong> uchun <strong id="modalAmount"></strong> so'mlik to'lovni tasdiqlamoqchimisiz?</p>
<p style="color: #ef4444; font-size: 13px; margin-top: 12px; display: flex; align-items: center; gap: 6px;">
<span>⚠️</span> Bu amalni bekor qilib bo'lmaydi
</p>
</div>
<div class="modal-actions">
<button id="cancelBtn" class="btn btn-cancel" type="button">Bekor qilish</button>
<button id="confirmBtn" class="btn btn-confirm" type="button">Tasdiqlamoq</button>
</div>
</div>
</div>
<style>
.table-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 10px;
}
.styled-table {
width: 100%;
border-collapse: collapse;
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
.styled-table th,
.styled-table td {
padding: 14px 16px;
text-align: center;
border-bottom: 1px solid #e5e7eb;
}
.styled-table th {
background: #4f46e5;
color: #fff;
font-weight: 600;
letter-spacing: 0.5px;
}
.styled-table tbody tr {
transition: background-color 0.2s ease;
background-color: #fff;
}
.styled-table tbody tr:hover {
opacity: 0.9;
}
.styled-table tbody tr:last-child {
border-bottom: none;
}
/* Row color coding based on days until due - ONLY for unpaid items */
.styled-table tbody tr.row-green {
background-color: #d1fae5;
}
.styled-table tbody tr.row-green:hover {
background-color: #a7f3d0;
}
.styled-table tbody tr.row-yellow {
background-color: #fef3c7;
}
.styled-table tbody tr.row-yellow:hover {
background-color: #fde68a;
}
.styled-table tbody tr.row-red {
background-color: #fee2e2;
}
.styled-table tbody tr.row-red:hover {
background-color: #fecaca;
}
.styled-table tbody tr.row-overdue {
background-color: #fee2e2;
}
.styled-table tbody tr.row-overdue:hover {
background-color: #fecaca;
}
.amount-cell {
font-weight: 600;
color: #059669;
}
input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: #4f46e5;
transition: transform 0.2s ease;
}
input[type="checkbox"]:hover {
transform: scale(1.1);
}
.paid-badge {
color: #10b981;
font-weight: 600;
background: #d1fae5;
padding: 4px 8px;
border-radius: 6px;
font-size: 13px;
}
/* Modal Styles */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.modal.hidden {
display: none;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: relative;
background: #fff;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 90%;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
padding: 20px;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
color: #1f2937;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
color: #6b7280;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: all 0.2s ease;
}
.modal-close:hover {
background: #f3f4f6;
color: #1f2937;
}
.modal-body {
padding: 20px;
color: #374151;
line-height: 1.6;
}
.modal-body p {
margin: 0;
}
.modal-actions {
padding: 16px 20px;
display: flex;
gap: 12px;
justify-content: flex-end;
border-top: 1px solid #e5e7eb;
}
.btn {
padding: 10px 16px;
border-radius: 6px;
border: none;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-cancel {
background: #f3f4f6;
color: #374151;
}
.btn-cancel:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
.btn-confirm {
background: #10b981;
color: #fff;
}
.btn-confirm:hover {
background: #059669;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
/* Responsive */
@media (max-width: 640px) {
.styled-table th,
.styled-table td {
padding: 10px 8px;
font-size: 13px;
}
.modal-content {
width: 95%;
}
.modal-header h3 {
font-size: 16px;
}
.btn {
padding: 8px 12px;
font-size: 13px;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('confirmModal');
const cancelBtn = document.getElementById('cancelBtn');
const confirmBtn = document.getElementById('confirmBtn');
const modalClose = document.querySelector('.modal-close');
const modalOverlay = document.querySelector('.modal-overlay');
let currentDeviceId = null;
// Open modal when checkbox is clicked
document.querySelectorAll('.payment-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function(e) {
if (this.checked) {
currentDeviceId = this.dataset.deviceId;
const deviceName = this.dataset.deviceName;
const amount = this.dataset.amount;
document.getElementById('modalDeviceName').textContent = deviceName;
document.getElementById('modalAmount').textContent = amount;
modal.classList.remove('hidden');
}
});
});
// Close modal
function closeModal() {
modal.classList.add('hidden');
document.querySelectorAll('.payment-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
}
cancelBtn.addEventListener('click', closeModal);
modalClose.addEventListener('click', closeModal);
modalOverlay.addEventListener('click', closeModal);
// Confirm payment
confirmBtn.addEventListener('click', function() {
if (currentDeviceId) {
// Get CSRF token from hidden input
const csrfToken = document.getElementById('csrfToken').value;
const form = document.createElement('form');
form.method = 'POST';
form.action = `/edit/device-payments/${currentDeviceId}/`;
if (csrfToken) {
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrfmiddlewaretoken';
csrfInput.value = csrfToken;
form.appendChild(csrfInput);
}
document.body.appendChild(form);
form.submit();
}
});
// Close modal on Escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
});
</script>
{% endblock %}

View File

@@ -4,33 +4,256 @@
{% block title %}Xarajatlar{% endblock %} {% block title %}Xarajatlar{% endblock %}
{% block content %} {% block content %}
<h2 style="margin-bottom:20px;">{{ title|default:"Xarajatlar" }}</h2> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">{{ title|default:"Xarajatlar" }}</h2>
<a href="{% url 'create_expense' %}" class="btn edit" style="padding: 12px 24px; text-decoration: none; background: #4f46e5; color: #fff; font-weight: 600; border-radius: 10px; transition: all 0.2s ease; display: inline-block; font-size: 15px;">+ Qo'shish</a>
</div>
<div class="cards-container"> <div class="cards-container">
{% for obj in expenses %} {% if expenses %}
<div class="card"> {% for obj in expenses %}
<div class="card-row"><strong>Miqdor:</strong> {{ obj.amount }}</div> <div class="card {% if obj.is_confirmed %}confirmed{% endif %}">
<div class="card-row"><strong>Tur:</strong> {{ obj|attr:"expense_type" }}</div> <div class="card-header">
{% if obj.device %}<div class="card-row"><strong>Aparat:</strong> {{ obj.device.name }}</div>{% else %}{% endif %} <div class="card-title">
{% if obj.employee %}<div class="card-row"><strong>Hodim:</strong> {{ obj.employee.get_full_name }}</div>{% else %}{% endif %} {% if obj.expense_type == 'other' and obj.comment %}
<div class="card-row"><strong>Yaratgan:</strong> {{ obj.created_by.get_full_name }}</div> {{ obj.comment }}
<div class="card-row"><strong>Tasdiqlanganmi:</strong> {% if obj.confirmed_by %}{{ obj.confirmed_by.get_full_name }}{% else %}Yo'q{% endif %}</div> {% else %}
<div class="card-row"><strong>Yaratilgan sana:</strong> {{ obj.created_at|date:"d.m.Y H:i" }}</div> {{ obj.get_expense_type_display }}
{% endif %}
</div>
<div class="card-number">#{{ forloop.counter }}</div>
</div>
{% if role == "manager" or role == "businessman" %} <div class="card-content">
<div class="card-actions"> <div class="card-row">
{% if not obj.is_confirmed %} <span class="label">Miqdor:</span>
<a href="{% url 'confirm_expense' obj.pk %}" class="btn confirm"></a> <span class="amount">{{ obj.amount|floatformat:2 }} so'm</span>
<a href="{% url 'decline_expense' obj.pk %}" class="btn decline"></a> </div>
{% endif %} <div class="card-row">
{% if role == "businessman" %} <span class="label">Sana:</span>
<a href="{% url 'edit_expense' obj.pk %}" class="btn edit">Tahrirlash</a> <span class="value">{{ obj.created_at|date:"d.m.Y H:i" }}</span>
</div>
<div class="card-row">
<span class="label">Yaratgan:</span>
<span class="value">{{ obj.created_by.get_full_name }}</span>
</div>
</div>
{% if role == "manager" or role == "businessman" %}
<div class="card-actions">
{% if not obj.is_confirmed %}
<a href="{% url 'confirm_expense' obj.pk %}" class="action-btn confirm" title="Tasdiqlash">✔ Tasdiqlash</a>
<a href="{% url 'decline_expense' obj.pk %}" class="action-btn decline" title="Rad etish">✖ Rad etish</a>
{% else %}
<span class="confirmed-badge">✅ Tasdiqlandi</span>
{% endif %}
{% if role == "businessman" %}
<a href="{% url 'edit_expense' obj.pk %}" class="action-btn edit" title="Tahrirlash">✎ Tahrirlash</a>
{% endif %}
</div>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endfor %}
{% else %}
<div style="grid-column: 1 / -1; text-align: center; padding: 60px 20px; color: #6b7280;">
<p style="font-style: italic; margin: 0;">Hech qanday xarajat topilmadi</p>
</div> </div>
{% empty %} {% endif %}
<p style="text-align:center; font-style:italic;">Hech narsa topilmadi</p>
{% endfor %}
</div> </div>
{% endblock %}
<style>
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.card {
background: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: all 0.2s ease;
border-left: 4px solid #4f46e5;
}
.card.confirmed {
border-left-color: #10b981;
background: #f0fdf4;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #e5e7eb;
}
.card-title {
font-weight: 700;
color: #1f2937;
font-size: 15px;
flex: 1;
}
.card-number {
font-size: 12px;
color: #9ca3af;
font-weight: 600;
}
.card-content {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
.card-row {
display: flex;
justify-content: space-between;
font-size: 13px;
}
.card-row .label {
color: #6b7280;
font-weight: 500;
}
.card-row .value {
color: #374151;
font-weight: 500;
}
.card-row .amount {
color: #059669;
font-weight: 700;
}
.card-actions {
display: flex;
gap: 6px;
flex-wrap: wrap;
padding-top: 12px;
border-top: 1px solid #e5e7eb;
}
.action-btn {
display: inline-block;
padding: 6px 10px;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
font-size: 12px;
transition: all 0.2s ease;
border: none;
cursor: pointer;
flex: 1;
text-align: center;
}
.action-btn.confirm {
background: #d1fae5;
color: #059669;
}
.action-btn.confirm:hover {
background: #10b981;
color: #fff;
}
.action-btn.decline {
background: #fee2e2;
color: #dc2626;
}
.action-btn.decline:hover {
background: #ef4444;
color: #fff;
}
.action-btn.edit {
background: #dbeafe;
color: #0284c7;
}
.action-btn.edit:hover {
background: #0284c7;
color: #fff;
}
.confirmed-badge {
display: inline-block;
background: #10b981;
color: #fff;
padding: 6px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
flex: 1;
text-align: center;
}
.btn {
display: inline-block;
padding: 8px 14px;
border-radius: 6px;
text-decoration: none;
color: #fff;
font-weight: 600;
font-size: 14px;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.btn.edit:hover {
background: #4338ca;
box-shadow: 0 2px 8px rgba(79,70,229,0.3);
transform: translateY(-1px);
}
@media (max-width: 768px) {
.cards-container {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.card {
padding: 14px;
}
.action-btn {
flex: none;
min-width: 80px;
}
}
@media (max-width: 480px) {
.cards-container {
grid-template-columns: 1fr;
}
div[style*="display: flex"] {
flex-direction: column;
gap: 10px;
}
.btn {
width: 100%;
text-align: center;
}
.action-btn {
flex: 1;
}
}
</style>
{% endblock %}

View File

@@ -0,0 +1,164 @@
{% extends "base.html" %}
{% block title %}Hisobotlar{% endblock %}
{% block content %}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">{{ title|default:"Hisobotlar" }}</h2>
</div>
<div class="cards-container">
{% if reports %}
{% for report in reports %}
<div class="card">
<div class="card-header">
<div class="card-title">
{{ report.device.address }}
</div>
<div class="card-number">#{{ forloop.counter }}</div>
</div>
<div class="card-content">
<div class="card-row">
<span class="label">Miqdor:</span>
<span class="quantity">{{ report.quantity }} dona</span>
</div>
<div class="card-row">
<span class="label">Sana:</span>
<span class="value">{{ report.created_at|date:"d.m.Y H:i" }}</span>
</div>
<div class="card-row">
<span class="label">Yaratgan:</span>
<span class="value">{{ report.created_by.get_full_name }}</span>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div style="grid-column: 1 / -1; text-align: center; padding: 60px 20px; color: #6b7280;">
<p style="font-style: italic; margin: 0;">Hech qanday hisobot topilmadi</p>
</div>
{% endif %}
</div>
<style>
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.card {
background: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: all 0.2s ease;
border-left: 4px solid #10b981;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #e5e7eb;
}
.card-title {
font-weight: 700;
color: #1f2937;
font-size: 15px;
flex: 1;
}
.card-number {
font-size: 12px;
color: #9ca3af;
font-weight: 600;
}
.card-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.card-row {
display: flex;
justify-content: space-between;
font-size: 13px;
}
.card-row .label {
color: #6b7280;
font-weight: 500;
}
.card-row .value {
color: #374151;
font-weight: 500;
}
.card-row .quantity {
color: #10b981;
font-weight: 700;
}
.btn {
display: inline-block;
padding: 8px 14px;
border-radius: 6px;
text-decoration: none;
color: #fff;
font-weight: 600;
font-size: 14px;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.btn.edit {
background: #4f46e5;
}
.btn.edit:hover {
background: #4338ca;
box-shadow: 0 2px 8px rgba(79,70,229,0.3);
transform: translateY(-1px);
}
@media (max-width: 768px) {
.cards-container {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.card {
padding: 14px;
}
}
@media (max-width: 480px) {
.cards-container {
grid-template-columns: 1fr;
}
div[style*="display: flex"] {
flex-direction: column;
gap: 10px;
}
.btn {
width: 100%;
text-align: center;
}
}
</style>
{% endblock %}

View File

@@ -3,32 +3,184 @@
{% block title %}O'yinchoq harakatlari{% endblock %} {% block title %}O'yinchoq harakatlari{% endblock %}
{% block content %} {% block content %}
<h2 style="margin-bottom:20px;">{{ title|default:"O'yinchoq harakatlari" }}</h2> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">{{ title|default:"O'yinchoq harakatlari" }}</h2>
{% if role == "manager" or role == "businessman" %} {% if role == "manager" or role == "businessman" %}
<div class="card-actions"> <a href="{% url 'create_toy_movement_auto' %}"
<a href="{% url 'create_toy_movement_auto' %}" class="btn edit">Yaratish</a> class="btn edit"
style="padding: 12px 24px; text-decoration: none; background: #4f46e5; color: #fff; font-weight: 600; border-radius: 10px; transition: all 0.2s ease; display: inline-block; font-size: 15px;">
+ Yaratish
</a>
{% elif role == "employee" %}
<a href="{% url 'create_toy_movement' %}"
class="btn edit"
style="padding: 12px 24px; text-decoration: none; background: #4f46e5; color: #fff; font-weight: 600; border-radius: 10px; transition: all 0.2s ease; display: inline-block; font-size: 15px;">
+ Yaratish
</a>
{% endif %}
</div> </div>
<br>
{% endif %}
<div class="cards-container"> <div class="cards-container">
{% for tm in toy_movements %} {% if toy_movements %}
<div class="card"> {% for tm in toy_movements %}
<div class="card-row"><strong>Tur:</strong> {{ tm.get_movement_type_display }}</div> <div class="card">
<div class="card-row"><strong>From:</strong> {{ tm.from_warehouse.name }}</div> <div class="card-header">
{% if tm.to_warehouse %} <div class="card-title">
<div class="card-row"><strong>To:</strong> {{ tm.to_warehouse.name }}</div> {% if tm.device %}
{% endif %} {{ tm.device.address }}
{% if tm.device %} {% elif tm.to_warehouse %}
<div class="card-row"><strong>Aparat:</strong> {{ tm.device.name }}</div> {{ tm.to_warehouse.name }}
{% endif %} {% else %}
<div class="card-row"><strong>Miqdor:</strong> {{ tm.quantity }}</div> {{ tm.from_warehouse.name }}
<div class="card-row"><strong>Yaratgan:</strong> {{ tm.created_by.get_full_name }}</div> {% endif %}
<div class="card-row"><strong>Sana:</strong> {{ tm.created_at|date:"d.m.Y H:i" }}</div> </div>
<div class="card-number">#{{ forloop.counter }}</div>
</div>
<div class="card-content">
<div class="card-row">
<span class="label">Miqdor:</span>
<span class="quantity">{{ tm.quantity }} dona</span>
</div>
<div class="card-row">
<span class="label">Sana:</span>
<span class="value">{{ tm.created_at|date:"d.m.Y H:i" }}</span>
</div>
<div class="card-row">
<span class="label">Yaratgan:</span>
<span class="value">{{ tm.created_by.get_full_name }}</span>
</div>
<div class="card-row">
<span class="label">Turi:</span>
<span class="value">{{ tm.get_movement_type_display }}</span>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div style="grid-column: 1 / -1; text-align: center; padding: 60px 20px; color: #6b7280;">
<p style="font-style: italic; margin: 0;">Hech qanday harakat topilmadi</p>
</div> </div>
{% empty %} {% endif %}
<p style="text-align:center; font-style:italic;">Hech narsa topilmadi</p>
{% endfor %}
</div> </div>
{% endblock %}
<style>
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.card {
background: #fff;
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: all 0.2s ease;
border-left: 4px solid #4f46e5;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #e5e7eb;
}
.card-title {
font-weight: 700;
color: #1f2937;
font-size: 15px;
flex: 1;
}
.card-number {
font-size: 12px;
color: #9ca3af;
font-weight: 600;
}
.card-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.card-row {
display: flex;
justify-content: space-between;
font-size: 13px;
}
.card-row .label {
color: #6b7280;
font-weight: 500;
}
.card-row .value {
color: #374151;
font-weight: 500;
}
.card-row .quantity {
color: #059669;
font-weight: 700;
}
.btn {
display: inline-block;
padding: 8px 14px;
border-radius: 6px;
text-decoration: none;
color: #fff;
font-weight: 600;
font-size: 14px;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.btn.edit:hover {
background: #4338ca;
box-shadow: 0 2px 8px rgba(79,70,229,0.3);
transform: translateY(-1px);
}
@media (max-width: 768px) {
.cards-container {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.card {
padding: 14px;
}
}
@media (max-width: 480px) {
.cards-container {
grid-template-columns: 1fr;
}
div[style*="display: flex"] {
flex-direction: column;
gap: 10px;
}
.btn {
width: 100%;
text-align: center;
}
}
</style>
{% endblock %}

View File

@@ -3,7 +3,20 @@
{% block title %}Foydalanuvchilar{% endblock %} {% block title %}Foydalanuvchilar{% endblock %}
{% block content %} {% block content %}
<h2 style="margin-bottom:20px;">{{ title|default:"Foydalanuvchilar" }}</h2> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">{{ title|default:"Foydalanuvchilar" }}</h2>
{% if role == "manager" or role == "businessman" %}
<a href="{% url 'create_user' %}" class="btn edit" style="padding: 12px 24px; text-decoration: none; background: #4f46e5; color: #fff; font-weight: 600; border-radius: 10px; transition: all 0.2s ease; display: inline-block; font-size: 15px;">+ Yaratish</a>
{% endif %}
</div>
<style>
a.btn.edit:hover {
background: #4338ca;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
}
</style>
<div class="cards-container"> <div class="cards-container">
{% for user in users %} {% for user in users %}

View File

@@ -3,7 +3,20 @@
{% block title %}Omborlar{% endblock %} {% block title %}Omborlar{% endblock %}
{% block content %} {% block content %}
<h2 style="margin-bottom:20px;">{{ title|default:"Omborlar" }}</h2> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 style="margin: 0;">{{ title|default:"Omborlar" }}</h2>
{% if role == "businessman" %}
<a href="{% url 'create_warehouse' %}" class="btn edit" style="padding: 12px 24px; text-decoration: none; background: #4f46e5; color: #fff; font-weight: 600; border-radius: 10px; transition: all 0.2s ease; display: inline-block; font-size: 15px;">+ Yaratish</a>
{% endif %}
</div>
<style>
a.btn.edit:hover {
background: #4338ca;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
}
</style>
<div class="cards-container"> <div class="cards-container">
{% for wh in warehouses %} {% for wh in warehouses %}

View File

@@ -1,105 +1,138 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h2 style="text-align:center; margin-bottom:24px; color:#111827;">Hodim Paneli</h2> <br>
<h2 style="text-align:center; margin-bottom:24px; color:#111827;">Hodim Paneli</h2>
<div class="dashboard-grid"> <div class="dashboard-grid">
<a href="{% url 'create_income' %}" class="dashboard-card"> <!-- Create Actions -->
<div class="icon">📥</div> <a href="{% url 'device_payment_list' %}" class="dashboard-card">
<div class="label">Kirim qo'shish</div> <div class="icon">🏠</div>
</a> <div class="label">Arendalar</div>
<a href="{% url 'create_expense' %}" class="dashboard-card"> </a>
<div class="icon">💸</div> <a href="{% url 'expense_list' %}" class="dashboard-card">
<div class="label">Xarajat qo'shish</div> <div class="icon">📋</div>
</a> <div class="label">Xarajatlar</div>
<a href="{% url 'create_toy_movement' %}" class="dashboard-card"> </a>
<div class="icon">🚚</div> <a href="{% url 'toy_movement_list' %}" class="dashboard-card">
<div class="label">Oʻyinchoq harakati yaratish</div> <div class="icon">📦</div>
</a> <div class="label">Oʻyinchoq harakatlari</div>
</div> </a>
<a href="{% url 'create_report' %}" class="dashboard-card">
<div class="icon">📊</div>
<div class="label">Yakuniy Hisobot</div>
</a>
</div>
<style>
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px;
max-width: 960px;
margin: 0 auto;
padding: 0 10px;
}
.dashboard-card {
background: #fff;
border-radius: 16px;
padding: 20px 12px;
text-align: center;
text-decoration: none;
color: #111827;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
transition: transform 0.2s, box-shadow 0.2s, background 0.2s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: none;
cursor: pointer;
}
<style> .dashboard-card .icon {
.dashboard-grid { font-size: 32px;
display: grid; margin-bottom: 10px;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
gap: 16px;
max-width: 960px;
margin: 0 auto;
padding: 0 10px;
}
.dashboard-card { .dashboard-card:hover {
background: #fff; transform: translateY(-4px);
border-radius: 16px; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
padding: 20px 12px; background: #4f46e5;
text-align: center; color: #fff;
text-decoration: none; }
color: #111827;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
transition: transform 0.2s, box-shadow 0.2s, background 0.2s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.dashboard-card .icon { .dashboard-card:hover .icon {
font-size: 32px; color: #fff;
margin-bottom: 10px; }
}
.dashboard-card:hover { .dashboard-card.secondary {
transform: translateY(-4px); background: #f0f4ff;
box-shadow: 0 6px 20px rgba(0,0,0,0.1); color: #4f46e5;
background: #4f46e5; }
color: #fff;
}
.dashboard-card:hover .icon { .dashboard-card.secondary:hover {
color: #fff; background: #4f46e5;
} color: #fff;
}
.label { .dashboard-card.logout-card {
font-size: 14px; background: #fee2e2;
text-align: center; color: #dc2626;
} }
/* Mobile adjustments: keep grid layout, adjust size */ .dashboard-card.logout-card:hover {
@media (max-width: 768px) { background: #dc2626;
.dashboard-grid { color: #fff;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
gap: 12px;
} .label {
.dashboard-card { font-size: 14px;
padding: 16px 10px; text-align: center;
} }
.dashboard-card .icon {
font-size: 28px; /* Mobile adjustments */
margin-bottom: 8px; @media (max-width: 768px) {
} .dashboard-grid {
.label { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
font-size: 13px; gap: 12px;
} }
}
.dashboard-card {
padding: 16px 10px;
}
.dashboard-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.label {
font-size: 13px;
}
}
@media (max-width: 480px) {
.dashboard-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.dashboard-card {
padding: 14px 8px;
}
.dashboard-card .icon {
font-size: 24px;
margin-bottom: 6px;
}
.label {
font-size: 12px;
}
}
</style>
@media (max-width: 480px) {
.dashboard-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.dashboard-card {
padding: 14px 8px;
}
.dashboard-card .icon {
font-size: 24px;
margin-bottom: 6px;
}
.label {
font-size: 12px;
}
}
</style>
{% endblock %} {% endblock %}

View File

@@ -1,112 +1,145 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h2 style="text-align:center; margin-bottom:24px; color:#111827;">Manager Paneli</h2> <br>
<h2 style="text-align:center; margin-bottom:24px; color:#111827;">Manager Paneli</h2>
<div class="dashboard-grid"> <div class="dashboard-grid">
<a href="{% url 'create_user' %}" class="dashboard-card"> <!-- View/Manage Actions -->
<div class="icon">👤</div> <a href="{% url 'expense_list' %}" class="dashboard-card">
<div class="label">Foydalanuvchi yaratish</div> <div class="icon">📋</div>
</a> <div class="label">Xarajatlar</div>
<a href="{% url 'create_device' %}" class="dashboard-card"> </a>
<div class="icon">💻</div> <a href="{% url 'toy_movement_list' %}" class="dashboard-card">
<div class="label">Aparat yaratish</div> <div class="icon">📦</div>
</a> <div class="label">Oʻyinchoq harakatlari</div>
<a href="{% url 'create_expense' %}" class="dashboard-card"> </a>
<div class="icon">💸</div> <a href="{% url 'device_list' %}" class="dashboard-card">
<div class="label">Xarajat qo'shish</div> <div class="icon">🖥️</div>
</a> <div class="label">Aparatlar</div>
<a href="{% url 'create_income' %}" class="dashboard-card"> </a>
<div class="icon">📥</div> <a href="{% url 'warehouse_list' %}" class="dashboard-card">
<div class="label">Kirim qo'shish</div> <div class="icon">🏭</div>
</a> <div class="label">Omborlar</div>
<a href="{% url 'create_toy_movement' %}" class="dashboard-card"> </a>
<div class="icon">🚚</div> <a href="{% url 'user_list' %}" class="dashboard-card">
<div class="label">Oʻyinchoq harakati yaratish</div> <div class="icon">👥</div>
</a> <div class="label">Foydalanuvchilar</div>
</div> </a>
<a href="{% url 'report_list' %}" class="dashboard-card">
<div class="icon">📊</div>
<div class="label">Kunlik Hisobotlar</div>
</a>
</div>
<style> <style>
.dashboard-grid { .dashboard-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px; gap: 16px;
max-width: 960px; max-width: 960px;
margin: 0 auto; margin: 0 auto;
padding: 0 10px; padding: 0 10px;
} }
.dashboard-card { .dashboard-card {
background: #fff; background: #fff;
border-radius: 16px; border-radius: 16px;
padding: 20px 12px; padding: 20px 12px;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
color: #111827; color: #111827;
font-weight: 600; font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,0.06); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
transition: transform 0.2s, box-shadow 0.2s, background 0.2s; transition: transform 0.2s, box-shadow 0.2s, background 0.2s;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} border: none;
cursor: pointer;
}
.dashboard-card .icon { .dashboard-card .icon {
font-size: 32px; font-size: 32px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.dashboard-card:hover { .dashboard-card:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 6px 20px rgba(0,0,0,0.1); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
background: #4f46e5; background: #4f46e5;
color: #fff; color: #fff;
} }
.dashboard-card:hover .icon { .dashboard-card:hover .icon {
color: #fff; color: #fff;
} }
.label { .dashboard-card.secondary {
font-size: 14px; background: #f0f4ff;
text-align: center; color: #4f46e5;
} }
/* Mobile adjustments: keep grid layout, adjust size */ .dashboard-card.secondary:hover {
@media (max-width: 768px) { background: #4f46e5;
.dashboard-grid { color: #fff;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
gap: 12px;
}
.dashboard-card {
padding: 16px 10px;
}
.dashboard-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.label {
font-size: 13px;
}
}
@media (max-width: 480px) { .dashboard-card.logout-card {
.dashboard-grid { background: #fee2e2;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); color: #dc2626;
gap: 10px; }
}
.dashboard-card {
padding: 14px 8px;
}
.dashboard-card .icon {
font-size: 24px;
margin-bottom: 6px;
}
.label {
font-size: 12px;
}
}
</style>
{% endblock %} .dashboard-card.logout-card:hover {
background: #dc2626;
color: #fff;
}
.label {
font-size: 14px;
text-align: center;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
}
.dashboard-card {
padding: 16px 10px;
}
.dashboard-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.label {
font-size: 13px;
}
}
@media (max-width: 480px) {
.dashboard-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.dashboard-card {
padding: 14px 8px;
}
.dashboard-card .icon {
font-size: 24px;
margin-bottom: 6px;
}
.label {
font-size: 12px;
}
}
</style>
{% endblock %}

View File

@@ -14,14 +14,18 @@ urlpatterns = [
path("create/user/", views.create_user, name="create_user"), path("create/user/", views.create_user, name="create_user"),
path("create/toy-movement/", views.create_toy_movement, name="create_toy_movement"), path("create/toy-movement/", views.create_toy_movement, name="create_toy_movement"),
path("create/toy-movement/auto/", views.create_toy_movement_auto, name="create_toy_movement_auto"), path("create/toy-movement/auto/", views.create_toy_movement_auto, name="create_toy_movement_auto"),
path("create/rent/", views.create_rent, name="create_rent"),
path("create/report/", views.create_report, name="create_report"),
# # List # # List
path("list/device-payments/", views.device_payment_list, name="device_payment_list"),
path("list/device/", views.device_list, name="device_list"), path("list/device/", views.device_list, name="device_list"),
path("list/income/", views.income_list, name="income_list"), path("list/income/", views.income_list, name="income_list"),
path("list/expense/", views.expense_list, name="expense_list"), path("list/expense/", views.expense_list, name="expense_list"),
path("list/warehouse/", views.warehouse_list, name="warehouse_list"), path("list/warehouse/", views.warehouse_list, name="warehouse_list"),
path("list/user/", views.user_list, name="user_list"), path("list/user/", views.user_list, name="user_list"),
path("list/toy-movement/", views.toy_movement_list, name="toy_movement_list"), path("list/toy-movement/", views.toy_movement_list, name="toy_movement_list"),
path("list/reports/", views.report_list, name="report_list"),
# Edit # Edit
path("edit/device/<int:pk>/", views.edit_device, name="edit_device"), path("edit/device/<int:pk>/", views.edit_device, name="edit_device"),
@@ -29,6 +33,8 @@ urlpatterns = [
path("edit/expense/<int:pk>/", views.edit_expense, name="edit_expense"), path("edit/expense/<int:pk>/", views.edit_expense, name="edit_expense"),
path("edit/warehouse/<int:pk>/", views.edit_warehouse, name="edit_warehouse"), path("edit/warehouse/<int:pk>/", views.edit_warehouse, name="edit_warehouse"),
path("edit/user/<int:pk>/", views.edit_user, name="edit_user"), path("edit/user/<int:pk>/", views.edit_user, name="edit_user"),
path("edit/device-payments/<int:pk>/", views.mark_device_paid, name="mark_device_paid"),
# path("edit/toy-movement/<int:pk>/", views.edit_toy_movement, name="edit_toy_movement"), # path("edit/toy-movement/<int:pk>/", views.edit_toy_movement, name="edit_toy_movement"),

View File

@@ -1,27 +1,42 @@
from django.db.models import F, Sum
from core.apps.management.forms import DeviceForm, IncomeForm, WarehouseForm, UserCreateForm, ExpenseFormEmployee, \ from core.apps.management.forms import DeviceForm, IncomeForm, WarehouseForm, UserCreateForm, ExpenseFormEmployee, \
ExpenseFormManager, ExpenseFormBusinessman ExpenseFormManager, ExpenseFormBusinessman, ReportForm
from django.db import transaction from django.db import transaction
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from core.apps.management.forms import ToyMovementForm, ToyMovementFormEmployee from core.apps.management.forms import ToyMovementForm, ToyMovementFormEmployee
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from core.apps.management.decorators import role_required from core.apps.management.decorators import role_required
from core.apps.management.forms import UserCreateFormManagerToEmployee, UserCreateFormBusinessman from core.apps.management.forms import UserCreateFormManagerToEmployee, UserCreateFormBusinessman
from core.apps.management.models import ToyMovement from core.apps.management.forms.RentForm import RentForm
from core.apps.management.models import ToyMovement, Warehouse, Report
@login_required @login_required
@role_required(["manager", "businessman"]) @role_required(["manager", "businessman"])
def create_device(request): def create_device(request):
form = DeviceForm(request.POST or None, user=request.user) user = request.user
if form.is_valid():
form.save() if request.method == "POST":
return redirect("dashboard") form = DeviceForm(request.POST, user=user)
return render(request, "common/create/device_create.html", { if form.is_valid():
"form": form, form.save()
"title": "Aparat Yaratish" return redirect("dashboard")
}) else:
form = DeviceForm(user=user)
return render(
request,
"common/create/device_create.html",
{
"form": form,
"title": "Aparat Yaratish"
}
)
@login_required @login_required
@role_required(["employee"])
def create_income(request): def create_income(request):
if request.method == "POST": if request.method == "POST":
form = IncomeForm(request.POST, user=request.user) form = IncomeForm(request.POST, user=request.user)
@@ -35,6 +50,24 @@ def create_income(request):
form = IncomeForm(user=request.user) form = IncomeForm(user=request.user)
return render(request, "common/create/income_create.html", {"form": form}) return render(request, "common/create/income_create.html", {"form": form})
@login_required
@role_required(['manager', 'businessman'])
def create_income_manager_and_businessman(request):
if request.method == "POST":
form = IncomeForm(request.POST, user=request.user)
if form.is_valid():
with transaction.atomic():
income = form.save(commit=False)
income.created_by = request.user
income.save()
warehouse = income.warehouse
warehouse.toys_count += income.amount
warehouse.save()
return redirect("common/create/income_create.html")
else:
form = IncomeForm(user=request.user)
return render(request=request, template_name="common/create/income_create.html", context={"form": form})
@login_required @login_required
def create_expense(request): def create_expense(request):
@@ -111,110 +144,239 @@ def create_user(request):
"title": "Foydalanuvchi yaratish", "title": "Foydalanuvchi yaratish",
}) })
@login_required @login_required
def create_toy_movement(request): def create_toy_movement(request):
user = request.user user = request.user
# Choose form based on role if user.role == "employee":
form_class = ToyMovementFormEmployee if user.role == "employee" else ToyMovementForm form_class = ToyMovementFormEmployee
else:
form_class = ToyMovementForm
if request.method == "POST": if request.method == "POST":
form = form_class(request.POST, user=user) form = form_class(request.POST, user=user)
if form.is_valid(): if form.is_valid():
with transaction.atomic(): with transaction.atomic():
movement = form.save(commit=False)
# Stock validation quantity = form.cleaned_data["quantity"]
from_wh = movement.from_warehouse
if from_wh.toys_count < movement.quantity: if user.role == "employee":
form.add_error("quantity", "Not enough toys in warehouse.") # Auto determine warehouse by region
return render( from_wh = Warehouse.objects.select_for_update().filter(
request, region=user.region
"common/create/toy_movement_create.html", ).first()
{"form": form, "user_role": user.role}
if not from_wh:
form.add_error(None, "No warehouse assigned to your region.")
return render(request,
"common/create/toy_movement_create.html",
{"form": form})
if from_wh.toys_count < quantity:
form.add_error("quantity", "Not enough toys in warehouse.")
return render(request,
"common/create/toy_movement_create.html",
{"form": form})
# Deduct stock
Warehouse.objects.filter(pk=from_wh.pk).update(
toys_count=F("toys_count") - quantity
) )
# Deduct from source warehouse movement = form.save(commit=False)
from_wh.toys_count -= movement.quantity movement.movement_type = "from_warehouse"
from_wh.save() movement.from_warehouse = from_wh
movement.to_warehouse = None
movement.created_by = user
movement.save()
# Add to destination warehouse if moving between warehouses else:
if movement.movement_type == "between_warehouses" and movement.to_warehouse: # Manager / Businessman normal logic
to_wh = movement.to_warehouse from_wh = form.cleaned_data["from_warehouse"]
to_wh.toys_count += movement.quantity movement_type = form.cleaned_data["movement_type"]
to_wh.save() to_wh = form.cleaned_data.get("to_warehouse")
# Set creator from_wh = Warehouse.objects.select_for_update().get(pk=from_wh.pk)
movement.created_by = user
movement.save() if from_wh.toys_count < quantity:
form.add_error("quantity", "Not enough toys in warehouse.")
return render(request,
"common/create/toy_movement_create.html",
{"form": form})
Warehouse.objects.filter(pk=from_wh.pk).update(
toys_count=F("toys_count") - quantity
)
if movement_type == "between_warehouses" and to_wh:
to_wh = Warehouse.objects.select_for_update().get(pk=to_wh.pk)
Warehouse.objects.filter(pk=to_wh.pk).update(
toys_count=F("toys_count") + quantity
)
movement = form.save(commit=False)
movement.created_by = user
movement.save()
return redirect("dashboard") return redirect("dashboard")
else: else:
form = form_class(user=user) form = form_class(user=user)
return render( return render(
request, request,
"common/create/toy_movement_create.html", "common/create/toy_movement_create.html",
{"form": form, "user_role": user.role} {"form": form}
) )
@login_required @login_required
@role_required(["manager", "businessman"]) @role_required(["manager", "businessman"])
def create_toy_movement_auto(request): def create_toy_movement_auto(request):
user = request.user user = request.user
# Only employees can use this auto-creation
if request.method == "POST": if request.method == "POST":
# We force movement_type to "between_warehouses" form = ToyMovementForm(request.POST, user=user)
movement = ToyMovement(
movement_type="between_warehouses",
from_warehouse=user.warehouse,
to_warehouse_id=request.POST.get("to_warehouse"),
device=None, # not used for between_warehouses
quantity=int(request.POST.get("quantity", 0)),
created_by=user
)
# Stock validation if form.is_valid():
from_wh = movement.from_warehouse with transaction.atomic():
if from_wh.toys_count < movement.quantity: # Get cleaned data
form = ToyMovementForm(user=user) # just render empty form with message from_wh = form.cleaned_data.get("from_warehouse")
return render( to_wh = form.cleaned_data.get("to_warehouse")
request, movement_type = form.cleaned_data.get("movement_type")
"common/create/toy_movement_create.html", quantity = form.cleaned_data.get("quantity")
{
"form": form,
"user_role": user.role,
"error": "Not enough toys in warehouse."
}
)
with transaction.atomic(): # Validate warehouse exists
# Update warehouse stock if not from_wh:
from_wh.toys_count -= movement.quantity form.add_error("from_warehouse", "Source warehouse is required.")
from_wh.save() return render(request, "common/create/toy_movement_create.html", {"form": form})
# Save movement # Lock and get fresh warehouse data
movement.save() from_wh = Warehouse.objects.select_for_update().get(pk=from_wh.pk)
return redirect("dashboard") # Check stock
if from_wh.toys_count < quantity:
form.add_error("quantity", "Not enough toys in warehouse.")
return render(request, "common/create/toy_movement_create.html", {"form": form})
# GET request → render the create form # Deduct from source warehouse
form = ToyMovementForm(user=user) Warehouse.objects.filter(pk=from_wh.pk).update(
# Pre-fill movement_type and from_warehouse toys_count=F("toys_count") - quantity
form.fields["movement_type"].initial = "between_warehouses" )
form.fields["from_warehouse"].initial = user.warehouse
# Optionally, disable editing these fields # Add to destination warehouse if between_warehouses
form.fields["movement_type"].widget.attrs["readonly"] = True if movement_type == "between_warehouses" and to_wh:
form.fields["from_warehouse"].widget.attrs["readonly"] = True to_wh = Warehouse.objects.select_for_update().get(pk=to_wh.pk)
Warehouse.objects.filter(pk=to_wh.pk).update(
toys_count=F("toys_count") + quantity
)
# Save movement
movement = form.save(commit=False)
movement.created_by = user
movement.save()
return redirect("toy_movement_list")
else:
# Form has errors, display them
return render(request, "common/create/toy_movement_create.html", {"form": form})
else:
# GET request → render the create form
form = ToyMovementForm(user=user)
return render( return render(
request, request,
"common/create/toy_movement_create.html", "common/create/toy_movement_create.html",
{"form": form, "user_role": user.role} {"form": form}
)
@login_required
@role_required(["manager", "businessman"])
def create_rent(request):
if request.method == "POST":
form = RentForm(request.POST)
if form.is_valid():
rent = form.save(commit=False)
rent.created_by = request.user
rent.save()
return redirect("dashboard")
else:
form = RentForm()
return render(request, "common/create/rent_create.html", {"form":form, "title":"Create Rent"})
@login_required
@role_required(["employee"])
def create_report(request):
if request.method == "POST":
form = ReportForm(request.POST, user=request.user)
if form.is_valid():
# 🔥 Employee MUST have warehouse
if not request.user.warehouse:
form.add_error(None, "Sizga ombor biriktirilmagan.")
return render(
request,
"common/create/report_create.html",
{"form": form, "title": "Yakuniy Hisobot"}
)
device = form.cleaned_data["device"]
entered_quantity = form.cleaned_data["quantity"]
with transaction.atomic():
# ✅ 1. Save last entered quantity as ToyMovement
ToyMovement.objects.create(
movement_type="from_warehouse",
from_warehouse=request.user.warehouse,
device=device,
quantity=entered_quantity,
created_by=request.user,
)
# ✅ 2. Find last report for this device
last_report = Report.objects.filter(
device=device
).order_by("-created_at").first()
if last_report:
last_time = last_report.created_at
total_since_last = ToyMovement.objects.filter(
device=device,
created_at__gt=last_time
).aggregate(
total=Sum("quantity")
)["total"] or 0
else:
total_since_last = ToyMovement.objects.filter(
device=device
).aggregate(
total=Sum("quantity")
)["total"] or 0
# ✅ 3. Create Report
Report.objects.create(
device=device,
quantity=total_since_last,
created_by=request.user
)
return redirect("employee_dashboard")
else:
form = ReportForm(user=request.user)
return render(
request,
"common/create/report_create.html",
{
"form": form,
"title": "Yakuniy Hisobot"
}
) )

View File

@@ -2,7 +2,8 @@ from django.contrib.auth.decorators import login_required
from core.apps.accounts.models import User from core.apps.accounts.models import User
from core.apps.management.forms import DeviceForm, IncomeForm, ExpenseForm, WarehouseForm, UserCreateForm, \ from core.apps.management.forms import DeviceForm, IncomeForm, ExpenseForm, WarehouseForm, UserCreateForm, \
ToyMovementEmployeeForm, ToyMovementForm, ExpenseFormEmployee, ExpenseFormManager, ExpenseFormBusinessman ToyMovementEmployeeForm, ToyMovementForm, ExpenseFormEmployee, ExpenseFormManager, ExpenseFormBusinessman, \
DevicePaymentForm
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from core.apps.management.models import Device, Income, Expense, Warehouse, ToyMovement from core.apps.management.models import Device, Income, Expense, Warehouse, ToyMovement
from django.db import transaction from django.db import transaction
@@ -30,11 +31,11 @@ def edit_income(request, pk):
form = IncomeForm(request.POST, instance=income) form = IncomeForm(request.POST, instance=income)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect("income_list") return redirect("common/create/income_create.html")
else: else:
form = IncomeForm(instance=income) form = IncomeForm(instance=income)
return render(request, "common/edit/income_edit.html", { return render(request, "common/create/income_create.html", {
"form": form, "form": form,
"title": "Kirimni tahrirlash" "title": "Kirimni tahrirlash"
}) })
@@ -125,6 +126,22 @@ def edit_user(request, pk):
"title": "Foydalanuvchini tahrirlash", "title": "Foydalanuvchini tahrirlash",
}) })
@login_required
@role_required(["employee"])
def mark_device_paid(request, pk):
device = get_object_or_404(Device, pk=pk)
# Security check
if device.district.region != request.user.region:
return redirect("device_payment_list")
if request.method == "POST":
device.is_paid = True
device.save()
return redirect("device_payment_list")
# @role_required(["businessman"]) # @role_required(["businessman"])
# @login_required # @login_required
# def edit_toy_movement(request, pk): # def edit_toy_movement(request, pk):

View File

@@ -1,6 +1,6 @@
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from core.apps.management.models import Device, Income, Expense, Warehouse, ToyMovement from core.apps.management.models import Device, Income, Expense, Warehouse, ToyMovement, Report
from core.apps.accounts.models import User from core.apps.accounts.models import User
from core.apps.management.decorators import role_required from core.apps.management.decorators import role_required
@@ -39,14 +39,34 @@ def device_list(request):
@login_required @login_required
def expense_list(request): def expense_list(request):
expenses = Expense.objects.select_related("created_by", "confirmed_by").order_by("-created_at") user = request.user
expenses = Expense.objects.select_related(
"created_by",
"confirmed_by"
).order_by("-created_at")
# 🔐 ROLE-BASED FILTERING
if user.role == "employee":
expenses = expenses.filter(created_by=user)
elif user.role == "manager":
# Optional: show only region expenses
expenses = expenses.filter(
created_by__region=user.region
)
# businessman sees everything (no filter)
context = { context = {
"expenses": expenses, "expenses": expenses,
"role": request.user.role, "role": user.role,
} }
return render(request, "common/lists/expense_list.html", context) return render(request, "common/lists/expense_list.html", context)
@login_required @login_required
def income_list(request): def income_list(request):
incomes = Income.objects.select_related("device", "created_by", "created_by__region").order_by("-created_at") incomes = Income.objects.select_related("device", "created_by", "created_by__region").order_by("-created_at")
@@ -87,4 +107,63 @@ def toy_movement_list(request):
"toy_movements": toy_movements, "toy_movements": toy_movements,
"role": request.user.role, "role": request.user.role,
} }
return render(request, "common/lists/toy_movement_list.html", context) return render(request, "common/lists/toy_movement_list.html", context)
@login_required
@role_required(["manager", "businessman"])
def income_list(request):
if request.user.role == "businessman":
incomes = Income.objects.all()
elif request.user.role == "manager":
incomes = Income.objects.filter(warehouse__region=request.user.region)
return render(request, "common/create/income_create.html", {"incomes": incomes})
@login_required
@role_required(["employee"])
def device_payment_list(request):
# Employee only sees devices in his region
devices = Device.objects.filter(
district__region=request.user.region
).order_by("due_date")
return render(
request,
"common/lists/device_payment_list.html",
{
"devices": devices,
"title": "Arendalar"
}
)
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.db.models import Q
from core.apps.management.models import Report
from core.apps.management.decorators import role_required
@login_required
@role_required(['manager', 'businessman'])
def report_list(request):
reports = (
Report.objects
.select_related("device", "device__district", "created_by")
.order_by("-created_at")
)
if request.user.role == "manager":
reports = reports.filter(
device__district__region=request.user.region
)
return render(
request,
"common/lists/report_list.html",
{
"reports": reports,
"title": "Hisobotlar"
}
)

View File

@@ -13,8 +13,6 @@ COPY ./ /code
COPY ./resources/scripts/$SCRIPT /code/$SCRIPT COPY ./resources/scripts/$SCRIPT /code/$SCRIPT
RUN chmod +x /code/resources/scripts/$SCRIPT
CMD sh /code/resources/scripts/$SCRIPT CMD sh /code/resources/scripts/$SCRIPT

0
resources/scripts/entrypoint.sh Normal file → Executable file
View File