Ejemplo n.º 1
0
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
Ejemplo n.º 2
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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)