from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ # این مدل‌ها معادل دقیق دیتابیس محلی (SQLite) اپ فلاتر هستند تا اطلاعات # مخاطبین/گروه‌ها/قالب‌های کاربر روی سرور هم پشتیبان‌گیری شود و با تعویض # گوشی از دست نرود. آی‌دی‌ها همان UUID تولیدشده در خود اپ هستند (نه # auto-increment) تا هماهنگی هنگام بکاپ/ریستور ساده باشد. # # فیلدهای دلخواه (ContactField) و قالب‌ها (MessageTemplate) به یک گروه # (ContactGroup) تعلق دارند — نه سراسری. مثال: گروه «تور» فیلد «شماره # گذرنامه» دارد، گروه دیگری «کد ملی». هر کاربر دقیقاً یک گروه با # is_general=True دارد که فیلد سفارشی ندارد و قالب‌های آن فقط از فیلدهای # پایه (نام/شماره) استفاده می‌کنند — برای کاربرانی که نیاز به پیچیدگی ندارند. class ContactGroup(models.Model): id = models.CharField(max_length=36, primary_key=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="contact_groups") name = models.CharField(max_length=150, verbose_name=_("نام گروه")) color = models.BigIntegerField(verbose_name=_("رنگ (ARGB)")) is_general = models.BooleanField( default=False, verbose_name=_("گروه عمومی"), help_text=_("گروه پیش‌فرض بدون فیلد سفارشی؛ هر کاربر دقیقاً یکی از این‌ها دارد."), ) # لیستی از contact.id های عضو این گروه member_ids = models.JSONField(default=list, blank=True, verbose_name=_("اعضا")) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = _("گروه") verbose_name_plural = _("گروه‌ها") ordering = ["-updated_at"] constraints = [ models.UniqueConstraint( fields=["user"], condition=models.Q(is_general=True), name="one_general_group_per_user", ) ] def __str__(self): return self.name class ContactField(models.Model): id = models.CharField(max_length=36, primary_key=True) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="contact_fields" ) group = models.ForeignKey(ContactGroup, on_delete=models.CASCADE, related_name="fields") label = models.CharField(max_length=100, verbose_name=_("عنوان فیلد")) sort_order = models.PositiveIntegerField(default=0) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = _("فیلد دلخواه مخاطب") verbose_name_plural = _("فیلدهای دلخواه مخاطبین") ordering = ["group", "sort_order"] def __str__(self): return f"{self.label} ({self.group.name})" class Contact(models.Model): id = models.CharField(max_length=36, primary_key=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="contacts") name = models.CharField(max_length=150, verbose_name=_("نام")) phone = models.CharField(max_length=20, verbose_name=_("شماره موبایل")) device_contact_id = models.CharField(max_length=100, blank=True, null=True) # {field_id: value} — معادل جدول contact_field_values در اپ. فیلدهای # مربوط به گروه‌هایی هستند که این مخاطب عضوشان است (member_ids گروه) field_values = models.JSONField(default=dict, blank=True, verbose_name=_("مقادیر فیلدهای دلخواه")) # {channel: external_id} — مثلا {"telegram": "123456789"} — برای ارسال # خودکار از طریق Bot API فقط وقتی گیرنده قبلا چت با بات را شروع کرده channel_ids = models.JSONField(default=dict, blank=True, verbose_name=_("شناسه‌ی کانال‌ها")) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = _("مخاطب") verbose_name_plural = _("مخاطبین") ordering = ["name"] def __str__(self): return f"{self.name} ({self.phone})" class MessageTemplate(models.Model): class Mode(models.TextChoices): MERGE = "merge", _("قالب‌بندی") QUICK = "quick", _("تمپلیت آماده") id = models.CharField(max_length=36, primary_key=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="templates") # null = قالب عمومی (فقط فیلدهای پایه‌ی نام/شماره) group = models.ForeignKey( ContactGroup, on_delete=models.CASCADE, related_name="templates", null=True, blank=True ) name = models.CharField(max_length=150, verbose_name=_("نام قالب")) body = models.TextField(verbose_name=_("متن قالب")) mode = models.CharField(max_length=10, choices=Mode.choices) # فیلدهای «زمان اجرا» — مقدارشان از دیتای مخاطب خوانده نمی‌شود، هر بار # موقع ارسال (یا رسیدن سفارش به مرحله‌ای که این قالب رویش است) از کاربر # پرسیده می‌شود. لیست است چون یک قالب می‌تواند چند فیلدِ این‌چنینی # داشته باشد (مثلا هم «کد رهگیری» هم «مبلغ نهایی»). runtime_field_labels = models.JSONField(default=list, blank=True, verbose_name=_("فیلدهای زمان اجرا")) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = _("قالب پیام") verbose_name_plural = _("قالب‌های پیام") ordering = ["-updated_at"] def __str__(self): return self.name class StructuredTemplate(models.Model): """قالب کلید-مقدار — برای گزارش‌های تکرارشونده مثل «امروز چقدر از هر کالا موجود دارم». برخلاف MessageTemplate (متن آزاد با {فیلد})، این‌جا فقط یک لیست از عنوان‌هاست (مثلا «کلم»، «کاهو») که اپ یکی‌یکی مقدارشان را از کاربر می‌پرسد و در نهایت خط‌به‌خط به‌شکل «عنوان: مقدار» می‌فرستد.""" id = models.CharField(max_length=36, primary_key=True) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="structured_templates" ) name = models.CharField(max_length=150, verbose_name=_("نام قالب")) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = _("قالب کلید-مقدار") verbose_name_plural = _("قالب‌های کلید-مقدار") ordering = ["-updated_at"] def __str__(self): return self.name class StructuredTemplateField(models.Model): id = models.CharField(max_length=36, primary_key=True) template = models.ForeignKey(StructuredTemplate, on_delete=models.CASCADE, related_name="fields") label = models.CharField(max_length=100, verbose_name=_("عنوان")) sort_order = models.PositiveIntegerField(default=0) class Meta: verbose_name = _("فیلد قالب کلید-مقدار") verbose_name_plural = _("فیلدهای قالب کلید-مقدار") ordering = ["template", "sort_order"] def __str__(self): return f"{self.label} ({self.template.name})"