Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0][Crédito de impostos]: Crédito de impostos pt1 - adiciona stock price e impostos creditáveis por Tax/CST #3158

Open
wants to merge 7 commits into
base: 14.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 102 additions & 102 deletions l10n_br_fiscal/data/l10n_br_fiscal.cst.csv

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions l10n_br_fiscal/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from . import comment
from . import ibpt
from . import cfop
from . import stock_price_mixin
from . import cst
from . import cnae
from . import nbs
Expand Down
7 changes: 7 additions & 0 deletions l10n_br_fiscal/models/cst.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ class CST(models.Model):
store=True,
)

default_creditable_tax = fields.Boolean(
string="Creditable Tax Default?",
default=True,
help="Defines default value for creditable tax boolean for tax_ids that have"
"self as CST In",
)

_sql_constraints = [
(
"l10n_br_fiscal_cst_code_tax_group_id_uniq",
Expand Down
156 changes: 156 additions & 0 deletions l10n_br_fiscal/models/stock_price_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Copyright (C) 2024 Diego Paradeda - KMEE <diego.paradeda@kmee.com.br>
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from odoo import api, fields, models
from odoo.tools.float_utils import float_round


class StockPriceMixin(models.AbstractModel):
_name = "l10n_br_fiscal.stock.price.mixin"
_description = "Stock Price Mixin"

freight_value_to_stock = fields.Boolean(
string="freight_value_to_stock",
default=True,
)

insurance_value_to_stock = fields.Boolean(
string="insurance_value_to_stock",
default=True,
)

other_value_to_stock = fields.Boolean(
string="other_value_to_stock",
default=True,
)

issqn_tax_is_creditable = fields.Boolean(
string="ISSQN Creditável",
default=lambda self: self.valuation_via_stock_price,
)

irpj_tax_is_creditable = fields.Boolean(
string="IRPJ Creditável",
default=lambda self: self.valuation_via_stock_price,
)

icmsst_tax_is_creditable = fields.Boolean(
string="ICMS ST Creditável",
default=lambda self: self.valuation_via_stock_price,
)

icms_tax_is_creditable = fields.Boolean(
string="ICMS Creditável",
default=lambda self: self.valuation_via_stock_price,
)
ipi_tax_is_creditable = fields.Boolean(
string="IPI Creditável",
default=lambda self: self.valuation_via_stock_price,
)
cofins_tax_is_creditable = fields.Boolean(
string="COFINS Creditável",
default=lambda self: self.valuation_via_stock_price,
)
pis_tax_is_creditable = fields.Boolean(
string="PIS Creditável",
default=lambda self: self.valuation_via_stock_price,
)

@api.onchange("icms_cst_id")
def _onchange_icms_cst_id(self):
if self.valuation_via_stock_price:
self.icms_tax_is_creditable = self.icms_cst_id.default_creditable_tax

@api.onchange("ipi_cst_id")
def _onchange_ipi_cst_id(self):
if self.valuation_via_stock_price:
self.ipi_tax_is_creditable = self.ipi_cst_id.default_creditable_tax

@api.onchange("cofins_cst_id")
def _onchange_cofins_cst_id(self):
if self.valuation_via_stock_price:
self.cofins_tax_is_creditable = self.cofins_cst_id.default_creditable_tax

@api.onchange("pis_cst_id")
def _onchange_pis_cst_id(self):
if self.valuation_via_stock_price:
self.pis_tax_is_creditable = self.pis_cst_id.default_creditable_tax

def _default_valuation_stock_price(self):
"""
Método para chavear o custo médio dos produtos entre:
- líquido de impostos
- com impostos (padrão do Odoo).
"""
company_default = self.company_id.stock_valuation_via_stock_price
return company_default

valuation_via_stock_price = fields.Boolean(
string="Valuation Via Stock Price",
default=_default_valuation_stock_price,
help="Determina se o valor utilizado no custeamento automático será padrão do"
" Odoo ou com base no campo stock_price_br.\n\n"
" * Usar True para valor de estoque líquido (sem imposto)",
)

stock_price_br_currency_id = fields.Many2one(
comodel_name="res.currency",
string="Currency",
default=lambda self: self.env.ref("base.BRL"),
)

stock_price_br = fields.Monetary(
string="Stock Price",
compute="_compute_stock_price_br",
currency_field="stock_price_br_currency_id",
)

@api.depends(
"amount_total",
"fiscal_tax_ids",
"valuation_via_stock_price",
"issqn_tax_is_creditable",
"irpj_tax_is_creditable",
"icmsst_tax_is_creditable",
"icms_tax_is_creditable",
"ipi_tax_is_creditable",
"cofins_tax_is_creditable",
"pis_tax_is_creditable",
"freight_value_to_stock",
"insurance_value_to_stock",
"other_value_to_stock",
)
def _compute_stock_price_br(self):
"""Subtract creditable taxes from stock price."""
for record in self:
record.stock_price_br = 0

if not hasattr(record, "product_uom_qty"):
continue

Check warning on line 129 in l10n_br_fiscal/models/stock_price_mixin.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_fiscal/models/stock_price_mixin.py#L129

Added line #L129 was not covered by tests

if record.fiscal_operation_line_id and record.product_uom_qty:
price = record.amount_total

if not record.freight_value_to_stock:
price -= record.freight_value

Check warning on line 135 in l10n_br_fiscal/models/stock_price_mixin.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_fiscal/models/stock_price_mixin.py#L135

Added line #L135 was not covered by tests
if not record.insurance_value_to_stock:
price -= record.insurance_value

Check warning on line 137 in l10n_br_fiscal/models/stock_price_mixin.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_fiscal/models/stock_price_mixin.py#L137

Added line #L137 was not covered by tests
if not record.other_value_to_stock:
price -= record.other_value

Check warning on line 139 in l10n_br_fiscal/models/stock_price_mixin.py

View check run for this annotation

Codecov / codecov/patch

l10n_br_fiscal/models/stock_price_mixin.py#L139

Added line #L139 was not covered by tests

for tax in record.fiscal_tax_ids:
try:
creditable = getattr(
record, "%s_tax_is_creditable" % (tax.tax_domain,)
)
if creditable:
price -= getattr(record, "%s_value" % (tax.tax_domain))
except AttributeError:
pass

price_precision = self.env["decimal.precision"].precision_get(
"Product Price"
)
record.stock_price_br = float_round(
(price / record.product_uom_qty), precision_digits=price_precision
)
2 changes: 1 addition & 1 deletion l10n_br_fiscal/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"l10n_br_fiscal_cfop_manager","Fiscal CFOP for Manager","model_l10n_br_fiscal_cfop","l10n_br_fiscal.group_manager",1,0,0,0
"l10n_br_fiscal_cfop_maintenance","Fiscal CFOP for Maintenance","model_l10n_br_fiscal_cfop","l10n_br_fiscal.group_data_maintenance",1,1,1,1
"l10n_br_fiscal_cst_user","Fiscal CST for User","model_l10n_br_fiscal_cst","l10n_br_fiscal.group_user",1,0,0,0
"l10n_br_fiscal_cst_manager","Fiscal CST for Manager","model_l10n_br_fiscal_cst","l10n_br_fiscal.group_manager",1,0,0,0
"l10n_br_fiscal_cst_manager","Fiscal CST for Manager","model_l10n_br_fiscal_cst","l10n_br_fiscal.group_manager",1,1,0,0
"l10n_br_fiscal_cst_maintenance","Fiscal CST for Maintenance","model_l10n_br_fiscal_cst","l10n_br_fiscal.group_data_maintenance",1,1,1,1
"l10n_br_fiscal_tax_group_user","Fiscal Tax Group for User","model_l10n_br_fiscal_tax_group","l10n_br_fiscal.group_user",1,0,0,0
"l10n_br_fiscal_tax_group_manager","Fiscal Tax Group for Manager","model_l10n_br_fiscal_tax_group","l10n_br_fiscal.group_manager",1,1,1,1
Expand Down
1 change: 1 addition & 0 deletions l10n_br_fiscal/views/cst_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<field name="name" />
<field name="cst_type" />
<field name="tax_group_id" />
<field name="default_creditable_tax" />
</group>
</sheet>
</form>
Expand Down
13 changes: 13 additions & 0 deletions l10n_br_purchase_stock/models/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ class PurchaseOrder(models.Model):
compute="_compute_get_button_create_invoice_invisible"
)

total_cost_of_goods_purchased = fields.Monetary(
string="Cost of Goods Purchased",
compute="_compute_cogp_amount",
)

@api.depends("order_line", "order_line.stock_price_br")
def _compute_cogp_amount(self):
for record in self:
cogp = 0
for line in record.order_line:
cogp += line.stock_price_br
record.total_cost_of_goods_purchased = cogp

@api.depends("state", "invoice_status")
def _compute_get_button_create_invoice_invisible(self):
for record in self:
Expand Down
6 changes: 5 additions & 1 deletion l10n_br_purchase_stock/models/purchase_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@


class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
_name = "purchase.order.line"
_inherit = [
_name,
"l10n_br_fiscal.stock.price.mixin",
]

def _prepare_stock_moves(self, picking):
"""Prepare the stock moves data for one order line.
Expand Down
127 changes: 127 additions & 0 deletions l10n_br_purchase_stock/tests/test_l10n_br_purchase_stock_lp.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,138 @@
# @ 2019 Akretion - www.akretion.com.br -
# Magno Costa <magno.costa@akretion.com.br>
# Renato Lima <renato.lima@akretion.com.br>
# @ 2024 KMEE - www.kmee.com.br -
# Diego Paradeda <diego.paradeda@kmee.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo.tools import float_compare
from odoo.tools.float_utils import float_round

from .test_l10n_br_purchase_stock import L10nBrPurchaseStockBase


class L10nBrPurchaseStockBase(L10nBrPurchaseStockBase):
def setUp(self):
super().setUp()
self.lp_purchase_po = self.env.ref(
"l10n_br_purchase_stock.lucro_presumido_po_only_products_1"
).copy()
self.picking_ids = False

self.stock_acc_id = self.env["account.account"].search(
[("code", "=", "1.1.3.1.02")], limit=1
)
self.stock_input_acc_id = self.env["account.account"].search(
[("code", "=", "1.1.9.0.01")], limit=1
)
self.stock_output_acc_id = self.env["account.account"].search(
[("code", "=", "1.1.9.0.02")], limit=1
)

def test_purchase_order_lp_stock_price_(self):
"""Test stock price compute in purchase order line."""

purchase = self.lp_purchase_po
self._change_user_company(self.company_lucro_presumido)
self._run_purchase_order_onchanges(purchase)

line1 = purchase.order_line[0]
line2 = purchase.order_line[1]

# line 1 -> 90.03
line1.icms_tax_is_creditable = True
line1.ipi_tax_is_creditable = True
line1.cofins_tax_is_creditable = True
line1.pis_tax_is_creditable = True
line1._compute_stock_price_br()

# price_precision = self.env["decimal.precision"].precision_get("Product Price")
price_precision = self.env["decimal.precision"].precision_get("Product Price")
expected_stock_price = float_round(
(
(
line1.amount_total
- line1.ipi_value
- line1.icms_value
- line1.pis_value
- line1.cofins_value
)
/ line1.product_qty
),
precision_digits=price_precision,
)
computed_stock_price = line1.stock_price_br

compare = float_compare(
expected_stock_price, computed_stock_price, precision_digits=price_precision
)
self.assertEqual(
compare,
0,
f"Stock value mispriced - comercialização\nexpected: \
{expected_stock_price}\ncomputed: {computed_stock_price}",
)

# line 2 -> 120.75 (price with taxes)
line2.icms_tax_is_creditable = False
line2.ipi_tax_is_creditable = False
line2.cofins_tax_is_creditable = False
line2.pis_tax_is_creditable = False
line2._compute_stock_price_br()
expected_stock_price = float_round(
line2.amount_total / line2.product_qty, precision_digits=price_precision
)
computed_stock_price = line2.stock_price_br

compare = float_compare(
expected_stock_price, computed_stock_price, precision_digits=price_precision
)
self.assertEqual(
compare,
0,
f"Stock value mispriced - industrialização\nexpected: \
{expected_stock_price}\ncomputed: {computed_stock_price}",
)

purchase.button_confirm()

# Test if product cost matches stock price. Requires AVCO and auto stock valuation.
self.picking_id = purchase.picking_ids

# product categories
product_id1 = self.po_products.order_line[0].product_id
categ_id1 = product_id1.categ_id

categ_id1.write(
{
"property_stock_valuation_account_id": self.stock_acc_id.id,
"property_stock_account_input_categ_id": self.stock_input_acc_id.id,
"property_stock_account_output_categ_id": self.stock_output_acc_id.id,
"property_valuation": "real_time",
"property_cost_method": "average",
}
)

self.assertEqual(
categ_id1.property_stock_valuation_account_id.code,
"1.1.3.1.02",
"Invalid account in product category!",
)
self.assertEqual(
categ_id1.property_stock_account_input_categ_id.code,
"1.1.9.0.01",
"Invalid account in product category!",
)
self.assertEqual(
categ_id1.property_stock_account_output_categ_id.code,
"1.1.9.0.02",
"Invalid account in product category!",
)

# Validate picking
self.picking_id.action_confirm()
self.picking_id.action_assign()
# Force picking qties and validate
for move in self.picking_id.move_ids_without_package:
move.quantity_done = move.product_uom_qty
self.picking_id.button_validate()
Loading
Loading