Merge pull request #33318 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
Ankush Menat
2022-12-13 17:59:30 +05:30
committed by GitHub
25 changed files with 213 additions and 63 deletions

View File

@@ -25,7 +25,8 @@
"in_list_view": 1,
"label": "Type",
"options": "DocType",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"columns": 2,
@@ -35,7 +36,8 @@
"in_list_view": 1,
"label": "Name",
"options": "reference_doctype",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fieldname": "due_date",
@@ -104,7 +106,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-26 17:06:55.597389",
"modified": "2022-12-12 12:31:44.919895",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
@@ -113,5 +115,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -256,7 +256,7 @@ def apply_pricing_rule(args, doc=None):
for item in item_list:
args_copy = copy.deepcopy(args)
args_copy.update(item)
data = get_pricing_rule_for_item(args_copy, item.get("price_list_rate"), doc=doc)
data = get_pricing_rule_for_item(args_copy, doc=doc)
out.append(data)
if (
@@ -293,7 +293,7 @@ def update_pricing_rule_uom(pricing_rule, args):
pricing_rule.uom = row.uom
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
def get_pricing_rule_for_item(args, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
get_pricing_rule_items,

View File

@@ -997,7 +997,7 @@ def make_pricing_rule(**args):
"apply_on": args.apply_on or "Item Code",
"applicable_for": args.applicable_for,
"selling": args.selling or 0,
"currency": "USD",
"currency": "INR",
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
"buying": args.buying or 0,
"min_qty": args.min_qty or 0.0,

View File

@@ -244,6 +244,17 @@ def get_other_conditions(conditions, values, args):
and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
values["transaction_date"] = args.get("transaction_date")
if args.get("doctype") in [
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"POS Invoice",
]:
conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1"""
else:
conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1"""
return conditions
@@ -663,7 +674,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
item_details.free_item_data.append(free_item_data_args)
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
if pricing_rule_args:
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)

View File

@@ -533,12 +533,13 @@ def get_accounts(root_type, companies):
],
filters={"company": company, "root_type": root_type},
):
if account.account_name not in added_accounts:
if account.account_number:
account_key = account.account_number + "-" + account.account_name
else:
account_key = account.account_name
if account_key not in added_accounts:
accounts.append(account)
if account.account_number:
account_key = account.account_number + "-" + account.account_name
else:
account_key = account.account_name
added_accounts.append(account_key)
return accounts

View File

@@ -234,8 +234,11 @@ def modify_report_columns(doctype, field, column):
if field in ["item_tax_rate", "base_net_amount"]:
return None
if doctype == "GL Entry" and field in ["debit", "credit"]:
column.update({"label": _("Amount"), "fieldname": "amount"})
if doctype == "GL Entry":
if field in ["debit", "credit"]:
column.update({"label": _("Amount"), "fieldname": "amount"})
elif field == "voucher_type":
column.update({"fieldtype": "Data", "options": ""})
if field == "taxes_and_charges":
column.update({"label": _("Taxes and Charges Template")})

View File

@@ -136,6 +136,10 @@ frappe.ui.form.on('Asset', {
}, __("Manage"));
}
if (frm.doc.depr_entry_posting_status === "Failed") {
frm.trigger("set_depr_posting_failure_alert");
}
frm.trigger("setup_chart");
}
@@ -146,6 +150,19 @@ frappe.ui.form.on('Asset', {
}
},
set_depr_posting_failure_alert: function (frm) {
const alert = `
<div class="row">
<div class="col-xs-12 col-sm-6">
<span class="indicator whitespace-nowrap red">
<span>Failed to post depreciation entries</span>
</span>
</div>
</div>`;
frm.dashboard.set_headline_alert(alert);
},
toggle_reference_doc: function(frm) {
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
frm.set_df_property('purchase_invoice', 'read_only', 1);

View File

@@ -70,6 +70,7 @@
"column_break_51",
"purchase_receipt_amount",
"default_finance_book",
"depr_entry_posting_status",
"amended_from"
],
"fields": [
@@ -488,6 +489,16 @@
"fieldtype": "Int",
"label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset"
},
{
"fieldname": "depr_entry_posting_status",
"fieldtype": "Select",
"hidden": 1,
"label": "Depreciation Entry Posting Status",
"no_copy": 1,
"options": "\nSuccessful\nFailed",
"print_hide": 1,
"read_only": 1
}
],
"idx": 72,
@@ -510,7 +521,7 @@
"link_fieldname": "asset"
}
],
"modified": "2022-07-20 10:15:12.887372",
"modified": "2022-12-05 16:21:30.024060",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -5,6 +5,8 @@
import frappe
from frappe import _
from frappe.utils import add_months, cint, flt, getdate, nowdate, today
from frappe.utils.data import get_link_to_form
from frappe.utils.user import get_users_with_role
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
@@ -12,7 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
def post_depreciation_entries(date=None, commit=True):
def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled
if not cint(
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
@@ -21,10 +23,22 @@ def post_depreciation_entries(date=None, commit=True):
if not date:
date = today()
for asset in get_depreciable_assets(date):
make_depreciation_entry(asset, date)
if commit:
failed_asset_names = []
for asset_name in get_depreciable_assets(date):
try:
make_depreciation_entry(asset_name, date)
frappe.db.commit()
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names)
frappe.db.commit()
def get_depreciable_assets(date):
@@ -123,6 +137,8 @@ def make_depreciation_entry(asset_name, date=None):
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
asset.set_status()
return asset
@@ -186,6 +202,42 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
return credit_account, debit_account
def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
for asset_name in failed_asset_names:
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
def notify_depr_entry_posting_error(failed_asset_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
recipients = get_users_with_role("System Manager")
subject = _("Error while posting depreciation entries")
asset_links = get_comma_separated_asset_links(failed_asset_names)
message = (
_("Hi,")
+ "<br>"
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+ "."
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names):
asset_links = []
for asset_name in asset_names:
asset_links.append(get_link_to_form("Asset", asset_name))
asset_links = ", ".join(asset_links)
return asset_links
@frappe.whitelist()
def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)

View File

@@ -1478,6 +1478,7 @@ def create_asset(**args):
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1,
"asset_quantity": args.get("asset_quantity") or 1,
"depr_entry_posting_status": args.depr_entry_posting_status or "",
}
)

View File

@@ -197,7 +197,7 @@ class AccountsController(TransactionBase):
validate_einvoice_fields(self)
if self.doctype != "Material Request":
if self.doctype != "Material Request" and not self.ignore_pricing_rule:
apply_pricing_rule_on_transaction(self)
def before_cancel(self):

View File

@@ -349,7 +349,7 @@ class StatusUpdater(Document):
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format(
action,
_(item["target_ref_field"].title()),
frappe.bold(item["reduce_by"]),

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Floor, Sum
from frappe.query_builder.functions import Sum
from pypika.terms import ExistsCriterion
@@ -58,9 +58,9 @@ def get_bom_stock(filters):
bom_item.description,
bom_item.stock_qty,
bom_item.stock_uom,
bom_item.stock_qty * qty_to_produce / bom.quantity,
Sum(bin.actual_qty).as_("actual_qty"),
Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))),
(bom_item.stock_qty / bom.quantity) * qty_to_produce,
Sum(bin.actual_qty),
Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity),
)
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code)

View File

@@ -49,7 +49,7 @@ class ProductionPlanReport(object):
parent.bom_no,
parent.fg_warehouse.as_("warehouse"),
)
.where(parent.status.notin(["Completed", "Stopped"]))
.where(parent.status.notin(["Completed", "Stopped", "Closed"]))
)
if order_by == "Planned Start Date":
@@ -79,10 +79,11 @@ class ProductionPlanReport(object):
query = query.where(child.parent.isin(self.filters.docnames))
if doctype == "Sales Order":
query = query.select(
child.delivery_date,
parent.base_grand_total,
).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0))
query = query.select(child.delivery_date, parent.base_grand_total,).where(
(child.stock_qty > child.produced_qty)
& (parent.per_delivered < 100.0)
& (parent.status.notin(["Completed", "Closed"]))
)
if order_by == "Delivery Date":
query = query.orderby(child.delivery_date, order=Order.asc)
@@ -91,7 +92,9 @@ class ProductionPlanReport(object):
elif doctype == "Material Request":
query = query.select(child.schedule_date,).where(
(parent.per_ordered < 100) & (parent.material_request_type == "Manufacture")
(parent.per_ordered < 100)
& (parent.material_request_type == "Manufacture")
& (parent.status != "Stopped")
)
if order_by == "Required Date":

View File

@@ -58,7 +58,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if (
in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
&& this.frm.doc.s_pos
&& this.frm.doc.is_pos
&& this.frm.doc.is_return
) {
this.set_total_amount_to_default_mop();

View File

@@ -1130,10 +1130,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
qty(doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn);
item.pricing_rules = ''
this.conversion_factor(doc, cdt, cdn, true);
this.calculate_stock_uom_rate(doc, cdt, cdn);
this.apply_pricing_rule(item, true);
// item.pricing_rules = ''
frappe.run_serially([
() => this.remove_pricing_rule(item),
() => this.conversion_factor(doc, cdt, cdn, true),
() => this.calculate_stock_uom_rate(doc, cdt, cdn),
() => this.apply_pricing_rule(item, true)
]);
}
calculate_stock_uom_rate(doc, cdt, cdn) {
@@ -1357,16 +1360,21 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
var item_list = [];
$.each(this.frm.doc["items"] || [], function(i, d) {
if (d.item_code && !d.is_free_item) {
item_list.push({
"doctype": d.doctype,
"name": d.name,
"item_code": d.item_code,
"pricing_rules": d.pricing_rules,
"parenttype": d.parenttype,
"parent": d.parent,
"price_list_rate": d.price_list_rate
})
if (d.item_code) {
if (d.is_free_item) {
// Simply remove free items
me.frm.get_field("items").grid.grid_rows[i].remove();
} else {
item_list.push({
"doctype": d.doctype,
"name": d.name,
"item_code": d.item_code,
"pricing_rules": d.pricing_rules,
"parenttype": d.parenttype,
"parent": d.parent,
"price_list_rate": d.price_list_rate
})
}
}
});
return this.frm.call({

View File

@@ -6,7 +6,7 @@ import json
import frappe
import frappe.defaults
from frappe import _, msgprint
from frappe import _, msgprint, qb
from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
@@ -732,12 +732,15 @@ def make_address(args, is_primary_address=1):
@frappe.validate_and_sanitize_search_inputs
def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, filters):
customer = filters.get("customer")
return frappe.db.sql(
"""
select `tabContact`.name from `tabContact`, `tabDynamic Link`
where `tabContact`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
and `tabDynamic Link`.link_doctype = 'Customer'
and `tabContact`.name like %(txt)s
""",
{"customer": customer, "txt": "%%%s%%" % txt},
con = qb.DocType("Contact")
dlink = qb.DocType("Dynamic Link")
return (
qb.from_(con)
.join(dlink)
.on(con.name == dlink.parent)
.select(con.name, con.full_name, con.email_id)
.where((dlink.link_name == customer) & (con.name.like(f"%{txt}%")))
.run()
)

View File

@@ -30,6 +30,24 @@ class TestQuotation(FrappeTestCase):
self.assertTrue(sales_order.get("payment_schedule"))
def test_maintain_rate_in_sales_cycle_is_enforced(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
maintain_rate = frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")
frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1)
quotation = frappe.copy_doc(test_records[0])
quotation.transaction_date = nowdate()
quotation.valid_till = add_months(quotation.transaction_date, 1)
quotation.insert()
quotation.submit()
sales_order = make_sales_order(quotation.name)
sales_order.items[0].rate = 1
self.assertRaises(frappe.ValidationError, sales_order.save)
frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", maintain_rate)
def test_make_sales_order_with_different_currency(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order

View File

@@ -194,7 +194,7 @@ class SalesOrder(SellingController):
)
if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")):
self.validate_rate_with_reference_doc([["Quotation", "prev_docname", "quotation_item"]])
self.validate_rate_with_reference_doc([["Quotation", "prevdoc_docname", "quotation_item"]])
def update_enquiry_status(self, prevdoc, flag):
enq = frappe.db.sql(

View File

@@ -12,7 +12,10 @@ def get_data():
"Auto Repeat": "reference_document",
"Maintenance Visit": "prevdoc_docname",
},
"internal_links": {"Quotation": ["items", "prevdoc_docname"]},
"internal_links": {
"Quotation": ["items", "prevdoc_docname"],
"Material Request": ["items", "material_request"],
},
"transactions": [
{
"label": _("Fulfillment"),

View File

@@ -152,7 +152,7 @@ def get_parent_item_groups(item_group_name, from_item=False):
if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1]
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
if last_page and last_page in ("shop-by-category", "all-products"):
base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}

View File

@@ -113,7 +113,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
if args.get(key) is None:
args[key] = value
data = get_pricing_rule_for_item(args, out.price_list_rate, doc, for_validate=for_validate)
data = get_pricing_rule_for_item(args, doc=doc, for_validate=for_validate)
out.update(data)
@@ -1305,7 +1305,7 @@ def apply_price_list_on_item(args):
item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
item_details = get_price_list_rate(args, item_doc)
item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate))
item_details.update(get_pricing_rule_for_item(args))
return item_details

View File

@@ -45,7 +45,7 @@ def get_warehouses(report_filters: StockBalanceFilter):
return frappe.get_all(
"Warehouse",
fields=["name", "parent_warehouse", "is_group"],
filters={"company": report_filters.company, "disabled": 0},
filters={"company": report_filters.company},
order_by="lft",
)
@@ -55,9 +55,10 @@ def get_data(filters: StockBalanceFilter):
warehouses = get_warehouses(filters)
for warehouse in warehouses:
warehouse["stock_balance"] = warehouse_balance.get(warehouse.name, 0)
warehouse.stock_balance = warehouse_balance.get(warehouse.name, 0) or 0.0
update_indent(warehouses)
set_balance_in_parent(warehouses)
return warehouses
@@ -69,13 +70,26 @@ def update_indent(warehouses):
warehouse.indent = indent
for child in warehouses:
if child.parent_warehouse == warehouse.name:
warehouse.stock_balance += child.stock_balance
add_indent(child, indent + 1)
if warehouse.is_group:
add_indent(warehouse, warehouse.indent or 0)
def set_balance_in_parent(warehouses):
# sort warehouses by indent in descending order
warehouses = sorted(warehouses, key=lambda x: x.get("indent", 0), reverse=1)
for warehouse in warehouses:
def update_balance(warehouse, balance):
for parent in warehouses:
if warehouse.parent_warehouse == parent.name:
parent.stock_balance += balance
update_balance(warehouse, warehouse.stock_balance)
def get_columns():
return [
{

View File

@@ -9916,3 +9916,4 @@ Cost and Freight,Kosten und Fracht,
Delivered at Place,Geliefert benannter Ort,
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
Delivered Duty Paid,Geliefert verzollt,
{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben."
Can't render this file because it is too large.

View File

@@ -110,6 +110,7 @@ def get_price(item_code, price_list, customer_group, company, qty=1):
"conversion_rate": 1,
"for_shopping_cart": True,
"currency": frappe.db.get_value("Price List", price_list, "currency"),
"doctype": "Quotation",
}
)