mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-20 03:47:11 +00:00
refactor: convert tax cluster to TaxService class in taxes.py
Replaces the shim+free-function pattern with a TaxService class so callers like TaxService(self).set_taxes() make the source location explicit. Class lives in taxes.py above the existing free functions. Deletes the intermediate tax_service.py. Updates AccountsController, sales_invoice, pos_invoice, subscription, and both GL composers to call TaxService directly.
This commit is contained in:
@@ -745,7 +745,9 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
# fetch charges
|
# fetch charges
|
||||||
if self.taxes_and_charges and not len(self.get("taxes")):
|
if self.taxes_and_charges and not len(self.get("taxes")):
|
||||||
self.set_taxes()
|
from erpnext.accounts.services.taxes import TaxService
|
||||||
|
|
||||||
|
TaxService(self).set_taxes()
|
||||||
|
|
||||||
if not self.account_for_change_amount:
|
if not self.account_for_change_amount:
|
||||||
self.account_for_change_amount = frappe.get_cached_value(
|
self.account_for_change_amount = frappe.get_cached_value(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from frappe.utils import cint, flt, get_link_to_form
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||||
from erpnext.accounts.services.base_gl_composer import BaseGLComposer
|
from erpnext.accounts.services.base_gl_composer import BaseGLComposer
|
||||||
|
from erpnext.accounts.services.taxes import TaxService
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
|
|
||||||
|
|
||||||
@@ -102,6 +103,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
doc = self.doc
|
doc = self.doc
|
||||||
|
tax_service = TaxService(doc)
|
||||||
stock_items = doc.get_stock_items()
|
stock_items = doc.get_stock_items()
|
||||||
if doc.update_stock and doc.auto_accounting_for_stock:
|
if doc.update_stock and doc.auto_accounting_for_stock:
|
||||||
inventory_account_map = doc.get_inventory_account_map()
|
inventory_account_map = doc.get_inventory_account_map()
|
||||||
@@ -292,7 +294,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
|||||||
else item.deferred_expense_account
|
else item.deferred_expense_account
|
||||||
)
|
)
|
||||||
account_currency = get_account_currency(expense_account)
|
account_currency = get_account_currency(expense_account)
|
||||||
amount, base_amount = doc.get_amount_and_base_amount(item, None)
|
amount, base_amount = tax_service.get_amount_and_base_amount(item, None)
|
||||||
|
|
||||||
if provisional_accounting_for_non_stock_items:
|
if provisional_accounting_for_non_stock_items:
|
||||||
self.make_provisional_gl_entry(gl_entries, item)
|
self.make_provisional_gl_entry(gl_entries, item)
|
||||||
@@ -552,10 +554,11 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
|||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
doc = self.doc
|
doc = self.doc
|
||||||
|
tax_service = TaxService(doc)
|
||||||
valuation_tax = {}
|
valuation_tax = {}
|
||||||
|
|
||||||
for tax in doc.get("taxes"):
|
for tax in doc.get("taxes"):
|
||||||
amount, base_amount = doc.get_tax_amounts(tax, None)
|
amount, base_amount = tax_service.get_tax_amounts(tax, None)
|
||||||
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
|
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
|
||||||
account_currency = get_account_currency(tax.account_head)
|
account_currency = get_account_currency(tax.account_head)
|
||||||
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
|
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
|
||||||
|
|||||||
@@ -988,7 +988,9 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
# fetch charges
|
# fetch charges
|
||||||
if self.taxes_and_charges and not len(self.get("taxes")):
|
if self.taxes_and_charges and not len(self.get("taxes")):
|
||||||
self.set_taxes()
|
from erpnext.accounts.services.taxes import TaxService
|
||||||
|
|
||||||
|
TaxService(self).set_taxes()
|
||||||
|
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from frappe.utils import cint, cstr, flt, get_link_to_form
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||||
from erpnext.accounts.services.base_gl_composer import BaseGLComposer
|
from erpnext.accounts.services.base_gl_composer import BaseGLComposer
|
||||||
|
from erpnext.accounts.services.taxes import TaxService
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
get_gl_entries_on_asset_disposal,
|
get_gl_entries_on_asset_disposal,
|
||||||
@@ -16,19 +17,14 @@ from erpnext.assets.doctype.asset.depreciation import (
|
|||||||
|
|
||||||
|
|
||||||
class SalesInvoiceGLComposer(BaseGLComposer):
|
class SalesInvoiceGLComposer(BaseGLComposer):
|
||||||
"""Assembles the GL entries for a Sales Invoice.
|
"""Assembles the GL entries for a Sales Invoice."""
|
||||||
|
|
||||||
The voucher-specific row builders live here and operate on ``self.doc``.
|
|
||||||
Shared helpers (get_gl_dict, make_discount_gl_entries, make_precision_loss_gl_entry,
|
|
||||||
set_transaction_currency_and_rate_in_gl_map, get_tax_amounts, get_amount_and_base_amount)
|
|
||||||
remain on the document for now and are invoked via ``self.doc``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def compose(self, inventory_account_map=None):
|
def compose(self, inventory_account_map=None):
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_regional_gl_entries
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_regional_gl_entries
|
||||||
from erpnext.accounts.general_ledger import merge_similar_entries
|
from erpnext.accounts.general_ledger import merge_similar_entries
|
||||||
|
|
||||||
doc = self.doc
|
doc = self.doc
|
||||||
|
tax_service = TaxService(doc)
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
self.make_customer_gl_entry(gl_entries)
|
self.make_customer_gl_entry(gl_entries)
|
||||||
@@ -44,7 +40,7 @@ class SalesInvoiceGLComposer(BaseGLComposer):
|
|||||||
self.stock_delivered_but_not_billed_gl_entries(gl_entries)
|
self.stock_delivered_but_not_billed_gl_entries(gl_entries)
|
||||||
|
|
||||||
doc.make_precision_loss_gl_entry(gl_entries)
|
doc.make_precision_loss_gl_entry(gl_entries)
|
||||||
doc.make_discount_gl_entries(gl_entries)
|
tax_service.make_discount_gl_entries(gl_entries)
|
||||||
|
|
||||||
gl_entries = make_regional_gl_entries(gl_entries, doc)
|
gl_entries = make_regional_gl_entries(gl_entries, doc)
|
||||||
|
|
||||||
@@ -181,12 +177,13 @@ class SalesInvoiceGLComposer(BaseGLComposer):
|
|||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
doc = self.doc
|
doc = self.doc
|
||||||
|
tax_service = TaxService(doc)
|
||||||
enable_discount_accounting = cint(
|
enable_discount_accounting = cint(
|
||||||
frappe.get_single_value("Selling Settings", "enable_discount_accounting")
|
frappe.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
)
|
)
|
||||||
|
|
||||||
for tax in doc.get("taxes"):
|
for tax in doc.get("taxes"):
|
||||||
amount, base_amount = doc.get_tax_amounts(tax, enable_discount_accounting)
|
amount, base_amount = tax_service.get_tax_amounts(tax, enable_discount_accounting)
|
||||||
|
|
||||||
if flt(tax.base_tax_amount_after_discount_amount):
|
if flt(tax.base_tax_amount_after_discount_amount):
|
||||||
account_currency = get_account_currency(tax.account_head)
|
account_currency = get_account_currency(tax.account_head)
|
||||||
@@ -234,6 +231,7 @@ class SalesInvoiceGLComposer(BaseGLComposer):
|
|||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
|
||||||
|
|
||||||
doc = self.doc
|
doc = self.doc
|
||||||
|
tax_service = TaxService(doc)
|
||||||
# income account gl entries
|
# income account gl entries
|
||||||
enable_discount_accounting = cint(
|
enable_discount_accounting = cint(
|
||||||
frappe.get_single_value("Selling Settings", "enable_discount_accounting")
|
frappe.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
@@ -258,7 +256,9 @@ class SalesInvoiceGLComposer(BaseGLComposer):
|
|||||||
else item.deferred_revenue_account
|
else item.deferred_revenue_account
|
||||||
)
|
)
|
||||||
|
|
||||||
amount, base_amount = doc.get_amount_and_base_amount(item, enable_discount_accounting)
|
amount, base_amount = tax_service.get_amount_and_base_amount(
|
||||||
|
item, enable_discount_accounting
|
||||||
|
)
|
||||||
|
|
||||||
account_currency = get_account_currency(income_account)
|
account_currency = get_account_currency(income_account)
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
|
|||||||
@@ -446,8 +446,10 @@ class Subscription(Document):
|
|||||||
tax_template = self.purchase_tax_template
|
tax_template = self.purchase_tax_template
|
||||||
|
|
||||||
if tax_template:
|
if tax_template:
|
||||||
|
from erpnext.accounts.services.taxes import TaxService
|
||||||
|
|
||||||
invoice.taxes_and_charges = tax_template
|
invoice.taxes_and_charges = tax_template
|
||||||
invoice.set_taxes()
|
TaxService(invoice).set_taxes()
|
||||||
|
|
||||||
# Due date
|
# Due date
|
||||||
if self.days_until_due:
|
if self.days_until_due:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
"""Tax template and validation helpers shared across buying and selling controllers."""
|
"""Tax helpers: TaxService class for doc-mutating operations, free functions for stateless utilities."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -20,6 +20,239 @@ from erpnext.stock.get_item_details import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TaxService:
|
||||||
|
def __init__(self, doc):
|
||||||
|
self.doc = doc
|
||||||
|
|
||||||
|
def set_taxes(self) -> None:
|
||||||
|
doc = self.doc
|
||||||
|
if not doc.meta.get_field("taxes"):
|
||||||
|
return
|
||||||
|
|
||||||
|
tax_master_doctype = doc.meta.get_field("taxes_and_charges").options
|
||||||
|
|
||||||
|
if (doc.is_new() or self.is_pos_profile_changed()) and not doc.get("taxes"):
|
||||||
|
if doc.company and not doc.get("taxes_and_charges"):
|
||||||
|
doc.taxes_and_charges = frappe.db.get_value(
|
||||||
|
tax_master_doctype, {"is_default": 1, "company": doc.company}
|
||||||
|
)
|
||||||
|
self.append_taxes_from_master(tax_master_doctype)
|
||||||
|
|
||||||
|
def is_pos_profile_changed(self) -> bool:
|
||||||
|
doc = self.doc
|
||||||
|
if (
|
||||||
|
doc.doctype == "Sales Invoice"
|
||||||
|
and doc.is_pos
|
||||||
|
and doc.pos_profile != frappe.db.get_value("Sales Invoice", doc.name, "pos_profile")
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_taxes_and_charges(self) -> None:
|
||||||
|
doc = self.doc
|
||||||
|
if doc.doctype == "Material Request":
|
||||||
|
return
|
||||||
|
|
||||||
|
if doc.get("taxes") or doc.get("is_pos"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if frappe.get_single_value(
|
||||||
|
"Accounts Settings", "add_taxes_from_taxes_and_charges_template"
|
||||||
|
) and hasattr(doc, "taxes_and_charges"):
|
||||||
|
if tax_master_doctype := doc.meta.get_field("taxes_and_charges").options:
|
||||||
|
self.append_taxes_from_master(tax_master_doctype)
|
||||||
|
|
||||||
|
if frappe.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
|
||||||
|
self.append_taxes_from_item_tax_template()
|
||||||
|
|
||||||
|
def append_taxes_from_master(self, tax_master_doctype=None) -> None:
|
||||||
|
doc = self.doc
|
||||||
|
if doc.get("taxes_and_charges"):
|
||||||
|
if not tax_master_doctype:
|
||||||
|
tax_master_doctype = doc.meta.get_field("taxes_and_charges").options
|
||||||
|
doc.extend("taxes", get_taxes_and_charges(tax_master_doctype, doc.get("taxes_and_charges")))
|
||||||
|
|
||||||
|
def append_taxes_from_item_tax_template(self) -> None:
|
||||||
|
doc = self.doc
|
||||||
|
if not frappe.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
|
||||||
|
return
|
||||||
|
|
||||||
|
for row in doc.items:
|
||||||
|
item_tax_rate = row.get("item_tax_rate")
|
||||||
|
if not item_tax_rate:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(item_tax_rate, str):
|
||||||
|
item_tax_rate = parse_json(item_tax_rate)
|
||||||
|
|
||||||
|
for account_head, _rate in item_tax_rate.items():
|
||||||
|
if not self.get_tax_row(account_head):
|
||||||
|
doc.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": account_head,
|
||||||
|
"rate": 0,
|
||||||
|
"description": account_head,
|
||||||
|
"set_by_item_tax_template": 1,
|
||||||
|
"category": "Total",
|
||||||
|
"add_deduct_tax": "Add",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_tax_row(self, account_head):
|
||||||
|
for row in self.doc.taxes:
|
||||||
|
if row.account_head == account_head:
|
||||||
|
return row
|
||||||
|
|
||||||
|
def set_other_charges(self) -> None:
|
||||||
|
self.doc.set("taxes", [])
|
||||||
|
self.set_taxes()
|
||||||
|
|
||||||
|
def validate_enabled_taxes_and_charges(self) -> None:
|
||||||
|
doc = self.doc
|
||||||
|
taxes_and_charges_doctype = doc.meta.get_options("taxes_and_charges")
|
||||||
|
if doc.taxes_and_charges and frappe.get_cached_value(
|
||||||
|
taxes_and_charges_doctype, doc.taxes_and_charges, "disabled"
|
||||||
|
):
|
||||||
|
frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, doc.taxes_and_charges))
|
||||||
|
|
||||||
|
def validate_tax_account_company(self) -> None:
|
||||||
|
doc = self.doc
|
||||||
|
for d in doc.get("taxes"):
|
||||||
|
if d.account_head:
|
||||||
|
tax_account_company = frappe.get_cached_value("Account", d.account_head, "company")
|
||||||
|
if tax_account_company != doc.company:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Account {1} does not belong to company {2}").format(
|
||||||
|
d.idx, d.account_head, doc.company
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_tax_map(self) -> dict:
|
||||||
|
tax_map = {}
|
||||||
|
for tax in self.doc.get("taxes"):
|
||||||
|
tax_map.setdefault(tax.account_head, 0.0)
|
||||||
|
tax_map[tax.account_head] += tax.tax_amount
|
||||||
|
return tax_map
|
||||||
|
|
||||||
|
def get_amount_and_base_amount(self, item, enable_discount_accounting):
|
||||||
|
doc = self.doc
|
||||||
|
amount = item.net_amount
|
||||||
|
base_amount = item.base_net_amount
|
||||||
|
|
||||||
|
if (
|
||||||
|
enable_discount_accounting
|
||||||
|
and doc.get("discount_amount")
|
||||||
|
and doc.get("additional_discount_account")
|
||||||
|
):
|
||||||
|
if not hasattr(doc, "__has_distributed_discount_set"):
|
||||||
|
doc.__has_distributed_discount_set = any(
|
||||||
|
i.distributed_discount_amount for i in doc.get("items")
|
||||||
|
)
|
||||||
|
|
||||||
|
if not doc.__has_distributed_discount_set:
|
||||||
|
return item.amount, item.base_amount
|
||||||
|
|
||||||
|
amount += item.distributed_discount_amount
|
||||||
|
base_amount += flt(
|
||||||
|
item.distributed_discount_amount * doc.get("conversion_rate"),
|
||||||
|
item.precision("distributed_discount_amount"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return amount, base_amount
|
||||||
|
|
||||||
|
def get_tax_amounts(self, tax, enable_discount_accounting):
|
||||||
|
doc = self.doc
|
||||||
|
amount = tax.tax_amount_after_discount_amount
|
||||||
|
base_amount = tax.base_tax_amount_after_discount_amount
|
||||||
|
|
||||||
|
if (
|
||||||
|
enable_discount_accounting
|
||||||
|
and doc.get("discount_amount")
|
||||||
|
and doc.get("additional_discount_account")
|
||||||
|
and doc.get("apply_discount_on") == "Grand Total"
|
||||||
|
):
|
||||||
|
amount = tax.tax_amount
|
||||||
|
base_amount = tax.base_tax_amount
|
||||||
|
|
||||||
|
return amount, base_amount
|
||||||
|
|
||||||
|
def make_discount_gl_entries(self, gl_entries: list) -> None:
|
||||||
|
doc = self.doc
|
||||||
|
enable_discount_accounting = cint(
|
||||||
|
frappe.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||||
|
)
|
||||||
|
|
||||||
|
if enable_discount_accounting:
|
||||||
|
for item in doc.get("items"):
|
||||||
|
if item.get("discount_amount") and item.get("discount_account"):
|
||||||
|
discount_amount = item.discount_amount * item.qty
|
||||||
|
income_account = (
|
||||||
|
item.income_account
|
||||||
|
if (not item.enable_deferred_revenue or doc.is_return)
|
||||||
|
else item.deferred_revenue_account
|
||||||
|
)
|
||||||
|
|
||||||
|
account_currency = get_account_currency(item.discount_account)
|
||||||
|
gl_entries.append(
|
||||||
|
doc.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": item.discount_account,
|
||||||
|
"against": doc.customer,
|
||||||
|
"debit": flt(
|
||||||
|
discount_amount * doc.get("conversion_rate"),
|
||||||
|
item.precision("discount_amount"),
|
||||||
|
),
|
||||||
|
"debit_in_transaction_currency": flt(
|
||||||
|
discount_amount, item.precision("discount_amount")
|
||||||
|
),
|
||||||
|
"cost_center": item.cost_center,
|
||||||
|
"project": item.project,
|
||||||
|
},
|
||||||
|
account_currency,
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
account_currency = get_account_currency(income_account)
|
||||||
|
gl_entries.append(
|
||||||
|
doc.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": income_account,
|
||||||
|
"against": doc.customer,
|
||||||
|
"credit": flt(
|
||||||
|
discount_amount * doc.get("conversion_rate"),
|
||||||
|
item.precision("discount_amount"),
|
||||||
|
),
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
discount_amount, item.precision("discount_amount")
|
||||||
|
),
|
||||||
|
"cost_center": item.cost_center,
|
||||||
|
"project": item.project or doc.project,
|
||||||
|
},
|
||||||
|
account_currency,
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(enable_discount_accounting or doc.get("is_cash_or_non_trade_discount"))
|
||||||
|
and doc.get("additional_discount_account")
|
||||||
|
and doc.get("discount_amount")
|
||||||
|
):
|
||||||
|
gl_entries.append(
|
||||||
|
doc.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": doc.additional_discount_account,
|
||||||
|
"against": doc.customer,
|
||||||
|
"debit": doc.base_discount_amount,
|
||||||
|
"cost_center": doc.cost_center or erpnext.get_default_cost_center(doc.company),
|
||||||
|
},
|
||||||
|
item=doc,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_tax_rate(account_head: str) -> dict:
|
def get_tax_rate(account_head: str) -> dict:
|
||||||
return frappe.get_cached_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
|
return frappe.get_cached_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
|
||||||
|
|
||||||
@@ -287,230 +520,3 @@ def merge_taxes(source_doc, target_doc) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_doc._item_wise_tax_details = item_tax_details
|
target_doc._item_wise_tax_details = item_tax_details
|
||||||
|
|
||||||
|
|
||||||
def set_taxes(doc) -> None:
|
|
||||||
if not doc.meta.get_field("taxes"):
|
|
||||||
return
|
|
||||||
|
|
||||||
tax_master_doctype = doc.meta.get_field("taxes_and_charges").options
|
|
||||||
|
|
||||||
if (doc.is_new() or is_pos_profile_changed(doc)) and not doc.get("taxes"):
|
|
||||||
if doc.company and not doc.get("taxes_and_charges"):
|
|
||||||
doc.taxes_and_charges = frappe.db.get_value(
|
|
||||||
tax_master_doctype, {"is_default": 1, "company": doc.company}
|
|
||||||
)
|
|
||||||
append_taxes_from_master(doc, tax_master_doctype)
|
|
||||||
|
|
||||||
|
|
||||||
def is_pos_profile_changed(doc) -> bool:
|
|
||||||
if (
|
|
||||||
doc.doctype == "Sales Invoice"
|
|
||||||
and doc.is_pos
|
|
||||||
and doc.pos_profile != frappe.db.get_value("Sales Invoice", doc.name, "pos_profile")
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def set_taxes_and_charges(doc) -> None:
|
|
||||||
if doc.doctype == "Material Request":
|
|
||||||
return
|
|
||||||
|
|
||||||
if doc.get("taxes") or doc.get("is_pos"):
|
|
||||||
return
|
|
||||||
|
|
||||||
if frappe.get_single_value("Accounts Settings", "add_taxes_from_taxes_and_charges_template") and hasattr(
|
|
||||||
doc, "taxes_and_charges"
|
|
||||||
):
|
|
||||||
if tax_master_doctype := doc.meta.get_field("taxes_and_charges").options:
|
|
||||||
append_taxes_from_master(doc, tax_master_doctype)
|
|
||||||
|
|
||||||
if frappe.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
|
|
||||||
append_taxes_from_item_tax_template(doc)
|
|
||||||
|
|
||||||
|
|
||||||
def append_taxes_from_master(doc, tax_master_doctype=None) -> None:
|
|
||||||
if doc.get("taxes_and_charges"):
|
|
||||||
if not tax_master_doctype:
|
|
||||||
tax_master_doctype = doc.meta.get_field("taxes_and_charges").options
|
|
||||||
doc.extend("taxes", get_taxes_and_charges(tax_master_doctype, doc.get("taxes_and_charges")))
|
|
||||||
|
|
||||||
|
|
||||||
def append_taxes_from_item_tax_template(doc) -> None:
|
|
||||||
if not frappe.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
|
|
||||||
return
|
|
||||||
|
|
||||||
for row in doc.items:
|
|
||||||
item_tax_rate = row.get("item_tax_rate")
|
|
||||||
if not item_tax_rate:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(item_tax_rate, str):
|
|
||||||
item_tax_rate = parse_json(item_tax_rate)
|
|
||||||
|
|
||||||
for account_head, _rate in item_tax_rate.items():
|
|
||||||
row = get_tax_row(doc, account_head)
|
|
||||||
|
|
||||||
if not row:
|
|
||||||
doc.append(
|
|
||||||
"taxes",
|
|
||||||
{
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": account_head,
|
|
||||||
"rate": 0,
|
|
||||||
"description": account_head,
|
|
||||||
"set_by_item_tax_template": 1,
|
|
||||||
"category": "Total",
|
|
||||||
"add_deduct_tax": "Add",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_tax_row(doc, account_head):
|
|
||||||
for row in doc.taxes:
|
|
||||||
if row.account_head == account_head:
|
|
||||||
return row
|
|
||||||
|
|
||||||
|
|
||||||
def set_other_charges(doc) -> None:
|
|
||||||
doc.set("taxes", [])
|
|
||||||
set_taxes(doc)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_enabled_taxes_and_charges(doc) -> None:
|
|
||||||
taxes_and_charges_doctype = doc.meta.get_options("taxes_and_charges")
|
|
||||||
if doc.taxes_and_charges and frappe.get_cached_value(
|
|
||||||
taxes_and_charges_doctype, doc.taxes_and_charges, "disabled"
|
|
||||||
):
|
|
||||||
frappe.throw(_("{0} '{1}' is disabled").format(taxes_and_charges_doctype, doc.taxes_and_charges))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_tax_account_company(doc) -> None:
|
|
||||||
for d in doc.get("taxes"):
|
|
||||||
if d.account_head:
|
|
||||||
tax_account_company = frappe.get_cached_value("Account", d.account_head, "company")
|
|
||||||
if tax_account_company != doc.company:
|
|
||||||
frappe.throw(
|
|
||||||
_("Row #{0}: Account {1} does not belong to company {2}").format(
|
|
||||||
d.idx, d.account_head, doc.company
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_tax_map(doc) -> dict:
|
|
||||||
tax_map = {}
|
|
||||||
for tax in doc.get("taxes"):
|
|
||||||
tax_map.setdefault(tax.account_head, 0.0)
|
|
||||||
tax_map[tax.account_head] += tax.tax_amount
|
|
||||||
return tax_map
|
|
||||||
|
|
||||||
|
|
||||||
def get_amount_and_base_amount(doc, item, enable_discount_accounting):
|
|
||||||
amount = item.net_amount
|
|
||||||
base_amount = item.base_net_amount
|
|
||||||
|
|
||||||
if enable_discount_accounting and doc.get("discount_amount") and doc.get("additional_discount_account"):
|
|
||||||
if not hasattr(doc, "__has_distributed_discount_set"):
|
|
||||||
doc.__has_distributed_discount_set = any(i.distributed_discount_amount for i in doc.get("items"))
|
|
||||||
|
|
||||||
if not doc.__has_distributed_discount_set:
|
|
||||||
return item.amount, item.base_amount
|
|
||||||
|
|
||||||
amount += item.distributed_discount_amount
|
|
||||||
base_amount += flt(
|
|
||||||
item.distributed_discount_amount * doc.get("conversion_rate"),
|
|
||||||
item.precision("distributed_discount_amount"),
|
|
||||||
)
|
|
||||||
|
|
||||||
return amount, base_amount
|
|
||||||
|
|
||||||
|
|
||||||
def get_tax_amounts(doc, tax, enable_discount_accounting):
|
|
||||||
amount = tax.tax_amount_after_discount_amount
|
|
||||||
base_amount = tax.base_tax_amount_after_discount_amount
|
|
||||||
|
|
||||||
if (
|
|
||||||
enable_discount_accounting
|
|
||||||
and doc.get("discount_amount")
|
|
||||||
and doc.get("additional_discount_account")
|
|
||||||
and doc.get("apply_discount_on") == "Grand Total"
|
|
||||||
):
|
|
||||||
amount = tax.tax_amount
|
|
||||||
base_amount = tax.base_tax_amount
|
|
||||||
|
|
||||||
return amount, base_amount
|
|
||||||
|
|
||||||
|
|
||||||
def make_discount_gl_entries(doc, gl_entries: list) -> None:
|
|
||||||
enable_discount_accounting = cint(
|
|
||||||
frappe.get_single_value("Selling Settings", "enable_discount_accounting")
|
|
||||||
)
|
|
||||||
|
|
||||||
if enable_discount_accounting:
|
|
||||||
for item in doc.get("items"):
|
|
||||||
if item.get("discount_amount") and item.get("discount_account"):
|
|
||||||
discount_amount = item.discount_amount * item.qty
|
|
||||||
income_account = (
|
|
||||||
item.income_account
|
|
||||||
if (not item.enable_deferred_revenue or doc.is_return)
|
|
||||||
else item.deferred_revenue_account
|
|
||||||
)
|
|
||||||
|
|
||||||
account_currency = get_account_currency(item.discount_account)
|
|
||||||
gl_entries.append(
|
|
||||||
doc.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": item.discount_account,
|
|
||||||
"against": doc.customer,
|
|
||||||
"debit": flt(
|
|
||||||
discount_amount * doc.get("conversion_rate"),
|
|
||||||
item.precision("discount_amount"),
|
|
||||||
),
|
|
||||||
"debit_in_transaction_currency": flt(
|
|
||||||
discount_amount, item.precision("discount_amount")
|
|
||||||
),
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"project": item.project,
|
|
||||||
},
|
|
||||||
account_currency,
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
account_currency = get_account_currency(income_account)
|
|
||||||
gl_entries.append(
|
|
||||||
doc.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": income_account,
|
|
||||||
"against": doc.customer,
|
|
||||||
"credit": flt(
|
|
||||||
discount_amount * doc.get("conversion_rate"),
|
|
||||||
item.precision("discount_amount"),
|
|
||||||
),
|
|
||||||
"credit_in_transaction_currency": flt(
|
|
||||||
discount_amount, item.precision("discount_amount")
|
|
||||||
),
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"project": item.project or doc.project,
|
|
||||||
},
|
|
||||||
account_currency,
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
(enable_discount_accounting or doc.get("is_cash_or_non_trade_discount"))
|
|
||||||
and doc.get("additional_discount_account")
|
|
||||||
and doc.get("discount_amount")
|
|
||||||
):
|
|
||||||
gl_entries.append(
|
|
||||||
doc.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": doc.additional_discount_account,
|
|
||||||
"against": doc.customer,
|
|
||||||
"debit": doc.base_discount_amount,
|
|
||||||
"cost_center": doc.cost_center or erpnext.get_default_cost_center(doc.company),
|
|
||||||
},
|
|
||||||
item=doc,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -242,11 +242,14 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
# Need to set taxes based on taxes_and_charges template
|
# Need to set taxes based on taxes_and_charges template
|
||||||
# before calculating taxes and totals
|
# before calculating taxes and totals
|
||||||
if self.meta.get_field("taxes_and_charges"):
|
from erpnext.accounts.services.taxes import TaxService
|
||||||
self.validate_enabled_taxes_and_charges()
|
|
||||||
self.validate_tax_account_company()
|
|
||||||
|
|
||||||
self.set_taxes_and_charges()
|
tax_service = TaxService(self)
|
||||||
|
if self.meta.get_field("taxes_and_charges"):
|
||||||
|
tax_service.validate_enabled_taxes_and_charges()
|
||||||
|
tax_service.validate_tax_account_company()
|
||||||
|
|
||||||
|
tax_service.set_taxes_and_charges()
|
||||||
|
|
||||||
if self.meta.get_field("currency"):
|
if self.meta.get_field("currency"):
|
||||||
self.calculate_taxes_and_totals()
|
self.calculate_taxes_and_totals()
|
||||||
@@ -1180,51 +1183,6 @@ class AccountsController(TransactionBase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_taxes(self):
|
|
||||||
from erpnext.accounts.services.taxes import set_taxes
|
|
||||||
|
|
||||||
set_taxes(self)
|
|
||||||
|
|
||||||
def is_pos_profile_changed(self):
|
|
||||||
from erpnext.accounts.services.taxes import is_pos_profile_changed
|
|
||||||
|
|
||||||
return is_pos_profile_changed(self)
|
|
||||||
|
|
||||||
def set_taxes_and_charges(self):
|
|
||||||
from erpnext.accounts.services.taxes import set_taxes_and_charges
|
|
||||||
|
|
||||||
set_taxes_and_charges(self)
|
|
||||||
|
|
||||||
def append_taxes_from_master(self, tax_master_doctype=None):
|
|
||||||
from erpnext.accounts.services.taxes import append_taxes_from_master
|
|
||||||
|
|
||||||
append_taxes_from_master(self, tax_master_doctype)
|
|
||||||
|
|
||||||
def append_taxes_from_item_tax_template(self):
|
|
||||||
from erpnext.accounts.services.taxes import append_taxes_from_item_tax_template
|
|
||||||
|
|
||||||
append_taxes_from_item_tax_template(self)
|
|
||||||
|
|
||||||
def get_tax_row(self, account_head):
|
|
||||||
from erpnext.accounts.services.taxes import get_tax_row
|
|
||||||
|
|
||||||
return get_tax_row(self, account_head)
|
|
||||||
|
|
||||||
def set_other_charges(self):
|
|
||||||
from erpnext.accounts.services.taxes import set_other_charges
|
|
||||||
|
|
||||||
set_other_charges(self)
|
|
||||||
|
|
||||||
def validate_enabled_taxes_and_charges(self):
|
|
||||||
from erpnext.accounts.services.taxes import validate_enabled_taxes_and_charges
|
|
||||||
|
|
||||||
validate_enabled_taxes_and_charges(self)
|
|
||||||
|
|
||||||
def validate_tax_account_company(self):
|
|
||||||
from erpnext.accounts.services.taxes import validate_tax_account_company
|
|
||||||
|
|
||||||
validate_tax_account_company(self)
|
|
||||||
|
|
||||||
def get_gl_dict(self, args, account_currency=None, item=None):
|
def get_gl_dict(self, args, account_currency=None, item=None):
|
||||||
from erpnext.accounts.services.base_gl_composer import get_gl_dict
|
from erpnext.accounts.services.base_gl_composer import get_gl_dict
|
||||||
|
|
||||||
@@ -1508,26 +1466,6 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
|
frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
|
||||||
|
|
||||||
def get_tax_map(self):
|
|
||||||
from erpnext.accounts.services.taxes import get_tax_map
|
|
||||||
|
|
||||||
return get_tax_map(self)
|
|
||||||
|
|
||||||
def get_amount_and_base_amount(self, item, enable_discount_accounting):
|
|
||||||
from erpnext.accounts.services.taxes import get_amount_and_base_amount
|
|
||||||
|
|
||||||
return get_amount_and_base_amount(self, item, enable_discount_accounting)
|
|
||||||
|
|
||||||
def get_tax_amounts(self, tax, enable_discount_accounting):
|
|
||||||
from erpnext.accounts.services.taxes import get_tax_amounts
|
|
||||||
|
|
||||||
return get_tax_amounts(self, tax, enable_discount_accounting)
|
|
||||||
|
|
||||||
def make_discount_gl_entries(self, gl_entries):
|
|
||||||
from erpnext.accounts.services.taxes import make_discount_gl_entries
|
|
||||||
|
|
||||||
make_discount_gl_entries(self, gl_entries)
|
|
||||||
|
|
||||||
def validate_multiple_billing(self, ref_dt: str, item_ref_dn: str, based_on: str) -> None:
|
def validate_multiple_billing(self, ref_dt: str, item_ref_dn: str, based_on: str) -> None:
|
||||||
from erpnext.accounts.services.billing_validation import validate_multiple_billing
|
from erpnext.accounts.services.billing_validation import validate_multiple_billing
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user