mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-13 02:01:21 +00:00
Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-31412
This commit is contained in:
1
.github/helper/.flake8_strict
vendored
1
.github/helper/.flake8_strict
vendored
@@ -66,6 +66,7 @@ ignore =
|
|||||||
F841,
|
F841,
|
||||||
E713,
|
E713,
|
||||||
E712,
|
E712,
|
||||||
|
B023
|
||||||
|
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
|
|||||||
36
CODEOWNERS
36
CODEOWNERS
@@ -3,33 +3,35 @@
|
|||||||
# These owners will be the default owners for everything in
|
# These owners will be the default owners for everything in
|
||||||
# the repo. Unless a later match takes precedence,
|
# the repo. Unless a later match takes precedence,
|
||||||
|
|
||||||
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
|
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/assets/ @nextchamp-saqib @deepeshgarg007
|
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/erpnext_integrations/ @nextchamp-saqib
|
erpnext/erpnext_integrations/ @nextchamp-saqib
|
||||||
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
||||||
erpnext/regional @nextchamp-saqib @deepeshgarg007
|
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/selling @nextchamp-saqib @deepeshgarg007
|
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||||
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
||||||
pos* @nextchamp-saqib
|
pos* @nextchamp-saqib
|
||||||
|
|
||||||
erpnext/buying/ @marination @rohitwaghchaure @ankush
|
erpnext/buying/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/e_commerce/ @marination
|
erpnext/e_commerce/ @marination
|
||||||
erpnext/maintenance/ @marination @rohitwaghchaure
|
erpnext/maintenance/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
|
erpnext/manufacturing/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/portal/ @marination
|
erpnext/portal/ @marination
|
||||||
erpnext/quality_management/ @marination @rohitwaghchaure
|
erpnext/quality_management/ @marination @rohitwaghchaure @s-aga-r
|
||||||
erpnext/shopping_cart/ @marination
|
erpnext/shopping_cart/ @marination
|
||||||
erpnext/stock/ @marination @rohitwaghchaure @ankush
|
erpnext/stock/ @marination @rohitwaghchaure @s-aga-r
|
||||||
|
|
||||||
erpnext/crm/ @ruchamahabal @pateljannat
|
erpnext/crm/ @NagariaHussain
|
||||||
erpnext/education/ @ruchamahabal @pateljannat
|
erpnext/education/ @rutwikhdev
|
||||||
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
|
erpnext/healthcare/ @chillaranand
|
||||||
erpnext/hr/ @ruchamahabal @pateljannat
|
erpnext/hr/ @ruchamahabal
|
||||||
erpnext/non_profit/ @ruchamahabal
|
erpnext/non_profit/ @ruchamahabal
|
||||||
erpnext/payroll @ruchamahabal @pateljannat
|
erpnext/payroll @ruchamahabal
|
||||||
erpnext/projects/ @ruchamahabal @pateljannat
|
erpnext/projects/ @ruchamahabal
|
||||||
|
|
||||||
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||||
|
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination rohitwaghchaure
|
||||||
|
erpnext/public/ @nextchamp-saqib @marination
|
||||||
|
|
||||||
.github/ @surajshetty3416 @ankush
|
.github/ @ankush
|
||||||
requirements.txt @gavindsouza
|
requirements.txt @gavindsouza @ankush
|
||||||
|
|||||||
@@ -476,7 +476,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
});
|
});
|
||||||
this.frm.trigger("calculate_timesheet_totals");
|
this.frm.trigger("calculate_timesheet_totals");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
is_cash_or_non_trade_discount() {
|
||||||
|
this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
|
||||||
|
if (!this.frm.doc.is_cash_or_non_trade_discount) {
|
||||||
|
this.frm.set_value("additional_discount_account", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// for backward compatibility: combine new and previous states
|
// for backward compatibility: combine new and previous states
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
"loyalty_redemption_cost_center",
|
"loyalty_redemption_cost_center",
|
||||||
"section_break_49",
|
"section_break_49",
|
||||||
"apply_discount_on",
|
"apply_discount_on",
|
||||||
|
"is_cash_or_non_trade_discount",
|
||||||
"base_discount_amount",
|
"base_discount_amount",
|
||||||
"additional_discount_account",
|
"additional_discount_account",
|
||||||
"column_break_51",
|
"column_break_51",
|
||||||
@@ -1790,8 +1791,6 @@
|
|||||||
"width": "50%"
|
"width": "50%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "sales_partner.commission_rate",
|
|
||||||
"fetch_if_empty": 1,
|
|
||||||
"fieldname": "commission_rate",
|
"fieldname": "commission_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@@ -1990,7 +1989,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "additional_discount_account",
|
"fieldname": "additional_discount_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Additional Discount Account",
|
"label": "Discount Account",
|
||||||
"options": "Account"
|
"options": "Account"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2028,6 +2027,13 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Amount Eligible for Commission",
|
"label": "Amount Eligible for Commission",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval: doc.apply_discount_on == \"Grand Total\"",
|
||||||
|
"fieldname": "is_cash_or_non_trade_discount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Cash or Non Trade Discount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@@ -2040,7 +2046,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-06-10 03:52:51.409913",
|
"modified": "2022-06-16 16:22:44.870575",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -1040,7 +1040,7 @@ class SalesInvoice(SellingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if grand_total and not self.is_internal_transfer():
|
if grand_total and not self.is_internal_transfer():
|
||||||
# Didnot use base_grand_total to book rounding loss gle
|
# Did not use base_grand_total to book rounding loss gle
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
@@ -1065,6 +1065,22 @@ class SalesInvoice(SellingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": self.additional_discount_account,
|
||||||
|
"against": self.debit_to,
|
||||||
|
"debit": self.base_discount_amount,
|
||||||
|
"debit_in_account_currency": self.discount_amount,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
"project": self.project,
|
||||||
|
},
|
||||||
|
self.currency,
|
||||||
|
item=self,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
for tax in self.get("taxes"):
|
for tax in self.get("taxes"):
|
||||||
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting)
|
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting)
|
||||||
@@ -2163,6 +2179,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
source_document_warehouse_field = "from_warehouse"
|
source_document_warehouse_field = "from_warehouse"
|
||||||
target_document_warehouse_field = "target_warehouse"
|
target_document_warehouse_field = "target_warehouse"
|
||||||
|
|
||||||
|
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||||
|
|
||||||
validate_inter_company_transaction(source_doc, doctype)
|
validate_inter_company_transaction(source_doc, doctype)
|
||||||
details = get_inter_company_details(source_doc, doctype)
|
details = get_inter_company_details(source_doc, doctype)
|
||||||
|
|
||||||
@@ -2227,12 +2245,17 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
shipping_address_name=target_doc.shipping_address_name,
|
shipping_address_name=target_doc.shipping_address_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_item(source, target, source_parent):
|
||||||
|
target.qty = flt(source.qty) - received_items.get(source.name, 0.0)
|
||||||
|
|
||||||
item_field_map = {
|
item_field_map = {
|
||||||
"doctype": target_doctype + " Item",
|
"doctype": target_doctype + " Item",
|
||||||
"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],
|
"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"rate": "rate",
|
"rate": "rate",
|
||||||
},
|
},
|
||||||
|
"postprocess": update_item,
|
||||||
|
"condition": lambda doc: doc.qty > 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||||
@@ -2270,6 +2293,28 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
|
||||||
|
def get_received_items(reference_name, doctype, reference_fieldname):
|
||||||
|
target_doctypes = frappe.get_all(
|
||||||
|
doctype,
|
||||||
|
filters={"inter_company_invoice_reference": reference_name, "docstatus": 1},
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if target_doctypes:
|
||||||
|
target_doctypes = list(target_doctypes[0])
|
||||||
|
|
||||||
|
received_items_map = frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
doctype + " Item",
|
||||||
|
filters={"parent": ("in", target_doctypes)},
|
||||||
|
fields=[reference_fieldname, "qty"],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return received_items_map
|
||||||
|
|
||||||
|
|
||||||
def set_purchase_references(doc):
|
def set_purchase_references(doc):
|
||||||
# add internal PO or PR links if any
|
# add internal PO or PR links if any
|
||||||
if doc.is_internal_transfer():
|
if doc.is_internal_transfer():
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ def get_data():
|
|||||||
"Payment Request": "reference_name",
|
"Payment Request": "reference_name",
|
||||||
"Sales Invoice": "return_against",
|
"Sales Invoice": "return_against",
|
||||||
"Auto Repeat": "reference_document",
|
"Auto Repeat": "reference_document",
|
||||||
|
"Purchase Invoice": "inter_company_invoice_reference",
|
||||||
},
|
},
|
||||||
"internal_links": {
|
"internal_links": {
|
||||||
"Sales Order": ["items", "sales_order"],
|
"Sales Order": ["items", "sales_order"],
|
||||||
@@ -30,5 +31,6 @@ def get_data():
|
|||||||
{"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]},
|
{"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]},
|
||||||
{"label": _("Returns"), "items": ["Sales Invoice"]},
|
{"label": _("Returns"), "items": ["Sales Invoice"]},
|
||||||
{"label": _("Subscription"), "items": ["Auto Repeat"]},
|
{"label": _("Subscription"), "items": ["Auto Repeat"]},
|
||||||
|
{"label": _("Internal Transfers"), "items": ["Purchase Invoice"]},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2620,6 +2620,63 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
einvoice = make_einvoice(si)
|
einvoice = make_einvoice(si)
|
||||||
validate_totals(einvoice)
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
def test_einvoice_discounts(self):
|
||||||
|
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||||
|
|
||||||
|
# Normal Itemized Discount
|
||||||
|
si = get_sales_invoice_for_e_invoice()
|
||||||
|
si.apply_discount_on = ""
|
||||||
|
si.items[0].discount_amount = 4000
|
||||||
|
si.items[1].discount_amount = 300
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000)
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["Discount"], 300)
|
||||||
|
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
|
||||||
|
|
||||||
|
# Invoice Discount on net total
|
||||||
|
si = get_sales_invoice_for_e_invoice()
|
||||||
|
si.apply_discount_on = "Net Total"
|
||||||
|
si.discount_amount = 400
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
self.assertEqual(einvoice["ItemList"][0]["Discount"], 316.83)
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17)
|
||||||
|
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
|
||||||
|
|
||||||
|
# Invoice Discount on grand total (Itemized Discount)
|
||||||
|
si = get_sales_invoice_for_e_invoice()
|
||||||
|
si.apply_discount_on = "Grand Total"
|
||||||
|
si.discount_amount = 400
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
self.assertEqual(einvoice["ItemList"][0]["Discount"], 268.5)
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48)
|
||||||
|
self.assertEqual(einvoice["ValDtls"]["Discount"], 0)
|
||||||
|
|
||||||
|
# Invoice Discount on grand total (Cash/Non-Trade Discount)
|
||||||
|
si = get_sales_invoice_for_e_invoice()
|
||||||
|
si.apply_discount_on = "Grand Total"
|
||||||
|
si.is_cash_or_non_trade_discount = 1
|
||||||
|
si.discount_amount = 400
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
self.assertEqual(einvoice["ItemList"][0]["Discount"], 0)
|
||||||
|
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
|
||||||
|
self.assertEqual(einvoice["ValDtls"]["Discount"], 400)
|
||||||
|
|
||||||
def test_item_tax_net_range(self):
|
def test_item_tax_net_range(self):
|
||||||
item = create_item("T Shirt")
|
item = create_item("T Shirt")
|
||||||
|
|
||||||
|
|||||||
@@ -181,12 +181,20 @@ class RequestforQuotation(BuyingController):
|
|||||||
doc_args = self.as_dict()
|
doc_args = self.as_dict()
|
||||||
doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
|
doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
|
||||||
|
|
||||||
|
# Get Contact Full Name
|
||||||
|
supplier_name = None
|
||||||
|
if data.get("contact"):
|
||||||
|
contact_name = frappe.db.get_value(
|
||||||
|
"Contact", data.get("contact"), ["first_name", "middle_name", "last_name"]
|
||||||
|
)
|
||||||
|
supplier_name = (" ").join(x for x in contact_name if x) # remove any blank values
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
"update_password_link": update_password_link,
|
"update_password_link": update_password_link,
|
||||||
"message": frappe.render_template(self.message_for_supplier, doc_args),
|
"message": frappe.render_template(self.message_for_supplier, doc_args),
|
||||||
"rfq_link": rfq_link,
|
"rfq_link": rfq_link,
|
||||||
"user_fullname": full_name,
|
"user_fullname": full_name,
|
||||||
"supplier_name": data.get("supplier_name"),
|
"supplier_name": supplier_name or data.get("supplier_name"),
|
||||||
"supplier_salutation": self.salutation or "Dear Mx.",
|
"supplier_salutation": self.salutation or "Dear Mx.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
raise_error_if_no_rate=False,
|
raise_error_if_no_rate=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
rate = flt(outgoing_rate * d.conversion_factor, d.precision("rate"))
|
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||||
else:
|
else:
|
||||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
|
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
|
||||||
|
|
||||||
|
|||||||
@@ -500,6 +500,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
else:
|
else:
|
||||||
self.doc.grand_total = flt(self.doc.net_total)
|
self.doc.grand_total = flt(self.doc.net_total)
|
||||||
|
|
||||||
|
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||||
|
self.doc.grand_total -= self.doc.discount_amount
|
||||||
|
|
||||||
if self.doc.get("taxes"):
|
if self.doc.get("taxes"):
|
||||||
self.doc.total_taxes_and_charges = flt(
|
self.doc.total_taxes_and_charges = flt(
|
||||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
||||||
@@ -594,6 +597,12 @@ class calculate_taxes_and_totals(object):
|
|||||||
if not self.doc.apply_discount_on:
|
if not self.doc.apply_discount_on:
|
||||||
frappe.throw(_("Please select Apply Discount On"))
|
frappe.throw(_("Please select Apply Discount On"))
|
||||||
|
|
||||||
|
if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
|
||||||
|
"is_cash_or_non_trade_discount"
|
||||||
|
):
|
||||||
|
self.discount_amount_applied = True
|
||||||
|
return
|
||||||
|
|
||||||
self.doc.base_discount_amount = flt(
|
self.doc.base_discount_amount = flt(
|
||||||
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -368,3 +368,4 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
|||||||
erpnext.patches.v13_0.update_employee_advance_status
|
erpnext.patches.v13_0.update_employee_advance_status
|
||||||
erpnext.patches.v13_0.job_card_status_on_hold
|
erpnext.patches.v13_0.job_card_status_on_hold
|
||||||
erpnext.patches.v13_0.add_cost_center_in_loans
|
erpnext.patches.v13_0.add_cost_center_in_loans
|
||||||
|
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import click
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if not frappe.db.exists("Company", {"country": "India"}):
|
||||||
|
return
|
||||||
|
|
||||||
|
click.secho(
|
||||||
|
"India-specific regional features have been moved to a separate app"
|
||||||
|
" and will be removed from ERPNext in Version 14."
|
||||||
|
" Please install India Compliance after upgrading to Version 14:\n"
|
||||||
|
"https://github.com/resilient-tech/india-compliance",
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
@@ -1223,9 +1223,25 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
is_a_mapped_document(item) {
|
||||||
|
const mapped_item_field_map = {
|
||||||
|
"Delivery Note Item": ["si_detail", "so_detail", "dn_detail"],
|
||||||
|
"Sales Invoice Item": ["dn_detail", "so_detail", "sales_invoice_item"],
|
||||||
|
"Purchase Receipt Item": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
|
||||||
|
"Purchase Invoice Item": ["purchase_order_item", "pr_detail", "po_detail"],
|
||||||
|
};
|
||||||
|
const mappped_fields = mapped_item_field_map[item.doctype] || [];
|
||||||
|
|
||||||
|
return mappped_fields
|
||||||
|
.map((field) => item[field])
|
||||||
|
.filter(Boolean).length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
batch_no: function(doc, cdt, cdn) {
|
batch_no: function(doc, cdt, cdn) {
|
||||||
let item = frappe.get_doc(cdt, cdn);
|
let item = frappe.get_doc(cdt, cdn);
|
||||||
this.apply_price_list(item, true);
|
if (!this.is_a_mapped_document(item)) {
|
||||||
|
this.apply_price_list(item, true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle_conversion_factor: function(item) {
|
toggle_conversion_factor: function(item) {
|
||||||
|
|||||||
@@ -272,14 +272,14 @@ def get_item_list(invoice):
|
|||||||
item.description = sanitize_for_json(d.item_name)
|
item.description = sanitize_for_json(d.item_name)
|
||||||
|
|
||||||
item.qty = abs(item.qty)
|
item.qty = abs(item.qty)
|
||||||
if flt(item.qty) != 0.0:
|
|
||||||
item.unit_rate = abs(item.taxable_value / item.qty)
|
|
||||||
else:
|
|
||||||
item.unit_rate = abs(item.taxable_value)
|
|
||||||
item.gross_amount = abs(item.taxable_value)
|
|
||||||
item.taxable_value = abs(item.taxable_value)
|
|
||||||
item.discount_amount = 0
|
|
||||||
|
|
||||||
|
if invoice.get("apply_discount_on"):
|
||||||
|
item.discount_amount = item.base_amount - item.base_net_amount
|
||||||
|
|
||||||
|
item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty
|
||||||
|
|
||||||
|
item.gross_amount = abs(item.taxable_value) + item.discount_amount
|
||||||
|
item.taxable_value = abs(item.taxable_value)
|
||||||
item.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N"
|
item.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N"
|
||||||
item.serial_no = ""
|
item.serial_no = ""
|
||||||
|
|
||||||
@@ -353,7 +353,14 @@ def update_item_taxes(invoice, item):
|
|||||||
def get_invoice_value_details(invoice):
|
def get_invoice_value_details(invoice):
|
||||||
invoice_value_details = frappe._dict(dict())
|
invoice_value_details = frappe._dict(dict())
|
||||||
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get("items")]))
|
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get("items")]))
|
||||||
invoice_value_details.invoice_discount_amt = 0
|
if (
|
||||||
|
invoice.apply_discount_on == "Grand Total"
|
||||||
|
and invoice.discount_amount
|
||||||
|
and invoice.get("is_cash_or_non_trade_discount")
|
||||||
|
):
|
||||||
|
invoice_value_details.invoice_discount_amt = invoice.discount_amount
|
||||||
|
else:
|
||||||
|
invoice_value_details.invoice_discount_amt = 0
|
||||||
|
|
||||||
invoice_value_details.round_off = invoice.base_rounding_adjustment
|
invoice_value_details.round_off = invoice.base_rounding_adjustment
|
||||||
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(
|
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(
|
||||||
|
|||||||
@@ -1061,8 +1061,16 @@ def update_taxable_values(doc, method):
|
|||||||
considered_rows.append(prev_row_id)
|
considered_rows.append(prev_row_id)
|
||||||
|
|
||||||
for item in doc.get("items"):
|
for item in doc.get("items"):
|
||||||
proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
|
if (
|
||||||
total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
|
doc.apply_discount_on == "Grand Total"
|
||||||
|
and doc.discount_amount
|
||||||
|
and doc.get("is_cash_or_non_trade_discount")
|
||||||
|
):
|
||||||
|
proportionate_value = item.base_amount if doc.base_total else item.qty
|
||||||
|
total_value = doc.base_total if doc.base_total else doc.total_qty
|
||||||
|
else:
|
||||||
|
proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
|
||||||
|
total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
|
||||||
|
|
||||||
applicable_charges = flt(
|
applicable_charges = flt(
|
||||||
flt(
|
flt(
|
||||||
|
|||||||
@@ -468,6 +468,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
and not d.is_fixed_asset
|
and not d.is_fixed_asset
|
||||||
and flt(d.qty)
|
and flt(d.qty)
|
||||||
and provisional_accounting_for_non_stock_items
|
and provisional_accounting_for_non_stock_items
|
||||||
|
and d.get("provisional_expense_account")
|
||||||
):
|
):
|
||||||
self.add_provisional_gl_entry(
|
self.add_provisional_gl_entry(
|
||||||
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.permissions import add_user_permission, remove_user_permission
|
from frappe.permissions import add_user_permission, remove_user_permission
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import flt, nowdate, nowtime
|
from frappe.utils import add_days, flt, nowdate, nowtime
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
@@ -1414,6 +1414,138 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
self.assertEqual(se.items[0].item_name, item.item_name)
|
self.assertEqual(se.items[0].item_name, item.item_name)
|
||||||
self.assertEqual(se.items[0].stock_uom, item.stock_uom)
|
self.assertEqual(se.items[0].stock_uom, item.stock_uom)
|
||||||
|
|
||||||
|
def test_reposting_for_depedent_warehouse(self):
|
||||||
|
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
|
# Inward at WH1 warehouse (Component)
|
||||||
|
# 1st Repack (Component (WH1) - Subcomponent (WH2))
|
||||||
|
# 2nd Repack (Subcomponent (WH2) - FG Item (WH3))
|
||||||
|
# Material Transfer of FG Item -> WH 3 -> WH2 -> Wh1 (Two transfer entries)
|
||||||
|
# Backdated transction which should update valuation rate in repack as well trasfer entries
|
||||||
|
|
||||||
|
for item_code in ["FG Item 1", "Sub Component 1", "Component 1"]:
|
||||||
|
create_item(item_code)
|
||||||
|
|
||||||
|
for warehouse in ["WH 1", "WH 2", "WH 3"]:
|
||||||
|
create_warehouse(warehouse)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code="Component 1",
|
||||||
|
rate=100,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
qty=10,
|
||||||
|
to_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -10),
|
||||||
|
)
|
||||||
|
|
||||||
|
repack1 = make_stock_entry(
|
||||||
|
item_code="Component 1",
|
||||||
|
purpose="Repack",
|
||||||
|
do_not_save=True,
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -9),
|
||||||
|
)
|
||||||
|
|
||||||
|
repack1.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": "Sub Component 1",
|
||||||
|
"qty": 10,
|
||||||
|
"t_warehouse": "WH 2 - _TC",
|
||||||
|
"transfer_qty": 10,
|
||||||
|
"uom": "Nos",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
repack1.save()
|
||||||
|
repack1.submit()
|
||||||
|
|
||||||
|
self.assertEqual(repack1.items[1].basic_rate, 100)
|
||||||
|
self.assertEqual(repack1.items[1].amount, 1000)
|
||||||
|
|
||||||
|
repack2 = make_stock_entry(
|
||||||
|
item_code="Sub Component 1",
|
||||||
|
purpose="Repack",
|
||||||
|
do_not_save=True,
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 2 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -8),
|
||||||
|
)
|
||||||
|
|
||||||
|
repack2.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": "FG Item 1",
|
||||||
|
"qty": 10,
|
||||||
|
"t_warehouse": "WH 3 - _TC",
|
||||||
|
"transfer_qty": 10,
|
||||||
|
"uom": "Nos",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
repack2.save()
|
||||||
|
repack2.submit()
|
||||||
|
|
||||||
|
self.assertEqual(repack2.items[1].basic_rate, 100)
|
||||||
|
self.assertEqual(repack2.items[1].amount, 1000)
|
||||||
|
|
||||||
|
transfer1 = make_stock_entry(
|
||||||
|
item_code="FG Item 1",
|
||||||
|
purpose="Material Transfer",
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 3 - _TC",
|
||||||
|
to_warehouse="WH 2 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -7),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(transfer1.items[0].basic_rate, 100)
|
||||||
|
self.assertEqual(transfer1.items[0].amount, 1000)
|
||||||
|
|
||||||
|
transfer2 = make_stock_entry(
|
||||||
|
item_code="FG Item 1",
|
||||||
|
purpose="Material Transfer",
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 2 - _TC",
|
||||||
|
to_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -6),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(transfer2.items[0].basic_rate, 100)
|
||||||
|
self.assertEqual(transfer2.items[0].amount, 1000)
|
||||||
|
|
||||||
|
# Backdated transaction
|
||||||
|
receipt2 = make_stock_entry(
|
||||||
|
item_code="Component 1",
|
||||||
|
rate=200,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
qty=10,
|
||||||
|
to_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -15),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(receipt2.items[0].basic_rate, 200)
|
||||||
|
self.assertEqual(receipt2.items[0].amount, 2000)
|
||||||
|
|
||||||
|
repost_name = frappe.db.get_value(
|
||||||
|
"Repost Item Valuation", {"voucher_no": receipt2.name, "docstatus": 1}, "name"
|
||||||
|
)
|
||||||
|
|
||||||
|
doc = frappe.get_doc("Repost Item Valuation", repost_name)
|
||||||
|
repost_sl_entries(doc)
|
||||||
|
|
||||||
|
for obj in [repack1, repack2, transfer1, transfer2]:
|
||||||
|
obj.load_from_db()
|
||||||
|
|
||||||
|
index = 1 if obj.purpose == "Repack" else 0
|
||||||
|
self.assertEqual(obj.items[index].basic_rate, 200)
|
||||||
|
self.assertEqual(obj.items[index].basic_amount, 2000)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -247,16 +247,11 @@ def repost_future_sle(
|
|||||||
data.sle_changed = False
|
data.sle_changed = False
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if doc and i % 2 == 0:
|
if doc:
|
||||||
update_args_in_repost_item_valuation(
|
update_args_in_repost_item_valuation(
|
||||||
doc, i, args, distinct_item_warehouses, affected_transactions
|
doc, i, args, distinct_item_warehouses, affected_transactions
|
||||||
)
|
)
|
||||||
|
|
||||||
if doc and args:
|
|
||||||
update_args_in_repost_item_valuation(
|
|
||||||
doc, i, args, distinct_item_warehouses, affected_transactions
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_args_in_repost_item_valuation(
|
def update_args_in_repost_item_valuation(
|
||||||
doc, index, args, distinct_item_warehouses, affected_transactions
|
doc, index, args, distinct_item_warehouses, affected_transactions
|
||||||
@@ -491,7 +486,8 @@ class update_entries_after(object):
|
|||||||
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
|
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
|
||||||
return entries_to_fix
|
return entries_to_fix
|
||||||
else:
|
else:
|
||||||
return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix)
|
self.append_future_sle_for_dependant(dependant_sle, entries_to_fix)
|
||||||
|
return entries_to_fix
|
||||||
|
|
||||||
def update_distinct_item_warehouses(self, dependant_sle):
|
def update_distinct_item_warehouses(self, dependant_sle):
|
||||||
key = (dependant_sle.item_code, dependant_sle.warehouse)
|
key = (dependant_sle.item_code, dependant_sle.warehouse)
|
||||||
@@ -510,14 +506,11 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
|
def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
|
||||||
self.initialize_previous_data(dependant_sle)
|
self.initialize_previous_data(dependant_sle)
|
||||||
|
self.distinct_item_warehouses[(self.item_code, dependant_sle.warehouse)] = frappe._dict(
|
||||||
args = self.data[dependant_sle.warehouse].previous_sle or frappe._dict(
|
{"sle": dependant_sle}
|
||||||
{"item_code": self.item_code, "warehouse": dependant_sle.warehouse}
|
|
||||||
)
|
)
|
||||||
future_sle_for_dependant = list(self.get_sle_after_datetime(args))
|
|
||||||
|
|
||||||
entries_to_fix.extend(future_sle_for_dependant)
|
self.new_items_found = True
|
||||||
return sorted(entries_to_fix, key=lambda k: k["timestamp"])
|
|
||||||
|
|
||||||
def process_sle(self, sle):
|
def process_sle(self, sle):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|||||||
Reference in New Issue
Block a user