This commit is contained in:
2026-08-24 15:54:08 +03:30
commit 236002a87c
133 changed files with 5946 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
# ریزاپ‌های ساده‌ی کسب‌وکار کوچک — مشتریان، دفتر درآمد/هزینه، انبار،
# نوبت‌دهی. دقیقا هم‌الگو با apps.banker: id همان UUID تولیدشده در خودِ اپ
# فلاتر است (نه auto-increment)، هر رکورد به user صاحبش محدود می‌شود.
class Customer(models.Model):
id = models.CharField(max_length=36, primary_key=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="customers")
name = models.CharField(max_length=150, verbose_name=_("نام"))
phone = models.CharField(max_length=20, blank=True, verbose_name=_("شماره تماس"))
email = models.CharField(max_length=150, blank=True, verbose_name=_("ایمیل"))
address = models.CharField(max_length=255, blank=True, verbose_name=_("آدرس"))
notes = models.TextField(blank=True, verbose_name=_("یادداشت"))
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _("مشتری")
verbose_name_plural = _("مشتریان")
ordering = ["name"]
def __str__(self):
return self.name
class LedgerEntry(models.Model):
class EntryType(models.TextChoices):
INCOME = "income", _("درآمد")
EXPENSE = "expense", _("هزینه")
id = models.CharField(max_length=36, primary_key=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="ledger_entries")
entry_type = models.CharField(max_length=10, choices=EntryType.choices, verbose_name=_("نوع"))
title = models.CharField(max_length=150, verbose_name=_("عنوان"))
amount = models.FloatField(verbose_name=_("مبلغ"))
category = models.CharField(max_length=100, blank=True, verbose_name=_("دسته"))
entry_date = models.DateField(verbose_name=_("تاریخ"))
note = models.TextField(blank=True, verbose_name=_("یادداشت"))
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _("ردیف دفتر درآمد/هزینه")
verbose_name_plural = _("دفتر درآمد و هزینه")
ordering = ["-entry_date"]
def __str__(self):
return f"{self.title} ({self.get_entry_type_display()})"
class Product(models.Model):
id = models.CharField(max_length=36, primary_key=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="products")
name = models.CharField(max_length=150, verbose_name=_("نام کالا"))
sku = models.CharField(max_length=100, blank=True, verbose_name=_("کد کالا"))
category = models.CharField(max_length=100, blank=True, verbose_name=_("دسته"))
quantity = models.FloatField(default=0, verbose_name=_("موجودی"))
unit_price = models.FloatField(default=0, verbose_name=_("قیمت واحد"))
low_stock_threshold = models.FloatField(default=0, verbose_name=_("آستانه‌ی هشدار موجودی کم"))
notes = models.TextField(blank=True, verbose_name=_("یادداشت"))
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _("کالا")
verbose_name_plural = _("انبار")
ordering = ["name"]
def __str__(self):
return self.name
class StockMovement(models.Model):
"""تاریخچه‌ی تغییرات موجودی — هر تعدیل (دستیِ +/- یا ویرایش مستقیم فرم)
یک ردیف اینجا ثبت می‌کند تا معلوم باشد موجودی کِی و چقدر تغییر کرده."""
id = models.AutoField(primary_key=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="movements")
delta = models.FloatField(verbose_name=_("تغییر"))
quantity_after = models.FloatField(verbose_name=_("موجودی بعد از تغییر"))
note = models.CharField(max_length=255, blank=True, verbose_name=_("یادداشت"))
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = _("تغییر موجودی")
verbose_name_plural = _("تاریخچه‌ی موجودی")
ordering = ["-created_at"]
def __str__(self):
return f"{self.product.name} — {self.delta:+g} — {self.created_at:%Y-%m-%d %H:%M}"
class Appointment(models.Model):
class Status(models.TextChoices):
SCHEDULED = "scheduled", _("زمان‌بندی‌شده")
DONE = "done", _("انجام‌شده")
CANCELLED = "cancelled", _("لغوشده")
id = models.CharField(max_length=36, primary_key=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="appointments")
customer_name = models.CharField(max_length=150, verbose_name=_("نام مشتری"))
customer_phone = models.CharField(max_length=20, blank=True, verbose_name=_("شماره مشتری"))
title = models.CharField(max_length=150, verbose_name=_("عنوان/خدمت"))
start_at = models.DateTimeField(verbose_name=_("زمان نوبت"))
status = models.CharField(max_length=10, choices=Status.choices, default=Status.SCHEDULED)
notes = models.TextField(blank=True, verbose_name=_("یادداشت"))
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = _("نوبت")
verbose_name_plural = _("نوبت‌دهی")
ordering = ["start_at"]
def __str__(self):
return f"{self.customer_name} — {self.title}"