Merge pull request #38071 from GursheenK/unequal-dr-cr-for-payments-with-partial-credit-note
fix: handle partial return against invoices in payment entries
diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
index cb0ed3d..5a281aa 100644
--- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
+++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
@@ -186,6 +186,7 @@
"label": "Image"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -833,7 +834,7 @@
],
"istable": 1,
"links": [],
- "modified": "2023-03-12 13:36:40.160468",
+ "modified": "2023-11-14 18:33:22.585715",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Item",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 424e942..bcedb7c 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -158,6 +158,7 @@
"width": "300px"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -915,7 +916,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-03 21:01:01.824892",
+ "modified": "2023-11-14 18:33:48.547297",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 5d2764b..a403b14 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -167,6 +167,7 @@
"print_hide": 1
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -901,7 +902,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-07-26 12:53:22.404057",
+ "modified": "2023-11-14 18:34:10.479329",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
@@ -911,4 +912,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 31bc6fd..7d91309 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -53,6 +53,9 @@
def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
):
+ if isinstance(boolean, str):
+ boolean = frappe.json.loads(boolean)
+
fiscal_years = get_fiscal_years(
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
)
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index 40f51ab..d6b9c46 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -481,11 +481,10 @@
"read_only": 1
},
{
- "depends_on": "eval.doc.asset_quantity",
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity",
- "read_only": 1
+ "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
},
{
"fieldname": "depr_entry_posting_status",
@@ -572,7 +571,7 @@
"link_fieldname": "target_asset"
}
],
- "modified": "2023-10-27 17:03:46.629617",
+ "modified": "2023-11-15 17:40:17.315203",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
index 0073170..dc54d60 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
@@ -1,30 +1,21 @@
-// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Bulk Transaction Log', {
-
- refresh: function(frm) {
- frm.disable_save();
- frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
- frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
- query(frm, 1);
- }
- );
- });
- }
+frappe.ui.form.on("Bulk Transaction Log", {
+ refresh(frm) {
+ frm.add_custom_button(__('Succeeded Entries'), function() {
+ frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Success"});
+ }, __("View"));
+ frm.add_custom_button(__('Failed Entries'), function() {
+ frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"});
+ }, __("View"));
+ if (frm.doc.failed) {
+ frm.add_custom_button(__('Retry Failed Transactions'), function() {
+ frappe.call({
+ method: "erpnext.utilities.bulk_transaction.retry",
+ args: {date: frm.doc.date}
+ });
+ });
+ }
+ },
});
-
-function query(frm) {
- frappe.call({
- method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
- args: {
- log_date: frm.doc.log_date
- }
- }).then((r) => {
- if (r.message === "No Failed Records") {
- frappe.show_alert(__(r.message), 5);
- } else {
- frappe.show_alert(__("Retrying Failed Transactions"), 5);
- }
- });
-}
\ No newline at end of file
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json
index da42cf1..75cb358 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json
@@ -1,31 +1,64 @@
{
"actions": [],
- "allow_rename": 1,
- "creation": "2021-11-30 13:41:16.343827",
+ "allow_copy": 1,
+ "creation": "2023-11-09 20:14:45.139593",
+ "default_view": "List",
"doctype": "DocType",
- "editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "log_date",
- "logger_data"
+ "date",
+ "column_break_bsan",
+ "log_entries",
+ "section_break_mdmv",
+ "succeeded",
+ "column_break_qryp",
+ "failed"
],
"fields": [
{
- "fieldname": "log_date",
+ "fieldname": "date",
"fieldtype": "Date",
- "label": "Log Date",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Date",
"read_only": 1
},
{
- "fieldname": "logger_data",
- "fieldtype": "Table",
- "label": "Logger Data",
- "options": "Bulk Transaction Log Detail"
+ "fieldname": "log_entries",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Log Entries",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_bsan",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_mdmv",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "succeeded",
+ "fieldtype": "Int",
+ "label": "Succeeded",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_qryp",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "failed",
+ "fieldtype": "Int",
+ "label": "Failed",
+ "read_only": 1
}
],
- "index_web_pages_for_search": 1,
+ "in_create": 1,
+ "is_virtual": 1,
"links": [],
- "modified": "2022-02-03 17:23:02.935325",
+ "modified": "2023-11-11 04:52:49.347376",
"modified_by": "Administrator",
"module": "Bulk Transaction",
"name": "Bulk Transaction Log",
@@ -47,5 +80,5 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
- "track_changes": 1
+ "title_field": "date"
}
\ No newline at end of file
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
index 0596be4..712caf1 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
@@ -1,67 +1,112 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-from datetime import date
-
import frappe
+from frappe import qb
from frappe.model.document import Document
-
-from erpnext.utilities.bulk_transaction import task, update_logger
+from frappe.query_builder.functions import Count
+from frappe.utils import cint
+from pypika import Order
class BulkTransactionLog(Document):
- pass
+ def db_insert(self, *args, **kwargs):
+ pass
+ def load_from_db(self):
+ log_detail = qb.DocType("Bulk Transaction Log Detail")
-@frappe.whitelist()
-def retry_failing_transaction(log_date=None):
- if not log_date:
- log_date = str(date.today())
- btp = frappe.qb.DocType("Bulk Transaction Log Detail")
- data = (
- frappe.qb.from_(btp)
- .select(btp.transaction_name, btp.from_doctype, btp.to_doctype)
- .distinct()
- .where(btp.retried != 1)
- .where(btp.transaction_status == "Failed")
- .where(btp.date == log_date)
- ).run(as_dict=True)
+ has_records = frappe.db.sql(
+ f"select exists (select * from `tabBulk Transaction Log Detail` where date = '{self.name}');"
+ )[0][0]
+ if not has_records:
+ raise frappe.DoesNotExistError
- if data:
- if len(data) > 10:
- frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
- else:
- job(data, log_date)
- else:
- return "No Failed Records"
+ succeeded_logs = (
+ qb.from_(log_detail)
+ .select(Count(log_detail.date).as_("count"))
+ .where((log_detail.date == self.name) & (log_detail.transaction_status == "Success"))
+ .run()
+ )[0][0] or 0
+ failed_logs = (
+ qb.from_(log_detail)
+ .select(Count(log_detail.date).as_("count"))
+ .where((log_detail.date == self.name) & (log_detail.transaction_status == "Failed"))
+ .run()
+ )[0][0] or 0
+ total_logs = succeeded_logs + failed_logs
+ transaction_log = frappe._dict(
+ {
+ "date": self.name,
+ "count": total_logs,
+ "succeeded": succeeded_logs,
+ "failed": failed_logs,
+ }
+ )
+ super(Document, self).__init__(serialize_transaction_log(transaction_log))
+ @staticmethod
+ def get_list(args):
+ filter_date = parse_list_filters(args)
+ limit = cint(args.get("page_length")) or 20
+ log_detail = qb.DocType("Bulk Transaction Log Detail")
-def job(data, log_date):
- for d in data:
- failed = []
- try:
- frappe.db.savepoint("before_creation_of_record")
- task(d.transaction_name, d.from_doctype, d.to_doctype)
- except Exception as e:
- frappe.db.rollback(save_point="before_creation_of_record")
- failed.append(e)
- update_logger(
- d.transaction_name,
- e,
- d.from_doctype,
- d.to_doctype,
- status="Failed",
- log_date=log_date,
- restarted=1,
+ dates_query = (
+ qb.from_(log_detail)
+ .select(log_detail.date)
+ .distinct()
+ .orderby(log_detail.date, order=Order.desc)
+ .limit(limit)
+ )
+ if filter_date:
+ dates_query = dates_query.where(log_detail.date == filter_date)
+ dates = dates_query.run()
+
+ transaction_logs = []
+ if dates:
+ transaction_logs_query = (
+ qb.from_(log_detail)
+ .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count"))
+ .where(log_detail.date.isin(dates))
+ .orderby(log_detail.date, order=Order.desc)
+ .groupby(log_detail.date)
+ .limit(limit)
)
+ transaction_logs = transaction_logs_query.run(as_dict=True)
- if not failed:
- update_logger(
- d.transaction_name,
- None,
- d.from_doctype,
- d.to_doctype,
- status="Success",
- log_date=log_date,
- restarted=1,
- )
+ return [serialize_transaction_log(x) for x in transaction_logs]
+
+ @staticmethod
+ def get_count(args):
+ pass
+
+ @staticmethod
+ def get_stats(args):
+ pass
+
+ def db_update(self, *args, **kwargs):
+ pass
+
+ def delete(self):
+ pass
+
+
+def serialize_transaction_log(data):
+ return frappe._dict(
+ name=data.date,
+ date=data.date,
+ log_entries=data.count,
+ succeeded=data.succeeded,
+ failed=data.failed,
+ )
+
+
+def parse_list_filters(args):
+ # parse date filter
+ filter_date = None
+ for fil in args.get("filters"):
+ if isinstance(fil, list):
+ for elem in fil:
+ if elem == "date":
+ filter_date = fil[3]
+ return filter_date
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
index c673be8..01bb615 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
@@ -1,79 +1,9 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
-from datetime import date
-
-import frappe
-
-from erpnext.utilities.bulk_transaction import transaction_processing
+# import frappe
+from frappe.tests.utils import FrappeTestCase
-class TestBulkTransactionLog(unittest.TestCase):
- def setUp(self):
- create_company()
- create_customer()
- create_item()
-
- def test_entry_in_log(self):
- so_name = create_so()
- transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
- doc = frappe.get_doc("Bulk Transaction Log", str(date.today()))
- for d in doc.get("logger_data"):
- if d.transaction_name == so_name:
- self.assertEqual(d.transaction_name, so_name)
- self.assertEqual(d.transaction_status, "Success")
- self.assertEqual(d.from_doctype, "Sales Order")
- self.assertEqual(d.to_doctype, "Sales Invoice")
- self.assertEqual(d.retried, 0)
-
-
-def create_company():
- if not frappe.db.exists("Company", "_Test Company"):
- frappe.get_doc(
- {
- "doctype": "Company",
- "company_name": "_Test Company",
- "country": "India",
- "default_currency": "INR",
- }
- ).insert()
-
-
-def create_customer():
- if not frappe.db.exists("Customer", "Bulk Customer"):
- frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert()
-
-
-def create_item():
- if not frappe.db.exists("Item", "MK"):
- frappe.get_doc(
- {
- "doctype": "Item",
- "item_code": "MK",
- "item_name": "Milk",
- "description": "Milk",
- "item_group": "Products",
- }
- ).insert()
-
-
-def create_so(intent=None):
- so = frappe.new_doc("Sales Order")
- so.customer = "Bulk Customer"
- so.company = "_Test Company"
- so.transaction_date = date.today()
-
- so.set_warehouse = "Finished Goods - _TC"
- so.append(
- "items",
- {
- "item_code": "MK",
- "delivery_date": date.today(),
- "qty": 10,
- "rate": 80,
- },
- )
- so.insert()
- so.submit()
- return so.name
+class TestBulkTransactionLog(FrappeTestCase):
+ pass
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js
new file mode 100644
index 0000000..5669601
--- /dev/null
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Bulk Transaction Log Detail", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json
index 8262caa..9590325 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json
@@ -6,12 +6,12 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "from_doctype",
"transaction_name",
"date",
"time",
"transaction_status",
"error_description",
- "from_doctype",
"to_doctype",
"retried"
],
@@ -20,8 +20,11 @@
"fieldname": "transaction_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
+ "in_standard_filter": 1,
"label": "Name",
- "options": "from_doctype"
+ "options": "from_doctype",
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "transaction_status",
@@ -39,9 +42,11 @@
{
"fieldname": "from_doctype",
"fieldtype": "Link",
+ "in_standard_filter": 1,
"label": "From Doctype",
"options": "DocType",
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "to_doctype",
@@ -54,8 +59,10 @@
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
+ "in_standard_filter": 1,
"label": "Date ",
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "time",
@@ -66,19 +73,33 @@
{
"fieldname": "retried",
"fieldtype": "Int",
+ "in_list_view": 1,
"label": "Retried",
"read_only": 1
}
],
+ "in_create": 1,
"index_web_pages_for_search": 1,
- "istable": 1,
"links": [],
- "modified": "2022-02-03 19:57:31.650359",
+ "modified": "2023-11-10 11:44:10.758342",
"modified_by": "Administrator",
"module": "Bulk Transaction",
"name": "Bulk Transaction Log Detail",
"owner": "Administrator",
- "permissions": [],
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py
new file mode 100644
index 0000000..5217b60
--- /dev/null
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestBulkTransactionLogDetail(FrappeTestCase):
+ pass
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 2b6ffb7..2d706f4 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -189,6 +189,7 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -916,7 +917,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-06 11:00:53.596417",
+ "modified": "2023-11-14 18:34:27.267382",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
index 82fcfa2..6cdd2ba 100644
--- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
+++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
@@ -87,6 +87,7 @@
"width": "300px"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -260,13 +261,15 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-09-24 17:26:46.276934",
+ "modified": "2023-11-14 18:34:48.327224",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Item",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
index 8d491fb..4bbcacf 100644
--- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
+++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
@@ -133,6 +133,7 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -559,13 +560,15 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2020-10-19 12:36:26.913211",
+ "modified": "2023-11-14 18:35:03.435817",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index ece08d8..a470b47 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -365,7 +365,7 @@
{
"item_code": d.item_code,
"warehouse": d.get("from_warehouse"),
- "posting_date": self.get("posting_date") or self.get("transation_date"),
+ "posting_date": self.get("posting_date") or self.get("transaction_date"),
"posting_time": posting_time,
"qty": -1 * flt(d.get("stock_qty")),
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 5fa66b1..3d55a08 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -626,6 +626,18 @@
(row.item_code, row.get(self.subcontract_data.order_field))
] -= row.qty
+ def __set_rate_for_serial_and_batch_bundle(self):
+ if self.doctype != "Subcontracting Receipt":
+ return
+
+ for row in self.get(self.raw_material_table):
+ if not row.get("serial_and_batch_bundle"):
+ continue
+
+ row.rate = frappe.get_cached_value(
+ "Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"
+ )
+
def __modify_serial_and_batch_bundle(self):
if self.is_new():
return
@@ -681,6 +693,7 @@
self.__remove_changed_rows()
self.__set_supplied_items()
self.__modify_serial_and_batch_bundle()
+ self.__set_rate_for_serial_and_batch_bundle()
def __validate_batch_no(self, row, key):
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
diff --git a/erpnext/crm/doctype/opportunity_item/opportunity_item.json b/erpnext/crm/doctype/opportunity_item/opportunity_item.json
index 1b4973c..732f80d 100644
--- a/erpnext/crm/doctype/opportunity_item/opportunity_item.json
+++ b/erpnext/crm/doctype/opportunity_item/opportunity_item.json
@@ -103,6 +103,7 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -165,7 +166,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-07-30 16:39:09.775720",
+ "modified": "2023-11-14 18:35:30.887278",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity Item",
@@ -173,5 +174,6 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json
index 9b1db63..c75ac32 100644
--- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json
+++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json
@@ -85,6 +85,7 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -169,7 +170,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-05-27 13:42:23.305455",
+ "modified": "2023-11-14 18:35:40.856895",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Explosion Item",
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index c526611..cb58af1 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -111,6 +111,7 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -289,7 +290,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-07-28 10:20:51.559010",
+ "modified": "2023-11-14 18:35:51.378513",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 907a775..1b10d8a 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -139,7 +139,6 @@
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
},
@@ -148,7 +147,6 @@
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
- "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
"reqd": 1,
"depends_on": "eval:doc.filter_based_on == 'Fiscal Year'"
},
@@ -197,5 +195,13 @@
}
]
+ // Dynamically set 'default' values for fiscal year filters
+ let fy_filters = filters.filter(x=>{return ["from_fiscal_year", "to_fiscal_year"].includes(x.fieldname);})
+ let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, true);
+ if (fiscal_year) {
+ let fy = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, false);
+ fy_filters.forEach(x=>{x.default = fy;})
+ }
+
return filters;
}
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index d435711..25fc754 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -404,7 +404,7 @@
});
},
- get_fiscal_year: function(date, with_dates=false) {
+ get_fiscal_year: function(date, with_dates=false, boolean=false) {
if(!date) {
date = frappe.datetime.get_today();
}
@@ -413,7 +413,8 @@
frappe.call({
method: "erpnext.accounts.utils.get_fiscal_year",
args: {
- date: date
+ date: date,
+ boolean: boolean
},
async: false,
callback: function(r) {
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 42932ad..ddc7e2a 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -3,17 +3,32 @@
frappe.ui.form.on("Customer", {
setup: function(frm) {
-
+ frm.custom_make_buttons = {
+ "Opportunity": "Opportunity",
+ "Quotation": "Quotation",
+ "Sales Order": "Sales Order",
+ "Pricing Rule": "Pricing Rule",
+ };
frm.make_methods = {
- 'Quotation': () => frappe.model.open_mapped_doc({
- method: "erpnext.selling.doctype.customer.customer.make_quotation",
- frm: cur_frm
- }),
- 'Opportunity': () => frappe.model.open_mapped_doc({
- method: "erpnext.selling.doctype.customer.customer.make_opportunity",
- frm: cur_frm
- })
- }
+ "Quotation": () =>
+ frappe.model.open_mapped_doc({
+ method: "erpnext.selling.doctype.customer.customer.make_quotation",
+ frm: frm,
+ }),
+ "Sales Order": () =>
+ frappe.model.with_doctype("Sales Order", function () {
+ var so = frappe.model.get_new_doc("Sales Order");
+ so.customer = frm.doc.name; // Set the current customer as the SO customer
+ frappe.set_route("Form", "Sales Order", so.name);
+ }),
+ "Opportunity": () =>
+ frappe.model.open_mapped_doc({
+ method: "erpnext.selling.doctype.customer.customer.make_opportunity",
+ frm: frm,
+ }),
+ "Pricing Rule": () =>
+ erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name),
+ };
frm.add_fetch('lead_name', 'company_name', 'customer_name');
frm.add_fetch('default_sales_partner','commission_rate','default_commission_rate');
@@ -146,9 +161,9 @@
{party_type: 'Customer', party: frm.doc.name, party_name: frm.doc.customer_name});
}, __('View'));
- frm.add_custom_button(__('Pricing Rule'), function () {
- erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
- }, __('Create'));
+ for (const doctype in frm.make_methods) {
+ frm.add_custom_button(__(doctype), frm.make_methods[doctype], __("Create"));
+ }
frm.add_custom_button(__('Get Customer Group Details'), function () {
frm.trigger("get_customer_group_details");
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index 5016f1f..0e25313 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -135,6 +135,7 @@
"width": "300px"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -666,7 +667,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-09-26 13:42:11.410294",
+ "modified": "2023-11-14 18:24:24.619832",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",
@@ -676,4 +677,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index f82047f..b4f7300 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -68,7 +68,6 @@
"total_weight",
"column_break_21",
"weight_uom",
- "accounting_dimensions_section",
"warehouse_and_reference",
"warehouse",
"target_warehouse",
@@ -177,6 +176,7 @@
"print_hide": 1
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -890,18 +890,12 @@
"label": "Production Plan Qty",
"no_copy": 1,
"read_only": 1
- },
- {
- "collapsible": 1,
- "fieldname": "accounting_dimensions_section",
- "fieldtype": "Section Break",
- "label": "Accounting Dimensions"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-17 18:18:26.475259",
+ "modified": "2023-11-14 18:37:12.787893",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 9bba4eb..feecd9c 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -609,11 +609,12 @@
// if item is clicked twice from item selector
// then "item_code, batch_no, uom, rate" will help in getting the exact item
// to increase the qty by one
- const has_batch_no = batch_no;
+ const has_batch_no = (batch_no !== 'null' && batch_no !== null);
item_row = this.frm.doc.items.find(
i => i.item_code === item_code
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
&& (i.uom === uom)
+ && (i.rate === flt(rate))
);
}
diff --git a/erpnext/setup/doctype/employee/employee.js b/erpnext/setup/doctype/employee/employee.js
index 39a215f..efc3fd1 100755
--- a/erpnext/setup/doctype/employee/employee.js
+++ b/erpnext/setup/doctype/employee/employee.js
@@ -81,8 +81,10 @@
employee: frm.doc.name,
email: frm.doc.prefered_email
},
+ freeze: true,
+ freeze_message: __("Creating User..."),
callback: function (r) {
- frm.set_value("user_id", r.message);
+ frm.reload_doc();
}
});
}
diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py
index 78fb4df..6f9176c 100755
--- a/erpnext/setup/doctype/employee/employee.py
+++ b/erpnext/setup/doctype/employee/employee.py
@@ -48,6 +48,9 @@
else:
existing_user_id = frappe.db.get_value("Employee", self.name, "user_id")
if existing_user_id:
+ user = frappe.get_doc("User", existing_user_id)
+ validate_employee_role(user, ignore_emp_check=True)
+ user.save(ignore_permissions=True)
remove_user_permission("Employee", self.name, existing_user_id)
def after_rename(self, old, new, merge):
@@ -230,12 +233,26 @@
frappe.cache().hdel("employees_with_number", prev_number)
-def validate_employee_role(doc, method):
+def validate_employee_role(doc, method=None, ignore_emp_check=False):
# called via User hook
- if "Employee" in [d.role for d in doc.get("roles")]:
- if not frappe.db.get_value("Employee", {"user_id": doc.name}):
- frappe.msgprint(_("Please set User ID field in an Employee record to set Employee Role"))
- doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0])
+ if not ignore_emp_check:
+ if frappe.db.get_value("Employee", {"user_id": doc.name}):
+ return
+
+ user_roles = [d.role for d in doc.get("roles")]
+ if "Employee" in user_roles:
+ frappe.msgprint(
+ _("User {0}: Removed Employee role as there is no mapped employee.").format(doc.name)
+ )
+ doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0])
+
+ if "Employee Self Service" in user_roles:
+ frappe.msgprint(
+ _("User {0}: Removed Employee Self Service role as there is no mapped employee.").format(
+ doc.name
+ )
+ )
+ doc.get("roles").remove(doc.get("roles", {"role": "Employee Self Service"})[0])
def update_user_permissions(doc, method):
@@ -347,6 +364,8 @@
}
)
user.insert()
+ emp.user_id = user.name
+ emp.save()
return user.name
diff --git a/erpnext/setup/doctype/employee/test_employee.py b/erpnext/setup/doctype/employee/test_employee.py
index 5a693c5..9b70683 100644
--- a/erpnext/setup/doctype/employee/test_employee.py
+++ b/erpnext/setup/doctype/employee/test_employee.py
@@ -25,6 +25,15 @@
employee1_doc.status = "Left"
self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
+ def test_user_has_employee(self):
+ employee = make_employee("test_emp_user_creation@company.com")
+ employee_doc = frappe.get_doc("Employee", employee)
+ user = employee_doc.user_id
+ self.assertTrue("Employee" in frappe.get_roles(user))
+ employee_doc.user_id = ""
+ employee_doc.save()
+ self.assertTrue("Employee" not in frappe.get_roles(user))
+
def tearDown(self):
frappe.db.rollback()
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
index 481a3a5..d266285 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -108,7 +108,16 @@
if no_of_docs > 0:
self.delete_version_log(docfield["parent"], docfield["fieldname"])
- self.delete_communications(docfield["parent"], docfield["fieldname"])
+
+ reference_docs = frappe.get_all(
+ docfield["parent"], filters={docfield["fieldname"]: self.company}
+ )
+ reference_doc_names = [r.name for r in reference_docs]
+
+ self.delete_communications(docfield["parent"], reference_doc_names)
+ self.delete_comments(docfield["parent"], reference_doc_names)
+ self.unlink_attachments(docfield["parent"], reference_doc_names)
+
self.populate_doctypes_table(tables, docfield["parent"], no_of_docs)
self.delete_child_tables(docfield["parent"], docfield["fieldname"])
@@ -197,19 +206,49 @@
(versions.ref_doctype == doctype) & (versions.docname.isin(batch))
).run()
- def delete_communications(self, doctype, company_fieldname):
- reference_docs = frappe.get_all(doctype, filters={company_fieldname: self.company})
- reference_doc_names = [r.name for r in reference_docs]
-
+ def delete_communications(self, doctype, reference_doc_names):
communications = frappe.get_all(
"Communication",
filters={"reference_doctype": doctype, "reference_name": ["in", reference_doc_names]},
)
communication_names = [c.name for c in communications]
+ if not communication_names:
+ return
+
for batch in create_batch(communication_names, self.batch_size):
frappe.delete_doc("Communication", batch, ignore_permissions=True)
+ def delete_comments(self, doctype, reference_doc_names):
+ comments = frappe.get_all(
+ "Comment",
+ filters={"reference_doctype": doctype, "reference_name": ["in", reference_doc_names]},
+ )
+ comment_names = [c.name for c in comments]
+
+ if not comment_names:
+ return
+
+ for batch in create_batch(comment_names, self.batch_size):
+ frappe.delete_doc("Comment", batch, ignore_permissions=True)
+
+ def unlink_attachments(self, doctype, reference_doc_names):
+ files = frappe.get_all(
+ "File",
+ filters={"attached_to_doctype": doctype, "attached_to_name": ["in", reference_doc_names]},
+ )
+ file_names = [c.name for c in files]
+
+ if not file_names:
+ return
+
+ file = qb.DocType("File")
+
+ for batch in create_batch(file_names, self.batch_size):
+ qb.update(file).set(file.attached_to_doctype, None).set(file.attached_to_name, None).where(
+ file.name.isin(batch)
+ ).run()
+
@frappe.whitelist()
def get_doctypes_to_be_ignored():
diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json
index 225da6d..0c4757f 100644
--- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json
+++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json
@@ -104,15 +104,6 @@
"read_only": 1
},
{
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "label": "Amended From",
- "no_copy": 1,
- "options": "Closing Stock Balance",
- "print_hide": 1,
- "read_only": 1
- },
- {
"fieldname": "include_uom",
"fieldtype": "Link",
"label": "Include UOM",
@@ -145,4 +136,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 6148950..a44b9ac 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -168,6 +168,7 @@
"width": "300px"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -893,7 +894,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-16 16:18:18.013379",
+ "modified": "2023-11-14 18:37:38.638144",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index 9912be1..5dc07c9 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -110,6 +110,7 @@
"width": "250px"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach Image",
"label": "Image",
@@ -478,7 +479,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-27 15:53:41.444236",
+ "modified": "2023-11-14 18:37:59.599115",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 718f007..ce2e5d7 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -192,6 +192,7 @@
"width": "300px"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -1090,7 +1091,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-30 17:32:24.560337",
+ "modified": "2023-11-14 18:38:15.251994",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
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 f2bbf2b..0a4cae7 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
@@ -1604,6 +1604,9 @@
)
for key, val in kwargs.items():
+ if not val:
+ continue
+
if key in ["get_subcontracted_item"]:
continue
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
index dcbd9b2..be37994 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -12,6 +12,7 @@
"posting_date",
"posting_time",
"is_adjustment_entry",
+ "auto_created_serial_and_batch_bundle",
"column_break_6",
"voucher_type",
"voucher_no",
@@ -340,6 +341,13 @@
"fieldname": "is_adjustment_entry",
"fieldtype": "Check",
"label": "Is Adjustment Entry"
+ },
+ {
+ "default": "0",
+ "depends_on": "serial_and_batch_bundle",
+ "fieldname": "auto_created_serial_and_batch_bundle",
+ "fieldtype": "Check",
+ "label": "Auto Created Serial and Batch Bundle"
}
],
"hide_toolbar": 1,
@@ -348,7 +356,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2023-10-23 18:07:42.063615",
+ "modified": "2023-11-14 16:47:39.791967",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Ledger Entry",
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 5998274..da98455 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -129,7 +129,9 @@
frappe.throw(_(error_msg))
def set_serial_and_batch_bundle(self, sn_doc):
- self.sle.db_set("serial_and_batch_bundle", sn_doc.name)
+ self.sle.db_set(
+ {"serial_and_batch_bundle": sn_doc.name, "auto_created_serial_and_batch_bundle": 1}
+ )
if sn_doc.is_rejected:
frappe.db.set_value(
@@ -143,6 +145,12 @@
@property
def child_doctype(self):
child_doctype = self.sle.voucher_type + " Item"
+
+ if (
+ self.sle.voucher_type == "Subcontracting Receipt" and self.sle.dependant_sle_voucher_detail_no
+ ):
+ child_doctype = "Subcontracting Receipt Supplied Item"
+
if self.sle.voucher_type == "Stock Entry":
child_doctype = "Stock Entry Detail"
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 6390894..9142a27 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -766,7 +766,9 @@
sle.doctype = "Stock Ledger Entry"
frappe.get_doc(sle).db_update()
- if not self.args.get("sle_id"):
+ if not self.args.get("sle_id") or (
+ sle.serial_and_batch_bundle and sle.auto_created_serial_and_batch_bundle
+ ):
self.update_outgoing_rate_on_transaction(sle)
def reset_actual_qty_for_stock_reco(self, sle):
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
index d77e774..46c229b 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
@@ -112,6 +112,7 @@
"fieldtype": "Column Break"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -337,7 +338,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-01-20 23:25:45.363281",
+ "modified": "2023-11-14 18:38:37.640677",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Item",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index 19a1c93..36001eb 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -13,6 +13,16 @@
frm.trigger('set_queries');
},
+ on_submit(frm) {
+ frm.events.refresh_serial_batch_bundle_field(frm);
+ },
+
+ refresh_serial_batch_bundle_field(frm) {
+ frappe.route_hooks.after_submit = (frm_obj) => {
+ frm_obj.reload_doc();
+ }
+ },
+
refresh: (frm) => {
if (frm.doc.docstatus > 0) {
frm.add_custom_button(__('Stock Ledger'), () => {
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 7e06444..8d705aa 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -148,6 +148,8 @@
if (
frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on")
== "BOM"
+ and self.supplied_items
+ and not any(item.serial_and_batch_bundle for item in self.supplied_items)
):
self.supplied_items = []
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 1828f696..6191a8c 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -6,7 +6,7 @@
import frappe
from frappe.tests.utils import FrappeTestCase
-from frappe.utils import add_days, cint, cstr, flt, today
+from frappe.utils import add_days, cint, cstr, flt, nowtime, today
import erpnext
from erpnext.accounts.doctype.account.test_account import get_inventory_account
@@ -26,6 +26,10 @@
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
+from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
+ get_batch_from_bundle,
+ make_serial_batch_bundle,
+)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
@@ -507,6 +511,162 @@
self.assertNotEqual(scr.supplied_items[0].rate, prev_cost)
self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate)
+ def test_subcontracting_receipt_for_batch_raw_materials_without_material_transfer(self):
+ set_backflush_based_on("BOM")
+
+ fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
+ rm_item1 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BNGS-.####",
+ }
+ ).name
+
+ bom = make_bom(item=fg_item, raw_materials=[rm_item1])
+
+ rm_batch_no = None
+ for row in bom.items:
+ se = make_stock_entry(
+ item_code=row.item_code,
+ qty=1,
+ target="_Test Warehouse 1 - _TC",
+ rate=300,
+ )
+
+ se.reload()
+ rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
+
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 1",
+ "qty": 1,
+ "rate": 100,
+ "fg_item": fg_item,
+ "fg_item_qty": 1,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+ scr = make_subcontracting_receipt(sco.name)
+ scr.save()
+ scr.reload()
+
+ bundle_doc = make_serial_batch_bundle(
+ {
+ "item_code": scr.supplied_items[0].rm_item_code,
+ "warehouse": "_Test Warehouse 1 - _TC",
+ "voucher_type": "Subcontracting Receipt",
+ "posting_date": today(),
+ "posting_time": nowtime(),
+ "qty": -1,
+ "batches": frappe._dict({rm_batch_no: 1}),
+ "type_of_transaction": "Outward",
+ "do_not_submit": True,
+ }
+ )
+
+ scr.supplied_items[0].serial_and_batch_bundle = bundle_doc.name
+ scr.submit()
+ scr.reload()
+
+ batch_no = get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle)
+ self.assertEqual(batch_no, rm_batch_no)
+ self.assertEqual(scr.items[0].rm_cost_per_qty, 300)
+ self.assertEqual(scr.items[0].service_cost_per_qty, 100)
+
+ def test_subcontracting_receipt_valuation_with_auto_created_serial_batch_bundle(self):
+ set_backflush_based_on("BOM")
+
+ fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
+ rm_item1 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BNGS-.####",
+ }
+ ).name
+
+ rm_item2 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "has_serial_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BNGS-.####",
+ "serial_no_series": "BNSS-.####",
+ }
+ ).name
+
+ rm_item3 = make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_serial_no": 1,
+ "serial_no_series": "BSSSS-.####",
+ }
+ ).name
+
+ bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3])
+
+ rm_batch_no = None
+ for row in bom.items:
+ make_stock_entry(
+ item_code=row.item_code,
+ qty=1,
+ target="_Test Warehouse 1 - _TC",
+ rate=300,
+ )
+
+ make_stock_entry(
+ item_code=row.item_code,
+ qty=1,
+ target="_Test Warehouse 1 - _TC",
+ rate=400,
+ )
+
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 1",
+ "qty": 1,
+ "rate": 100,
+ "fg_item": fg_item,
+ "fg_item_qty": 1,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+
+ frappe.db.set_single_value(
+ "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1
+ )
+ scr = make_subcontracting_receipt(sco.name)
+ scr.save()
+ for row in scr.supplied_items:
+ self.assertNotEqual(row.rate, 300.00)
+ self.assertFalse(row.serial_and_batch_bundle)
+
+ scr.submit()
+ scr.reload()
+
+ for row in scr.supplied_items:
+ self.assertEqual(row.rate, 300.00)
+ self.assertTrue(row.serial_and_batch_bundle)
+ auto_created_serial_batch = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": scr.name, "voucher_detail_no": row.name},
+ "auto_created_serial_and_batch_bundle",
+ )
+
+ self.assertTrue(auto_created_serial_batch)
+
+ self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
+ self.assertEqual(scr.items[0].service_cost_per_qty, 100)
+ frappe.db.set_single_value(
+ "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0
+ )
+
def test_subcontracting_receipt_raw_material_rate(self):
# Step - 1: Set Backflush Based On as "BOM"
set_backflush_based_on("BOM")
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
index 38432be..26a29dd 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
@@ -109,6 +109,7 @@
"width": "300px"
},
{
+ "fetch_from": "item_code.image",
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
@@ -521,7 +522,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-09-03 17:04:21.214316",
+ "modified": "2023-11-14 18:38:26.459669",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",
diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py
index fcee265..402c305 100644
--- a/erpnext/utilities/bulk_transaction.py
+++ b/erpnext/utilities/bulk_transaction.py
@@ -3,6 +3,7 @@
import frappe
from frappe import _
+from frappe.utils import get_link_to_form, today
@frappe.whitelist()
@@ -28,6 +29,48 @@
job(deserialized_data, from_doctype, to_doctype)
+@frappe.whitelist()
+def retry(date: str | None):
+ if date:
+ failed_docs = frappe.db.get_all(
+ "Bulk Transaction Log Detail",
+ filters={"date": date, "transaction_status": "Failed", "retried": 0},
+ fields=["name", "transaction_name", "from_doctype", "to_doctype"],
+ )
+ if not failed_docs:
+ frappe.msgprint(_("There are no Failed transactions"))
+ else:
+ job = frappe.enqueue(
+ retry_failed_transactions,
+ failed_docs=failed_docs,
+ )
+ frappe.msgprint(
+ _("Job: {0} has been triggered for processing failed transactions").format(
+ get_link_to_form("RQ Job", job.id)
+ )
+ )
+
+
+def retry_failed_transactions(failed_docs: list | None):
+ if failed_docs:
+ for log in failed_docs:
+ try:
+ frappe.db.savepoint("before_creation_state")
+ task(log.transaction_name, log.from_doctype, log.to_doctype)
+ except Exception as e:
+ frappe.db.rollback(save_point="before_creation_state")
+ update_log(log.name, "Failed", 1, str(frappe.get_traceback()))
+ else:
+ update_log(log.name, "Success", 1)
+
+
+def update_log(log_name, status, retried, err=None):
+ frappe.db.set_value("Bulk Transaction Log Detail", log_name, "transaction_status", status)
+ frappe.db.set_value("Bulk Transaction Log Detail", log_name, "retried", retried)
+ if err:
+ frappe.db.set_value("Bulk Transaction Log Detail", log_name, "error_description", err)
+
+
def job(deserialized_data, from_doctype, to_doctype):
fail_count = 0
for d in deserialized_data:
@@ -38,7 +81,7 @@
except Exception as e:
frappe.db.rollback(save_point="before_creation_state")
fail_count += 1
- update_logger(
+ create_log(
doc_name,
str(frappe.get_traceback()),
from_doctype,
@@ -47,7 +90,7 @@
log_date=str(date.today()),
)
else:
- update_logger(
+ create_log(
doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today())
)
@@ -108,45 +151,18 @@
obj.insert(ignore_mandatory=True)
-def check_logger_doc_exists(log_date):
- return frappe.db.exists("Bulk Transaction Log", log_date)
-
-
-def get_logger_doc(log_date):
- return frappe.get_doc("Bulk Transaction Log", log_date)
-
-
-def create_logger_doc():
- log_doc = frappe.new_doc("Bulk Transaction Log")
- log_doc.set_new_name(set_name=str(date.today()))
- log_doc.log_date = date.today()
-
- return log_doc
-
-
-def append_data_to_logger(log_doc, doc_name, error, from_doctype, to_doctype, status, restarted):
- row = log_doc.append("logger_data", {})
- row.transaction_name = doc_name
- row.date = date.today()
+def create_log(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0):
+ transaction_log = frappe.new_doc("Bulk Transaction Log Detail")
+ transaction_log.transaction_name = doc_name
+ transaction_log.date = today()
now = datetime.now()
- row.time = now.strftime("%H:%M:%S")
- row.transaction_status = status
- row.error_description = str(error)
- row.from_doctype = from_doctype
- row.to_doctype = to_doctype
- row.retried = restarted
-
-
-def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0):
- if not check_logger_doc_exists(log_date):
- log_doc = create_logger_doc()
- append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted)
- log_doc.insert()
- else:
- log_doc = get_logger_doc(log_date)
- if record_exists(log_doc, doc_name, status):
- append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted)
- log_doc.save()
+ transaction_log.time = now.strftime("%H:%M:%S")
+ transaction_log.transaction_status = status
+ transaction_log.error_description = str(e)
+ transaction_log.from_doctype = from_doctype
+ transaction_log.to_doctype = to_doctype
+ transaction_log.retried = restarted
+ transaction_log.save()
def show_job_status(fail_count, deserialized_data_count, to_doctype):
@@ -176,23 +192,3 @@
title="Failed",
indicator="red",
)
-
-
-def record_exists(log_doc, doc_name, status):
- record = mark_retrired_transaction(log_doc, doc_name)
- if record and status == "Failed":
- return False
- elif record and status == "Success":
- return True
- else:
- return True
-
-
-def mark_retrired_transaction(log_doc, doc_name):
- record = 0
- for d in log_doc.get("logger_data"):
- if d.transaction_name == doc_name and d.transaction_status == "Failed":
- frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1)
- record = record + 1
-
- return record