This commit is contained in:
javad committed 2026-09-10 11:34:19 +03:30
commit fa9d154db9
260 files changed
+17643

No files matched your search

View File
Whitespace-only changes.
Whitespace-only changes.
+147
View File
@@ -0,0 +1,147 @@
from django.contrib.auth.models import Group, Permission
from django.core.management.base import BaseCommand
from apps.attachments.models import AttachmentPolicy
from apps.tickets.models import SLAConfig, TicketCategory
from apps.accounts.models import User
from apps.customers.models import ContactGroup
from apps.staff.models import OrganizationalUnit, StaffProfile, UnitMembership
from apps.tickets.category_catalog import CATEGORY_CATALOG, UNIT_CONTENT
class Command(BaseCommand):
help = "Create initial categories, policies and permission groups"
def merge_legacy_unit(self, legacy_slug, canonical_slug, canonical_name, canonical_description):
legacy = OrganizationalUnit.objects.filter(slug=legacy_slug).first()
canonical = OrganizationalUnit.objects.filter(slug=canonical_slug).first()
if legacy is None:
return
if canonical is None:
legacy.slug = canonical_slug
legacy.name = canonical_name
legacy.description = canonical_description
legacy.save()
return
from apps.accounts.models import TicketMagicLink
from apps.notifications.models import SMSTemplate
from apps.tickets.models import GroupDispatch, Ticket
canonical.permissions.add(*legacy.permissions.all())
TicketCategory.objects.filter(unit=legacy).update(unit=canonical)
Ticket.objects.filter(owning_unit=legacy).update(owning_unit=canonical)
Ticket.objects.filter(assigned_unit=legacy).update(assigned_unit=canonical)
GroupDispatch.objects.filter(unit=legacy).update(unit=canonical)
TicketMagicLink.objects.filter(created_unit=legacy).update(created_unit=canonical)
StaffProfile.objects.filter(primary_unit=legacy).update(primary_unit=canonical)
for membership in UnitMembership.objects.filter(unit=legacy):
existing = UnitMembership.objects.filter(user=membership.user, unit=canonical).first()
if existing is None:
membership.unit = canonical
membership.save(update_fields=["unit", "updated_at"])
continue
existing.permissions.add(*membership.permissions.all())
existing.is_active = existing.is_active or membership.is_active
existing.is_primary = existing.is_primary or membership.is_primary
if membership.ticket_scope == UnitMembership.TicketScope.UNIT:
existing.ticket_scope = UnitMembership.TicketScope.UNIT
existing.save(update_fields=["is_active", "is_primary", "ticket_scope", "updated_at"])
membership.delete()
for group in ContactGroup.objects.filter(unit=legacy):
existing = ContactGroup.objects.filter(unit=canonical, name=group.name).first()
if existing is None:
group.unit = canonical
group.save(update_fields=["unit", "updated_at"])
continue
existing.contacts.add(*group.contacts.all())
GroupDispatch.objects.filter(contact_group=group).update(contact_group=existing)
group.delete()
for template in SMSTemplate.objects.filter(unit=legacy):
if SMSTemplate.objects.filter(unit=canonical, event_type=template.event_type).exists():
template.delete()
else:
template.unit = canonical
template.save(update_fields=["unit", "updated_at"])
legacy.delete()
def handle(self, *args, **opts):
self.merge_legacy_unit("accounting", "finance", "مالی / حسابداری", "واحد مالی و حسابداری (یک مفهوم سازمانی)")
self.merge_legacy_unit("logistics", "warehouse-loading", "بارگیری انبار", "بارگیری، انبار و ارسال")
self.merge_legacy_unit("warehouse", "warehouse-loading", "بارگیری انبار", "بارگیری، انبار و ارسال")
categories = [("استعلام قیمت","price"),("درصد تخفیف","discount"),("شرایط فروش","sales-terms"),("موجودی","inventory"),("شرایط ارسال","delivery"),("درخواست نمایندگی","agency"),("شکایت","complaint"),("مشاوره","consulting"),("سایر","other")]
for i, (title, slug) in enumerate(categories):
TicketCategory.objects.get_or_create(slug=slug, defaults={"title":title,"sort_order":i})
sizes = {"IMAGE":10,"VOICE":25,"VIDEO":100,"PDF":30,"WORD":30,"EXCEL":30,"ZIP":50,"RAR":50}
exts = {"IMAGE":[".jpg",".jpeg",".png",".gif",".webp"],"VOICE":[".webm",".ogg",".mp3",".m4a",".wav"],"VIDEO":[".mp4",".webm",".mov"],"PDF":[".pdf"],"WORD":[".doc",".docx"],"EXCEL":[".xls",".xlsx"],"ZIP":[".zip"],"RAR":[".rar"]}
for i, (kind, mb) in enumerate(sizes.items()):
AttachmentPolicy.objects.get_or_create(attachment_type=kind, defaults={"max_file_size":mb*1024*1024,"allowed_extensions":exts[kind],"sort_order":i})
for priority, first, resolution in [("NORMAL",240,2880),("IMPORTANT",120,1440),("URGENT",30,480)]:
SLAConfig.objects.get_or_create(priority=priority, defaults={"first_response_minutes":first,"resolution_minutes":resolution})
expert_codes = ["view_ticket","add_ticket","claim_ticket","transfer_ticket","share_ticket","change_ticket","add_internal_note","edit_own_message","delete_own_message","download_attachment"]
supervisor_codes = expert_codes + ["view_message_revisions","change_ticket_priority","view_customer_history"]
manager_codes = supervisor_codes + ["view_deleted_message_content","delete_ticket"]
role_permissions = {
"Sales Expert": expert_codes,
"Sales Supervisor": supervisor_codes,
"Sales Manager": manager_codes,
"Warehouse Expert": expert_codes,
"Finance Expert": expert_codes,
"CEO": manager_codes + ["view_all_tickets", "create_bulk_dispatch", "view_all_bulk_dispatches", "create_inquiry", "view_all_inquiries"],
"General Admin": manager_codes + ["view_all_tickets"],
}
for group_name, codes in role_permissions.items():
group, _ = Group.objects.get_or_create(name=group_name)
group.permissions.set(Permission.objects.filter(codename__in=codes))
units = {}
for order, (slug, name, description, external, customer_visible) in enumerate([
("sales", "فروش", UNIT_CONTENT["sales"]["description"], True, True),
("warehouse-loading", "بارگیری انبار", "عملیات داخلی بارگیری و انبار", True, False),
("finance", "مالی / حسابداری", UNIT_CONTENT["finance"]["description"], True, True),
("executive", "مدیریت ارشد", "مدیریت عامل", False, False),
], 10):
unit, _ = OrganizationalUnit.objects.update_or_create(slug=slug, defaults={"name": name, "description": description, "search_keywords": UNIT_CONTENT.get(slug, {}).get("search_keywords", ""), "is_active": True, "accepts_external_tickets": external, "show_in_customer_form": customer_visible, "sort_order": order})
units[slug] = unit
for unit_slug, rows in CATEGORY_CATALOG.items():
for sort_order, slug, title, description, search_keywords in rows:
TicketCategory.objects.update_or_create(slug=slug, defaults={"unit": units[unit_slug], "title": title, "description": description, "search_keywords": search_keywords, "sort_order": sort_order, "is_active": True, "show_in_customer_form": True})
ContactGroup.objects.get_or_create(unit=units[unit_slug], name=f"مخاطبان {units[unit_slug].name}")
TicketCategory.objects.update_or_create(
slug="warehouse-loading-general",
defaults={"unit": units["warehouse-loading"], "title": "عملیات بارگیری انبار", "description": "هماهنگی داخلی عملیات انبار و بارگیری.", "search_keywords": "انبار بارگیری عملیات داخلی", "sort_order": 10, "is_active": True, "show_in_customer_form": False},
)
TicketCategory.objects.filter(slug__in=("other", "e2e-sms")).update(show_in_customer_form=False)
unit_permission_pairs = [
("tickets", "view_ticket"), ("tickets", "add_ticket"), ("tickets", "claim_ticket"),
("messaging", "add_message"), ("customers", "view_customerprofile"),
("customers", "add_customerprofile"), ("customers", "change_customerprofile"),
("customers", "view_contactgroup"), ("customers", "add_contactgroup"), ("customers", "change_contactgroup"),
]
for unit in (units["sales"], units["warehouse-loading"], units["finance"]):
unit.permissions.add(*[Permission.objects.get(content_type__app_label=app, codename=code) for app, code in unit_permission_pairs])
people = [
("kimiya_ghaffari", "کیمیا", "غفاری", "کیمیا غفاری", StaffProfile.Role.SALES_EXPERT, "sales", "کارشناس فروش", UnitMembership.TicketScope.SELF),
("zahra_jamali", "زهرا", "جمالی", "زهرا جمالی", StaffProfile.Role.SALES_EXPERT, "sales", "کارشناس فروش", UnitMembership.TicketScope.SELF),
("maryam_fotovat", "مریم", "فتوت", "مریم فتوت", StaffProfile.Role.SALES_SUPERVISOR, "sales", "سرپرست فروش", UnitMembership.TicketScope.UNIT),
("morteza_sokhaeifard", "مرتضی", "سخائی فرد", "مرتضی سخائی فرد", StaffProfile.Role.SALES_MANAGER, "sales", "مدیر فروش", UnitMembership.TicketScope.UNIT),
("mostafa_sokhaeifard", "مصطفی", "سخائی فرد", "مصطفی سخائی فرد", StaffProfile.Role.CEO, "executive", "مدیرعامل", UnitMembership.TicketScope.UNIT),
("ms_sabzali", "", "سبزعلی", "خانم سبزعلی", StaffProfile.Role.WAREHOUSE_EXPERT, "warehouse-loading", "کارشناس بارگیری انبار", UnitMembership.TicketScope.UNIT),
("mehdi_azami", "مهدی", "عظمی", "آقای مهدی عظمی", StaffProfile.Role.WAREHOUSE_EXPERT, "warehouse-loading", "کارشناس بارگیری انبار", UnitMembership.TicketScope.UNIT),
("fereshteh_yavari", "فرشته", "یاوری", "خانم فرشته یاوری", StaffProfile.Role.FINANCE_EXPERT, "finance", "کارشناس مالی / حسابداری", UnitMembership.TicketScope.UNIT),
("fatemeh_sokhaei", "فاطمه", "سخایی", "خانم فاطمه سخایی", StaffProfile.Role.FINANCE_EXPERT, "finance", "کارشناس مالی / حسابداری", UnitMembership.TicketScope.UNIT),
("rana_nobakht", "رعنا", "نوبخت", "خانم رعنا نوبخت", StaffProfile.Role.FINANCE_EXPERT, "finance", "کارشناس مالی / حسابداری", UnitMembership.TicketScope.UNIT),
("mehdi_liaqat", "مهدی", "لیاقت", "آقای مهدی لیاقت", StaffProfile.Role.FINANCE_EXPERT, "finance", "کارشناس مالی / حسابداری", UnitMembership.TicketScope.UNIT),
("aliasghar_sokhaeifard", "علی اصغر", "سخائی فرد", "علی اصغر سخائی فرد", StaffProfile.Role.FINANCE_EXPERT, "finance", "کارشناس مالی / حسابداری", UnitMembership.TicketScope.UNIT),
]
for username, first_name, last_name, display_name, role, unit_slug, title, scope in people:
user, created = User.objects.get_or_create(username=username, defaults={"account_type": User.AccountType.STAFF})
user.account_type = User.AccountType.STAFF; user.first_name = first_name; user.last_name = last_name; user.is_active = True
if created: user.set_unusable_password()
user.save()
profile, _ = StaffProfile.objects.update_or_create(user=user, defaults={"primary_unit": units[unit_slug], "role": role, "display_name": display_name, "first_name": first_name, "last_name": last_name, "job_title": title, "is_active": True})
membership = UnitMembership.objects.get(user=user, unit=units[unit_slug])
membership.ticket_scope = scope; membership.is_primary = True; membership.is_active = True; membership.save()
user.unit_memberships.exclude(pk=membership.pk).delete()
sabzali = User.objects.get(username="ms_sabzali")
sabzali.unit_memberships.get(unit=units["warehouse-loading"]).permissions.add(*Permission.objects.filter(content_type__app_label="tickets", codename__in=["create_bulk_dispatch", "create_inquiry"]))
ceo = User.objects.get(username="mostafa_sokhaeifard")
ceo.user_permissions.add(*Permission.objects.filter(content_type__app_label="tickets", codename__in=["view_all_tickets", "add_ticket", "create_bulk_dispatch", "view_all_bulk_dispatches", "create_inquiry", "view_all_inquiries"]))
self.stdout.write(self.style.SUCCESS("Initial data created."))
@@ -0,0 +1,30 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from apps.customers.models import CustomerProfile
from apps.messaging.models import Message, MessageRevision
from apps.staff.models import StaffProfile
from apps.tickets.models import Ticket, TicketCategory
class Command(BaseCommand):
help = "Normalize existing human-authored Persian fields using the model policy."
@transaction.atomic
def handle(self, *args, **options):
specs = (
(CustomerProfile, CustomerProfile.persian_text_fields),
(StaffProfile, StaffProfile.persian_text_fields),
(TicketCategory, TicketCategory.persian_text_fields),
(Ticket, Ticket.persian_text_fields),
(Message, Message.persian_text_fields),
(MessageRevision, MessageRevision.persian_text_fields),
)
changed = 0
for model, fields in specs:
for instance in model.objects.all().iterator():
before = tuple(getattr(instance, name) for name in fields)
instance.save(update_fields=[*fields])
after = tuple(getattr(instance, name) for name in fields)
changed += before != after
self.stdout.write(self.style.SUCCESS(f"Normalized {changed} existing records."))