first
This commit is contained in:
commit
fa9d154db9
260 files changed
+17643
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,51 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from .forms import StaffProfileAdminForm
|
||||
from .models import OrganizationalUnit, StaffProfile, UnitMembership
|
||||
|
||||
|
||||
class UnitMembershipInline(admin.TabularInline):
|
||||
model = UnitMembership
|
||||
extra = 0
|
||||
filter_horizontal = ("permissions",)
|
||||
|
||||
|
||||
@admin.register(OrganizationalUnit)
|
||||
class OrganizationalUnitAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "slug", "sort_order", "show_in_customer_form", "accepts_external_tickets", "is_active", "updated_at")
|
||||
list_filter = ("show_in_customer_form", "accepts_external_tickets", "is_active")
|
||||
search_fields = ("name", "slug")
|
||||
list_editable = ("sort_order", "show_in_customer_form", "accepts_external_tickets", "is_active")
|
||||
filter_horizontal = ("permissions",)
|
||||
inlines = (UnitMembershipInline,)
|
||||
|
||||
|
||||
@admin.register(UnitMembership)
|
||||
class UnitMembershipAdmin(admin.ModelAdmin):
|
||||
list_display = ("user", "unit", "ticket_scope", "is_primary", "is_active")
|
||||
list_filter = ("unit", "ticket_scope", "is_primary", "is_active")
|
||||
search_fields = ("user__username", "user__mobile", "unit__name")
|
||||
filter_horizontal = ("permissions",)
|
||||
|
||||
@admin.register(StaffProfile)
|
||||
class StaffProfileAdmin(admin.ModelAdmin):
|
||||
form = StaffProfileAdminForm
|
||||
list_display = ("display_name", "primary_unit", "role", "sort_order", "job_title", "mobile", "is_active", "is_customer_selectable")
|
||||
list_editable = ("sort_order",)
|
||||
list_filter = ("primary_unit", "role", "is_active", "is_customer_selectable", "bale_notifications_enabled")
|
||||
search_fields = ("first_name", "last_name", "display_name", "mobile", "user__username")
|
||||
fieldsets = (
|
||||
("حساب کاربری", {"fields": ("user",)}),
|
||||
("مشخصات کارشناس", {"fields": ("primary_unit", "role", "first_name", "last_name", "display_name", "job_title", "avatar_preview", "avatar", "mobile")}),
|
||||
("اطلاعرسانی", {"fields": ("bale_chat_id", "bale_notifications_enabled")}),
|
||||
("نمایش و دسترسی", {"fields": ("is_customer_selectable", "sort_order", "is_active")}),
|
||||
)
|
||||
readonly_fields = ("avatar_preview",)
|
||||
|
||||
@admin.display(description="تصویر فعلی")
|
||||
def avatar_preview(self, obj):
|
||||
if not obj or not obj.pk or not obj.avatar:
|
||||
return "تصویری ثبت نشده است."
|
||||
url = reverse("staff_tickets:expert_avatar", args=[obj.user.public_id])
|
||||
return format_html('<img src="{}?v={}" alt="تصویر کارشناس" style="width:96px;height:96px;border-radius:16px;object-fit:cover;border:1px solid #ccd8e5">', url, obj.updated_at.timestamp())
|
||||
@@ -0,0 +1,2 @@
|
||||
from django.apps import AppConfig
|
||||
class StaffConfig(AppConfig): name = "apps.staff"
|
||||
@@ -0,0 +1,31 @@
|
||||
from django import forms
|
||||
|
||||
from .models import StaffProfile
|
||||
|
||||
|
||||
class StaffProfileAdminForm(forms.ModelForm):
|
||||
"""Upload/replace avatars without exposing a direct public media URL."""
|
||||
role = forms.ChoiceField(
|
||||
choices=StaffProfile.Role.choices,
|
||||
required=False,
|
||||
initial=StaffProfile.Role.SALES_EXPERT,
|
||||
label="نقش سازمانی",
|
||||
)
|
||||
avatar = forms.ImageField(
|
||||
required=False,
|
||||
widget=forms.FileInput(attrs={"accept": "image/jpeg,image/png,image/webp"}),
|
||||
help_text="تصویر JPG، PNG یا WebP انتخاب کنید. تصویر جدید جایگزین تصویر فعلی میشود.",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = StaffProfile
|
||||
fields = "__all__"
|
||||
|
||||
def clean_avatar(self):
|
||||
avatar = self.cleaned_data.get("avatar")
|
||||
if avatar and avatar.size > 5 * 1024 * 1024:
|
||||
raise forms.ValidationError("حجم تصویر پروفایل نباید بیشتر از ۵ مگابایت باشد.")
|
||||
return avatar
|
||||
|
||||
def clean_role(self):
|
||||
return self.cleaned_data.get("role") or StaffProfile.Role.SALES_EXPERT
|
||||
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-05 07:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StaffProfile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('display_name', models.CharField(max_length=150)),
|
||||
('avatar', models.ImageField(blank=True, upload_to='staff/avatars/')),
|
||||
('mobile', models.CharField(blank=True, max_length=16)),
|
||||
('bale_chat_id', models.CharField(blank=True, max_length=100)),
|
||||
('bale_notifications_enabled', models.BooleanField(default=True)),
|
||||
('is_customer_selectable', models.BooleanField(default=True)),
|
||||
('sort_order', models.PositiveIntegerField(default=0)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff_profile', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['sort_order', 'display_name'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-06 10:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('staff', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='staffprofile',
|
||||
name='first_name',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='staffprofile',
|
||||
name='job_title',
|
||||
field=models.CharField(blank=True, default='کارشناس فروش', max_length=150),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='staffprofile',
|
||||
name='last_name',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-07 11:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('staff', '0002_staffprofile_first_name_staffprofile_job_title_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='staffprofile',
|
||||
name='role',
|
||||
field=models.CharField(choices=[('SALES_EXPERT', 'کارشناس فروش'), ('SALES_SUPERVISOR', 'سرپرست فروش'), ('SALES_MANAGER', 'مدیر فروش'), ('CEO', 'مدیرعامل'), ('GENERAL_ADMIN', 'مدیرکل Admin')], db_index=True, default='SALES_EXPERT', max_length=24),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-08 09:57
|
||||
|
||||
import apps.core.models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('staff', '0003_staffprofile_role'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrganizationalUnit',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('slug', models.SlugField(max_length=80, unique=True)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('permissions', models.ManyToManyField(blank=True, help_text='مجوزهایی که به تمام اعضای فعال این واحد داده می\u200cشود.', related_name='organizational_units', to='auth.permission')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'واحد سازمانی',
|
||||
'verbose_name_plural': 'واحدهای سازمانی',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
bases=(apps.core.models.PersianTextNormalizationMixin, models.Model),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='staffprofile',
|
||||
name='primary_unit',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='primary_staff', to='staff.organizationalunit'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UnitMembership',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('is_primary', models.BooleanField(default=False)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('permissions', models.ManyToManyField(blank=True, help_text='مجوزهای اضافه مخصوص این عضو در همین واحد.', related_name='unit_memberships', to='auth.permission')),
|
||||
('unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberships', to='staff.organizationalunit')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_memberships', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'عضویت واحد',
|
||||
'verbose_name_plural': 'عضویت\u200cهای واحد',
|
||||
'ordering': ['unit__name', 'user__username'],
|
||||
'constraints': [models.UniqueConstraint(fields=('user', 'unit'), name='unique_unit_membership')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-09 05:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('staff', '0004_organizationalunit_staffprofile_primary_unit_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='organizationalunit',
|
||||
name='accepts_external_tickets',
|
||||
field=models.BooleanField(default=True, help_text='اگر غیرفعال باشد، مشتریان نمی\u200cتوانند مستقیماً برای این واحد تیکت ثبت کنند.', verbose_name='قابل انتخاب برای تیکت مشتری'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-09 09:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('staff', '0005_organizationalunit_accepts_external_tickets'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='organizationalunit',
|
||||
options={'ordering': ['sort_order', 'name', 'pk'], 'verbose_name': 'واحد سازمانی', 'verbose_name_plural': 'واحدهای سازمانی'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organizationalunit',
|
||||
name='search_keywords',
|
||||
field=models.TextField(blank=True, help_text='واژه\u200cها و عبارت\u200cهای مترادف را با فاصله یا ویرگول وارد کنید.', verbose_name='کلیدواژه\u200cهای جستجو'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organizationalunit',
|
||||
name='sort_order',
|
||||
field=models.PositiveIntegerField(db_index=True, default=0, verbose_name='ترتیب نمایش'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-09 09:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('staff', '0006_alter_organizationalunit_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='organizationalunit',
|
||||
name='show_in_customer_form',
|
||||
field=models.BooleanField(default=True, help_text='در صورت خاموش بودن، واحد در فرم ثبت تیکت و جستجوی مشتری نمایش داده نمی\u200cشود.', verbose_name='نمایش در فرم مشتری'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [("staff", "0007_organizationalunit_show_in_customer_form")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="unitmembership",
|
||||
name="ticket_scope",
|
||||
field=models.CharField(
|
||||
choices=[("SELF", "عمومی، ایجادشده یا تخصیصیافته به خود"), ("UNIT", "تمام تیکتهای واحد")],
|
||||
default="SELF",
|
||||
help_text="دامنه مشاهده تیکتها در همین واحد؛ مجوز سراسری مستقل است.",
|
||||
max_length=12,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="staffprofile",
|
||||
name="role",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("SALES_EXPERT", "کارشناس فروش"),
|
||||
("SALES_SUPERVISOR", "سرپرست فروش"),
|
||||
("SALES_MANAGER", "مدیر فروش"),
|
||||
("WAREHOUSE_EXPERT", "کارشناس بارگیری انبار"),
|
||||
("FINANCE_EXPERT", "کارشناس مالی / حسابداری"),
|
||||
("CEO", "مدیرعامل"),
|
||||
("GENERAL_ADMIN", "مدیرکل Admin"),
|
||||
],
|
||||
db_index=True,
|
||||
default="SALES_EXPERT",
|
||||
max_length=24,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.1 on 2026-09-10 05:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('staff', '0008_unitmembership_ticket_scope_and_roles'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='staffprofile',
|
||||
name='sort_order',
|
||||
field=models.PositiveIntegerField(db_index=True, default=0, help_text='عدد کمتر، کارشناس را زودتر در فهرست همان واحد نمایش می\u200cدهد.', verbose_name='ترتیب نمایش در انتخاب کارشناس'),
|
||||
),
|
||||
]
|
||||
Whitespace-only changes.
@@ -0,0 +1,155 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import models
|
||||
from apps.core.models import PersianTextNormalizationMixin, TimeStampedModel
|
||||
|
||||
|
||||
class OrganizationalUnit(PersianTextNormalizationMixin, TimeStampedModel):
|
||||
"""A business unit whose access rules are managed from Django Admin."""
|
||||
|
||||
persian_text_fields = {"name": False, "description": True, "search_keywords": True}
|
||||
name = models.CharField(max_length=120, unique=True)
|
||||
slug = models.SlugField(max_length=80, unique=True)
|
||||
description = models.TextField(blank=True)
|
||||
search_keywords = models.TextField(blank=True, verbose_name="کلیدواژههای جستجو", help_text="واژهها و عبارتهای مترادف را با فاصله یا ویرگول وارد کنید.")
|
||||
sort_order = models.PositiveIntegerField(default=0, db_index=True, verbose_name="ترتیب نمایش")
|
||||
permissions = models.ManyToManyField(
|
||||
Permission,
|
||||
blank=True,
|
||||
related_name="organizational_units",
|
||||
help_text="مجوزهایی که به تمام اعضای فعال این واحد داده میشود.",
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
accepts_external_tickets = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name="قابل انتخاب برای تیکت مشتری",
|
||||
help_text="اگر غیرفعال باشد، مشتریان نمیتوانند مستقیماً برای این واحد تیکت ثبت کنند.",
|
||||
)
|
||||
show_in_customer_form = models.BooleanField(default=True, verbose_name="نمایش در فرم مشتری", help_text="در صورت خاموش بودن، واحد در فرم ثبت تیکت و جستجوی مشتری نمایش داده نمیشود.")
|
||||
|
||||
class Meta:
|
||||
ordering = ["sort_order", "name", "pk"]
|
||||
verbose_name = "واحد سازمانی"
|
||||
verbose_name_plural = "واحدهای سازمانی"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class UnitMembership(TimeStampedModel):
|
||||
"""Dynamic per-user unit membership with optional extra permissions."""
|
||||
|
||||
class TicketScope(models.TextChoices):
|
||||
SELF = "SELF", "عمومی، ایجادشده یا تخصیصیافته به خود"
|
||||
UNIT = "UNIT", "تمام تیکتهای واحد"
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="unit_memberships",
|
||||
)
|
||||
unit = models.ForeignKey(
|
||||
OrganizationalUnit,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="memberships",
|
||||
)
|
||||
permissions = models.ManyToManyField(
|
||||
Permission,
|
||||
blank=True,
|
||||
related_name="unit_memberships",
|
||||
help_text="مجوزهای اضافه مخصوص این عضو در همین واحد.",
|
||||
)
|
||||
is_primary = models.BooleanField(default=False)
|
||||
is_active = models.BooleanField(default=True)
|
||||
ticket_scope = models.CharField(
|
||||
max_length=12,
|
||||
choices=TicketScope.choices,
|
||||
default=TicketScope.SELF,
|
||||
help_text="دامنه مشاهده تیکتها در همین واحد؛ مجوز سراسری مستقل است.",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=["user", "unit"], name="unique_unit_membership")
|
||||
]
|
||||
ordering = ["unit__name", "user__username"]
|
||||
verbose_name = "عضویت واحد"
|
||||
verbose_name_plural = "عضویتهای واحد"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user} — {self.unit}"
|
||||
|
||||
|
||||
class StaffProfile(PersianTextNormalizationMixin, TimeStampedModel):
|
||||
class Role(models.TextChoices):
|
||||
SALES_EXPERT = "SALES_EXPERT", "کارشناس فروش"
|
||||
SALES_SUPERVISOR = "SALES_SUPERVISOR", "سرپرست فروش"
|
||||
SALES_MANAGER = "SALES_MANAGER", "مدیر فروش"
|
||||
WAREHOUSE_EXPERT = "WAREHOUSE_EXPERT", "کارشناس بارگیری انبار"
|
||||
FINANCE_EXPERT = "FINANCE_EXPERT", "کارشناس مالی / حسابداری"
|
||||
CEO = "CEO", "مدیرعامل"
|
||||
GENERAL_ADMIN = "GENERAL_ADMIN", "مدیرکل Admin"
|
||||
|
||||
ROLE_GROUPS = {
|
||||
Role.SALES_EXPERT: "Sales Expert",
|
||||
Role.SALES_SUPERVISOR: "Sales Supervisor",
|
||||
Role.SALES_MANAGER: "Sales Manager",
|
||||
Role.WAREHOUSE_EXPERT: "Warehouse Expert",
|
||||
Role.FINANCE_EXPERT: "Finance Expert",
|
||||
Role.CEO: "CEO",
|
||||
Role.GENERAL_ADMIN: "General Admin",
|
||||
}
|
||||
persian_text_fields = {"first_name": False, "last_name": False, "display_name": False, "job_title": False}
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="staff_profile")
|
||||
primary_unit = models.ForeignKey(
|
||||
OrganizationalUnit,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="primary_staff",
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
role = models.CharField(max_length=24, choices=Role.choices, default=Role.SALES_EXPERT, db_index=True)
|
||||
display_name = models.CharField(max_length=150)
|
||||
first_name = models.CharField(max_length=100, blank=True)
|
||||
last_name = models.CharField(max_length=100, blank=True)
|
||||
job_title = models.CharField(max_length=150, blank=True, default="کارشناس فروش")
|
||||
avatar = models.ImageField(upload_to="staff/avatars/", blank=True)
|
||||
mobile = models.CharField(max_length=16, blank=True)
|
||||
bale_chat_id = models.CharField(max_length=100, blank=True)
|
||||
bale_notifications_enabled = models.BooleanField(default=True)
|
||||
is_customer_selectable = models.BooleanField(default=True)
|
||||
sort_order = models.PositiveIntegerField(
|
||||
default=0,
|
||||
db_index=True,
|
||||
verbose_name="ترتیب نمایش در انتخاب کارشناس",
|
||||
help_text="عدد کمتر، کارشناس را زودتر در فهرست همان واحد نمایش میدهد.",
|
||||
)
|
||||
is_active = models.BooleanField(default=True)
|
||||
class Meta: ordering = ["sort_order", "display_name"]
|
||||
def __str__(self): return self.display_name
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
group_name = self.ROLE_GROUPS[self.role]
|
||||
managed_names = tuple(self.ROLE_GROUPS.values())
|
||||
self.user.groups.remove(*self.user.groups.filter(name__in=managed_names))
|
||||
from django.contrib.auth.models import Group
|
||||
group, _ = Group.objects.get_or_create(name=group_name)
|
||||
self.user.groups.add(group)
|
||||
if self.primary_unit_id:
|
||||
membership, membership_created = UnitMembership.objects.get_or_create(
|
||||
user=self.user,
|
||||
unit=self.primary_unit,
|
||||
defaults={"is_primary": True},
|
||||
)
|
||||
desired_scope = (
|
||||
UnitMembership.TicketScope.UNIT
|
||||
if self.role in {self.Role.SALES_SUPERVISOR, self.Role.SALES_MANAGER, self.Role.WAREHOUSE_EXPERT, self.Role.FINANCE_EXPERT, self.Role.CEO, self.Role.GENERAL_ADMIN}
|
||||
else UnitMembership.TicketScope.SELF
|
||||
)
|
||||
if not membership.is_primary or not membership.is_active or (membership_created and membership.ticket_scope != desired_scope):
|
||||
membership.is_primary = True
|
||||
membership.is_active = True
|
||||
if membership_created:
|
||||
membership.ticket_scope = desired_scope
|
||||
membership.save(update_fields=["is_primary", "is_active", "ticket_scope", "updated_at"])
|
||||
self.user.unit_memberships.exclude(pk=membership.pk).filter(is_primary=True).update(is_primary=False)
|
||||
Reference in new issue
Block a user