Source code for stoqlib.domain.taxes

# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4

##
## Copyright (C) 2010 Async Open Source <http://www.async.com.br>
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
## Author(s): Stoq Team <stoq-devel@async.com.br>
##

# pylint: enable=E1101

from decimal import Decimal

from storm.info import get_cls_info
from storm.references import Reference
from zope.interface import implementer

from stoqlib.database.properties import (EnumCol, UnicodeCol, QuantityCol, DateTimeCol,
                                         PriceCol, IntCol, BoolCol, PercentCol,
                                         IdCol)
from stoqlib.domain.base import Domain
from stoqlib.domain.interfaces import IDescribable
from stoqlib.lib.dateutils import localtoday

# SIGLAS:
# BC - Base de Calculo
# ST - Situação tributária
# CST - Codigo ST
# MVA - Margem de valor adicionado


def check_tax_info_presence(kwargs, store):
    if 'ipi_info' not in kwargs:
        kwargs['ipi_info'] = InvoiceItemIpi(store=store)

    if 'icms_info' not in kwargs:
        kwargs['icms_info'] = InvoiceItemIcms(store=store)

    if 'pis_info' not in kwargs:
        kwargs['pis_info'] = InvoiceItemPis(store=store)

    if 'cofins_info' not in kwargs:
        kwargs['cofins_info'] = InvoiceItemCofins(store=store)


#
#   Base Tax Classes
#


class BaseTax(Domain):

    def set_item_tax(self, invoice_item, template=None):
        """ Set the tax of an invoice item.

        :param invoice_item: the item of in/out invoice
        """
        template = template or self.get_tax_template(invoice_item)
        if not template:
            return

        for column in get_cls_info(template.__class__).columns:
            if column.name in ['product_tax_template_id', 'te_id', 'id']:
                continue

            value = getattr(template, column.name)
            setattr(self, column.name, value)

        self.set_initial_values(invoice_item)

    @classmethod
    def get_tax_template(cls, invoice_item):  # pragma no cover
        """Use this method in InvoiceItemIpi or InvoiceItemIcms classes to get
        the respective tax template.

        :param invoice_item: the item of in/out invoice
        """
        raise NotImplementedError

    def set_initial_values(self, invoice_item):
        """Use this method to setup the initial values of the fields.
        """
        self.update_values(invoice_item)

    def update_values(self, invoice_item):  # pragma no cover
        pass


[docs]class BaseICMS(BaseTax): """NfeProductIcms stores the default values that will be used when creating NfeItemIcms objects """ # FIXME: this is only used by pylint __storm_table__ = 'invalid' orig = IntCol(default=None) cst = IntCol(default=None) mod_bc = IntCol(default=None) p_icms = PercentCol(default=None) mod_bc_st = IntCol(default=None) p_mva_st = PercentCol(default=None) p_red_bc_st = PercentCol(default=None) p_icms_st = PercentCol(default=None) p_red_bc = PercentCol(default=None) bc_include_ipi = BoolCol(default=True) bc_st_include_ipi = BoolCol(default=True) # Simples Nacional csosn = IntCol(default=None) p_cred_sn = PercentCol(default=None)
class BaseIPI(BaseTax): CALC_ALIQUOTA = u'aliquot' CALC_UNIDADE = u'unit' cl_enq = UnicodeCol(default=u'') cnpj_prod = UnicodeCol(default=u'') c_selo = UnicodeCol(default=u'') q_selo = IntCol(default=None) c_enq = UnicodeCol(default=u'') cst = IntCol(default=None) p_ipi = PercentCol(default=None) q_unid = QuantityCol(default=None) calculo = EnumCol(default=CALC_ALIQUOTA, allow_none=False)
[docs]class BasePIS(BaseTax): """Contains attributes to be used to calculate PIS tax in Brazil.""" CALC_PERCENTAGE = u'percentage' CALC_VALUE = u'value' cst = IntCol(default=None) #: Operation type (percentage or value) calculo = EnumCol(default=CALC_PERCENTAGE, allow_none=False) #: Aliquot in percentage p_pis = PercentCol(default=None)
[docs]class BaseCOFINS(BaseTax): """Contains attributes to be used to calculate PIS tax in Brazil.""" CALC_PERCENTAGE = u'percentage' CALC_VALUE = u'value' cst = IntCol(default=None) #: Operation type (percentage or value) calculo = EnumCol(default=CALC_PERCENTAGE, allow_none=False) #: Aliquot in percentage p_cofins = PercentCol(default=None)
# # Product Tax Classes # class ProductIcmsTemplate(BaseICMS): __storm_table__ = 'product_icms_template' product_tax_template_id = IdCol() product_tax_template = Reference(product_tax_template_id, 'ProductTaxTemplate.id') # Simples Nacional p_cred_sn_valid_until = DateTimeCol(default=None) def is_p_cred_sn_valid(self): """Returns if p_cred_sn has expired.""" if not self.p_cred_sn_valid_until: # If we don't have a valid_until, means p_cred_sn will never # expire. Therefore, p_cred_sn is valid. return True elif self.p_cred_sn_valid_until.date() < localtoday().date(): return False return True
[docs]class ProductIpiTemplate(BaseIPI): """Template of IPI tax""" __storm_table__ = 'product_ipi_template' product_tax_template_id = IdCol() product_tax_template = Reference(product_tax_template_id, 'ProductTaxTemplate.id')
@implementer(IDescribable)
[docs]class ProductPisTemplate(BasePIS): """Template of PIS tax""" __storm_table__ = 'product_pis_template' product_tax_template_id = IdCol() product_tax_template = Reference(product_tax_template_id, 'ProductTaxTemplate.id') def get_description(self): return self.product_tax_template.name
@implementer(IDescribable)
[docs]class ProductCofinsTemplate(BaseCOFINS): """Template of COFINS tax""" __storm_table__ = 'product_cofins_template' product_tax_template_id = IdCol() product_tax_template = Reference(product_tax_template_id, 'ProductTaxTemplate.id') def get_description(self): return self.product_tax_template.name
class ProductTaxTemplate(Domain): TYPE_ICMS = u'icms' TYPE_IPI = u'ipi' TYPE_PIS = u'pis' TYPE_COFINS = u'cofins' __storm_table__ = 'product_tax_template' types = {TYPE_ICMS: u"ICMS", TYPE_IPI: u"IPI", TYPE_PIS: u"PIS", TYPE_COFINS: u"COFINS"} type_map = {TYPE_ICMS: ProductIcmsTemplate, TYPE_IPI: ProductIpiTemplate, TYPE_PIS: ProductPisTemplate, TYPE_COFINS: ProductCofinsTemplate} name = UnicodeCol(default=u'') tax_type = EnumCol(default=TYPE_ICMS, allow_none=False) def get_tax_model(self): klass = self.type_map[self.tax_type] store = self.store return store.find(klass, product_tax_template=self).one() def get_tax_type_str(self): return self.types[self.tax_type] class InvoiceItemIcms(BaseICMS): __storm_table__ = 'invoice_item_icms' v_bc = PriceCol(default=None) v_icms = PriceCol(default=None) v_bc_st = PriceCol(default=None) v_icms_st = PriceCol(default=None) # Simples Nacional v_cred_icms_sn = PriceCol(default=None) v_bc_st_ret = PriceCol(default=None) v_icms_st_ret = PriceCol(default=None) def _calc_cred_icms_sn(self, invoice_item): if self.p_cred_sn >= 0: self.v_cred_icms_sn = invoice_item.get_total() * self.p_cred_sn / 100 def _calc_st(self, invoice_item): self.v_bc_st = invoice_item.price * invoice_item.quantity if self.bc_st_include_ipi and invoice_item.ipi_info: self.v_bc_st += invoice_item.ipi_info.v_ipi if self.p_red_bc_st is not None: self.v_bc_st -= self.v_bc_st * self.p_red_bc_st / 100 if self.p_mva_st is not None: self.v_bc_st += self.v_bc_st * self.p_mva_st / 100 if self.v_bc_st is not None and self.p_icms_st is not None: self.v_icms_st = self.v_bc_st * self.p_icms_st / 100 if self.v_icms is not None and self.v_icms_st is not None: self.v_icms_st -= self.v_icms def _calc_normal(self, invoice_item): self.v_bc = invoice_item.price * invoice_item.quantity if self.bc_include_ipi and invoice_item.ipi_info: self.v_bc += invoice_item.ipi_info.v_ipi if self.p_red_bc is not None: self.v_bc -= self.v_bc * self.p_red_bc / 100 if self.p_icms is not None and self.v_bc is not None: self.v_icms = self.v_bc * self.p_icms / 100 def _update_normal(self, invoice_item): """Atualiza os dados de acordo com os calculos do Regime Tributário Normal (Não simples) """ if self.cst == 0: self.p_red_bc = Decimal(0) self._calc_normal(invoice_item) elif self.cst == 10: self.p_red_bc = Decimal(0) self._calc_normal(invoice_item) self._calc_st(invoice_item) elif self.cst == 20: self._calc_normal(invoice_item) elif self.cst == 30: self.v_icms = 0 self.v_bc = 0 self._calc_st(invoice_item) elif self.cst in (40, 41, 50): self.v_icms = 0 self.v_bc = 0 elif self.cst == 51: self._calc_normal(invoice_item) elif self.cst == 60: self.v_bc_st_ret = 0 self.v_icms_st_ret = 0 elif self.cst in (70, 90): self._calc_normal(invoice_item) self._calc_st(invoice_item) def _update_simples(self, invoice_item): if self.csosn in [300, 400, 500]: self.v_bc_st_ret = 0 self.v_icms_st_ret = 0 if self.csosn in [101, 201]: if self.p_cred_sn is None: self.p_cred_sn = Decimal(0) self._calc_cred_icms_sn(invoice_item) if self.csosn in [201, 202, 203]: self._calc_st(invoice_item) if self.csosn == 900: if self.p_cred_sn is None: self.p_cred_sn = Decimal(0) self._calc_cred_icms_sn(invoice_item) self._calc_normal(invoice_item) self._calc_st(invoice_item) def update_values(self, invoice_item): branch = invoice_item.parent.branch # Simples nacional if branch.crt in [1, 2]: self._update_simples(invoice_item) else: self._update_normal(invoice_item) @classmethod def get_tax_template(cls, invoice_item): return invoice_item.sellable.product.icms_template
[docs]class InvoiceItemIpi(BaseIPI): """Invoice of IPI tax.""" __storm_table__ = 'invoice_item_ipi' v_ipi = PriceCol(default=0) v_bc = PriceCol(default=None) v_unid = PriceCol(default=None) # # Public API # def set_initial_values(self, invoice_item): self.q_unid = invoice_item.quantity self.v_unid = invoice_item.price self.update_values(invoice_item) def update_values(self, invoice_item): # IPI is only calculated if cst is one of the following if not self.cst in [0, 49, 50, 99]: return if self.calculo == self.CALC_ALIQUOTA: self.v_bc = invoice_item.price * invoice_item.quantity if self.p_ipi is not None: self.v_ipi = self.v_bc * self.p_ipi / 100 elif self.calculo == self.CALC_UNIDADE: if self.q_unid is not None and self.v_unid is not None: self.v_ipi = self.q_unid * self.v_unid @classmethod def get_tax_template(cls, invoice_item): return invoice_item.sellable.product.ipi_template
[docs]class InvoiceItemPis(BasePIS): """Invoice of PIS tax.""" __storm_table__ = 'invoice_item_pis' #: Value of PIS tax. v_pis = PriceCol(default=0) #: Value of the PIS tax calculation basis. v_bc = PriceCol(default=None) #: Quantity sold q_bc_prod = QuantityCol(default=None) # # Public API # def set_initial_values(self, invoice_item): self.update_values(invoice_item) def update_values(self, invoice_item): self.q_bc_prod = invoice_item.quantity # When the CST is contained in the list the calculation is not performed # because the taxpayer is exempt. if self.cst in [4, 5, 6, 7, 8, 9]: return # When the branch is Simples Nacional (CRT 1 or 2) and the pis is 99, # the values should be zero if self.cst == 99 and invoice_item.parent.branch.crt in [1, 2]: self.v_bc = 0 self.p_pis = 0 self.v_pis = 0 return cost = self._get_item_cost(invoice_item) self.v_bc = invoice_item.quantity * (invoice_item.price - cost) if self.p_pis is not None: self.v_pis = self.v_bc * self.p_pis / 100 @classmethod def get_tax_template(cls, invoice_item): return invoice_item.sellable.product.pis_template # # Private API # def _get_item_cost(self, invoice_item): from stoqlib.domain.sale import SaleItem if isinstance(invoice_item, SaleItem): return invoice_item.average_cost return 0
[docs]class InvoiceItemCofins(BaseCOFINS): """Invoice of COFINS tax.""" __storm_table__ = 'invoice_item_cofins' #: Value of COFINS tax v_cofins = PriceCol(default=0) #: Value of the COFINS tax calculation basis. v_bc = PriceCol(default=None) #: Quantity sold q_bc_prod = QuantityCol(default=None) # # Public API # def set_initial_values(self, invoice_item): self.update_values(invoice_item) def update_values(self, invoice_item): self.q_bc_prod = invoice_item.quantity # When the CST is contained in the list the calculation is not performed # because the taxpayer is exempt. if self.cst in [4, 5, 6, 7, 8, 9]: return # When the branch is Simples Nacional (CRT 1 or 2) and the cofins is 99, # the values should be zero if self.cst == 99 and invoice_item.parent.branch.crt in [1, 2]: self.v_bc = 0 self.p_cofins = 0 self.v_cofins = 0 return cost = self._get_item_cost(invoice_item) self.v_bc = invoice_item.quantity * (invoice_item.price - cost) if self.p_cofins is not None: self.v_cofins = self.v_bc * self.p_cofins / 100 @classmethod def get_tax_template(cls, invoice_item): return invoice_item.sellable.product.cofins_template # # Private API # def _get_item_cost(self, invoice_item): from stoqlib.domain.sale import SaleItem if isinstance(invoice_item, SaleItem): return invoice_item.average_cost return 0