diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 2987bac677c..a122e303c68 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -12,6 +12,7 @@ from frappe.utils import cint, flt
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
+from erpnext.accounts.party import get_party_account
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
get_entries,
@@ -284,54 +285,56 @@ def create_payment_entry_bts(
bank_transaction = frappe.db.get_values(
"Bank Transaction",
bank_transaction_name,
- fieldname=["name", "unallocated_amount", "deposit", "bank_account"],
+ fieldname=["name", "unallocated_amount", "deposit", "bank_account", "currency"],
as_dict=True,
)[0]
- paid_amount = bank_transaction.unallocated_amount
+
payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay"
- company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
- company = frappe.get_value("Account", company_account, "company")
- payment_entry_dict = {
- "company": company,
- "payment_type": payment_type,
- "reference_no": reference_number,
- "reference_date": reference_date,
- "party_type": party_type,
- "party": party,
- "posting_date": posting_date,
- "paid_amount": paid_amount,
- "received_amount": paid_amount,
- }
- payment_entry = frappe.new_doc("Payment Entry")
+ bank_account = frappe.get_cached_value("Bank Account", bank_transaction.bank_account, "account")
+ company = frappe.get_cached_value("Account", bank_account, "company")
+ party_account = get_party_account(party_type, party, company)
- payment_entry.update(payment_entry_dict)
+ bank_currency = bank_transaction.currency
+ party_currency = frappe.get_cached_value("Account", party_account, "account_currency")
- if mode_of_payment:
- payment_entry.mode_of_payment = mode_of_payment
- if project:
- payment_entry.project = project
- if cost_center:
- payment_entry.cost_center = cost_center
- if payment_type == "Receive":
- payment_entry.paid_to = company_account
- else:
- payment_entry.paid_from = company_account
+ exc_rate = get_exchange_rate(bank_currency, party_currency, posting_date)
- payment_entry.validate()
+ amt_in_bank_acc_currency = bank_transaction.unallocated_amount
+ amount_in_party_currency = bank_transaction.unallocated_amount * exc_rate
+
+ pe = frappe.new_doc("Payment Entry")
+ pe.payment_type = payment_type
+ pe.company = company
+ pe.reference_no = reference_number
+ pe.reference_date = reference_date
+ pe.party_type = party_type
+ pe.party = party
+ pe.posting_date = posting_date
+ pe.paid_from = party_account if payment_type == "Receive" else bank_account
+ pe.paid_to = party_account if payment_type == "Pay" else bank_account
+ pe.paid_from_account_currency = party_currency if payment_type == "Receive" else bank_currency
+ pe.paid_to_account_currency = party_currency if payment_type == "Pay" else bank_currency
+ pe.paid_amount = amount_in_party_currency if payment_type == "Receive" else amt_in_bank_acc_currency
+ pe.received_amount = amount_in_party_currency if payment_type == "Pay" else amt_in_bank_acc_currency
+ pe.mode_of_payment = mode_of_payment
+ pe.project = project
+ pe.cost_center = cost_center
+
+ pe.validate()
if allow_edit:
- return payment_entry
+ return pe
- payment_entry.insert()
+ pe.insert()
+ pe.submit()
- payment_entry.submit()
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
- "payment_name": payment_entry.name,
- "amount": paid_amount,
+ "payment_name": pe.name,
+ "amount": amt_in_bank_acc_currency,
}
]
)
@@ -455,8 +458,12 @@ def get_linked_payments(
def subtract_allocations(gl_account, vouchers):
"Look up & subtract any existing Bank Transaction allocations"
copied = []
+
+ voucher_docs = [(voucher[1], voucher[2]) for voucher in vouchers]
+ voucher_allocated_amounts = get_total_allocated_amount(voucher_docs)
+
for voucher in vouchers:
- rows = get_total_allocated_amount(voucher[1], voucher[2])
+ rows = voucher_allocated_amounts.get((voucher[1], voucher[2])) or []
amount = None
for row in rows:
if row["gl_account"] == gl_account:
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 89565e9908b..6c1beb16d5d 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -93,10 +93,16 @@ class BankTransaction(StatusUpdater):
- clear means: set the latest transaction date as clearance date
"""
remaining_amount = self.unallocated_amount
+ payment_entry_docs = [(pe.payment_document, pe.payment_entry) for pe in self.payment_entries]
+ pe_bt_allocations = get_total_allocated_amount(payment_entry_docs)
+
for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0:
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
- self, payment_entry
+ self,
+ payment_entry,
+ pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry))
+ or [],
)
if 0.0 == unallocated_amount:
@@ -182,7 +188,7 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes")
-def get_clearance_details(transaction, payment_entry):
+def get_clearance_details(transaction, payment_entry, bt_allocations):
"""
There should only be one bank gle for a voucher.
Could be none for a Bank Transaction.
@@ -191,7 +197,6 @@ def get_clearance_details(transaction, payment_entry):
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
- bt_allocations = get_total_allocated_amount(payment_entry.payment_document, payment_entry.payment_entry)
unallocated_amount = min(
transaction.unallocated_amount,
@@ -247,44 +252,52 @@ def get_related_bank_gl_entries(doctype, docname):
return result
-def get_total_allocated_amount(doctype, docname):
+def get_total_allocated_amount(docs):
"""
Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
"""
+ if not docs:
+ return {}
+
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
- SELECT total, latest_name, latest_date, gl_account FROM (
+ SELECT total, latest_name, latest_date, gl_account, payment_document, payment_entry FROM (
SELECT
ROW_NUMBER() OVER w AS rownum,
- SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total,
+ SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date,
- ba.account AS gl_account
+ ba.account AS gl_account,
+ btp.payment_document,
+ btp.payment_entry
FROM
`tabBank Transaction Payments` btp
LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent
LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account
WHERE
- btp.payment_document = %(doctype)s
- AND btp.payment_entry = %(docname)s
+ (btp.payment_document, btp.payment_entry) IN %(docs)s
AND bt.docstatus = 1
- WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc)
+ WINDOW w AS (PARTITION BY ba.account, btp.payment_document, btp.payment_entry ORDER BY bt.date DESC)
) temp
WHERE
rownum = 1
""",
- dict(doctype=doctype, docname=docname),
+ dict(docs=docs),
as_dict=True,
)
+
+ payment_allocation_details = {}
for row in result:
# Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"])
- return result
+ payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), []).append(row)
+
+ return payment_allocation_details
def get_paid_amount(payment_entry, currency, gl_bank_account):
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index dc879cfc1b4..4a8c8ad82a7 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -366,6 +366,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
hide_fields(this.frm.doc);
if(cint(this.frm.doc.is_paid)) {
this.frm.set_value("allocate_advances_automatically", 0);
+ this.frm.set_value("payment_terms_template", "");
+ this.frm.set_value("payment_schedule", []);
if(!this.frm.doc.company) {
this.frm.set_value("is_paid", 0)
frappe.msgprint(__("Please specify Company to proceed"));
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 6e4c6002016..b7546a9ce33 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -36,6 +36,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import (
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
+from erpnext.stock.get_item_details import get_item_tax_map
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
@@ -2817,13 +2818,26 @@ class TestSalesInvoice(FrappeTestCase):
item.save()
sales_invoice = create_sales_invoice(item="T Shirt", rate=700, do_not_submit=True)
+ item_tax_map = get_item_tax_map(
+ company=sales_invoice.company,
+ item_tax_template=sales_invoice.items[0].item_tax_template,
+ )
+
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
+ self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
# Apply discount
sales_invoice.apply_discount_on = "Net Total"
sales_invoice.discount_amount = 300
sales_invoice.save()
+
+ item_tax_map = get_item_tax_map(
+ company=sales_invoice.company,
+ item_tax_template=sales_invoice.items[0].item_tax_template,
+ )
+
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
+ self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
def test_sales_invoice_with_discount_accounting_enabled(self):
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.json b/erpnext/accounts/report/tax_detail/test_tax_detail.json
deleted file mode 100644
index e4903167cba..00000000000
--- a/erpnext/accounts/report/tax_detail/test_tax_detail.json
+++ /dev/null
@@ -1,840 +0,0 @@
-[
- {
- "account_manager": null,
- "accounts": [],
- "companies": [],
- "credit_limits": [],
- "customer_details": null,
- "customer_group": "All Customer Groups",
- "customer_name": "_Test Customer",
- "customer_pos_id": null,
- "customer_primary_address": null,
- "customer_primary_contact": null,
- "customer_type": "Company",
- "default_bank_account": null,
- "default_commission_rate": 0.0,
- "default_currency": null,
- "default_price_list": null,
- "default_sales_partner": null,
- "disabled": 0,
- "dn_required": 0,
- "docstatus": 0,
- "doctype": "Customer",
- "email_id": null,
- "gender": null,
- "image": null,
- "industry": null,
- "is_frozen": 0,
- "is_internal_customer": 0,
- "language": "en",
- "lead_name": null,
- "loyalty_program": null,
- "loyalty_program_tier": null,
- "market_segment": null,
- "mobile_no": null,
- "modified": "2021-02-15 05:18:03.624724",
- "name": "_Test Customer",
- "naming_series": "CUST-.YYYY.-",
- "pan": null,
- "parent": null,
- "parentfield": null,
- "parenttype": null,
- "payment_terms": null,
- "primary_address": null,
- "represents_company": "",
- "sales_team": [],
- "salutation": null,
- "so_required": 0,
- "tax_category": null,
- "tax_id": null,
- "tax_withholding_category": null,
- "territory": "All Territories",
- "website": null
- },{
- "accounts": [],
- "allow_purchase_invoice_creation_without_purchase_order": 0,
- "allow_purchase_invoice_creation_without_purchase_receipt": 0,
- "companies": [],
- "country": "United Kingdom",
- "default_bank_account": null,
- "default_currency": null,
- "default_price_list": null,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Supplier",
- "hold_type": "",
- "image": null,
- "is_frozen": 0,
- "is_internal_supplier": 0,
- "is_transporter": 0,
- "language": "en",
- "modified": "2021-03-31 16:47:10.109316",
- "name": "_Test Supplier",
- "naming_series": "SUP-.YYYY.-",
- "on_hold": 0,
- "pan": null,
- "parent": null,
- "parentfield": null,
- "parenttype": null,
- "payment_terms": null,
- "prevent_pos": 0,
- "prevent_rfqs": 0,
- "release_date": null,
- "represents_company": null,
- "supplier_details": null,
- "supplier_group": "Raw Material",
- "supplier_name": "_Test Supplier",
- "supplier_type": "Company",
- "tax_category": null,
- "tax_id": null,
- "tax_withholding_category": null,
- "warn_pos": 0,
- "warn_rfqs": 0,
- "website": null
- },{
- "account_currency": "GBP",
- "account_name": "Debtors",
- "account_number": "",
- "account_type": "Receivable",
- "balance_must_be": "",
- "company": "_T",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Account",
- "freeze_account": "No",
- "include_in_gross": 0,
- "inter_company_account": 0,
- "is_group": 0,
- "lft": 58,
- "modified": "2021-03-26 04:44:19.955468",
- "name": "Debtors - _T",
- "old_parent": null,
- "parent": null,
- "parent_account": "Application of Funds (Assets) - _T",
- "parentfield": null,
- "parenttype": null,
- "report_type": "Balance Sheet",
- "rgt": 59,
- "root_type": "Asset",
- "tax_rate": 0.0
- },{
- "account_currency": "GBP",
- "account_name": "Sales",
- "account_number": "",
- "account_type": "Income Account",
- "balance_must_be": "",
- "company": "_T",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Account",
- "freeze_account": "No",
- "include_in_gross": 0,
- "inter_company_account": 0,
- "is_group": 0,
- "lft": 291,
- "modified": "2021-03-26 04:50:21.697703",
- "name": "Sales - _T",
- "old_parent": null,
- "parent": null,
- "parent_account": "Income - _T",
- "parentfield": null,
- "parenttype": null,
- "report_type": "Profit and Loss",
- "rgt": 292,
- "root_type": "Income",
- "tax_rate": 0.0
- },{
- "account_currency": "GBP",
- "account_name": "VAT on Sales",
- "account_number": "",
- "account_type": "Tax",
- "balance_must_be": "",
- "company": "_T",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Account",
- "freeze_account": "No",
- "include_in_gross": 0,
- "inter_company_account": 0,
- "is_group": 0,
- "lft": 317,
- "modified": "2021-03-26 04:50:21.697703",
- "name": "VAT on Sales - _T",
- "old_parent": null,
- "parent": null,
- "parent_account": "Source of Funds (Liabilities) - _T",
- "parentfield": null,
- "parenttype": null,
- "report_type": "Balance Sheet",
- "rgt": 318,
- "root_type": "Liability",
- "tax_rate": 0.0
- },{
- "account_currency": "GBP",
- "account_name": "Cost of Goods Sold",
- "account_number": "",
- "account_type": "Cost of Goods Sold",
- "balance_must_be": "",
- "company": "_T",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Account",
- "freeze_account": "No",
- "include_in_gross": 0,
- "inter_company_account": 0,
- "is_group": 0,
- "lft": 171,
- "modified": "2021-03-26 04:44:19.994857",
- "name": "Cost of Goods Sold - _T",
- "old_parent": null,
- "parent": null,
- "parent_account": "Expenses - _T",
- "parentfield": null,
- "parenttype": null,
- "report_type": "Profit and Loss",
- "rgt": 172,
- "root_type": "Expense",
- "tax_rate": 0.0
- },{
- "account_currency": "GBP",
- "account_name": "VAT on Purchases",
- "account_number": "",
- "account_type": "Tax",
- "balance_must_be": "",
- "company": "_T",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Account",
- "freeze_account": "No",
- "include_in_gross": 0,
- "inter_company_account": 0,
- "is_group": 0,
- "lft": 80,
- "modified": "2021-03-26 04:44:19.961983",
- "name": "VAT on Purchases - _T",
- "old_parent": null,
- "parent": null,
- "parent_account": "Application of Funds (Assets) - _T",
- "parentfield": null,
- "parenttype": null,
- "report_type": "Balance Sheet",
- "rgt": 81,
- "root_type": "Asset",
- "tax_rate": 0.0
- },{
- "account_currency": "GBP",
- "account_name": "Creditors",
- "account_number": "",
- "account_type": "Payable",
- "balance_must_be": "",
- "company": "_T",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Account",
- "freeze_account": "No",
- "include_in_gross": 0,
- "inter_company_account": 0,
- "is_group": 0,
- "lft": 302,
- "modified": "2021-03-26 04:50:21.697703",
- "name": "Creditors - _T",
- "old_parent": null,
- "parent": null,
- "parent_account": "Source of Funds (Liabilities) - _T",
- "parentfield": null,
- "parenttype": null,
- "report_type": "Balance Sheet",
- "rgt": 303,
- "root_type": "Liability",
- "tax_rate": 0.0
- },{
- "additional_discount_percentage": 0.0,
- "address_display": null,
- "adjust_advance_taxes": 0,
- "advances": [],
- "against_expense_account": "Cost of Goods Sold - _T",
- "allocate_advances_automatically": 0,
- "amended_from": null,
- "apply_discount_on": "Grand Total",
- "apply_tds": 0,
- "auto_repeat": null,
- "base_discount_amount": 0.0,
- "base_grand_total": 511.68,
- "base_in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
- "base_net_total": 426.4,
- "base_paid_amount": 0.0,
- "base_rounded_total": 511.68,
- "base_rounding_adjustment": 0.0,
- "base_taxes_and_charges_added": 85.28,
- "base_taxes_and_charges_deducted": 0.0,
- "base_total": 426.4,
- "base_total_taxes_and_charges": 85.28,
- "base_write_off_amount": 0.0,
- "bill_date": null,
- "bill_no": null,
- "billing_address": null,
- "billing_address_display": null,
- "buying_price_list": "Standard Buying",
- "cash_bank_account": null,
- "clearance_date": null,
- "company": "_T",
- "contact_display": null,
- "contact_email": null,
- "contact_mobile": null,
- "contact_person": null,
- "conversion_rate": 1.0,
- "cost_center": null,
- "credit_to": "Creditors - _T",
- "currency": "GBP",
- "disable_rounded_total": 0,
- "discount_amount": 0.0,
- "docstatus": 0,
- "doctype": "Purchase Invoice",
- "due_date": null,
- "from_date": null,
- "grand_total": 511.68,
- "group_same_items": 0,
- "hold_comment": null,
- "ignore_pricing_rule": 0,
- "in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
- "inter_company_invoice_reference": null,
- "is_internal_supplier": 0,
- "is_opening": "No",
- "is_paid": 0,
- "is_return": 0,
- "is_subcontracted": 0,
- "items": [
- {
- "allow_zero_valuation_rate": 0,
- "amount": 426.4,
- "asset_category": null,
- "asset_location": null,
- "base_amount": 426.4,
- "base_net_amount": 426.4,
- "base_net_rate": 5.33,
- "base_price_list_rate": 5.33,
- "base_rate": 5.33,
- "base_rate_with_margin": 0.0,
- "batch_no": null,
- "bom": null,
- "brand": null,
- "conversion_factor": 0.0,
- "cost_center": "Main - _T",
- "deferred_expense_account": null,
- "description": "
",
- "discount_amount": 0.0,
- "discount_percentage": 0.0,
- "enable_deferred_expense": 0,
- "expense_account": "Cost of Goods Sold - _T",
- "from_warehouse": null,
- "image": null,
- "include_exploded_items": 0,
- "is_fixed_asset": 0,
- "is_free_item": 0,
- "item_code": null,
- "item_group": null,
- "item_name": "Widget Fluid 1Litre",
- "item_tax_amount": 0.0,
- "item_tax_rate": "{\"VAT on Purchases - _T\": 20.0}",
- "item_tax_template": null,
- "landed_cost_voucher_amount": 0.0,
- "manufacturer": null,
- "manufacturer_part_no": null,
- "margin_rate_or_amount": 0.0,
- "margin_type": "",
- "net_amount": 426.4,
- "net_rate": 5.33,
- "page_break": 0,
- "parent": null,
- "parentfield": "items",
- "parenttype": "Purchase Invoice",
- "po_detail": null,
- "pr_detail": null,
- "price_list_rate": 5.33,
- "pricing_rules": null,
- "project": null,
- "purchase_invoice_item": null,
- "purchase_order": null,
- "purchase_receipt": null,
- "qty": 80.0,
- "quality_inspection": null,
- "rate": 5.33,
- "rate_with_margin": 0.0,
- "received_qty": 0.0,
- "rejected_qty": 0.0,
- "rejected_serial_no": null,
- "rejected_warehouse": null,
- "rm_supp_cost": 0.0,
- "sales_invoice_item": null,
- "serial_no": null,
- "service_end_date": null,
- "service_start_date": null,
- "service_stop_date": null,
- "stock_qty": 0.0,
- "stock_uom": "Nos",
- "stock_uom_rate": 0.0,
- "total_weight": 0.0,
- "uom": "Nos",
- "valuation_rate": 0.0,
- "warehouse": null,
- "weight_per_unit": 0.0,
- "weight_uom": null
- }
- ],
- "language": "en",
- "letter_head": null,
- "mode_of_payment": null,
- "modified": "2021-04-03 03:33:09.180453",
- "name": null,
- "naming_series": "ACC-PINV-.YYYY.-",
- "net_total": 426.4,
- "on_hold": 0,
- "other_charges_calculation": "\n\t
\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t| Item | \n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tTaxable Amount | \n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tVAT on Purchases | \n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t| Widget Fluid 1Litre | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 426.40\n\t\t\t\t\t\t\n\t\t\t\t\t | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 85.28\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t | \n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\n\t\t\n\t
\n
",
- "outstanding_amount": 511.68,
- "paid_amount": 0.0,
- "parent": null,
- "parentfield": null,
- "parenttype": null,
- "party_account_currency": "GBP",
- "payment_schedule": [],
- "payment_terms_template": null,
- "plc_conversion_rate": 1.0,
- "posting_date": null,
- "posting_time": "16:59:56.789522",
- "price_list_currency": "GBP",
- "pricing_rules": [],
- "project": null,
- "rejected_warehouse": null,
- "release_date": null,
- "remarks": "No Remarks",
- "represents_company": null,
- "return_against": null,
- "rounded_total": 511.68,
- "rounding_adjustment": 0.0,
- "scan_barcode": null,
- "select_print_heading": null,
- "set_from_warehouse": null,
- "set_posting_time": 0,
- "set_warehouse": null,
- "shipping_address": null,
- "shipping_address_display": "",
- "shipping_rule": null,
- "status": "Unpaid",
- "supplied_items": [],
- "supplier": "_Test Supplier",
- "supplier_address": null,
- "supplier_name": "_Test Supplier",
- "supplier_warehouse": "Stores - _T",
- "tax_category": null,
- "tax_id": null,
- "tax_withholding_category": null,
- "taxes": [
- {
- "account_head": "VAT on Purchases - _T",
- "add_deduct_tax": "Add",
- "base_tax_amount": 85.28,
- "base_tax_amount_after_discount_amount": 85.28,
- "base_total": 511.68,
- "category": "Total",
- "charge_type": "On Net Total",
- "cost_center": "Main - _T",
- "description": "VAT on Purchases",
- "included_in_print_rate": 0,
- "item_wise_tax_detail": "{\"Widget Fluid 1Litre\":[20.0,85.28]}",
- "parent": null,
- "parentfield": "taxes",
- "parenttype": "Purchase Invoice",
- "rate": 0.0,
- "row_id": null,
- "tax_amount": 85.28,
- "tax_amount_after_discount_amount": 85.28,
- "total": 511.68
- }
- ],
- "taxes_and_charges": null,
- "taxes_and_charges_added": 85.28,
- "taxes_and_charges_deducted": 0.0,
- "tc_name": null,
- "terms": null,
- "title": "_Purchase Invoice",
- "to_date": null,
- "total": 426.4,
- "total_advance": 0.0,
- "total_net_weight": 0.0,
- "total_qty": 80.0,
- "total_taxes_and_charges": 85.28,
- "unrealized_profit_loss_account": null,
- "update_stock": 0,
- "write_off_account": null,
- "write_off_amount": 0.0,
- "write_off_cost_center": null
- },{
- "account_for_change_amount": null,
- "additional_discount_percentage": 0.0,
- "address_display": null,
- "advances": [],
- "against_income_account": "Sales - _T",
- "allocate_advances_automatically": 0,
- "amended_from": null,
- "apply_discount_on": "Grand Total",
- "auto_repeat": null,
- "base_change_amount": 0.0,
- "base_discount_amount": 0.0,
- "base_grand_total": 868.25,
- "base_in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
- "base_net_total": 825.0,
- "base_paid_amount": 0.0,
- "base_rounded_total": 868.25,
- "base_rounding_adjustment": 0.0,
- "base_total": 825.0,
- "base_total_taxes_and_charges": 43.25,
- "base_write_off_amount": 0.0,
- "c_form_applicable": "No",
- "c_form_no": null,
- "campaign": null,
- "cash_bank_account": null,
- "change_amount": 0.0,
- "commission_rate": 0.0,
- "company": "_T",
- "company_address": null,
- "company_address_display": null,
- "company_tax_id": null,
- "contact_display": null,
- "contact_email": null,
- "contact_mobile": null,
- "contact_person": null,
- "conversion_rate": 1.0,
- "cost_center": null,
- "currency": "GBP",
- "customer": "_Test Customer",
- "customer_address": null,
- "customer_group": "All Customer Groups",
- "customer_name": "_Test Customer",
- "debit_to": "Debtors - _T",
- "discount_amount": 0.0,
- "docstatus": 0,
- "doctype": "Sales Invoice",
- "due_date": null,
- "from_date": null,
- "grand_total": 868.25,
- "group_same_items": 0,
- "ignore_pricing_rule": 0,
- "in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
- "inter_company_invoice_reference": null,
- "is_consolidated": 0,
- "is_discounted": 0,
- "is_internal_customer": 0,
- "is_opening": "No",
- "is_pos": 0,
- "is_return": 0,
- "items": [
- {
- "actual_batch_qty": 0.0,
- "actual_qty": 0.0,
- "allow_zero_valuation_rate": 0,
- "amount": 200.0,
- "asset": null,
- "barcode": null,
- "base_amount": 200.0,
- "base_net_amount": 200.0,
- "base_net_rate": 50.0,
- "base_price_list_rate": 0.0,
- "base_rate": 50.0,
- "base_rate_with_margin": 0.0,
- "batch_no": null,
- "brand": null,
- "conversion_factor": 1.0,
- "cost_center": "Main - _T",
- "customer_item_code": null,
- "deferred_revenue_account": null,
- "delivered_by_supplier": 0,
- "delivered_qty": 0.0,
- "delivery_note": null,
- "description": "",
- "discount_amount": 0.0,
- "discount_percentage": 0.0,
- "dn_detail": null,
- "enable_deferred_revenue": 0,
- "expense_account": null,
- "finance_book": null,
- "image": null,
- "income_account": "Sales - _T",
- "incoming_rate": 0.0,
- "is_fixed_asset": 0,
- "is_free_item": 0,
- "item_code": null,
- "item_group": null,
- "item_name": "Dunlop tyres",
- "item_tax_rate": "{\"VAT on Sales - _T\": 20.0}",
- "item_tax_template": null,
- "margin_rate_or_amount": 0.0,
- "margin_type": "",
- "net_amount": 200.0,
- "net_rate": 50.0,
- "page_break": 0,
- "parent": null,
- "parentfield": "items",
- "parenttype": "Sales Invoice",
- "price_list_rate": 0.0,
- "pricing_rules": null,
- "project": null,
- "qty": 4.0,
- "quality_inspection": null,
- "rate": 50.0,
- "rate_with_margin": 0.0,
- "sales_invoice_item": null,
- "sales_order": null,
- "serial_no": null,
- "service_end_date": null,
- "service_start_date": null,
- "service_stop_date": null,
- "so_detail": null,
- "stock_qty": 4.0,
- "stock_uom": "Nos",
- "stock_uom_rate": 50.0,
- "target_warehouse": null,
- "total_weight": 0.0,
- "uom": "Nos",
- "warehouse": null,
- "weight_per_unit": 0.0,
- "weight_uom": null
- },
- {
- "actual_batch_qty": 0.0,
- "actual_qty": 0.0,
- "allow_zero_valuation_rate": 0,
- "amount": 65.0,
- "asset": null,
- "barcode": null,
- "base_amount": 65.0,
- "base_net_amount": 65.0,
- "base_net_rate": 65.0,
- "base_price_list_rate": 0.0,
- "base_rate": 65.0,
- "base_rate_with_margin": 0.0,
- "batch_no": null,
- "brand": null,
- "conversion_factor": 1.0,
- "cost_center": "Main - _T",
- "customer_item_code": null,
- "deferred_revenue_account": null,
- "delivered_by_supplier": 0,
- "delivered_qty": 0.0,
- "delivery_note": null,
- "description": "",
- "discount_amount": 0.0,
- "discount_percentage": 0.0,
- "dn_detail": null,
- "enable_deferred_revenue": 0,
- "expense_account": null,
- "finance_book": null,
- "image": null,
- "income_account": "Sales - _T",
- "incoming_rate": 0.0,
- "is_fixed_asset": 0,
- "is_free_item": 0,
- "item_code": "",
- "item_group": null,
- "item_name": "Continental tyres",
- "item_tax_rate": "{\"VAT on Sales - _T\": 5.0}",
- "item_tax_template": null,
- "margin_rate_or_amount": 0.0,
- "margin_type": "",
- "net_amount": 65.0,
- "net_rate": 65.0,
- "page_break": 0,
- "parent": null,
- "parentfield": "items",
- "parenttype": "Sales Invoice",
- "price_list_rate": 0.0,
- "pricing_rules": null,
- "project": null,
- "qty": 1.0,
- "quality_inspection": null,
- "rate": 65.0,
- "rate_with_margin": 0.0,
- "sales_invoice_item": null,
- "sales_order": null,
- "serial_no": null,
- "service_end_date": null,
- "service_start_date": null,
- "service_stop_date": null,
- "so_detail": null,
- "stock_qty": 1.0,
- "stock_uom": null,
- "stock_uom_rate": 65.0,
- "target_warehouse": null,
- "total_weight": 0.0,
- "uom": "Nos",
- "warehouse": null,
- "weight_per_unit": 0.0,
- "weight_uom": null
- },
- {
- "actual_batch_qty": 0.0,
- "actual_qty": 0.0,
- "allow_zero_valuation_rate": 0,
- "amount": 560.0,
- "asset": null,
- "barcode": null,
- "base_amount": 560.0,
- "base_net_amount": 560.0,
- "base_net_rate": 70.0,
- "base_price_list_rate": 0.0,
- "base_rate": 70.0,
- "base_rate_with_margin": 0.0,
- "batch_no": null,
- "brand": null,
- "conversion_factor": 1.0,
- "cost_center": "Main - _T",
- "customer_item_code": null,
- "deferred_revenue_account": null,
- "delivered_by_supplier": 0,
- "delivered_qty": 0.0,
- "delivery_note": null,
- "description": "",
- "discount_amount": 0.0,
- "discount_percentage": 0.0,
- "dn_detail": null,
- "enable_deferred_revenue": 0,
- "expense_account": null,
- "finance_book": null,
- "image": null,
- "income_account": "Sales - _T",
- "incoming_rate": 0.0,
- "is_fixed_asset": 0,
- "is_free_item": 0,
- "item_code": null,
- "item_group": null,
- "item_name": "Toyo tyres",
- "item_tax_rate": "{\"VAT on Sales - _T\": 0.0}",
- "item_tax_template": null,
- "margin_rate_or_amount": 0.0,
- "margin_type": "",
- "net_amount": 560.0,
- "net_rate": 70.0,
- "page_break": 0,
- "parent": null,
- "parentfield": "items",
- "parenttype": "Sales Invoice",
- "price_list_rate": 0.0,
- "pricing_rules": null,
- "project": null,
- "qty": 8.0,
- "quality_inspection": null,
- "rate": 70.0,
- "rate_with_margin": 0.0,
- "sales_invoice_item": null,
- "sales_order": null,
- "serial_no": null,
- "service_end_date": null,
- "service_start_date": null,
- "service_stop_date": null,
- "so_detail": null,
- "stock_qty": 8.0,
- "stock_uom": null,
- "stock_uom_rate": 70.0,
- "target_warehouse": null,
- "total_weight": 0.0,
- "uom": "Nos",
- "warehouse": null,
- "weight_per_unit": 0.0,
- "weight_uom": null
- }
- ],
- "language": "en",
- "letter_head": null,
- "loyalty_amount": 0.0,
- "loyalty_points": 0,
- "loyalty_program": null,
- "loyalty_redemption_account": null,
- "loyalty_redemption_cost_center": null,
- "modified": "2021-02-16 05:18:59.755144",
- "name": null,
- "naming_series": "ACC-SINV-.YYYY.-",
- "net_total": 825.0,
- "other_charges_calculation": "\n\t
\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t| Item | \n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tTaxable Amount | \n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tVAT on Sales | \n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t| Dunlop tyres | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 200.00\n\t\t\t\t\t\t\n\t\t\t\t\t | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 40.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t | \n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t| Continental tyres | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 65.00\n\t\t\t\t\t\t\n\t\t\t\t\t | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(5.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 3.25\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t | \n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t| Toyo tyres | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 560.00\n\t\t\t\t\t\t\n\t\t\t\t\t | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(0.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 0.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t | \n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\n\t\t\n\t
\n
",
- "outstanding_amount": 868.25,
- "packed_items": [],
- "paid_amount": 0.0,
- "parent": null,
- "parentfield": null,
- "parenttype": null,
- "party_account_currency": "GBP",
- "payment_schedule": [],
- "payment_terms_template": null,
- "payments": [],
- "plc_conversion_rate": 1.0,
- "po_date": null,
- "po_no": "",
- "pos_profile": null,
- "posting_date": null,
- "posting_time": "5:19:02.994077",
- "price_list_currency": "GBP",
- "pricing_rules": [],
- "project": null,
- "redeem_loyalty_points": 0,
- "remarks": "No Remarks",
- "represents_company": "",
- "return_against": null,
- "rounded_total": 868.25,
- "rounding_adjustment": 0.0,
- "sales_partner": null,
- "sales_team": [],
- "scan_barcode": null,
- "select_print_heading": null,
- "selling_price_list": "Standard Selling",
- "set_posting_time": 0,
- "set_target_warehouse": null,
- "set_warehouse": null,
- "shipping_address": null,
- "shipping_address_name": "",
- "shipping_rule": null,
- "source": null,
- "status": "Overdue",
- "tax_category": "",
- "tax_id": null,
- "taxes": [
- {
- "account_head": "VAT on Sales - _T",
- "base_tax_amount": 43.25,
- "base_tax_amount_after_discount_amount": 43.25,
- "base_total": 868.25,
- "charge_type": "On Net Total",
- "cost_center": "Main - _T",
- "description": "VAT on Sales",
- "included_in_print_rate": 0,
- "item_wise_tax_detail": "{\"Dunlop tyres\":[20.0,40.0],\"Continental tyres\":[5.0,3.25],\"Toyo tyres\":[0.0,0.0]}",
- "parent": null,
- "parentfield": "taxes",
- "parenttype": "Sales Invoice",
- "rate": 0.0,
- "row_id": null,
- "tax_amount": 43.25,
- "tax_amount_after_discount_amount": 43.25,
- "total": 868.25
- }
- ],
- "taxes_and_charges": null,
- "tc_name": null,
- "terms": null,
- "territory": "All Territories",
- "timesheets": [],
- "title": "_Sales Invoice",
- "to_date": null,
- "total": 825.0,
- "total_advance": 0.0,
- "total_billing_amount": 0.0,
- "total_commission": 0.0,
- "total_net_weight": 0.0,
- "total_qty": 13.0,
- "total_taxes_and_charges": 43.25,
- "unrealized_profit_loss_account": null,
- "update_billed_amount_in_sales_order": 0,
- "update_stock": 0,
- "write_off_account": null,
- "write_off_amount": 0.0,
- "write_off_cost_center": null,
- "write_off_outstanding_amount_automatically": 0
- }
-]
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.py b/erpnext/accounts/report/tax_detail/test_tax_detail.py
deleted file mode 100644
index 55ae32a2c6a..00000000000
--- a/erpnext/accounts/report/tax_detail/test_tax_detail.py
+++ /dev/null
@@ -1,220 +0,0 @@
-import datetime
-import json
-import os
-import unittest
-
-import frappe
-from frappe.utils import (
- add_to_date,
- get_first_day,
- get_last_day,
- get_year_ending,
- get_year_start,
- getdate,
-)
-
-from .tax_detail import filter_match, save_custom_report
-
-
-class TestTaxDetail(unittest.TestCase):
- def load_testdocs(self):
- from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
-
- datapath, _ = os.path.splitext(os.path.realpath(__file__))
- with open(datapath + ".json") as fp:
- docs = json.load(fp)
-
- now = getdate()
- self.from_date = get_first_day(now)
- self.to_date = get_last_day(now)
-
- try:
- get_fiscal_year(now, company="_T")
- except FiscalYearError:
- docs = [
- {
- "companies": [
- {
- "company": "_T",
- "parent": "_Test Fiscal",
- "parentfield": "companies",
- "parenttype": "Fiscal Year",
- }
- ],
- "doctype": "Fiscal Year",
- "year": "_Test Fiscal",
- "year_end_date": get_year_ending(now),
- "year_start_date": get_year_start(now),
- },
- *docs,
- ]
-
- docs = [
- {
- "abbr": "_T",
- "company_name": "_T",
- "country": "United Kingdom",
- "default_currency": "GBP",
- "doctype": "Company",
- "name": "_T",
- },
- *docs,
- ]
-
- for doc in docs:
- try:
- db_doc = frappe.get_doc(doc)
- if "Invoice" in db_doc.doctype:
- db_doc.due_date = add_to_date(now, days=1)
- db_doc.insert()
- # Create GL Entries:
- db_doc.submit()
- else:
- db_doc.insert(ignore_if_duplicate=True)
- except frappe.exceptions.DuplicateEntryError:
- pass
-
- def load_defcols(self):
- self.company = frappe.get_doc("Company", "_T")
- custom_report = frappe.get_doc("Report", "Tax Detail")
- self.default_columns, _ = custom_report.run_query_report(
- filters={
- "from_date": "2021-03-01",
- "to_date": "2021-03-31",
- "company": self.company.name,
- "mode": "run",
- "report_name": "Tax Detail",
- },
- user=frappe.session.user,
- )
-
- def rm_testdocs(self):
- "Remove the Company and all data"
- from erpnext.setup.doctype.company.company import create_transaction_deletion_request
-
- create_transaction_deletion_request(self.company.name)
-
- def test_report(self):
- self.load_testdocs()
- self.load_defcols()
- report_name = save_custom_report(
- "Tax Detail",
- "_Test Tax Detail",
- json.dumps(
- {
- "columns": self.default_columns,
- "sections": {
- "Box1": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Sales"}}},
- "Box2": {"Filter0": {"type": "filter", "filters": {"4": "Acquisition"}}},
- "Box3": {"Box1": {"type": "section"}, "Box2": {"type": "section"}},
- "Box4": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Purchases"}}},
- "Box5": {"Box3": {"type": "section"}, "Box4": {"type": "section"}},
- "Box6": {"Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales"}}},
- "Box7": {"Filter0": {"type": "filter", "filters": {"2": "Expense", "3": "!=Tax"}}},
- "Box8": {
- "Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales", "12": "EU"}}
- },
- "Box9": {
- "Filter0": {
- "type": "filter",
- "filters": {"2": "Expense", "3": "!=Tax", "12": "EU"},
- }
- },
- },
- "show_detail": 1,
- }
- ),
- )
- data = frappe.desk.query_report.run(
- report_name,
- filters={
- "from_date": self.from_date,
- "to_date": self.to_date,
- "company": self.company.name,
- "mode": "run",
- "report_name": report_name,
- },
- user=frappe.session.user,
- )
-
- self.assertListEqual(data.get("columns"), self.default_columns)
- expected = (
- ("Box1", 43.25),
- ("Box2", 0.0),
- ("Box3", 43.25),
- ("Box4", -85.28),
- ("Box5", -42.03),
- ("Box6", 825.0),
- ("Box7", -426.40),
- ("Box8", 0.0),
- ("Box9", 0.0),
- )
- exrow = iter(expected)
- for row in data.get("result"):
- if row.get("voucher_no") and not row.get("posting_date"):
- label, value = next(exrow)
- self.assertDictEqual(row, {"voucher_no": label, "amount": value})
- self.assertListEqual(
- data.get("report_summary"),
- [{"label": label, "datatype": "Currency", "value": value} for label, value in expected],
- )
-
- self.rm_testdocs()
-
- def test_filter_match(self):
- # None - treated as -inf number except range
- self.assertTrue(filter_match(None, "!="))
- self.assertTrue(filter_match(None, "<"))
- self.assertTrue(filter_match(None, "3.4"))
- self.assertFalse(filter_match(None, " <"))
- self.assertFalse(filter_match(None, "ew"))
- self.assertFalse(filter_match(None, " "))
- self.assertFalse(filter_match(None, " f :"))
-
- # Numbers
- self.assertTrue(filter_match(3.4, "3.4"))
- self.assertTrue(filter_match(3.4, ".4"))
- self.assertTrue(filter_match(3.4, "3"))
- self.assertTrue(filter_match(-3.4, "< -3"))
- self.assertTrue(filter_match(-3.4, "> -4"))
- self.assertTrue(filter_match(3.4, "= 3.4 "))
- self.assertTrue(filter_match(3.4, "!=4.5"))
- self.assertTrue(filter_match(3.4, " 3 : 4 "))
- self.assertTrue(filter_match(0.0, " : "))
- self.assertFalse(filter_match(3.4, "=4.5"))
- self.assertFalse(filter_match(3.4, " = 3.4 "))
- self.assertFalse(filter_match(3.4, "!=3.4"))
- self.assertFalse(filter_match(3.4, ">6"))
- self.assertFalse(filter_match(3.4, "<-4.5"))
- self.assertFalse(filter_match(3.4, "4.5"))
- self.assertFalse(filter_match(3.4, "5:9"))
-
- # Strings
- self.assertTrue(filter_match("ACC-SINV-2021-00001", "SINV"))
- self.assertTrue(filter_match("ACC-SINV-2021-00001", "sinv"))
- self.assertTrue(filter_match("ACC-SINV-2021-00001", "-2021"))
- self.assertTrue(filter_match(" ACC-SINV-2021-00001", " acc"))
- self.assertTrue(filter_match("ACC-SINV-2021-00001", "=2021"))
- self.assertTrue(filter_match("ACC-SINV-2021-00001", "!=zz"))
- self.assertTrue(filter_match("ACC-SINV-2021-00001", "< zzz "))
- self.assertTrue(filter_match("ACC-SINV-2021-00001", " : sinv "))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", " sinv :"))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", " acc"))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", "= 2021 "))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", "!=sinv"))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", " >"))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", ">aa"))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", " <"))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", "< "))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", " ="))
- self.assertFalse(filter_match("ACC-SINV-2021-00001", "="))
-
- # Date - always match
- self.assertTrue(filter_match(datetime.date(2021, 3, 19), " kdsjkldfs "))
diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py
index 9496a1aa664..c2e10f8fd47 100644
--- a/erpnext/accounts/test/test_reports.py
+++ b/erpnext/accounts/test/test_reports.py
@@ -29,10 +29,6 @@ REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
("Sales Register", {}),
("Sales Register", {"item_group": "All Item Groups"}),
("Purchase Register", {}),
- (
- "Tax Detail",
- {"mode": "run", "report_name": "Tax Detail"},
- ),
]
OPTIONAL_FILTERS = {}
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 6a5914ddbe0..15748868510 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -415,9 +415,16 @@ class AccountsController(TransactionBase):
)
def validate_invoice_documents_schedule(self):
- if self.is_return:
+ if (
+ self.is_return
+ or (self.doctype == "Purchase Invoice" and self.is_paid)
+ or (self.doctype == "Sales Invoice" and self.is_pos)
+ or self.get("is_opening") == "Yes"
+ ):
self.payment_terms_template = ""
self.payment_schedule = []
+
+ if self.is_return:
return
self.validate_payment_schedule_dates()
@@ -2196,7 +2203,7 @@ class AccountsController(TransactionBase):
frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates))
def validate_payment_schedule_amount(self):
- if self.doctype == "Sales Invoice" and self.is_pos:
+ if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes":
return
party_account_currency = self.get("party_account_currency")
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index f7bf5634878..5b6a7b1506b 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -18,7 +18,7 @@ from erpnext.controllers.accounts_controller import (
validate_inclusive_tax,
validate_taxes_and_charges,
)
-from erpnext.stock.get_item_details import _get_item_tax_template
+from erpnext.stock.get_item_details import _get_item_tax_template, get_item_tax_map
from erpnext.utilities.regional import temporary_flag
@@ -67,6 +67,7 @@ class calculate_taxes_and_totals:
self.validate_conversion_rate()
self.calculate_item_values()
self.validate_item_tax_template()
+ self.update_item_tax_map()
self.initialize_taxes()
self.determine_exclusive_rate()
self.calculate_net_total()
@@ -130,6 +131,14 @@ class calculate_taxes_and_totals:
)
)
+ def update_item_tax_map(self):
+ for item in self.doc.items:
+ item.item_tax_rate = get_item_tax_map(
+ company=self.doc.get("company"),
+ item_tax_template=item.item_tax_template,
+ as_json=True,
+ )
+
def validate_conversion_rate(self):
# validate conversion rate
company_currency = erpnext.get_company_currency(self.doc.company)
diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py
index 3e20f310ff9..f9c3154a63b 100644
--- a/erpnext/manufacturing/report/test_reports.py
+++ b/erpnext/manufacturing/report/test_reports.py
@@ -4,61 +4,67 @@ import frappe
from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
-DEFAULT_FILTERS = {
- "company": "_Test Company",
- "from_date": "2010-01-01",
- "to_date": "2030-01-01",
- "warehouse": "_Test Warehouse - _TC",
-}
-
-
-REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
- ("BOM Explorer", {"bom": frappe.get_last_doc("BOM").name}),
- ("BOM Operations Time", {}),
- ("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}),
- ("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}),
- ("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}),
- ("Downtime Analysis", {}),
- (
- "Exponential Smoothing Forecasting",
- {
- "based_on_document": "Sales Order",
- "based_on_field": "Qty",
- "no_of_years": 3,
- "periodicity": "Yearly",
- "smoothing_constant": 0.3,
- },
- ),
- ("Job Card Summary", {"fiscal_year": "2021-2022"}),
- ("Production Analytics", {"range": "Monthly"}),
- ("Quality Inspection Summary", {}),
- ("Process Loss Report", {}),
- ("Work Order Stock Report", {}),
- ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}),
-]
-
-
-if frappe.db.a_row_exists("Production Plan"):
- REPORT_FILTER_TEST_CASES.append(
- ("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name})
- )
-
-OPTIONAL_FILTERS = {
- "warehouse": "_Test Warehouse - _TC",
- "item": "_Test Item",
- "item_group": "_Test Item Group",
-}
-
class TestManufacturingReports(unittest.TestCase):
+ def setUp(self):
+ self.setup_default_filters()
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+ def setup_default_filters(self):
+ self.last_bom = frappe.get_last_doc("BOM").name
+ self.DEFAULT_FILTERS = {
+ "company": "_Test Company",
+ "from_date": "2010-01-01",
+ "to_date": "2030-01-01",
+ "warehouse": "_Test Warehouse - _TC",
+ }
+
+ self.REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
+ ("BOM Explorer", {"bom": self.last_bom}),
+ ("BOM Operations Time", {}),
+ ("BOM Stock Calculated", {"bom": self.last_bom, "qty_to_make": 2}),
+ ("BOM Stock Report", {"bom": self.last_bom, "qty_to_produce": 2}),
+ ("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}),
+ ("Downtime Analysis", {}),
+ (
+ "Exponential Smoothing Forecasting",
+ {
+ "based_on_document": "Sales Order",
+ "based_on_field": "Qty",
+ "no_of_years": 3,
+ "periodicity": "Yearly",
+ "smoothing_constant": 0.3,
+ },
+ ),
+ ("Job Card Summary", {"fiscal_year": "2021-2022"}),
+ ("Production Analytics", {"range": "Monthly"}),
+ ("Quality Inspection Summary", {}),
+ ("Process Loss Report", {}),
+ ("Work Order Stock Report", {}),
+ ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}),
+ ]
+
+ if frappe.db.a_row_exists("Production Plan"):
+ self.REPORT_FILTER_TEST_CASES.append(
+ ("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name})
+ )
+
+ self.OPTIONAL_FILTERS = {
+ "warehouse": "_Test Warehouse - _TC",
+ "item": "_Test Item",
+ "item_group": "_Test Item Group",
+ }
+
def test_execute_all_manufacturing_reports(self):
"""Test that all script report in manufacturing modules are executable with supported filters"""
- for report, filter in REPORT_FILTER_TEST_CASES:
+ for report, filter in self.REPORT_FILTER_TEST_CASES:
with self.subTest(report=report):
execute_script_report(
report_name=report,
module="Manufacturing",
filters=filter,
- default_filters=DEFAULT_FILTERS,
- optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
+ default_filters=self.DEFAULT_FILTERS,
+ optional_filters=self.OPTIONAL_FILTERS if filter.get("_optional") else None,
)
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 82e1d518362..8ca72d55c23 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -828,13 +828,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
);
}
- if(!this.frm.doc.is_return){
- this.frm.doc.payments.find(payment => {
- if (payment.default) {
- payment.amount = total_amount_to_pay;
- }
- });
- }
+ this.frm.doc.payments.find(payment => {
+ if (payment.default) {
+ payment.amount = total_amount_to_pay;
+ } else {
+ payment.amount = 0
+ }
+ });
this.frm.refresh_fields();
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 9f8aa84f836..5d282779e75 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -713,6 +713,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
validate() {
+ this.apply_pricing_rule()
this.calculate_taxes_and_totals(false);
}
@@ -841,6 +842,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
transaction_date() {
+ this.apply_pricing_rule()
if (this.frm.doc.transaction_date) {
this.frm.transaction_date = this.frm.doc.transaction_date;
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
@@ -849,6 +851,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
posting_date() {
var me = this;
+ me.apply_pricing_rule()
if (this.frm.doc.posting_date) {
this.frm.posting_date = this.frm.doc.posting_date;
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 86aa8dd0961..35622a77e2c 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -284,6 +284,7 @@ erpnext.PointOfSale.Controller = class {
edit_cart: () => this.payment.edit_cart(),
customer_details_updated: (details) => {
+ this.item_selector.load_items_data();
this.customer_details = details;
// will add/remove LP payment method
this.payment.render_loyalty_points_payment_mode();
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 7e16280eb37..85609e1a8ed 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -235,7 +235,7 @@ erpnext.PointOfSale.Payment = class {
frappe.ui.form.on("Sales Invoice Payment", "amount", (frm, cdt, cdn) => {
// for setting correct amount after loyalty points are redeemed
const default_mop = locals[cdt][cdn];
- const mode = default_mop.mode_of_payment.replace(/ +/g, "_").toLowerCase();
+ const mode = this.sanitize_mode_of_payment(default_mop.mode_of_payment);
if (this[`${mode}_control`] && this[`${mode}_control`].get_value() != default_mop.amount) {
this[`${mode}_control`].set_value(default_mop.amount);
}
@@ -383,7 +383,7 @@ erpnext.PointOfSale.Payment = class {
this.$payment_modes.html(
`${payments
.map((p, i) => {
- const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
+ const mode = this.sanitize_mode_of_payment(p.mode_of_payment);
const payment_type = p.type;
const margin = i % 2 === 0 ? "pr-2" : "pl-2";
const amount = p.amount > 0 ? format_currency(p.amount, currency) : "";
@@ -402,7 +402,7 @@ erpnext.PointOfSale.Payment = class {
);
payments.forEach((p) => {
- const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
+ const mode = this.sanitize_mode_of_payment(p.mode_of_payment);
const me = this;
this[`${mode}_control`] = frappe.ui.form.make_control({
df: {
@@ -437,7 +437,7 @@ erpnext.PointOfSale.Payment = class {
const doc = this.events.get_frm().doc;
const payments = doc.payments;
payments.forEach((p) => {
- const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase();
+ const mode = this.sanitize_mode_of_payment(p.mode_of_payment);
if (p.default) {
setTimeout(() => {
this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click();
@@ -607,4 +607,12 @@ erpnext.PointOfSale.Payment = class {
toggle_component(show) {
show ? this.$component.css("display", "flex") : this.$component.css("display", "none");
}
+
+ sanitize_mode_of_payment(mode_of_payment) {
+ return mode_of_payment
+ .replace(/ +/g, "_")
+ .replace(/[^\p{L}\p{N}_-]/gu, "")
+ .replace(/^[^_a-zA-Z\p{L}]+/u, "")
+ .toLowerCase();
+ }
};
diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
index 70aa57f4c12..eda768b1c56 100644
--- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
+++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
@@ -44,7 +44,7 @@ class ClosingStockBalance(Document):
& (
(table.from_date.between(self.from_date, self.to_date))
| (table.to_date.between(self.from_date, self.to_date))
- | ((table.from_date >= self.from_date) & (table.to_date >= self.to_date))
+ | ((self.from_date >= table.from_date) & (table.from_date >= self.to_date))
)
)
)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index c078a426c42..79bfc480d6a 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -640,7 +640,10 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
if tax.valid_from or tax.maximum_net_rate:
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date
- validation_date = args.get("bill_date") or args.get("transaction_date")
+
+ validation_date = (
+ args.get("bill_date") or args.get("posting_date") or args.get("transaction_date")
+ )
if getdate(tax.valid_from) <= getdate(validation_date) and is_within_valid_range(args, tax):
taxes_with_validity.append(tax)
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index 0060ab39cc9..42f46ac8d12 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -12,14 +12,14 @@
{% endif %}
{% for d in doc.taxes %}
- {% if d.base_tax_amount %}
+ {% if d.tax_amount %}
{{ d.description }}
- {{ doc.get_formatted("net_total") }}
+ {{ d.get_formatted("tax_amount") }}