Merge pull request #38644 from barredterra/fix-attribute-error
fix: attribute error
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
index dda0ec7..1bce1a6 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
@@ -22,7 +22,7 @@
self.create_item()
self.update_repost_settings()
- def teadDown(self):
+ def tearDown(self):
frappe.db.rollback()
def update_repost_settings(self):
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1b8f66c..abcea44 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -292,6 +292,7 @@
def on_trash(self):
self._remove_references_in_repost_doctypes()
self._remove_references_in_unreconcile()
+ self.remove_serial_and_batch_bundle()
# delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
@@ -307,6 +308,15 @@
(self.doctype, self.name),
)
+ def remove_serial_and_batch_bundle(self):
+ bundles = frappe.get_all(
+ "Serial and Batch Bundle",
+ filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)},
+ )
+
+ for bundle in bundles:
+ frappe.delete_doc("Serial and Batch Bundle", bundle.name)
+
def validate_deferred_income_expense_account(self):
field_map = {
"Sales Invoice": "deferred_revenue_account",
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 0860d9c..3ed7fc7 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -36,14 +36,14 @@
// no idea where me is coming from
if(this.frm.get_field('shipping_address')) {
- this.frm.set_query("shipping_address", function() {
- if(me.frm.doc.customer) {
+ this.frm.set_query("shipping_address", () => {
+ if(this.frm.doc.customer) {
return {
query: 'frappe.contacts.doctype.address.address.address_query',
- filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer }
+ filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer }
};
} else
- return erpnext.queries.company_address_query(me.frm.doc)
+ return erpnext.queries.company_address_query(this.frm.doc)
});
}
}
diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py
index da7edbf..5a60d2f 100644
--- a/erpnext/startup/leaderboard.py
+++ b/erpnext/startup/leaderboard.py
@@ -1,5 +1,5 @@
import frappe
-from frappe.utils import cint
+from frappe.utils.deprecations import deprecated
def get_leaderboards():
@@ -54,12 +54,13 @@
@frappe.whitelist()
def get_all_customers(date_range, company, field, limit=None):
+ filters = [["docstatus", "=", "1"], ["company", "=", company]]
+ from_date, to_date = parse_date_range(date_range)
if field == "outstanding_amount":
- filters = [["docstatus", "=", "1"], ["company", "=", company]]
- if date_range:
- date_range = frappe.parse_json(date_range)
- filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]])
- return frappe.db.get_all(
+ if from_date and to_date:
+ filters.append(["posting_date", "between", [from_date, to_date]])
+
+ return frappe.get_list(
"Sales Invoice",
fields=["customer as name", "sum(outstanding_amount) as value"],
filters=filters,
@@ -69,26 +70,20 @@
)
else:
if field == "total_sales_amount":
- select_field = "sum(so_item.base_net_amount)"
+ select_field = "base_net_total"
elif field == "total_qty_sold":
- select_field = "sum(so_item.stock_qty)"
+ select_field = "total_qty"
- date_condition = get_date_condition(date_range, "so.transaction_date")
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select so.customer as name, {0} as value
- FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item
- ON so.name = so_item.parent
- where so.docstatus = 1 {1} and so.company = %s
- group by so.customer
- order by value DESC
- limit %s
- """.format(
- select_field, date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
+ return frappe.get_list(
+ "Sales Order",
+ fields=["customer as name", f"sum({select_field}) as value"],
+ filters=filters,
+ group_by="customer",
+ order_by="value desc",
+ limit=limit,
)
@@ -96,55 +91,58 @@
def get_all_items(date_range, company, field, limit=None):
if field in ("available_stock_qty", "available_stock_value"):
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
- return frappe.db.get_all(
+ results = frappe.db.get_all(
"Bin",
fields=["item_code as name", "{0} as value".format(select_field)],
group_by="item_code",
order_by="value desc",
limit=limit,
)
+ readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name"))
+ return [item for item in results if item["name"] in readable_active_items]
else:
if field == "total_sales_amount":
- select_field = "sum(order_item.base_net_amount)"
+ select_field = "base_net_amount"
select_doctype = "Sales Order"
elif field == "total_purchase_amount":
- select_field = "sum(order_item.base_net_amount)"
+ select_field = "base_net_amount"
select_doctype = "Purchase Order"
elif field == "total_qty_sold":
- select_field = "sum(order_item.stock_qty)"
+ select_field = "stock_qty"
select_doctype = "Sales Order"
elif field == "total_qty_purchased":
- select_field = "sum(order_item.stock_qty)"
+ select_field = "stock_qty"
select_doctype = "Purchase Order"
- date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+ filters = [["docstatus", "=", "1"], ["company", "=", company]]
+ from_date, to_date = parse_date_range(date_range)
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select order_item.item_code as name, {0} as value
- from `tab{1}` sales_order join `tab{1} Item` as order_item
- on sales_order.name = order_item.parent
- where sales_order.docstatus = 1
- and sales_order.company = %s {2}
- group by order_item.item_code
- order by value desc
- limit %s
- """.format(
- select_field, select_doctype, date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
- ) # nosec
+ child_doctype = f"{select_doctype} Item"
+ return frappe.get_list(
+ select_doctype,
+ fields=[
+ f"`tab{child_doctype}`.item_code as name",
+ f"sum(`tab{child_doctype}`.{select_field}) as value",
+ ],
+ filters=filters,
+ order_by="value desc",
+ group_by=f"`tab{child_doctype}`.item_code",
+ limit=limit,
+ )
@frappe.whitelist()
def get_all_suppliers(date_range, company, field, limit=None):
+ filters = [["docstatus", "=", "1"], ["company", "=", company]]
+ from_date, to_date = parse_date_range(date_range)
+
if field == "outstanding_amount":
- filters = [["docstatus", "=", "1"], ["company", "=", company]]
- if date_range:
- date_range = frappe.parse_json(date_range)
- filters.append(["posting_date", "between", [date_range[0], date_range[1]]])
- return frappe.db.get_all(
+ if from_date and to_date:
+ filters.append(["posting_date", "between", [from_date, to_date]])
+
+ return frappe.get_list(
"Purchase Invoice",
fields=["supplier as name", "sum(outstanding_amount) as value"],
filters=filters,
@@ -154,48 +152,40 @@
)
else:
if field == "total_purchase_amount":
- select_field = "sum(purchase_order_item.base_net_amount)"
+ select_field = "base_net_total"
elif field == "total_qty_purchased":
- select_field = "sum(purchase_order_item.stock_qty)"
+ select_field = "total_qty"
- date_condition = get_date_condition(date_range, "purchase_order.modified")
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select purchase_order.supplier as name, {0} as value
- FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item`
- as purchase_order_item ON purchase_order.name = purchase_order_item.parent
- where
- purchase_order.docstatus = 1
- {1}
- and purchase_order.company = %s
- group by purchase_order.supplier
- order by value DESC
- limit %s""".format(
- select_field, date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
- ) # nosec
+ return frappe.get_list(
+ "Purchase Order",
+ fields=["supplier as name", f"sum({select_field}) as value"],
+ filters=filters,
+ group_by="supplier",
+ order_by="value desc",
+ limit=limit,
+ )
@frappe.whitelist()
def get_all_sales_partner(date_range, company, field, limit=None):
if field == "total_sales_amount":
- select_field = "sum(`base_net_total`)"
+ select_field = "base_net_total"
elif field == "total_commission":
- select_field = "sum(`total_commission`)"
+ select_field = "total_commission"
- filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company}
- if date_range:
- date_range = frappe.parse_json(date_range)
- filters["transaction_date"] = ["between", [date_range[0], date_range[1]]]
+ filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]]
+ from_date, to_date = parse_date_range(date_range)
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
return frappe.get_list(
"Sales Order",
fields=[
- "`sales_partner` as name",
- "{} as value".format(select_field),
+ "sales_partner as name",
+ f"sum({select_field}) as value",
],
filters=filters,
group_by="sales_partner",
@@ -206,27 +196,29 @@
@frappe.whitelist()
def get_all_sales_person(date_range, company, field=None, limit=0):
- date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+ filters = [
+ ["docstatus", "=", "1"],
+ ["company", "=", company],
+ ["Sales Team", "sales_person", "is", "set"],
+ ]
+ from_date, to_date = parse_date_range(date_range)
+ if from_date and to_date:
+ filters.append(["transaction_date", "between", [from_date, to_date]])
- return frappe.db.sql(
- """
- select sales_team.sales_person as name, sum(sales_order.base_net_total) as value
- from `tabSales Order` as sales_order join `tabSales Team` as sales_team
- on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order'
- where sales_order.docstatus = 1
- and sales_order.company = %s
- {date_condition}
- group by sales_team.sales_person
- order by value DESC
- limit %s
- """.format(
- date_condition=date_condition
- ),
- (company, cint(limit)),
- as_dict=1,
+ return frappe.get_list(
+ "Sales Order",
+ fields=[
+ "`tabSales Team`.sales_person as name",
+ "sum(`tabSales Team`.allocated_amount) as value",
+ ],
+ filters=filters,
+ group_by="`tabSales Team`.sales_person",
+ order_by="value desc",
+ limit=limit,
)
+@deprecated
def get_date_condition(date_range, field):
date_condition = ""
if date_range:
@@ -236,3 +228,11 @@
field, frappe.db.escape(from_date), frappe.db.escape(to_date)
)
return date_condition
+
+
+def parse_date_range(date_range):
+ if date_range:
+ date_range = frappe.parse_json(date_range)
+ return date_range[0], date_range[1]
+
+ return None, None
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index cda4445..9f01ee9 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -121,7 +121,7 @@
frappe.throw(__("Please attach CSV file"));
}
- if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) {
+ if (frm.doc.has_serial_no && !prompt_data.csv_file && !prompt_data.serial_nos) {
frappe.throw(__("Please enter serial nos"));
}
},
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 108a2e0..6785881 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -511,6 +511,22 @@
serial_batches = {}
for row in self.entries:
+ if self.has_serial_no and not row.serial_no:
+ frappe.throw(
+ _("At row {0}: Serial No is mandatory for Item {1}").format(
+ bold(row.idx), bold(self.item_code)
+ ),
+ title=_("Serial No is mandatory"),
+ )
+
+ if self.has_batch_no and not row.batch_no:
+ frappe.throw(
+ _("At row {0}: Batch No is mandatory for Item {1}").format(
+ bold(row.idx), bold(self.item_code)
+ ),
+ title=_("Batch No is mandatory"),
+ )
+
if row.serial_no:
serial_nos.append(row.serial_no)
@@ -794,6 +810,9 @@
if index == 0:
has_serial_no = row[0] == "Serial No"
has_batch_no = row[0] == "Batch No"
+ if not has_batch_no:
+ has_batch_no = row[1] == "Batch No"
+
continue
if not row[0]:
@@ -810,6 +829,13 @@
}
)
+ batch_nos.append(
+ {
+ "batch_no": row[1],
+ "qty": row[2],
+ }
+ )
+
serial_nos.append(_dict)
elif has_batch_no:
batch_nos.append(
@@ -845,6 +871,9 @@
serial_nos_details = []
user = frappe.session.user
for serial_no in serial_nos:
+ if frappe.db.exists("Serial No", serial_no):
+ continue
+
serial_nos_details.append(
(
serial_no,
@@ -875,7 +904,7 @@
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
- frappe.msgprint(_("Serial Nos are created successfully"))
+ frappe.msgprint(_("Serial Nos are created successfully"), alert=True)
def make_batch_nos(item_code, batch_nos):
@@ -886,6 +915,9 @@
batch_nos_details = []
user = frappe.session.user
for batch_no in batch_nos:
+ if frappe.db.exists("Batch", batch_no):
+ continue
+
batch_nos_details.append(
(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
)
@@ -904,7 +936,7 @@
frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))
- frappe.msgprint(_("Batch Nos are created successfully"))
+ frappe.msgprint(_("Batch Nos are created successfully"), alert=True)
def parse_serial_nos(data):
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
index 0e01b20..d74d657 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
@@ -368,6 +368,58 @@
# Batch does not belong to serial no
self.assertRaises(frappe.exceptions.ValidationError, doc.save)
+ def test_auto_delete_draft_serial_and_batch_bundle(self):
+ serial_and_batch_code = "New Serial No Auto Delete 1"
+ make_item(
+ serial_and_batch_code,
+ {
+ "has_serial_no": 1,
+ "serial_no_series": "TEST-SER-VALL-.#####",
+ "is_stock_item": 1,
+ },
+ )
+
+ ste = make_stock_entry(
+ item_code=serial_and_batch_code,
+ target="_Test Warehouse - _TC",
+ qty=1,
+ rate=500,
+ do_not_submit=True,
+ )
+
+ serial_no = "SN-TEST-AUTO-DEL"
+ if not frappe.db.exists("Serial No", serial_no):
+ frappe.get_doc(
+ {
+ "doctype": "Serial No",
+ "serial_no": serial_no,
+ "item_code": serial_and_batch_code,
+ "company": "_Test Company",
+ }
+ ).insert(ignore_permissions=True)
+
+ bundle_doc = make_serial_batch_bundle(
+ {
+ "item_code": serial_and_batch_code,
+ "warehouse": "_Test Warehouse - _TC",
+ "voucher_type": "Stock Entry",
+ "posting_date": ste.posting_date,
+ "posting_time": ste.posting_time,
+ "qty": 1,
+ "serial_nos": [serial_no],
+ "type_of_transaction": "Inward",
+ "do_not_submit": True,
+ }
+ )
+
+ bundle_doc.reload()
+ ste.items[0].serial_and_batch_bundle = bundle_doc.name
+ ste.save()
+ ste.reload()
+
+ ste.delete()
+ self.assertFalse(frappe.db.exists("Serial and Batch Bundle", bundle_doc.name))
+
def get_batch_from_bundle(bundle):
from erpnext.stock.serial_batch_bundle import get_batch_nos
diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
index 09565cb..5de2c2e 100644
--- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
+++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
@@ -27,7 +27,6 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Serial No",
- "mandatory_depends_on": "eval:parent.has_serial_no == 1",
"options": "Serial No",
"search_index": 1
},
@@ -38,7 +37,6 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Batch No",
- "mandatory_depends_on": "eval:parent.has_batch_no == 1",
"options": "Batch",
"search_index": 1
},
@@ -122,7 +120,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-07-03 15:29:50.199075",
+ "modified": "2023-12-10 19:47:48.227772",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial and Batch Entry",