Merge branch 'develop' into FIX-ISS-22-23-05936
This commit is contained in:
commit
a07a5572ea
@ -394,7 +394,13 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
|||||||
|
|
||||||
if ancestors and not allow_independent_account_creation:
|
if ancestors and not allow_independent_account_creation:
|
||||||
for ancestor in ancestors:
|
for ancestor in ancestors:
|
||||||
if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
|
old_name = frappe.db.get_value(
|
||||||
|
"Account",
|
||||||
|
{"account_number": old_acc_number, "account_name": old_acc_name, "company": ancestor},
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
|
||||||
|
if old_name:
|
||||||
# same account in parent company exists
|
# same account in parent company exists
|
||||||
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
||||||
|
|
||||||
|
|||||||
@ -859,7 +859,7 @@ class ReceivablePayableReport(object):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.qb_selection_filter.append(
|
self.qb_selection_filter.append(
|
||||||
self.ple[dimension.fieldname] == self.filters[dimension.fieldname]
|
self.ple[dimension.fieldname].isin(self.filters[dimension.fieldname])
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_invoice(self, ple):
|
def is_invoice(self, ple):
|
||||||
|
|||||||
@ -501,7 +501,14 @@ class GrossProfitGenerator(object):
|
|||||||
):
|
):
|
||||||
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
||||||
for returned_item_row in returned_item_rows:
|
for returned_item_row in returned_item_rows:
|
||||||
row.qty += flt(returned_item_row.qty)
|
# returned_items 'qty' should be stateful
|
||||||
|
if returned_item_row.qty != 0:
|
||||||
|
if row.qty >= abs(returned_item_row.qty):
|
||||||
|
row.qty += returned_item_row.qty
|
||||||
|
returned_item_row.qty = 0
|
||||||
|
else:
|
||||||
|
row.qty = 0
|
||||||
|
returned_item_row.qty += row.qty
|
||||||
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
||||||
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
||||||
if flt(row.qty) or row.base_amount:
|
if flt(row.qty) or row.base_amount:
|
||||||
@ -734,6 +741,8 @@ class GrossProfitGenerator(object):
|
|||||||
if self.filters.to_date:
|
if self.filters.to_date:
|
||||||
conditions += " and posting_date <= %(to_date)s"
|
conditions += " and posting_date <= %(to_date)s"
|
||||||
|
|
||||||
|
conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
|
||||||
|
|
||||||
if self.filters.item_group:
|
if self.filters.item_group:
|
||||||
conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
|
conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
|
||||||
|
|
||||||
|
|||||||
@ -381,3 +381,82 @@ class TestGrossProfit(FrappeTestCase):
|
|||||||
}
|
}
|
||||||
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
||||||
self.assertDictContainsSubset(expected_entry, gp_entry[0])
|
self.assertDictContainsSubset(expected_entry, gp_entry[0])
|
||||||
|
|
||||||
|
def test_crnote_against_invoice_with_multiple_instances_of_same_item(self):
|
||||||
|
"""
|
||||||
|
Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items.
|
||||||
|
"""
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||||
|
|
||||||
|
# Invoice with an item added twice
|
||||||
|
sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True)
|
||||||
|
sinv.append("items", frappe.copy_doc(sinv.items[0], ignore_no_copy=False))
|
||||||
|
sinv = sinv.save().submit()
|
||||||
|
|
||||||
|
# Create Credit Note for Invoice
|
||||||
|
cr_note = make_sales_return(sinv.name)
|
||||||
|
cr_note = cr_note.save().submit()
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
|
||||||
|
)
|
||||||
|
|
||||||
|
columns, data = execute(filters=filters)
|
||||||
|
expected_entry = {
|
||||||
|
"parent_invoice": sinv.name,
|
||||||
|
"currency": "INR",
|
||||||
|
"sales_invoice": self.item,
|
||||||
|
"customer": self.customer,
|
||||||
|
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
|
||||||
|
"item_code": self.item,
|
||||||
|
"item_name": self.item,
|
||||||
|
"warehouse": "Stores - _GP",
|
||||||
|
"qty": 0.0,
|
||||||
|
"avg._selling_rate": 0.0,
|
||||||
|
"valuation_rate": 0.0,
|
||||||
|
"selling_amount": -100.0,
|
||||||
|
"buying_amount": 0.0,
|
||||||
|
"gross_profit": -100.0,
|
||||||
|
"gross_profit_%": 100.0,
|
||||||
|
}
|
||||||
|
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
||||||
|
# Both items of Invoice should have '0' qty
|
||||||
|
self.assertEqual(len(gp_entry), 2)
|
||||||
|
self.assertDictContainsSubset(expected_entry, gp_entry[0])
|
||||||
|
self.assertDictContainsSubset(expected_entry, gp_entry[1])
|
||||||
|
|
||||||
|
def test_standalone_cr_notes(self):
|
||||||
|
"""
|
||||||
|
Standalone cr notes will be reported as usual
|
||||||
|
"""
|
||||||
|
# Make Cr Note
|
||||||
|
sinv = self.create_sales_invoice(
|
||||||
|
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
||||||
|
)
|
||||||
|
sinv.is_return = 1
|
||||||
|
sinv = sinv.save().submit()
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
|
||||||
|
)
|
||||||
|
|
||||||
|
columns, data = execute(filters=filters)
|
||||||
|
expected_entry = {
|
||||||
|
"parent_invoice": sinv.name,
|
||||||
|
"currency": "INR",
|
||||||
|
"sales_invoice": self.item,
|
||||||
|
"customer": self.customer,
|
||||||
|
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
|
||||||
|
"item_code": self.item,
|
||||||
|
"item_name": self.item,
|
||||||
|
"warehouse": "Stores - _GP",
|
||||||
|
"qty": -1.0,
|
||||||
|
"avg._selling_rate": 100.0,
|
||||||
|
"valuation_rate": 0.0,
|
||||||
|
"selling_amount": -100.0,
|
||||||
|
"buying_amount": 0.0,
|
||||||
|
"gross_profit": -100.0,
|
||||||
|
"gross_profit_%": 100.0,
|
||||||
|
}
|
||||||
|
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
|
||||||
|
self.assertDictContainsSubset(expected_entry, gp_entry[0])
|
||||||
|
|||||||
@ -455,7 +455,9 @@ def reconcile_against_document(args): # nosemgrep
|
|||||||
try:
|
try:
|
||||||
doc.validate_total_debit_and_credit()
|
doc.validate_total_debit_and_credit()
|
||||||
except Exception as validation_exception:
|
except Exception as validation_exception:
|
||||||
raise frappe.ValidationError(_(f"Validation Error for {doc.name}")) from validation_exception
|
raise frappe.ValidationError(
|
||||||
|
_("Validation Error for {0}").format(doc.name)
|
||||||
|
) from validation_exception
|
||||||
|
|
||||||
doc.save(ignore_permissions=True)
|
doc.save(ignore_permissions=True)
|
||||||
# re-submit advance entry
|
# re-submit advance entry
|
||||||
|
|||||||
@ -322,6 +322,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
|
|||||||
erpnext.patches.v14_0.update_entry_type_for_journal_entry
|
erpnext.patches.v14_0.update_entry_type_for_journal_entry
|
||||||
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
|
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
|
||||||
erpnext.patches.v14_0.set_pick_list_status
|
erpnext.patches.v14_0.set_pick_list_status
|
||||||
|
erpnext.patches.v13_0.update_docs_link
|
||||||
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
||||||
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
|
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
|
||||||
# below migration patches should always run last
|
# below migration patches should always run last
|
||||||
|
|||||||
14
erpnext/patches/v13_0/update_docs_link.py
Normal file
14
erpnext/patches/v13_0/update_docs_link.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
navbar_settings = frappe.get_single("Navbar Settings")
|
||||||
|
for item in navbar_settings.help_dropdown:
|
||||||
|
if item.is_standard and item.route == "https://erpnext.com/docs/user/manual":
|
||||||
|
item.route = "https://docs.erpnext.com/docs/v14/user/manual/en/introduction"
|
||||||
|
|
||||||
|
navbar_settings.save()
|
||||||
@ -155,7 +155,7 @@ def add_standard_navbar_items():
|
|||||||
{
|
{
|
||||||
"item_label": "Documentation",
|
"item_label": "Documentation",
|
||||||
"item_type": "Route",
|
"item_type": "Route",
|
||||||
"route": "https://erpnext.com/docs/user/manual",
|
"route": "https://docs.erpnext.com/docs/v14/user/manual/en/introduction",
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -172,8 +172,8 @@ class PickList(Document):
|
|||||||
if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
|
if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}."
|
"You are picking more than required quantity for the item {0}. Check if there is any other pick list created for the sales order {1}."
|
||||||
)
|
).format(row.item_code, row.sales_order)
|
||||||
)
|
)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@ -658,6 +658,7 @@ class StockEntry(StockController):
|
|||||||
)
|
)
|
||||||
finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
|
finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
|
||||||
|
|
||||||
|
items = []
|
||||||
# Set basic rate for incoming items
|
# Set basic rate for incoming items
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.s_warehouse or d.set_basic_rate_manually:
|
if d.s_warehouse or d.set_basic_rate_manually:
|
||||||
@ -665,12 +666,7 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
if d.allow_zero_valuation_rate:
|
if d.allow_zero_valuation_rate:
|
||||||
d.basic_rate = 0.0
|
d.basic_rate = 0.0
|
||||||
frappe.msgprint(
|
items.append(d.item_code)
|
||||||
_(
|
|
||||||
"Row {0}: Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {1}"
|
|
||||||
).format(d.idx, d.item_code),
|
|
||||||
alert=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
elif d.is_finished_item:
|
elif d.is_finished_item:
|
||||||
if self.purpose == "Manufacture":
|
if self.purpose == "Manufacture":
|
||||||
@ -697,6 +693,20 @@ class StockEntry(StockController):
|
|||||||
d.basic_rate = flt(d.basic_rate)
|
d.basic_rate = flt(d.basic_rate)
|
||||||
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
|
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
|
||||||
|
|
||||||
|
if items:
|
||||||
|
message = ""
|
||||||
|
|
||||||
|
if len(items) > 1:
|
||||||
|
message = _(
|
||||||
|
"Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}"
|
||||||
|
).format(", ".join(frappe.bold(item) for item in items))
|
||||||
|
else:
|
||||||
|
message = _(
|
||||||
|
"Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}"
|
||||||
|
).format(frappe.bold(items[0]))
|
||||||
|
|
||||||
|
frappe.msgprint(message, alert=True)
|
||||||
|
|
||||||
def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
|
def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
|
||||||
outgoing_items_cost = 0.0
|
outgoing_items_cost = 0.0
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user