from django.db import models from django.utils import timezone class Branch(models.Model): CATEGORY_CHOICES = ( ('A型', 'A型'), ('B型', 'B型'), ('C型', 'C型'), ('不适用', '不适用'), ) name = models.CharField(max_length=255, unique=True, verbose_name='分支机构名称') location = models.CharField(max_length=255, verbose_name='所在省份') contact_info = models.CharField(max_length=255, verbose_name='主要联系人') description = models.TextField(blank=True, verbose_name='备注') background_color = models.CharField(max_length=7, default='#EFF6FF', verbose_name='背景色', help_text='使用#RRGGBB格式的颜色代码') category = models.CharField(max_length=10, choices=CATEGORY_CHOICES, default='C型', verbose_name='分类') is_mature = models.BooleanField(default=False, verbose_name='是否成熟') def __str__(self): return f'{self.name} 💼' if self.is_mature else self.name class Meta: verbose_name = '分支机构' verbose_name_plural = '分支机构(基础信息)' class Contact(models.Model): branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构') CATEGORY_CHOICES = [ ('机房/设备间巡检人', '机房/设备间巡检人'), ('信息安全联系人', '信息安全联系人'), ('兼岗', '兼岗'), ('安全员', '安全员') # 可以添加更多类别 ] # 修改为支持多选的 CharField category = models.CharField( max_length=255, # 增大长度(原50可能不足) choices=CATEGORY_CHOICES, verbose_name='联系人分类', help_text='按住 Ctrl/Command 键多选(值将以逗号分隔存储)' ) name = models.CharField(max_length=255, verbose_name='姓名') phone = models.CharField(max_length=20, verbose_name='电话') email = models.EmailField(blank=True, verbose_name='邮箱,可不填') description = models.TextField(blank=True, verbose_name='描述,可不填') def __str__(self): return self.name class Meta: verbose_name = '联系人群' verbose_name_plural = '联系人群' class Activity(models.Model): branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构') name = models.CharField(max_length=255, verbose_name='活动名称') scope = models.CharField(max_length=255, choices=( ('新建', '新建'), ('搬迁', '搬迁'), ('原址装修', '原址装修'), ('撤销', '撤销'), ('其他技术问题', '其他技术问题') ), verbose_name='活动类型') start_time = models.DateField(verbose_name='开始日期') end_time = models.DateField(blank=True, null=True, verbose_name='结束日期') # 可以为空,表示活动尚未结束 location = models.CharField(max_length=255, verbose_name='所在地点') description = models.TextField(verbose_name='其它内容') def __str__(self): return self.name class Meta: verbose_name = '运营活动内容' verbose_name_plural = '运营活动内容(新建搬迁装修和技术)' class EquipmentImage(models.Model): branch = models.ForeignKey(Branch, related_name='equipment_images', on_delete=models.CASCADE) image = models.ImageField(upload_to='equipment_room_images/') uploaded_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"设备间图片 {self.id} - {self.branch.name}" class Meta: verbose_name = '设备间图' verbose_name_plural = '设备间图' # 图纸的类 class Drawing(models.Model): branch = models.ForeignKey(Branch, related_name='drawings', on_delete=models.CASCADE) image = models.ImageField(upload_to='drawings/') uploaded_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"图纸 {self.id} - {self.branch.name}" class Meta: verbose_name = '图纸' verbose_name_plural = '图纸' # 公共电子屏 class PublicScreen(models.Model): SCREEN_TYPES = ( ('marquee', '跑马灯'), ('advertisement', '广告屏'), ('information', '信息发布屏'), ) branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='public_screens') image = models.ImageField(upload_to='public_screen_images/', null=True, blank=True) screen_type = models.CharField(max_length=20, choices=SCREEN_TYPES, verbose_name='功能类型', null=True, blank=True) description = models.TextField(blank=True, null=True, verbose_name='功能描述') last_drill = models.ForeignKey('Event', on_delete=models.SET_NULL, blank=True, null=True, related_name='public_screens', verbose_name='最后演练事件') created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f'{self.branch.name} - {self.get_screen_type_display()} {self.id}' class Meta: verbose_name = '公共电子屏' verbose_name_plural = '公共电子屏' class Event(models.Model): branches = models.ManyToManyField(Branch, related_name='events', verbose_name='分支机构') name = models.CharField(max_length=255, verbose_name='事件名称') start_time = models.DateField(verbose_name='开始时间') end_time = models.DateField(blank=True, null=True, verbose_name='结束时间') # 可以为空,表示活动尚未结束 description = models.TextField(verbose_name='事件描述') def __str__(self): return self.name class Meta: verbose_name = '运营事件' verbose_name_plural = '运营事件(其它)' class VideoTerminal(models.Model): TERMINAL_TYPES = ( ('polycom', '宝利通终端'), ('zte', '中兴终端'), ('logitech', '罗技摄像头'), ('laptop_tv', '笔记本加电视'), ('laptop_projector', '笔记本加投影仪'), ('other', '其它'), ) branch = models.ForeignKey(Branch, on_delete=models.CASCADE, related_name='video_terminals', verbose_name='分支机构') terminal_type = models.CharField(max_length=20, choices=TERMINAL_TYPES, verbose_name='设备类型') description = models.TextField(blank=True, verbose_name='设备描述') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') def __str__(self): return f"{self.branch.name} - {self.get_terminal_type_display()}" class Meta: verbose_name = '视频设备终端' verbose_name_plural = '视频设备终端' class Evaluation(models.Model): activity = models.ForeignKey(Activity, on_delete=models.CASCADE) branch = models.ForeignKey(Branch, on_delete=models.CASCADE) score = models.DecimalField(max_digits=4, decimal_places=2) comment = models.TextField() file_path = models.CharField(max_length=255, blank=True, null=True) status = models.CharField(max_length=20, choices=(('pending', '待审核'), ('approved', '已通过'), ('rejected', '已拒绝')), default='pending') def __str__(self): return f"{self.activity.name} - {self.branch.name}" # 预算相关模型 class Budget(models.Model): """预算主表""" branch = models.ForeignKey(Branch, on_delete=models.CASCADE, verbose_name='分支机构') name = models.CharField(max_length=255, verbose_name='预算名称') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') total_budget = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name='总预算') def __str__(self): return f"{self.branch.name} - {self.name} 预算" class Meta: verbose_name = '预算主表' verbose_name_plural = '预算主表' class EquipmentBudget(models.Model): """设备预算明细""" BUDGET_TYPES = ( ('本地询价采购', '本地询价采购'), ('订单采购', '订单采购'), ('按照总部配置要求本地询价采购', '按照总部配置要求本地询价采购'), ('本地询价采购或订单采购', '本地询价采购或订单采购') ) budget = models.ForeignKey(Budget, related_name='equipment_budgets', on_delete=models.CASCADE, verbose_name='预算主表') project = models.CharField(max_length=255, verbose_name='项目') model = models.CharField(max_length=255, verbose_name='型号') unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价') procurement_method = models.CharField(max_length=50, choices=BUDGET_TYPES, verbose_name='采购方式') quantity = models.IntegerField(default=1, verbose_name='数量') subtotal = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name='小计') def save(self, *args, **kwargs): self.subtotal = self.unit_price * self.quantity super().save(*args, **kwargs) # 更新预算主表的总预算 self.update_total_budget() def delete(self, *args, **kwargs): super().delete(*args, **kwargs) # 更新预算主表的总预算 self.update_total_budget() def update_total_budget(self): """更新预算主表的总预算""" total = self.budget.equipment_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0 total += self.budget.infrastructure_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0 self.budget.total_budget = total self.budget.save() def __str__(self): return f"{self.project} - {self.model}" class Meta: verbose_name = '设备预算明细' verbose_name_plural = '设备预算明细' class InfrastructureBudget(models.Model): """基础设施预算明细""" budget = models.ForeignKey(Budget, related_name='infrastructure_budgets', on_delete=models.CASCADE, verbose_name='预算主表') name = models.CharField(max_length=255, verbose_name='名称') remarks = models.TextField(blank=True, verbose_name='备注') unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价') unit = models.CharField(max_length=20, verbose_name='单位') description = models.TextField(blank=True, verbose_name='说明') quantity = models.IntegerField(default=1, verbose_name='数量') subtotal = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name='小计') def save(self, *args, **kwargs): self.subtotal = self.unit_price * self.quantity super().save(*args, **kwargs) # 更新预算主表的总预算 self.update_total_budget() def delete(self, *args, **kwargs): super().delete(*args, **kwargs) # 更新预算主表的总预算 self.update_total_budget() def update_total_budget(self): """更新预算主表的总预算""" total = self.budget.equipment_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0 total += self.budget.infrastructure_budgets.aggregate(total=models.Sum('subtotal'))['total'] or 0 self.budget.total_budget = total self.budget.save() def __str__(self): return self.name class Meta: verbose_name = '基础设施预算明细' verbose_name_plural = '基础设施预算明细' class BudgetTemplate(models.Model): """预算模板""" name = models.CharField(max_length=255, verbose_name='模板名称') description = models.TextField(blank=True, verbose_name='模板描述') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') is_default = models.BooleanField(default=False, verbose_name='是否默认模板') def __str__(self): return self.name class Meta: verbose_name = '预算模板' verbose_name_plural = '预算模板' class TemplateEquipmentItem(models.Model): """模板设备项""" template = models.ForeignKey(BudgetTemplate, related_name='equipment_items', on_delete=models.CASCADE, verbose_name='模板') project = models.CharField(max_length=255, verbose_name='项目') model = models.CharField(max_length=255, verbose_name='型号') unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='参考单价') procurement_method = models.CharField(max_length=50, choices=EquipmentBudget.BUDGET_TYPES, verbose_name='采购方式') def __str__(self): return f"{self.project} - {self.model}" class Meta: verbose_name = '模板设备项' verbose_name_plural = '模板设备项' class TemplateInfrastructureItem(models.Model): """模板基础设施项""" template = models.ForeignKey(BudgetTemplate, related_name='infrastructure_items', on_delete=models.CASCADE, verbose_name='模板') name = models.CharField(max_length=255, verbose_name='名称') remarks = models.TextField(blank=True, verbose_name='备注') unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='参考单价') unit = models.CharField(max_length=20, verbose_name='单位') description = models.TextField(blank=True, verbose_name='说明') def __str__(self): return self.name class Meta: verbose_name = '模板基础设施项' verbose_name_plural = '模板基础设施项'