class IncomeType(FakerMixin, CommonFieldMixin, DescriptionFieldMixin): name = models.CharField('收入类型', max_length=50) data_path = os.path.join(BASE_DIR, 'data/income_type.json') objects = models.Manager() enabled_objects = EnabledEntityManager() class Meta: verbose_name = '收入类型' verbose_name_plural = '收入类型' def __str__(self): return self.name
class Stage(FakerMixin, CommonFieldMixin): name = models.CharField('阶段', max_length=100) objects = models.Manager() enabled_objects = EnabledEntityManager() data_path = os.path.join(BASE_DIR, 'data/case_stage.json') class Meta: verbose_name = '案件阶段' verbose_name_plural = '案件阶段' def __str__(self): return '{}'.format(self.name)
class Category(FakerMixin, CommonFieldMixin): name = models.CharField('类别', max_length=100) parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) objects = models.Manager() enabled_objects = EnabledEntityManager() data_path = os.path.join(BASE_DIR, 'data/case_category.json') class Meta: verbose_name = '案件分类' verbose_name_plural = '案件分类' def __str__(self): return '{}'.format(self.name) @classmethod def get_choices(cls, parent=None): """ 生成带有一级optgroup的HTML select options数据 :param parent: 用于筛选返回结果中的类别,支持list或者单个int值 :return: """ choices = [] for c in cls.enabled_objects.all(): if c.parent is not None: continue if parent and (isinstance(parent, list) and c.id not in parent or c.id != parent): # 筛选分类 # 在Trademark/Pattern relatedView中会调用 continue choices.append((c.name, [(child_c.id, child_c.name) for child_c in c.category_set.all()])) return choices
class Receipts(CommonFieldMixin, DescriptionFieldMixin): amount = models.DecimalField('已收款金额', max_digits=10, decimal_places=2) exchange_rate = models.DecimalField('收款汇率', max_digits=8, decimal_places=4) received_date = models.DateField('收款日期') currency = models.ForeignKey('base.Currency', verbose_name='收款货币', on_delete=models.SET_NULL, null=True) receivable = models.ForeignKey(Receivable, verbose_name='待收款账单', on_delete=models.SET_NULL, null=True) deposit = models.ForeignKey('income.Deposit', verbose_name='客户预存款', on_delete=models.SET_NULL, null=True) objects = models.Manager() enabled_objects = EnabledEntityManager() modelform_class = 'sale.forms.ReceiptsModelForm' datatables_class = 'sale.datatables.ReceiptsDataTable' related_entity_config = {} class Meta: verbose_name = '已收款项' verbose_name_plural = '已收款项' def __str__(self): return '{}{}-{}'.format(self.currency_id, self.amount, self.received_date) def get_absolute_url(self): return reverse('receipts:detail', kwargs={'receipts_id': self.id}) def get_deletion_url(self): return reverse('receipts:disable', kwargs={'receipts_id': self.id}) def get_deletion_success_url(self): return reverse('receipts:detail', kwargs={'receipts_id': self.id}) @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = '金额:{}'.format(self.amount) detail_info['sub_title'] = '' desc['收款汇率'] = self.exchange_rate or '未设置' desc['货币'] = self.currency.name_chs desc['收款日期'] = self.received_date or '未指定' desc['手续费'] = self.transfer_charge or '未指定' desc['所属待收账单'] = getattr(self.receivable, 'name', '未指定编号') detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info @cached_property def amount_cny(self): if self.currency_id == 'CNY': return self.amount else: return self.amount * self.exchange_rate
class Receivable(CommonFieldMixin, DescriptionFieldMixin): no = models.CharField('待收款账单编号', max_length=100) sent_date = models.DateField('账单发送日期', null=True, blank=True) due_date = models.DateField('待收期限', null=True, blank=True) amount = models.DecimalField('待收总金额', max_digits=10, decimal_places=2) unsettled_amount = models.DecimalField('未收总金额', max_digits=10, decimal_places=2, null=True, blank=True) settled = models.BooleanField('客户是否付清', default=False) subcase = models.ForeignKey('case.SubCase', verbose_name='关联分案', on_delete=models.SET_NULL, null=True) currency = models.ForeignKey('base.Currency', verbose_name='货币', on_delete=models.SET_NULL, null=True) objects = models.Manager() enabled_objects = EnabledEntityManager() modelform_class = 'sale.forms.ReceivableModelForm' datatables_class = 'sale.datatables.ReceivableDataTable' related_entity_config = { 'sale.receipts': { 'query_path': 'receivable', 'verbose_name': '已收款项' } } class Meta: verbose_name = '待收款项' verbose_name_plural = '待收款项' def __str__(self): return '{}-{}{}'.format(self.no, self.currency_id, self.amount) def get_absolute_url(self): return reverse('receivable:detail', kwargs={'receivable_id': self.id}) def get_deletion_url(self): return reverse('receivable:disable', kwargs={'receivable_id': self.id}) def get_deletion_success_url(self): return reverse('receivable:detail', kwargs={'receivable_id': self.id}) @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = self.no or '未指定编号' detail_info['sub_title'] = '<a href="{}">{}</a>'.format( reverse('subcase:detail', kwargs={'subcase_id': self.subcase_id}), self.subcase.name) desc['所属案件'] = '<a href="{}">{}</a>'.format( reverse('case:detail', kwargs={'case_id': self.subcase.case_id}), self.subcase.case.name) desc['金额'] = self.amount desc['未收金额'] = self.unsettled_amount desc['货币'] = self.currency.name_chs desc['收款期限'] = self.due_date or '未指定' desc['账单发送日期'] = self.sent_date or '未指定' detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info
class Income(CommonFieldMixin, DescriptionFieldMixin): amount = models.DecimalField('收入金额', max_digits=10, decimal_places=2) exchange_rate = models.DecimalField('汇率', max_digits=8, decimal_places=4, default=Decimal('1')) incurred_date = models.DateField('收入日期', null=True, blank=True) currency = models.ForeignKey('base.Currency', verbose_name='货币', default='CNY', on_delete=models.SET_NULL, null=True) income_type = models.ForeignKey(IncomeType, verbose_name='收入类型', on_delete=models.SET_NULL, null=True) subcase = models.ForeignKey('case.SubCase', verbose_name='关联分案件', on_delete=models.SET_NULL, null=True) objects = models.Manager() enabled_objects = EnabledEntityManager() modelform_class = 'income.forms.IncomeModelForm' datatables_class = 'income.datatables.IncomeDataTable' related_entity_config = {} class Meta: verbose_name = '其它收入' verbose_name_plural = '其它收入' def __str__(self): return '{}{}'.format(self.currency_id, self.amount) def get_absolute_url(self): return reverse('income:detail', kwargs={'income_id': self.id}) def get_deletion_url(self): return reverse('income:disable', kwargs={'income_id': self.id}) def get_deletion_success_url(self): return reverse('income:detail', kwargs={'income_id': self.id}) @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = '金额:{}'.format(self.amount) detail_info['sub_title'] = self.income_type.name desc['货币'] = self.currency.name_chs desc['汇率'] = self.exchange_rate or '未设置' desc['日期'] = self.incurred_date or '未指定' if self.subcase: desc['关联分案件'] = '<a href="{}">{}</a>'.format( reverse('subcase:detail', kwargs={'subcase_id': self.subcase_id}), self.subcase.name) detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info @cached_property def amount_cny(self): if self.currency_id == 'CNY': return self.amount else: return self.amount * self.exchange_rate
class Payment(CommonFieldMixin, DescriptionFieldMixin): amount = models.DecimalField('已付款金额', max_digits=10, decimal_places=2) exchange_rate = models.DecimalField('付款汇率', max_digits=8, decimal_places=4) paid_date = models.DateField('付款日期') currency = models.ForeignKey( 'base.Currency', verbose_name='付款货币', on_delete=models.SET_NULL, null=True ) payable = models.ForeignKey( Payable, verbose_name='应付款账单', on_delete=models.SET_NULL, null=True ) objects = models.Manager() enabled_objects = EnabledEntityManager() modelform_class = 'purchase.forms.PaymentModelForm' datatables_class = 'purchase.datatables.PaymentDataTable' related_entity_config = { 'purchase.paymentlink': { 'query_path': 'payment', 'verbose_name': '转移' } } class Meta: verbose_name = '已付款项' verbose_name_plural = '已付款项' def __str__(self): return '{}{}-{}'.format(self.currency_id, self.amount, self.paid_date) def get_absolute_url(self): return reverse('payment:detail', kwargs={'payment_id': self.id}) def get_deletion_url(self): return reverse('payment:disable', kwargs={'payment_id': self.id}) def get_deletion_success_url(self): return reverse('payment:detail', kwargs={'payment_id': self.id}) @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = '金额:{}'.format(self.amount) detail_info['sub_title'] = '' desc['付款汇率'] = self.exchange_rate or '未设置' desc['货币'] = self.currency.name_chs desc['付款日期'] = self.paid_date or '未指定' desc['手续费(人民币)'] = self.transfer_charge.amount or '未指定' desc['已转移金额'] = self.linked_amount desc['未转移金额'] = self.unlinked_amount desc['所属待付账单'] = '<a href="{}">{}</a>'.format( reverse('payable:detail', kwargs={'payable_id': self.payable_id}), self.payable.no ) desc['所属分案'] = '<a href="{}">{}</a>'.format( reverse('subcase:detail', kwargs={'subcase_id': self.payable.subcase_id}), self.payable.subcase.name ) desc['所属案件'] = '<a href="{}">{}</a>'.format( reverse('case:detail', kwargs={'case_id': self.payable.subcase.case_id}), self.payable.subcase.case.name ) detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info @cached_property def amount_cny(self): if self.currency_id == 'CNY': return self.amount else: return self.amount * self.exchange_rate @cached_property def linked_amount(self): return sum(link.amount for link in self.paymentlink_set.filter(enabled=1).all()) @cached_property def unlinked_amount(self): return self.amount - self.linked_amount @cached_property def unlinked_amount_cny(self): return self.unlinked_amount * self.exchange_rate
class PaymentLink(CommonFieldMixin, DescriptionFieldMixin): amount = models.DecimalField('转移金额', max_digits=10, decimal_places=2) payment = models.ForeignKey( Payment, verbose_name='已付账单', on_delete=models.SET_NULL, null=True ) subcase = models.ForeignKey( 'case.SubCase', verbose_name='转移目标(分案件)', on_delete=models.SET_NULL, null=True ) objects = models.Manager() enabled_objects = EnabledEntityManager() modelform_class = 'purchase.forms.PaymentLinkModelForm' datatables_class = 'purchase.datatables.PaymentLinkDataTable' related_entity_config = {} class Meta: verbose_name = '已付款转移' verbose_name_plural = '已付款转移' def __str__(self): return '转移已付款项: {}'.format(self.amount) def get_absolute_url(self): return reverse('paymentlink:detail', kwargs={'paymentlink_id': self.id}) def get_deletion_url(self): return reverse('paymentlink:disable', kwargs={'paymentlink_id': self.id}) def get_deletion_success_url(self): return reverse('paymentlink:detail', kwargs={'paymentlink_id': self.id}) @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = '金额:{}'.format(self.amount) detail_info['sub_title'] = '' desc['关联已付款金额'] = '<a href="{}">{}</a>'.format( reverse('payment:detail', kwargs={'payment_id': self.payment_id}), self.payment.amount ) desc['关联已付款货币'] = self.payment.currency.name_chs desc['关联分案'] = '<a href="{}">{}</a>'.format( reverse('subcase:detail', kwargs={'subcase_id': self.subcase_id}), self.subcase.name ) detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info @cached_property def amount_cny(self): if self.payment.currency_id == 'CNY': return self.amount else: return self.amount * self.payment.exchange_rate
class Expense(CommonFieldMixin, DescriptionFieldMixin): amount = models.DecimalField('支出金额', max_digits=10, decimal_places=2) exchange_rate = models.DecimalField( '汇率', max_digits=8, decimal_places=4, default=Decimal('1'), ) incurred_date = models.DateField('支出日期', null=True, blank=True) currency = models.ForeignKey('base.Currency', verbose_name='货币', default='CNY', on_delete=models.SET_NULL, null=True) expense_type = models.ForeignKey(ExpenseType, verbose_name='支出类型', on_delete=models.SET_NULL, null=True) subcase = models.ForeignKey('case.SubCase', verbose_name='关联分案件', on_delete=models.SET_NULL, null=True) payment = models.OneToOneField('purchase.Payment', verbose_name='关联已付款项', related_name='transfer_charge', on_delete=models.SET_NULL, null=True) receipts = models.OneToOneField('sale.Receipts', verbose_name='关联已收款项', related_name='transfer_charge', on_delete=models.SET_NULL, null=True) objects = models.Manager() enabled_objects = EnabledEntityManager() modelform_class = 'expense.forms.ExpenseModelForm' datatables_class = 'expense.datatables.ExpenseDataTable' related_entity_config = {} class Meta: verbose_name = '支出' verbose_name_plural = '支出' def __str__(self): return '{}{}'.format(self.currency_id, self.amount) def get_absolute_url(self): return reverse('expense:detail', kwargs={'expense_id': self.id}) def get_deletion_url(self): return reverse('expense:disable', kwargs={'expense_id': self.id}) def get_deletion_success_url(self): return reverse('expense:detail', kwargs={'expense_id': self.id}) @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = '金额:{}'.format(self.amount) detail_info['sub_title'] = self.expense_type.name desc['货币'] = self.currency.name_chs desc['汇率'] = self.exchange_rate or '未设置' desc['日期'] = self.incurred_date or '未指定' if self.receipts: desc['关联已收款项'] = '<a href="{}">{} {}(<i>{}</i>)</a>'.format( reverse('receipts:detail', kwargs={'receipts_id': self.receipts_id}), self.receipts.currency_id, self.receipts.amount, self.receipts.received_date) if self.payment: desc['关联已付款项'] = '<a href="{}">{} {}(<i>{}</i>)</a>'.format( reverse('payment:detail', kwargs={'payment_id': self.payment_id}), self.payment.currency_id, self.payment.amount, self.payment.paid_date) if self.subcase: desc['关联分案件'] = '<a href="{}">{}</a>'.format( reverse('subcase:detail', kwargs={'subcase_id': self.subcase_id}), self.subcase.name) detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info @cached_property def amount_cny(self): if self.currency_id == 'CNY': return self.amount else: return self.amount * self.exchange_rate
class Case(FakerMixin, CommonFieldMixin, DescriptionFieldMixin): name = models.CharField('案件名称', max_length=200) archive_no = models.CharField('档案号', max_length=100, null=True, blank=True) closed = models.BooleanField('结案', default=False) client = models.ForeignKey('base.Client', verbose_name='客户', on_delete=models.SET_NULL, null=True) owner = models.ForeignKey('base.Owner', verbose_name='所属部门', on_delete=models.SET_NULL, null=True) category = models.ForeignKey(Category, verbose_name='案件类型', on_delete=models.SET_NULL, null=True) stage = models.ForeignKey(Stage, verbose_name='所处阶段', on_delete=models.SET_NULL, null=True) trademark = models.ForeignKey('base.Trademark', verbose_name='商标', on_delete=models.SET_NULL, null=True, blank=True) pattern = models.ForeignKey('base.Pattern', verbose_name='专利', on_delete=models.SET_NULL, null=True, blank=True) objects = models.Manager() enabled_objects = EnabledEntityManager() datatables_class = 'case.datatables.CaseDataTable' modelform_class = 'case.forms.CaseModelForm' related_entity_config = { 'case.subcase': { 'query_path': 'case', 'verbose_name': '分案信息', } } faker_fields = { 'name': 'sentence', 'archive_no': 'isbn13', 'settled': 'pybool', 'closed': 'pybool', 'client': Client, 'owner': Owner, 'category': Category, 'stage': Stage, 'desc': 'paragraph' } class Meta: verbose_name = '案件' verbose_name_plural = '案件' def __str__(self): return '{}:{}'.format(self.archive_no, self.name) def get_absolute_url(self): return reverse('case:detail', kwargs={'case_id': self.id}) def get_deletion_url(self): return reverse('case:disable', kwargs={'case_id': self.id}) def get_deletion_success_url(self): return reverse('case:detail', kwargs={'case_id': self.id}) def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = self.name detail_info['sub_title'] = self.archive_no desc['客户'] = '<a href="{}">{}</a>'.format( reverse('client:detail', kwargs={'client_id': self.client_id}), self.client.name) desc['案件类型'] = self.category.name desc['所属阶段'] = self.stage.name desc['所属部门'] = self.owner.name desc['结案'] = '是' if self.closed else '否' detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config @cached_property def balance_amount_cny(self): balance = sum(subcase.receipts_sum_cny + subcase.income_sum_cny - (subcase.payment_sum_cny + subcase.expense_sum_cny + subcase.paymentlink_sum_cny) for subcase in self.subcase_set.filter(enabled=1).all()) return balance
class SubCase(FakerMixin, CommonFieldMixin, DescriptionFieldMixin): name = models.CharField('分案名', max_length=200) closed = models.BooleanField('结案', default=False) agent = models.ForeignKey( 'base.Client', related_name='agent_subcase', verbose_name='代理', on_delete=models.SET_NULL, null=True, ) case = models.ForeignKey( Case, verbose_name='所属案件', on_delete=models.SET_NULL, null=True, ) category = models.ForeignKey(Category, verbose_name='案件类型', on_delete=models.SET_NULL, null=True) stage = models.ForeignKey( Stage, verbose_name='所处阶段', on_delete=models.SET_NULL, null=True, ) trademarknation = models.ForeignKey('base.TrademarkNation', verbose_name='商标-进入国家', on_delete=models.SET_NULL, null=True, blank=True) patternnation = models.ForeignKey('base.PatternNation', verbose_name='专利-进入国家', on_delete=models.SET_NULL, null=True, blank=True) objects = models.Manager() enabled_objects = EnabledEntityManager() modelform_class = 'case.forms.SubCaseModelForm' datatables_class = 'case.datatables.SubCaseDataTable' related_entity_config = { 'sale.receivable': { 'query_path': 'subcase', 'verbose_name': '待收款项', }, 'purchase.payable': { 'query_path': 'subcase', 'verbose_name': '待付款项', }, 'expense.expense': { 'query_path': 'subcase', 'verbose_name': '其它支出' }, 'income.income': { 'query_path': 'subcase', 'verbose_name': '其它收入' }, } class Meta: verbose_name = '分案' verbose_name_plural = '分案' def __str__(self): return '分案: {}'.format(self.name) def get_absolute_url(self): return reverse('subcase:detail', kwargs={'subcase_id': self.id}) def get_deletion_url(self): return reverse('subcase:disable', kwargs={'subcase_id': self.id}) def get_deletion_success_url(self): return reverse('subcase:detail', kwargs={'subcase_id': self.id}) @classmethod def get_related_entity_config(cls): if cls.related_entity_config is not None: return cls.related_entity_config def get_detail_info(self): detail_info = {} desc = OrderedDict() detail_info['title'] = self.name detail_info['sub_title'] = '<a href="{}">{}</a>'.format( reverse('case:detail', kwargs={'case_id': self.case_id}), self.case.name) desc['代理'] = '<a href="{}">{}</a>'.format( reverse('client:detail', kwargs={'client_id': self.agent_id}), self.agent.name) desc['案件类型'] = self.category.name desc['所处阶段'] = self.stage.name if self.trademarknation: desc['商标(国家)'] = '<a href="{}">{}</a>'.format( reverse('trademarknation:detail', kwargs={'trademarknation_id': self.trademarknation_id}), self.trademarknation) if self.patternnation: desc['专利(国家)'] = '<a href="{}">{}</a>'.format( reverse('patternnation:detail', kwargs={'patternnation_id': self.patternnation_id}), self.patternnation) desc['结案'] = '是' if self.closed else '否' detail_info['desc'] = desc detail_info['enabled'] = self.enabled return detail_info @property def receipts_iter(self): # 因为这个iter要多次使用 # 所以不能设置为cached_property # 注意要使用filter enabled=1 return itertools.chain.from_iterable( rv.receipts_set.filter(enabled=1).all() for rv in self.receivable_set.all()) @cached_property def receipts_sum_cny(self): # 所有关联receipts的总金额 # 减去transfer_charge return sum(rv.amount_cny - rv.transfer_charge.amount for rv in self.receipts_iter) @property def payment_iter(self): # 因为这个iter要多次使用 # 所以不能设置为cached_property # 注意要使用filter enabled=1 return itertools.chain.from_iterable( pa.payment_set.filter(enabled=1).all() for pa in self.payable_set.all()) @cached_property def payment_sum_cny(self): # 所有关联payment的总金额 # 包含transfer_charge # 注意使用的是unlinked_amount_cny,表示当前payment未被转移的金额(CNY) return sum(pm.unlinked_amount_cny + pm.transfer_charge.amount for pm in self.payment_iter) @cached_property def paymentlink_sum_cny(self): # 注意要使用filter enabled=1 return sum(plink.amount_cny for plink in self.paymentlink_set.filter(enabled=1).all()) @cached_property def income_sum_cny(self): # 注意要使用filter enabled=1 return sum(income.amount_cny for income in self.income_set.filter(enabled=1).all()) @cached_property def expense_sum_cny(self): # 注意要使用filter enabled=1 return sum(expense.amount_cny for expense in self.expense_set.filter(enabled=1).all())