class BaseTransactionItem(BMFModel): """ """ account = models.ForeignKey( settings.CONTRIB_ACCOUNT, null=True, blank=False, related_name="transactions", on_delete=models.PROTECT, ) transaction = models.ForeignKey( settings.CONTRIB_TRANSACTION, null=True, blank=False, related_name="items", on_delete=models.CASCADE, ) date = models.DateField( _('Date'), null=True, blank=False, ) amount_currency = CurrencyField() amount = MoneyField(blank=False) credit = models.BooleanField( choices=((True, _('Credit')), (False, _('Debit'))), default=True, ) draft = models.BooleanField(_('Draft'), default=True, editable=False) objects = TransactionItemManager() class Meta: abstract = True swappable = "BMF_CONTRIB_TRANSACTIONITEM" ordering = ('-date', '-draft', 'credit', 'transaction__text') class BMFMeta: serializer = TransactionItemSerializer # def set_debit(self, amount): # if self.get_type in [ACCOUNTING_ASSET, ACCOUNTING_EXPENSE]: # self.amount = amount # else: # self.amount = -amount # def set_credit(self, amount): # if self.get_type in [ACCOUNTING_ASSET, ACCOUNTING_EXPENSE]: # self.amount = -amount # else: # self.amount = amount @property def get_type(self): try: return getattr(self, 'type', self.account.type) except AttributeError: return 0
class InvoiceProduct(BMFModel): invoice = models.ForeignKey( CONTRIB_INVOICE, null=True, blank=True, related_name="invoice_products", on_delete=models.CASCADE, ) product = models.ForeignKey( CONTRIB_PRODUCT, null=True, blank=True, related_name="invoice_products", on_delete=models.PROTECT, ) name = models.CharField(_("Name"), max_length=255, null=True, blank=False) price_currency = CurrencyField() price_precision = models.PositiveSmallIntegerField( default=0, blank=True, null=True, editable=False, ) price = MoneyField(_("Price"), blank=False) amount = models.FloatField(_("Amount"), null=True, blank=False, default=1.0) # unit = models.CharField() # TODO add units description = models.TextField(_("Description"), null=True, blank=True) class BMFMeta: only_related = True def calc_all(self): if hasattr(self, '_calcs'): return self._calcs self._calcs = self.product.calc_tax(self.amount, self.price) return self._calcs def calc_net_unit(self): return self.calc_all()[0] def calc_net(self): return self.calc_all()[1] def calc_gross(self): return self.calc_all()[2] def calc_taxes(self): return self.calc_all()[3] def clean(self): if self.product and not self.name: self.name = self.product.name if self.product and not self.price: self.price = self.product.price
class AbstractProduct(BMFModel): """ """ name = models.CharField( _("Name"), max_length=255, null=False, blank=False, ) code = models.CharField( _("Product Code"), max_length=255, null=False, blank=True, db_index=True, ) type = models.PositiveSmallIntegerField( _("Product type"), null=False, blank=False, choices=PRODUCT_TYPES, default=PRODUCT_SERVICE, ) can_sold = models.BooleanField( _("Can be sold"), null=False, blank=True, default=False, db_index=True, ) can_purchased = models.BooleanField( _("Can be purchased"), null=False, blank=True, default=False, db_index=True, ) description = models.TextField(_("Description"), null=False, blank=True) price_currency = CurrencyField() price_precision = models.PositiveSmallIntegerField( default=0, blank=True, null=True, editable=False, ) price = MoneyField(_("Price"), blank=False) taxes = models.ManyToManyField( CONTRIB_TAX, blank=True, related_name="product_taxes", limit_choices_to={'is_active': True}, through='ProductTax', editable=False, ) # discount = models.FloatField(_('Max. discount'), default=0.0) # Accounting income_account = models.ForeignKey( CONTRIB_ACCOUNT, null=False, blank=False, related_name="product_income", limit_choices_to={ 'type': ACCOUNTING_INCOME, 'read_only': False }, on_delete=models.PROTECT, ) expense_account = models.ForeignKey( CONTRIB_ACCOUNT, null=False, blank=False, related_name="product_expense", limit_choices_to={ 'type': ACCOUNTING_EXPENSE, 'read_only': False }, on_delete=models.PROTECT, ) objects = ProductManager() # warehouse # number = models.PositiveSmallIntegerField( _("Product number"), null=True, blank=True, choices=PRODUCT_NO) # uos = models.CharField( "UOS", max_length=255, null=False, blank=True, help_text=_("Unit of Service")) # uom = models.CharField( "UOM", max_length=255, null=False, blank=True, help_text=_("Unit of Measurement")) # customer_taxes # supplier_taxes # image # category # warehouse # description_web # validation method / FIFO or Running average - first in first out # aktiv posten # garantie # end of live # netto weight # UOM weight # supplier # cost-center # pricelist # inspection # manufactoring # online available # discount # sale_price # product_manager # warranty: months # description_quotation # description_suppliers # customer_lead_time: days # FIFO - First in First out # LIFO - Last-in-First-Out # sku Product SKU required string new_product # name Product name required # meta_title Product meta title optional string new product # meta_description # price Product price required # weight Product weight required # visibility Product visibility. Can have the following values: # 1 - Not Visible Individually, 2 - Catalog, 3 - Search, 4 - Catalog, Search. required # description Product description. required # short_description Product short description. required # UOM to UOS # Unit weight (Kg) # Sales price 0.00 # Sales currency EUR # Max sales discount (%) 0.00 # Sales tax (%) 0.00 # Description empty # Categories empty # Tags empty class Meta(BMFModel.Meta): # only needed for abstract models verbose_name = _('Product') verbose_name_plural = _('Products') ordering = ['name'] abstract = True swappable = "BMF_CONTRIB_PRODUCT" class BMFMeta: search_fields = ['name', 'code'] def __str__(self): return self.name # def calc_default_price(self, project, amount, price): # return self.get_price(1.0, self.price) def calc_tax(self, amount, price, related=False): # TODO add currency for calculation of taxes if not isinstance(amount, Decimal): amount = Decimal(str(amount)) if isinstance(price, BaseCurrency): price = price.value elif not isinstance(price, Decimal): price = Decimal(str(price)) if price.as_tuple().exponent > -2: price = price.quantize(Decimal('0.01')) taxes = self.product_tax.select_related('tax') tax_inc_sum = Decimal(1) for tax in taxes: if tax.included: tax_inc_sum += tax.tax.get_rate() # net price of one unit unit_exact = (price / tax_inc_sum).quantize(price) used_taxes = [] net = (amount * unit_exact).quantize(Decimal('0.01')) gross = (amount * unit_exact).quantize(Decimal('0.01')) for tax in taxes: tax_value = (net * tax.tax.get_rate()).quantize(Decimal('0.01')) gross += tax_value if related: used_taxes.append((tax.tax, tax_value, tax)) else: used_taxes.append((tax.tax, tax_value)) return unit_exact, net, gross, used_taxes
class AbstractPosition(BMFModel): """ """ project = models.ForeignKey( # TODO: make optional CONTRIB_PROJECT, null=True, blank=False, related_name="+", on_delete=models.SET_NULL, ) employee = models.ForeignKey( # TODO: make optional CONTRIB_EMPLOYEE, null=True, blank=False, on_delete=models.SET_NULL, ) date = models.DateField(_("Date"), null=True, blank=False) name = models.CharField(_("Name"), max_length=255, null=True, blank=False) invoice = models.ForeignKey( CONTRIB_INVOICE, null=True, blank=True, related_name="+", editable=False, on_delete=models.PROTECT, ) product = models.ForeignKey( CONTRIB_PRODUCT, null=True, blank=False, on_delete=models.PROTECT, ) invoiceable = models.PositiveSmallIntegerField( _("Invoiceable"), null=True, blank=False, default=1, choices=RATE_CHOICES, editable=False, ) price_currency = CurrencyField() price_precision = models.PositiveSmallIntegerField(default=0, blank=True, null=True, editable=False) price = MoneyField(_("Price"), blank=False) amount = models.FloatField(_("Amount"), null=True, blank=False, default=1.0) description = models.TextField(_("Description"), null=True, blank=True) objects = PositionManager() def __str__(self): return '%s' % (self.name) def has_invoice(self): return bool(self.invoice) def bmfget_customer(self): if hasattr(self, 'project'): return self.project.customer else: return None def bmfget_project(self): if hasattr(self, 'project'): return self.project else: return None def clean(self): if self.product and not self.name: self.name = self.product.name if self.product and not self.price: self.price = self.product.price class Meta: verbose_name = _('Position') verbose_name_plural = _('Positions') abstract = True swappable = "BMF_CONTRIB_POSITION" class BMFMeta: has_logging = False
class BaseAccount(BMFModel): """ Accounts ============== ======== ======== Account-Type Credit Debit ============== ======== ======== Asset Decrease Increase Liability Increase Decrease Income/Revenue Increase Decrease Expense Decrease Increase Equity/Capital Increase Decrease ============== ======== ======== """ parent = models.ForeignKey( 'self', null=True, blank=True, related_name='child', on_delete=models.CASCADE, ) parents = models.ManyToManyField( 'self', related_name='children', editable=False, symmetrical=False, ) balance_currency = CurrencyField(editable=False) balance = MoneyField(editable=False, default=0) number = models.CharField(_('Number'), max_length=30, null=True, blank=True, unique=True, db_index=True) name = models.CharField(_('Name'), max_length=100, null=False, blank=False) type = models.PositiveSmallIntegerField( _('Type'), null=True, blank=True, choices=ACCOUNTING_TYPES, ) read_only = models.BooleanField(_('Read-only'), default=False) def credit_increase(self): if self.type in [ACCOUNTING_ASSET, ACCOUNTING_EXPENSE]: return False else: return True class Meta: verbose_name = _('Account') verbose_name_plural = _('Accounts') ordering = ['number', 'name', 'type'] abstract = True swappable = "BMF_CONTRIB_ACCOUNT" class BMFMeta: observed_fields = [ 'name', ] serializer = AccountSerializer def __init__(self, *args, **kwargs): super(BaseAccount, self).__init__(*args, **kwargs) self.initial_parent = self.parent_id def save(self, update_parents=True, *args, **kwargs): super(BaseAccount, self).save(*args, **kwargs) if update_parents: if self.parent: self.parents = list( self.parent.parents.values_list( 'pk', flat=True)) + [self.parent_id] else: self.parents = [] if self.initial_parent != self.parent_id: # Update children ... for account in Account.objects.filter(parents=self): account.save() def clean(self): if self.parent: if not self.type: self.type = self.parent.type elif self.type != self.parent.type: raise ValidationError( _('The type does not match the model parents type')) elif not self.type: raise ValidationError(_('Root accounts must define a type')) def __str__(self): return '%s: %s' % (self.number, self.name)
class BaseAccount(BMFModelMPTT): """ Accounts ============== ======== ======== Account-Type Credit Debit ============== ======== ======== Asset Decrease Increase Liability Increase Decrease Income/Revenue Increase Decrease Expense Decrease Increase Equity/Capital Increase Decrease ============== ======== ======== """ parent = TreeForeignKey( 'self', null=True, blank=True, related_name='children', on_delete=models.CASCADE, ) balance_currency = CurrencyField(editable=False) balance = MoneyField(editable=False) number = models.CharField(_('Number'), max_length=30, null=True, blank=True, unique=True, db_index=True) name = models.CharField(_('Name'), max_length=100, null=False, blank=False) type = models.PositiveSmallIntegerField( _('Type'), null=True, blank=True, choices=ACCOUNTING_TYPES, ) read_only = models.BooleanField(_('Read-only'), default=False) def credit_increase(self): if self.type in [ACCOUNTING_ASSET, ACCOUNTING_EXPENSE]: return False else: return True class Meta: verbose_name = _('Account') verbose_name_plural = _('Accounts') ordering = ['number', 'name', 'type'] abstract = True swappable = "BMF_CONTRIB_ACCOUNT" class BMFMeta: observed_fields = [ 'name', ] class MPTTMeta: order_insertion_by = ['number', 'name', 'type'] def __init__(self, *args, **kwargs): super(BaseAccount, self).__init__(*args, **kwargs) self.initial_number = self.number @staticmethod def post_save(sender, instance, created, *args, **kwargs): if not created and instance.initial_number != instance.number: # TODO this get's the job done, but there might be a more efficient way to do this if instance.parent: instance._meta.model.objects.partial_rebuild(instance.tree_id) else: instance._meta.model.objects.rebuild() def clean(self): if self.parent: if not self.type: self.type = self.parent.type elif self.type != self.parent.type: raise ValidationError( _('The type does not match the model parents type')) elif not self.type: raise ValidationError(_('Root accounts must define a type')) def __str__(self): return '%s: %s' % (self.number, self.name)