|
|
from django.db import models
|
|
|
from django.contrib.auth.models import AbstractUser, Permission, Group
|
|
|
|
|
|
from product_directory.models import Product
|
|
|
from warehouse.models import Warehouse
|
|
|
|
|
|
|
|
|
# todo: нужно перенести уникальные поля в новую модель
|
|
|
|
|
|
# Справочник товаров
|
|
|
# class Product(models.Model):
|
|
|
# name = models.CharField(max_length=255, verbose_name="Наименование товара")
|
|
|
# manufacturer_name = models.CharField(max_length=255, verbose_name="Производитель")
|
|
|
# manufacturer_country = models.CharField(max_length=255, verbose_name="Страна производителя")
|
|
|
# manufacturer_code = models.CharField(max_length=50, verbose_name="Код производителя", blank=True, null=True)
|
|
|
# dimensions = models.CharField(max_length=255, verbose_name="Размеры", blank=True, null=True)
|
|
|
# unit_of_measure = models.CharField(max_length=50, verbose_name="Единица измерения")
|
|
|
# shelf_life_days = models.IntegerField(verbose_name="Срок годности (дни)")
|
|
|
# barcode = models.CharField(max_length=50, unique=True, verbose_name="Штрихкод")
|
|
|
#
|
|
|
# def __str__(self):
|
|
|
# return self.name
|
|
|
|
|
|
# Справочник должностей
|
|
|
class Position(models.Model):
|
|
|
name = models.CharField(max_length=255, verbose_name="Наименование должности")
|
|
|
hourly_rate = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Ставка в час")
|
|
|
monthly_hours = models.IntegerField(verbose_name="Часы в месяц")
|
|
|
|
|
|
def __str__(self):
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
# Справочник сотрудников
|
|
|
class Employee(AbstractUser):
|
|
|
position = models.ForeignKey(Position, on_delete=models.SET_NULL, null=True, verbose_name="Должность")
|
|
|
department = models.CharField(max_length=255, verbose_name="Подразделение")
|
|
|
work_phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="Рабочий телефон")
|
|
|
personal_phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="Личный телефон")
|
|
|
email = models.EmailField(unique=True, verbose_name="Электронная почта")
|
|
|
|
|
|
groups = models.ManyToManyField(
|
|
|
Group,
|
|
|
related_name='employee_groups',
|
|
|
blank=True,
|
|
|
verbose_name='Группы'
|
|
|
)
|
|
|
user_permissions = models.ManyToManyField(
|
|
|
Permission,
|
|
|
related_name='employee_permissions',
|
|
|
blank=True,
|
|
|
verbose_name='Разрешения'
|
|
|
)
|
|
|
|
|
|
def __str__(self):
|
|
|
return self.get_full_name()
|
|
|
|
|
|
# Справочник мест хранения
|
|
|
class StorageLocation(models.Model):
|
|
|
TYPE_CHOICES = [
|
|
|
('Store', 'Магазин'),
|
|
|
('Distribution Center', 'Распределительный центр'),
|
|
|
('Office', 'Офис'),
|
|
|
]
|
|
|
name = models.CharField(max_length=255, verbose_name="Наименование")
|
|
|
type = models.CharField(max_length=50, choices=TYPE_CHOICES, verbose_name="Тип места хранения")
|
|
|
|
|
|
def __str__(self):
|
|
|
return self.name
|
|
|
|
|
|
# Справочник контрагентов и договоров
|
|
|
class Contractor(models.Model):
|
|
|
name = models.CharField(max_length=255, verbose_name="Наименование контрагента")
|
|
|
address = models.TextField(verbose_name="Адрес")
|
|
|
phone = models.CharField(max_length=20, verbose_name="Телефон")
|
|
|
email = models.EmailField(verbose_name="Электронная почта")
|
|
|
|
|
|
def __str__(self):
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
class SupplyContract(models.Model):
|
|
|
contractor = models.ForeignKey(Contractor, on_delete=models.CASCADE, verbose_name="Контрагент")
|
|
|
description = models.TextField(verbose_name="Описание")
|
|
|
start_date = models.DateField(verbose_name="Дата начала")
|
|
|
end_date = models.DateField(verbose_name="Дата окончания", blank=True, null=True)
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"Договор с {self.contractor.name}"
|
|
|
|
|
|
|
|
|
# Справочник грузовиков
|
|
|
class Truck(models.Model):
|
|
|
plate_number = models.CharField(max_length=20, verbose_name="Номерной знак")
|
|
|
model = models.CharField(max_length=50, verbose_name="Модель")
|
|
|
capacity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Грузоподъемность (тонны)")
|
|
|
|
|
|
def __str__(self):
|
|
|
return self.plate_number
|
|
|
|
|
|
class StockOperation(models.Model):
|
|
|
OPERATION_TYPE_CHOICES = [
|
|
|
('Incoming', 'Приход'),
|
|
|
('Outgoing', 'Расход'),
|
|
|
('Transfer', 'Перемещение'),
|
|
|
]
|
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name="Товар")
|
|
|
location = models.ForeignKey(StorageLocation, on_delete=models.CASCADE, verbose_name="Место хранения")
|
|
|
operation_type = models.CharField(max_length=50, choices=OPERATION_TYPE_CHOICES, verbose_name="Тип операции")
|
|
|
quantity = models.IntegerField(verbose_name="Количество")
|
|
|
operation_date = models.DateTimeField(auto_now_add=True, verbose_name="Дата операции")
|
|
|
expiration_date = models.DateField(blank=True, null=True, verbose_name="Срок годности")
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"{self.operation_type} - {self.product.name}"
|
|
|
|
|
|
|
|
|
class PriceList(models.Model):
|
|
|
PRICE_TYPE_CHOICES = [
|
|
|
('Regular', 'Регулярная'),
|
|
|
('Discount', 'Скидочная'),
|
|
|
('Promotional', 'Акционная'),
|
|
|
]
|
|
|
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name="Товар")
|
|
|
price_type = models.CharField(max_length=50, choices=PRICE_TYPE_CHOICES, verbose_name="Тип цены")
|
|
|
entry_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Входная цена")
|
|
|
final_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Итоговая цена")
|
|
|
date_effective = models.DateField(verbose_name="Дата вступления в силу")
|
|
|
constraint_percent_limit = models.DecimalField(max_digits=5, decimal_places=2, default=1000, verbose_name="Лимит на наценку (%)")
|
|
|
constraint_price_change = models.DecimalField(max_digits=5, decimal_places=2, default=90, verbose_name="Лимит на изменение цены (%)")
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"{self.price_type} - {self.product.name}"
|
|
|
|
|
|
|
|
|
class Inventory(models.Model):
|
|
|
warehouse = models.ForeignKey(
|
|
|
Warehouse,
|
|
|
on_delete=models.CASCADE,
|
|
|
verbose_name="Склад",
|
|
|
null=True,
|
|
|
blank=True # Разрешить null и пустое значение
|
|
|
)
|
|
|
storage_location = models.ForeignKey(
|
|
|
StorageLocation,
|
|
|
on_delete=models.CASCADE,
|
|
|
verbose_name="Место хранения",
|
|
|
null=True,
|
|
|
blank=True # Разрешить null и пустое значение
|
|
|
)
|
|
|
product = models.ForeignKey(
|
|
|
Product,
|
|
|
on_delete=models.CASCADE,
|
|
|
verbose_name="Товар",
|
|
|
null=True,
|
|
|
blank=True # Разрешить null и пустое значение
|
|
|
)
|
|
|
actual_quantity = models.PositiveIntegerField(
|
|
|
null=True,
|
|
|
blank=True, # Разрешить null и пустое значение
|
|
|
verbose_name="Фактическое количество"
|
|
|
)
|
|
|
recorded_quantity = models.PositiveIntegerField(
|
|
|
null=True,
|
|
|
blank=True, # Разрешить null и пустое значение
|
|
|
verbose_name="Учтенное количество"
|
|
|
)
|
|
|
difference = models.IntegerField(
|
|
|
null=True,
|
|
|
blank=True, # Разрешить null и пустое значение
|
|
|
verbose_name="Разница",
|
|
|
default=0
|
|
|
)
|
|
|
inventory_date = models.DateTimeField(
|
|
|
auto_now_add=True,
|
|
|
verbose_name="Дата инвентаризации",
|
|
|
null=True,
|
|
|
blank=True # Разрешить null и пустое значение
|
|
|
)
|
|
|
|
|
|
class Meta:
|
|
|
verbose_name = "Инвентаризация"
|
|
|
verbose_name_plural = "Инвентаризации"
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
if self.actual_quantity is not None and self.recorded_quantity is not None:
|
|
|
self.difference = self.actual_quantity - self.recorded_quantity
|
|
|
else:
|
|
|
self.difference = 0
|
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
def __str__(self):
|
|
|
return f"{self.product.name if self.product else 'No Product'} ({self.warehouse.name if self.warehouse else 'No Warehouse'}) - {self.difference}" |