Merge pull request #30606 from deepeshgarg007/exchange_gain_loss_button
fix: Exchange gain and loss button in Payment Entry
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index c45b069..2438f4b 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -35,10 +35,11 @@
self.margin_rate_or_amount = 0.0
def validate_duplicate_apply_on(self):
- field = apply_on_dict.get(self.apply_on)
- values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
- if len(values) != len(set(values)):
- frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
+ if self.apply_on != "Transaction":
+ field = apply_on_dict.get(self.apply_on)
+ values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
+ if len(values) != len(set(values)):
+ frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
def validate_mandatory(self):
for apply_on, field in apply_on_dict.items():
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
index 77e6ae2..3a46fb0 100644
--- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
@@ -1,9 +1,9 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
+from itertools import groupby
import frappe
-import pandas
from frappe import _
from frappe.utils import flt
@@ -101,18 +101,19 @@
self.convert_to_base_currency()
- dataframe = pandas.DataFrame.from_records(self.query_result)
- dataframe.replace(to_replace=[None], value="Not Assigned", inplace=True)
- result = dataframe.groupby(["sales_stage", based_on], as_index=False)["amount"].sum()
+ for row in self.query_result:
+ if not row.get(based_on):
+ row[based_on] = "Not Assigned"
self.grouped_data = []
- for i in range(len(result["amount"])):
+ grouping_key = lambda o: (o["sales_stage"], o[based_on]) # noqa
+ for (sales_stage, _based_on), rows in groupby(self.query_result, grouping_key):
self.grouped_data.append(
{
- "sales_stage": result["sales_stage"][i],
- based_on: result[based_on][i],
- "amount": result["amount"][i],
+ "sales_stage": sales_stage,
+ based_on: _based_on,
+ "amount": sum(flt(r["amount"]) for r in rows),
}
)
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
index b0c174b..d23a22a 100644
--- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
@@ -3,9 +3,9 @@
import json
from datetime import date
+from itertools import groupby
import frappe
-import pandas
from dateutil.relativedelta import relativedelta
from frappe import _
from frappe.utils import cint, flt
@@ -109,18 +109,15 @@
self.convert_to_base_currency()
- dataframe = pandas.DataFrame.from_records(self.query_result)
- dataframe.replace(to_replace=[None], value="Not Assigned", inplace=True)
- result = dataframe.groupby([self.pipeline_by, self.period_by], as_index=False)["amount"].sum()
-
self.grouped_data = []
- for i in range(len(result["amount"])):
+ grouping_key = lambda o: (o.get(self.pipeline_by) or "Not Assigned", o[self.period_by]) # noqa
+ for (pipeline_by, period_by), rows in groupby(self.query_result, grouping_key):
self.grouped_data.append(
{
- self.pipeline_by: result[self.pipeline_by][i],
- self.period_by: result[self.period_by][i],
- "amount": result["amount"][i],
+ self.pipeline_by: pipeline_by,
+ self.period_by: period_by,
+ "amount": sum(flt(r["amount"]) for r in rows),
}
)
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
index 9742387..aef4412 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
@@ -34,6 +34,15 @@
});
}
}
+
+ // make new leaves allocated field read only if allocation is created via leave policy assignment
+ // and leave type is earned leave, since these leaves would be allocated via the scheduler
+ if (frm.doc.leave_policy_assignment) {
+ frappe.db.get_value("Leave Type", frm.doc.leave_type, "is_earned_leave", (r) => {
+ if (r && cint(r.is_earned_leave))
+ frm.set_df_property("new_leaves_allocated", "read_only", 1);
+ });
+ }
},
expire_allocation: function(frm) {
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 9ecbe01..9d1db9b 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -237,7 +237,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-01-18 19:15:53.262536",
+ "modified": "2022-04-07 09:50:33.145825",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
@@ -281,5 +281,6 @@
"sort_order": "DESC",
"states": [],
"timeline_field": "employee",
- "title_field": "employee_name"
+ "title_field": "employee_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index fd69a9b..40ab805 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -353,6 +353,17 @@
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+ if e_leave_type.based_on_date_of_joining:
+ text = _("allocated {0} leave(s) via scheduler on {1} based on the date of joining").format(
+ frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
+ )
+ else:
+ text = _("allocated {0} leave(s) via scheduler on {1}").format(
+ frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
+ )
+
+ allocation.add_comment(comment_type="Info", text=text)
+
def get_monthly_earned_leave(annual_leaves, frequency, rounding):
earned_leaves = 0.0
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index d85b8a6..b2824e1 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -28,12 +28,12 @@
frappe.flags.resume_job = 0;
let has_items = frm.doc.items && frm.doc.items.length;
- if (frm.doc.__onload.work_order_closed) {
+ if (!frm.is_new() && frm.doc.__onload.work_order_closed) {
frm.disable_save();
return;
}
- if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) {
+ if (!frm.is_new() && has_items && frm.doc.docstatus < 2) {
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index f484545..64c5ee5 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -609,8 +609,8 @@
&& erpnext.stock.bom
&& erpnext.stock.bom.name === doc.bom_no;
const itemChecks = !!item
- && !item.allow_alternative_item
- && erpnext.stock.bom && erpnext.stock.items
+ && !item.original_item
+ && erpnext.stock.bom && erpnext.stock.bom.items
&& (item.item_code in erpnext.stock.bom.items);
return docChecks && itemChecks;
}
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py
index c626f5b..6b33a71 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.py
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.py
@@ -1,10 +1,11 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
+from itertools import groupby
import frappe
-import pandas as pd
from frappe import _
+from frappe.utils import flt
from erpnext.accounts.report.utils import convert
@@ -89,28 +90,21 @@
for x in opportunities
]
- df = (
- pd.DataFrame(cp_opportunities)
- .groupby(["source", "sales_stage"], as_index=False)
- .agg({"compound_amount": "sum"})
- )
+ summary = {}
+ sales_stages = set()
+ group_key = lambda o: (o["source"], o["sales_stage"]) # noqa
+ for (source, sales_stage), rows in groupby(cp_opportunities, group_key):
+ summary.setdefault(source, {})[sales_stage] = sum(r["compound_amount"] for r in rows)
+ sales_stages.add(sales_stage)
- result = {}
- result["labels"] = list(set(df.source.values))
- result["datasets"] = []
+ pivot_table = []
+ for sales_stage in sales_stages:
+ row = []
+ for source, sales_stage_values in summary.items():
+ row.append(flt(sales_stage_values.get(sales_stage)))
+ pivot_table.append({"chartType": "bar", "name": sales_stage, "values": row})
- for s in set(df.sales_stage.values):
- result["datasets"].append(
- {"name": s, "values": [0] * len(result["labels"]), "chartType": "bar"}
- )
-
- for row in df.itertuples():
- source_index = result["labels"].index(row.source)
-
- for dataset in result["datasets"]:
- if dataset["name"] == row.sales_stage:
- dataset["values"][source_index] = row.compound_amount
-
+ result = {"datasets": pivot_table, "labels": list(summary.keys())}
return result
else:
@@ -148,20 +142,14 @@
for x in opportunities
]
- df = (
- pd.DataFrame(cp_opportunities)
- .groupby(["sales_stage"], as_index=True)
- .agg({"compound_amount": "sum"})
- .to_dict()
- )
+ summary = {}
+ for sales_stage, rows in groupby(cp_opportunities, lambda o: o["sales_stage"]):
+ summary[sales_stage] = sum(flt(r["compound_amount"]) for r in rows)
- result = {}
- result["labels"] = df["compound_amount"].keys()
- result["datasets"] = []
- result["datasets"].append(
- {"name": _("Total Amount"), "values": df["compound_amount"].values(), "chartType": "bar"}
- )
-
+ result = {
+ "labels": list(summary.keys()),
+ "datasets": [{"name": _("Total Amount"), "values": list(summary.values()), "chartType": "bar"}],
+ }
return result
else:
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
index 0e36b3f..c068ae3 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
@@ -27,28 +27,55 @@
"default": frappe.datetime.get_today()
},
{
- "fieldname":"sales_order",
- "label": __("Sales Order"),
- "fieldtype": "MultiSelectList",
+ "fieldname":"customer_group",
+ "label": __("Customer Group"),
+ "fieldtype": "Link",
"width": 100,
- "options": "Sales Order",
- "get_data": function(txt) {
- return frappe.db.get_link_options("Sales Order", txt, this.filters());
- },
- "filters": () => {
- return {
- docstatus: 1,
- payment_terms_template: ['not in', ['']],
- company: frappe.query_report.get_filter_value("company"),
- transaction_date: ['between', [frappe.query_report.get_filter_value("period_start_date"), frappe.query_report.get_filter_value("period_end_date")]]
+ "options": "Customer Group",
+ },
+ {
+ "fieldname":"customer",
+ "label": __("Customer"),
+ "fieldtype": "Link",
+ "width": 100,
+ "options": "Customer",
+ "get_query": () => {
+ var customer_group = frappe.query_report.get_filter_value('customer_group');
+ return{
+ "query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
+ "filters": [
+ ['Customer', 'disabled', '=', '0'],
+ ['Customer Group','name', '=', customer_group]
+ ]
}
- },
- on_change: function(){
- frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"item_group",
+ "label": __("Item Group"),
+ "fieldtype": "Link",
+ "width": 100,
+ "options": "Item Group",
+
+ },
+ {
+ "fieldname":"item",
+ "label": __("Item"),
+ "fieldtype": "Link",
+ "width": 100,
+ "options": "Item",
+ "get_query": () => {
+ var item_group = frappe.query_report.get_filter_value('item_group');
+ return{
+ "query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
+ "filters": [
+ ['Item', 'disabled', '=', '0'],
+ ['Item Group','name', '=', item_group]
+ ]
+ }
}
}
]
-
return filters;
}
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
index 7f797f6..cb22fb6 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
@@ -3,7 +3,7 @@
import frappe
from frappe import _, qb, query_builder
-from frappe.query_builder import functions
+from frappe.query_builder import Criterion, functions
def get_columns():
@@ -15,6 +15,12 @@
"options": "Sales Order",
},
{
+ "label": _("Customer"),
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "options": "Customer",
+ },
+ {
"label": _("Posting Date"),
"fieldname": "submitted",
"fieldtype": "Date",
@@ -67,6 +73,55 @@
return columns
+def get_descendants_of(doctype, group_name):
+ group_doc = qb.DocType(doctype)
+ # get lft and rgt of group node
+ lft, rgt = (
+ qb.from_(group_doc).select(group_doc.lft, group_doc.rgt).where(group_doc.name == group_name)
+ ).run()[0]
+
+ # get all children of group node
+ query = (
+ qb.from_(group_doc).select(group_doc.name).where((group_doc.lft >= lft) & (group_doc.rgt <= rgt))
+ )
+
+ child_nodes = []
+ for x in query.run():
+ child_nodes.append(x[0])
+
+ return child_nodes
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_customers_or_items(doctype, txt, searchfield, start, page_len, filters):
+ filter_list = []
+ if isinstance(filters, list):
+ for item in filters:
+ if item[0] == doctype:
+ filter_list.append(item)
+ elif item[0] == "Customer Group":
+ if item[3] != "":
+ filter_list.append(
+ [doctype, "customer_group", "in", get_descendants_of("Customer Group", item[3])]
+ )
+ elif item[0] == "Item Group":
+ if item[3] != "":
+ filter_list.append([doctype, "item_group", "in", get_descendants_of("Item Group", item[3])])
+
+ if searchfield and txt:
+ filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
+
+ return frappe.desk.reportview.execute(
+ doctype,
+ filters=filter_list,
+ fields=["name", "customer_group"] if doctype == "Customer" else ["name", "item_group"],
+ limit_start=start,
+ limit_page_length=page_len,
+ as_list=True,
+ )
+
+
def get_conditions(filters):
"""
Convert filter options to conditions used in query
@@ -79,11 +134,37 @@
conditions.start_date = filters.period_start_date or frappe.utils.add_months(
conditions.end_date, -1
)
- conditions.sales_order = filters.sales_order or []
return conditions
+def build_filter_criterions(filters):
+ filters = frappe._dict(filters) if filters else frappe._dict({})
+ qb_criterions = []
+
+ if filters.customer_group:
+ qb_criterions.append(
+ qb.DocType("Sales Order").customer_group.isin(
+ get_descendants_of("Customer Group", filters.customer_group)
+ )
+ )
+
+ if filters.customer:
+ qb_criterions.append(qb.DocType("Sales Order").customer == filters.customer)
+
+ if filters.item_group:
+ qb_criterions.append(
+ qb.DocType("Sales Order Item").item_group.isin(
+ get_descendants_of("Item Group", filters.item_group)
+ )
+ )
+
+ if filters.item:
+ qb_criterions.append(qb.DocType("Sales Order Item").item_code == filters.item)
+
+ return qb_criterions
+
+
def get_so_with_invoices(filters):
"""
Get Sales Order with payment terms template with their associated Invoices
@@ -92,16 +173,23 @@
so = qb.DocType("Sales Order")
ps = qb.DocType("Payment Schedule")
+ soi = qb.DocType("Sales Order Item")
+
+ conditions = get_conditions(filters)
+ filter_criterions = build_filter_criterions(filters)
+
datediff = query_builder.CustomFunction("DATEDIFF", ["cur_date", "due_date"])
ifelse = query_builder.CustomFunction("IF", ["condition", "then", "else"])
- conditions = get_conditions(filters)
query_so = (
qb.from_(so)
+ .join(soi)
+ .on(soi.parent == so.name)
.join(ps)
.on(ps.parent == so.name)
.select(
so.name,
+ so.customer,
so.transaction_date.as_("submitted"),
ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
ps.payment_term,
@@ -117,12 +205,10 @@
& (so.company == conditions.company)
& (so.transaction_date[conditions.start_date : conditions.end_date])
)
+ .where(Criterion.all(filter_criterions))
.orderby(so.name, so.transaction_date, ps.due_date)
)
- if conditions.sales_order != []:
- query_so = query_so.where(so.name.isin(conditions.sales_order))
-
sorders = query_so.run(as_dict=True)
invoices = []
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
index 89940a6..9d542f5 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
@@ -11,10 +11,13 @@
)
from erpnext.stock.doctype.item.test_item import create_item
-test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template"]
+test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template", "Customer"]
class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
+ def tearDown(self):
+ frappe.db.rollback()
+
def create_payment_terms_template(self):
# create template for 50-50 payments
template = None
@@ -48,9 +51,9 @@
template.insert()
self.template = template
- def test_payment_terms_status(self):
+ def test_01_payment_terms_status(self):
self.create_payment_terms_template()
- item = create_item(item_code="_Test Excavator", is_stock_item=0)
+ item = create_item(item_code="_Test Excavator 1", is_stock_item=0)
so = make_sales_order(
transaction_date="2021-06-15",
delivery_date=add_days("2021-06-15", -30),
@@ -78,13 +81,14 @@
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
- "sales_order": [so.name],
+ "item": item.item_code,
}
)
expected_value = [
{
"name": so.name,
+ "customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Completed",
"payment_term": None,
@@ -98,6 +102,7 @@
},
{
"name": so.name,
+ "customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Partly Paid",
"payment_term": None,
@@ -132,11 +137,11 @@
)
doc.insert()
- def test_alternate_currency(self):
+ def test_02_alternate_currency(self):
transaction_date = "2021-06-15"
self.create_payment_terms_template()
self.create_exchange_rate(transaction_date)
- item = create_item(item_code="_Test Excavator", is_stock_item=0)
+ item = create_item(item_code="_Test Excavator 2", is_stock_item=0)
so = make_sales_order(
transaction_date=transaction_date,
currency="USD",
@@ -166,7 +171,7 @@
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
- "sales_order": [so.name],
+ "item": item.item_code,
}
)
@@ -174,6 +179,7 @@
expected_value = [
{
"name": so.name,
+ "customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Completed",
"payment_term": None,
@@ -187,6 +193,7 @@
},
{
"name": so.name,
+ "customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Partly Paid",
"payment_term": None,
@@ -200,3 +207,134 @@
},
]
self.assertEqual(data, expected_value)
+
+ def test_03_group_filters(self):
+ transaction_date = "2021-06-15"
+ self.create_payment_terms_template()
+ item1 = create_item(item_code="_Test Excavator 1", is_stock_item=0)
+ item1.item_group = "Products"
+ item1.save()
+
+ so1 = make_sales_order(
+ transaction_date=transaction_date,
+ delivery_date=add_days(transaction_date, -30),
+ item=item1.item_code,
+ qty=1,
+ rate=1000000,
+ do_not_save=True,
+ )
+ so1.po_no = ""
+ so1.taxes_and_charges = ""
+ so1.taxes = ""
+ so1.payment_terms_template = self.template.name
+ so1.save()
+ so1.submit()
+
+ item2 = create_item(item_code="_Test Steel", is_stock_item=0)
+ item2.item_group = "Raw Material"
+ item2.save()
+
+ so2 = make_sales_order(
+ customer="_Test Customer 1",
+ transaction_date=transaction_date,
+ delivery_date=add_days(transaction_date, -30),
+ item=item2.item_code,
+ qty=100,
+ rate=1000,
+ do_not_save=True,
+ )
+ so2.po_no = ""
+ so2.taxes_and_charges = ""
+ so2.taxes = ""
+ so2.payment_terms_template = self.template.name
+ so2.save()
+ so2.submit()
+
+ base_filters = {
+ "company": "_Test Company",
+ "period_start_date": "2021-06-01",
+ "period_end_date": "2021-06-30",
+ }
+
+ expected_value_so1 = [
+ {
+ "name": so1.name,
+ "customer": so1.customer,
+ "submitted": datetime.date(2021, 6, 15),
+ "status": "Overdue",
+ "payment_term": None,
+ "description": "_Test 50-50",
+ "due_date": datetime.date(2021, 6, 30),
+ "invoice_portion": 50.0,
+ "currency": "INR",
+ "base_payment_amount": 500000.0,
+ "paid_amount": 0.0,
+ "invoices": "",
+ },
+ {
+ "name": so1.name,
+ "customer": so1.customer,
+ "submitted": datetime.date(2021, 6, 15),
+ "status": "Overdue",
+ "payment_term": None,
+ "description": "_Test 50-50",
+ "due_date": datetime.date(2021, 7, 15),
+ "invoice_portion": 50.0,
+ "currency": "INR",
+ "base_payment_amount": 500000.0,
+ "paid_amount": 0.0,
+ "invoices": "",
+ },
+ ]
+
+ expected_value_so2 = [
+ {
+ "name": so2.name,
+ "customer": so2.customer,
+ "submitted": datetime.date(2021, 6, 15),
+ "status": "Overdue",
+ "payment_term": None,
+ "description": "_Test 50-50",
+ "due_date": datetime.date(2021, 6, 30),
+ "invoice_portion": 50.0,
+ "currency": "INR",
+ "base_payment_amount": 50000.0,
+ "paid_amount": 0.0,
+ "invoices": "",
+ },
+ {
+ "name": so2.name,
+ "customer": so2.customer,
+ "submitted": datetime.date(2021, 6, 15),
+ "status": "Overdue",
+ "payment_term": None,
+ "description": "_Test 50-50",
+ "due_date": datetime.date(2021, 7, 15),
+ "invoice_portion": 50.0,
+ "currency": "INR",
+ "base_payment_amount": 50000.0,
+ "paid_amount": 0.0,
+ "invoices": "",
+ },
+ ]
+
+ group_filters = [
+ {"customer_group": "All Customer Groups"},
+ {"item_group": "All Item Groups"},
+ {"item_group": "Products"},
+ {"item_group": "Raw Material"},
+ ]
+
+ expected_values_for_group_filters = [
+ expected_value_so1 + expected_value_so2,
+ expected_value_so1 + expected_value_so2,
+ expected_value_so1,
+ expected_value_so2,
+ ]
+
+ for idx, g in enumerate(group_filters, 0):
+ # build filter
+ filters = frappe._dict({}).update(base_filters).update(g)
+ with self.subTest(filters=filters):
+ columns, data, message, chart = execute(filters)
+ self.assertEqual(data, expected_values_for_group_filters[idx])
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 535f565..5fdecc9 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -18,6 +18,7 @@
now_datetime,
nowtime,
strip,
+ strip_html,
)
from frappe.utils.html_utils import clean_html
@@ -69,10 +70,6 @@
self.item_code = strip(self.item_code)
self.name = self.item_code
- def before_insert(self):
- if not self.description:
- self.description = self.item_name
-
def after_insert(self):
"""set opening stock and item price"""
if self.standard_rate:
@@ -86,7 +83,7 @@
if not self.item_name:
self.item_name = self.item_code
- if not self.description:
+ if not strip_html(cstr(self.description)).strip():
self.description = self.item_name
self.validate_uom()
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 328d937..8dd35d7 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -744,6 +744,13 @@
self.assertTrue(get_data(warehouse="_Test Warehouse - _TC"))
self.assertTrue(get_data(item_group="All Item Groups"))
+ def test_empty_description(self):
+ item = make_item(properties={"description": "<p></p>"})
+ self.assertEqual(item.description, item.item_name)
+ item.description = ""
+ item.save()
+ self.assertEqual(item.description, item.item_name)
+
def set_item_variant_settings(fields):
doc = frappe.get_doc("Item Variant Settings")
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index 1e9d01a..5a7228a 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -38,6 +38,16 @@
self.assertEqual(p_warehouse.name, child_warehouse.parent_warehouse)
self.assertEqual(child_warehouse.is_group, 0)
+ def test_naming(self):
+ company = "Wind Power LLC"
+ warehouse_name = "Named Warehouse - WP"
+ wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
+ self.assertEqual(wh.name, warehouse_name)
+
+ warehouse_name = "Unnamed Warehouse"
+ wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
+ self.assertIn(warehouse_name, wh.name)
+
def test_unlinking_warehouse_from_item_defaults(self):
company = "_Test Company"
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index c892ba3..3b18a9a 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -21,8 +21,9 @@
suffix = " - " + frappe.get_cached_value("Company", self.company, "abbr")
if not self.warehouse_name.endswith(suffix):
self.name = self.warehouse_name + suffix
- else:
- self.name = self.warehouse_name
+ return
+
+ self.name = self.warehouse_name
def onload(self):
"""load account name for General Ledger Report"""
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index ea01ec6..db454de 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -9855,7 +9855,6 @@
Role Allowed to Over Deliver/Receive, Rôle autorisé à dépasser cette limite
Users with this role are allowed to over deliver/receive against orders above the allowance percentage,Rôle Utilisateur qui sont autorisé à livrée/commandé au-delà de la limite
Over Transfer Allowance,Autorisation de limite de transfert
-The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units, Le pourcentage de quantité que vous pourrez receptionné en plus de la quantité commandée
Quality Inspection Settings,Paramétre de l'inspection qualité
Action If Quality Inspection Is Rejected,Action si l'inspection qualité est rejetée
Disable Serial No And Batch Selector,Désactiver le sélecteur de numéro de lot/série
@@ -9865,7 +9864,7 @@
No stock transactions can be created or modified before this date.,Aucune transaction ne peux être créée ou modifié avant cette date.
Stock transactions that are older than the mentioned days cannot be modified.,Les transactions de stock plus ancienne que le nombre de jours ci-dessus ne peuvent être modifiées
Role Allowed to Create/Edit Back-dated Transactions,Rôle autorisé à créer et modifier des transactions anti-datée
-If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.,LEs utilisateur de ce role pourront creer et modifier des transactions dans le passé. Si vide tout les utilisateurs pourrons le faire
+"If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.","LEs utilisateur de ce role pourront creer et modifier des transactions dans le passé. Si vide tout les utilisateurs pourrons le faire"
Auto Insert Item Price If Missing,Création du prix de l'article dans les listes de prix si abscent
Update Existing Price List Rate,Mise a jour automatique du prix dans les listes de prix
Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les transactions de stock