XH_Digital_Management/application/pjt_mgnt/models.py

332 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from decimal import Decimal
from django.apps import apps
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models, IntegrityError
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.functional import cached_property
from application.hrm_mgnt.models import EmployeeInformation
from application.org_mgnt.models import PrimaryDepartment
# 项目台账
class ProjectLedger(models.Model):
# 业务人员填报部分
project_id = models.AutoField(primary_key=True, verbose_name='项目编号')
project_name = models.CharField(max_length=255, verbose_name='项目名称', null=False, blank=False)
start_date = models.DateField(verbose_name='登记日期', null=True, blank=True)
project_approval_date = models.DateField(verbose_name='立项日期', null=True, blank=True)
end_date = models.DateField(verbose_name='结束日期', null=True, blank=True)
primary_department = models.CharField(max_length=255, verbose_name='一级部门', null=True, blank=True)
customer_name = models.CharField(max_length=255, verbose_name='客户名称', null=True, blank=True)
province = models.CharField(max_length=255, verbose_name='', null=True, blank=True)
city = models.CharField(max_length=255, verbose_name='', null=True, blank=True)
district = models.CharField(max_length=255, verbose_name='区县', null=True, blank=True)
project_leader = models.CharField(max_length=255, verbose_name='负责人', null=False, blank=False)
project_members = models.TextField(verbose_name='项目组员', null=True, blank=True)
project_status = models.CharField(max_length=100, choices=[('进行中', '进行中'), ('暂停', '暂停'), ('待收款', '待收款'), ('完成', '完成')], verbose_name='项目状态', null=True, blank=True)
# 管理部人员填报部分:
resource_type = models.CharField(max_length=50, choices=[('公司', '公司'), ('个人', '个人')], verbose_name='资源类型', null=True, blank=True)
project_nature = models.CharField(max_length=100, choices=[('新增', '新增'), ('存量', '存量'), ('新增及存量', '新增及存量'), ('老客户新业务', '老客户新业务')], verbose_name='项目性质', null=True, blank=True)
project_progress = models.CharField(max_length=255, verbose_name='项目进度', null=True, blank=True)
contract_date = models.DateField(verbose_name='签约时间', null=True, blank=True)
contract_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='标的金额(元)', null=True, blank=True)
contract_rate = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='合同费率(%', null=True, blank=True)
revenue = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='收入(元)', null=True, blank=True)
cost_rate = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='成本费率(%', null=True, blank=True)
cost = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='成本(元)', null=True, blank=True)
net_income = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='净收入(元)', null=True, blank=True)
# 开票记录
total_amount_including_tax = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='价税合计金额(元)', null=True, blank=True)
# 回款记录
repayment_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='回款金额(元)', null=True, blank=True)
# 计算得出
receivable_net_income = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='应收净收入(元)', null=True, blank=True)
actual_net_income = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='实收净收入(元)', null=True, blank=True)
outstanding_net_income = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='待收净收入(元)', null=True, blank=True)
notes = models.TextField(verbose_name='备注', null=True, blank=True)
@property
def net_income_ratio(self):
if self.revenue and self.revenue > Decimal('0'):
return Decimal('1') - (self.cost / self.revenue)
return Decimal('0')
@cached_property
def calculate_net_income(self):
if self.revenue and self.cost:
return self.revenue - self.cost
return Decimal('0')
def calculate_receivable_net_income(self):
try:
self.receivable_net_income = self.net_income_ratio * self.total_amount_including_tax
except TypeError:
self.receivable_net_income = 0
def calculate_actual_net_income(self):
try:
self.actual_net_income = self.net_income_ratio * self.repayment_amount
except TypeError:
self.actual_net_income = 0
def calculate_outstanding_net_income(self):
self.outstanding_net_income = self.receivable_net_income - self.actual_net_income
def save(self, *args, **kwargs):
self.net_income = self.calculate_net_income
project_leader = EmployeeInformation.objects.filter(name=self.project_leader).first()
primary_department = PrimaryDepartment.objects.filter(department_name=self.primary_department).first()
if self.project_leader:
if not project_leader:
raise ValueError("费用当事人不存在")
if self.primary_department:
if not primary_department:
raise ValueError("一级部门不存在")
if self.pk is None:
# 新增操作,进行唯一性校验
if ProjectLedger.objects.filter(project_leader=self.project_leader, primary_department=self.primary_department, project_name=self.project_name).exists():
raise IntegrityError("该一级部门的负责人名下已存在该项目名称。")
super(ProjectLedger, self).save(*args, **kwargs)
def create_sub_table(self):
if self.primary_department == '天信' or self.primary_department == '混改':
ChildProjectLedgerA.objects.create(project_id=self)
elif self.primary_department == '艾力芬特':
ChildProjectLedgerB.objects.create(project_id=self)
elif self.primary_department == '星河':
ChildProjectLedgerC.objects.create(project_id=self)
elif self.primary_department == '星海':
ChildProjectLedgerD.objects.create(project_id=self)
class Meta:
verbose_name = '项目台账'
verbose_name_plural = '项目台账'
def __str__(self):
return self.project_name
@receiver(post_save, sender=ProjectLedger)
def create_sub_table(sender, instance, created, **kwargs):
if created:
if instance.primary_department == '天信' or instance.primary_department == '混改':
ChildProjectLedgerA.objects.create(project_id=instance)
elif instance.primary_department == '艾力芬特':
ChildProjectLedgerB.objects.create(project_id=instance)
elif instance.primary_department == '星河':
ChildProjectLedgerC.objects.create(project_id=instance)
elif instance.primary_department == '星海':
ChildProjectLedgerD.objects.create(project_id=instance)
# 项目问题记录表
class ProjectIssuesLog(models.Model):
issue_id = models.AutoField(primary_key=True, verbose_name='问题ID')
project_id = models.ForeignKey(ProjectLedger, on_delete=models.CASCADE, verbose_name='项目ID')
record_date = models.DateTimeField(verbose_name='记录时间', null=True, blank=True)
description = models.TextField(verbose_name='问题描述', null=True, blank=True)
handler = models.CharField(max_length=255, verbose_name='经办人', null=True, blank=True)
reference = models.TextField(verbose_name='参考依据', null=True, blank=True)
solution = models.TextField(verbose_name='解决方案', null=True, blank=True)
decision_maker = models.CharField(max_length=255, verbose_name='决策人', null=True, blank=True)
STATUS_CHOICES = [
('解决', '解决'),
('未解决', '未解决')
]
status = models.CharField(max_length=255, choices=STATUS_CHOICES, verbose_name='状态', null=True, blank=True, default='未解决')
class Meta:
verbose_name = '项目问题记录表'
verbose_name_plural = '项目问题记录表'
def __str__(self):
return f"Issue #{self.issue_id} - Project: {self.project_id.project_name}"
# 项目进度表
class ProjectProgress(models.Model):
record_id = models.AutoField(primary_key=True, verbose_name='记录ID')
project_id = models.ForeignKey(ProjectLedger, on_delete=models.CASCADE, verbose_name='项目编号')
flow_nodes = models.JSONField(verbose_name='进度点')
class Meta:
verbose_name = '项目进度表'
verbose_name_plural = '项目进度表'
def __str__(self):
return f"Record #{self.record_id} - Project: {self.project_id}"
# 项目进度设置表
class ProjectProgressSettings(models.Model):
config_id = models.AutoField(primary_key=True, verbose_name='配置ID')
project_type = models.CharField(max_length=255, verbose_name='项目类型', null=False, blank=False)
flow_nodes_setting = models.CharField(max_length=255, verbose_name='项目进度设置', null=False, blank=False,
help_text="项目进度设置示例:买菜;洗菜;切菜;烧菜;")
class Meta:
verbose_name = '项目进度设置表'
verbose_name_plural = '项目进度设置表'
def __str__(self):
return f"Config #{self.config_id} - Project Type: {self.project_type}"
# 项目台账续表A
# 项目台账续表A
class ChildProjectLedgerA(models.Model):
project_id = models.OneToOneField(ProjectLedger, primary_key=True, on_delete=models.CASCADE, verbose_name='项目编号')
PROJECT_TYPE_CHOICES = [('cbc', 'cbc'), ('债拍', '债拍'), ('其他', '其他')]
project_type = models.CharField(max_length=255, choices=PROJECT_TYPE_CHOICES, verbose_name='项目类型', null=True, blank=True)
resource_party = models.CharField(max_length=255, verbose_name='资源方', null=True, blank=True)
cooperation_party = models.CharField(max_length=255, verbose_name='合作方', null=True, blank=True)
transaction_amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='成交金额(元)', null=True, blank=True)
target_amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='标的金额(元)', null=True, blank=True)
@property
def contract_completion_rate(self):
if self.target_amount != 0:
return round((self.transaction_amount / self.target_amount) * 100, 2)
return None
class Meta:
verbose_name = '项目台账续表A'
verbose_name_plural = '项目台账续表A'
def __str__(self):
return f"Project #{self.project_id} - Type: {self.project_type}"
# 项目台账续表B
class ChildProjectLedgerB(models.Model):
project_id = models.OneToOneField(ProjectLedger, primary_key=True, on_delete=models.CASCADE, verbose_name='项目编号')
PROJECT_TYPE_CHOICES = [('承销', '承销'), ('贸易', '贸易'), ('其他', '其他')]
project_type = models.CharField(max_length=255, choices=PROJECT_TYPE_CHOICES, verbose_name='项目类型', null=True, blank=True)
partner = models.CharField(max_length=255, verbose_name='合作方', null=True, blank=True)
start_interest_date = models.DateField(verbose_name='起息日期', null=True, blank=True)
interest_payment_date = models.DateField(verbose_name='付息日期', null=True, blank=True)
number_of_people = models.IntegerField(verbose_name='人数', null=True, blank=True)
amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='金额', null=True, blank=True)
class Meta:
verbose_name = '项目台账续表B'
verbose_name_plural = '项目台账续表B'
def __str__(self):
return f"Project #{self.project_id} - Type: {self.project_type}"
# 项目台账续表C
class ChildProjectLedgerC(models.Model):
project_id = models.OneToOneField(ProjectLedger, primary_key=True, on_delete=models.CASCADE, verbose_name='项目编号')
PROJECT_TYPE_CHOICES = [('咨询', '咨询'), ('科技', '科技'), ('新媒体', '新媒体'), ('其他', '其他')]
project_type = models.CharField(max_length=100, choices=PROJECT_TYPE_CHOICES, verbose_name='项目类型', null=True, blank=True)
resource_party = models.CharField(max_length=255, verbose_name='资源方', null=True, blank=True)
cooperation_party = models.CharField(max_length=255, verbose_name='合作方', null=True, blank=True)
service_period = models.CharField(max_length=100, verbose_name='服务周期', null=True, blank=True)
class Meta:
verbose_name = '项目台账续表C'
verbose_name_plural = '项目台账续表C'
def __str__(self):
return f"Project #{self.project_id} - Type: {self.project_type}"
# 项目台账续表D
class ChildProjectLedgerD(models.Model):
project_id = models.OneToOneField(ProjectLedger, primary_key=True, on_delete=models.CASCADE, verbose_name='项目编号')
PROJECT_TYPE_CHOICES = [('非标', '非标'), ('贸易', '贸易'), ('其他', '其他')]
project_type = models.CharField(max_length=50, choices=PROJECT_TYPE_CHOICES, verbose_name='项目类型', null=True, blank=True)
capital_demand_party = models.CharField(max_length=255, verbose_name='资金需求方', null=True, blank=True)
capital_provider = models.CharField(max_length=255, verbose_name='资金提供方', null=True, blank=True)
financing_method = models.CharField(max_length=255, verbose_name='融资方式', null=True, blank=True)
target_amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='标的金额(元)', null=True, blank=True)
term = models.CharField(max_length=100, verbose_name='期限', null=True, blank=True)
nominal_rate = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='票面利率', null=True, blank=True)
total_cost = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='综合成本(元)', null=True, blank=True)
TRADE_TYPE_CHOICES = [('带量', '带量'), ('取信', '取信'), ('补票', '补票')]
trade_type = models.CharField(max_length=50, choices=TRADE_TYPE_CHOICES, verbose_name='贸易类型', null=True, blank=True)
trade_entity = models.CharField(max_length=255, verbose_name='贸易主体', null=True, blank=True)
trade_service_provider = models.CharField(max_length=255, verbose_name='贸易服务商', null=True, blank=True)
trade_variety = models.CharField(max_length=255, verbose_name='贸易品种', null=True, blank=True)
demand_party = models.CharField(max_length=255, verbose_name='需求方', null=True, blank=True)
supply_party = models.CharField(max_length=255, verbose_name='供给方', null=True, blank=True)
class Meta:
verbose_name = '项目台账续表D'
verbose_name_plural = '项目台账续表D'
def __str__(self):
return f"Project #{self.project_id} - Type: {self.project_type}"
# 项目组员收入结算表
class EmployeeProjectIncomeSettlement(models.Model):
record_id = models.AutoField(primary_key=True, verbose_name='记录ID')
project_name = models.ForeignKey('ProjectLedger', on_delete=models.CASCADE, verbose_name='项目名称')
year_month = models.DateField(max_length=7, verbose_name='年月', help_text='格式为 YYYY-MM从开票记录中关联')
total_amount_including_tax = models.DecimalField(
max_digits=10,
decimal_places=2,
verbose_name='价税合计金额(元)',
help_text='从开票记录中获取',
null=True,
blank=True
)
name = models.CharField(max_length=255, verbose_name='姓名', help_text='员工姓名', null=True, blank=True)
primary_department = models.CharField(max_length=255, verbose_name='一级部门', null=True, blank=True)
contribution_rate = models.DecimalField(
max_digits=5,
decimal_places=2,
verbose_name='贡献率(%',
validators=[MinValueValidator(0), MaxValueValidator(100)],
help_text='员工对项目的贡献率,单位为百分比',
null=True,
blank=True
)
sales_income = models.DecimalField(
max_digits=10,
decimal_places=2,
verbose_name='销售收入(元)',
help_text='计算得出,等于价税合计金额×贡献率',
null=True,
blank=True
)
class Meta:
verbose_name = '项目组员收入结算表'
verbose_name_plural = '项目组员收入结算表'
def __str__(self):
return f"Record #{self.record_id} - Project: {self.project_name}, Year-Month: {self.year_month}, Employee: {self.name or 'N/A'}"
def save(self, *args, **kwargs):
primary_department_name = PrimaryDepartment.objects.filter(department_name=self.primary_department).first()
name = EmployeeInformation.objects.filter(name=self.name).first()
if self.primary_department:
if not primary_department_name:
raise ValueError("一级部门不存在")
if self.name:
if not name:
raise ValueError("姓名不存在")
if self.pk is None:
# 新增操作,进行唯一性校验
if EmployeeProjectIncomeSettlement.objects.filter(project_name=self.project_name, year_month=self.year_month, name=self.name, primary_department=self.primary_department).exists():
raise IntegrityError("该部门下的员工在同一年月的同一项目下已存在一个项目结算。")
# 只在贡献率不为空时计算销售收入
if all([self.contribution_rate, self.total_amount_including_tax]):
self.sales_income = self.total_amount_including_tax * (self.contribution_rate / 100)
super().save(*args, **kwargs)