Merge branch 'develop' into fg_based_operating_cost
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
index 28e79b5..c083189 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -21,13 +21,22 @@
frm.trigger('bank_account');
},
+ filter_by_reference_date: function (frm) {
+ if (frm.doc.filter_by_reference_date) {
+ frm.set_value("bank_statement_from_date", "");
+ frm.set_value("bank_statement_to_date", "");
+ } else {
+ frm.set_value("from_reference_date", "");
+ frm.set_value("to_reference_date", "");
+ }
+ },
+
refresh: function (frm) {
frappe.require("bank-reconciliation-tool.bundle.js", () =>
frm.trigger("make_reconciliation_tool")
);
- frm.upload_statement_button = frm.page.set_secondary_action(
- __("Upload Bank Statement"),
- () =>
+
+ frm.add_custom_button(__("Upload Bank Statement"), () =>
frappe.call({
method:
"erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement",
@@ -49,6 +58,20 @@
},
})
);
+
+ frm.add_custom_button(__('Auto Reconcile'), function() {
+ frappe.call({
+ method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
+ args: {
+ bank_account: frm.doc.bank_account,
+ from_date: frm.doc.bank_statement_from_date,
+ to_date: frm.doc.bank_statement_to_date,
+ filter_by_reference_date: frm.doc.filter_by_reference_date,
+ from_reference_date: frm.doc.from_reference_date,
+ to_reference_date: frm.doc.to_reference_date,
+ },
+ })
+ });
},
after_save: function (frm) {
@@ -160,6 +183,9 @@
).$wrapper,
bank_statement_from_date: frm.doc.bank_statement_from_date,
bank_statement_to_date: frm.doc.bank_statement_to_date,
+ filter_by_reference_date: frm.doc.filter_by_reference_date,
+ from_reference_date: frm.doc.from_reference_date,
+ to_reference_date: frm.doc.to_reference_date,
bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cards_manager: frm.cards_manager,
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
index f666101..80993d6 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
@@ -10,6 +10,9 @@
"column_break_1",
"bank_statement_from_date",
"bank_statement_to_date",
+ "from_reference_date",
+ "to_reference_date",
+ "filter_by_reference_date",
"column_break_2",
"account_opening_balance",
"bank_statement_closing_balance",
@@ -36,13 +39,13 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "eval: doc.bank_account",
+ "depends_on": "eval: doc.bank_account && !doc.filter_by_reference_date",
"fieldname": "bank_statement_from_date",
"fieldtype": "Date",
"label": "From Date"
},
{
- "depends_on": "eval: doc.bank_statement_from_date",
+ "depends_on": "eval: doc.bank_account && !doc.filter_by_reference_date",
"fieldname": "bank_statement_to_date",
"fieldtype": "Date",
"label": "To Date"
@@ -81,14 +84,33 @@
},
{
"fieldname": "no_bank_transactions",
- "fieldtype": "HTML"
+ "fieldtype": "HTML",
+ "options": "<div class=\"text-muted text-center\">No Matching Bank Transactions Found</div>"
+ },
+ {
+ "depends_on": "eval:doc.filter_by_reference_date",
+ "fieldname": "from_reference_date",
+ "fieldtype": "Date",
+ "label": "From Reference Date"
+ },
+ {
+ "depends_on": "eval:doc.filter_by_reference_date",
+ "fieldname": "to_reference_date",
+ "fieldtype": "Date",
+ "label": "To Reference Date"
+ },
+ {
+ "default": "0",
+ "fieldname": "filter_by_reference_date",
+ "fieldtype": "Check",
+ "label": "Filter by Reference Date"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-04-21 11:13:49.831769",
+ "modified": "2023-01-13 13:00:02.022919",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",
@@ -107,5 +129,6 @@
],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "DESC"
-}
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index f5f04ae..4ba6146 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -8,7 +8,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
-from frappe.utils import flt
+from frappe.utils import cint, flt
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
@@ -50,6 +50,7 @@
"party",
],
filters=filters,
+ order_by="date",
)
return transactions
@@ -266,6 +267,80 @@
@frappe.whitelist()
+def auto_reconcile_vouchers(
+ bank_account,
+ from_date=None,
+ to_date=None,
+ filter_by_reference_date=None,
+ from_reference_date=None,
+ to_reference_date=None,
+):
+ frappe.flags.auto_reconcile_vouchers = True
+ document_types = ["payment_entry", "journal_entry"]
+ bank_transactions = get_bank_transactions(bank_account)
+ matched_transaction = []
+ for transaction in bank_transactions:
+ linked_payments = get_linked_payments(
+ transaction.name,
+ document_types,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+ )
+ vouchers = []
+ for r in linked_payments:
+ vouchers.append(
+ {
+ "payment_doctype": r[1],
+ "payment_name": r[2],
+ "amount": r[4],
+ }
+ )
+ transaction = frappe.get_doc("Bank Transaction", transaction.name)
+ account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
+ matched_trans = 0
+ for voucher in vouchers:
+ gl_entry = frappe.db.get_value(
+ "GL Entry",
+ dict(
+ account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
+ ),
+ ["credit", "debit"],
+ as_dict=1,
+ )
+ gl_amount, transaction_amount = (
+ (gl_entry.credit, transaction.deposit)
+ if gl_entry.credit > 0
+ else (gl_entry.debit, transaction.withdrawal)
+ )
+ allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
+ transaction.append(
+ "payment_entries",
+ {
+ "payment_document": voucher["payment_doctype"],
+ "payment_entry": voucher["payment_name"],
+ "allocated_amount": allocated_amount,
+ },
+ )
+ matched_transaction.append(str(transaction.name))
+ transaction.save()
+ transaction.update_allocations()
+ matched_transaction_len = len(set(matched_transaction))
+ if matched_transaction_len == 0:
+ frappe.msgprint(_("No matching references found for auto reconciliation"))
+ elif matched_transaction_len == 1:
+ frappe.msgprint(_("{0} transaction is reconcilied").format(matched_transaction_len))
+ else:
+ frappe.msgprint(_("{0} transactions are reconcilied").format(matched_transaction_len))
+
+ frappe.flags.auto_reconcile_vouchers = False
+
+ return frappe.get_doc("Bank Transaction", transaction.name)
+
+
+@frappe.whitelist()
def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers)
@@ -327,20 +402,58 @@
@frappe.whitelist()
-def get_linked_payments(bank_transaction_name, document_types=None):
+def get_linked_payments(
+ bank_transaction_name,
+ document_types=None,
+ from_date=None,
+ to_date=None,
+ filter_by_reference_date=None,
+ from_reference_date=None,
+ to_reference_date=None,
+):
# get all matching payments for a bank transaction
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
bank_account = frappe.db.get_values(
"Bank Account", transaction.bank_account, ["account", "company"], as_dict=True
)[0]
(account, company) = (bank_account.account, bank_account.company)
- matching = check_matching(account, company, transaction, document_types)
+ matching = check_matching(
+ account,
+ company,
+ transaction,
+ document_types,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+ )
return matching
-def check_matching(bank_account, company, transaction, document_types):
+def check_matching(
+ bank_account,
+ company,
+ transaction,
+ document_types,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+):
# combine all types of vouchers
- subquery = get_queries(bank_account, company, transaction, document_types)
+ subquery = get_queries(
+ bank_account,
+ company,
+ transaction,
+ document_types,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+ )
filters = {
"amount": transaction.unallocated_amount,
"payment_type": "Receive" if transaction.deposit > 0 else "Pay",
@@ -361,11 +474,20 @@
filters,
)
)
-
return sorted(matching_vouchers, key=lambda x: x[0], reverse=True) if matching_vouchers else []
-def get_queries(bank_account, company, transaction, document_types):
+def get_queries(
+ bank_account,
+ company,
+ transaction,
+ document_types,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+):
# get queries to get matching vouchers
amount_condition = "=" if "exact_match" in document_types else "<="
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
@@ -381,6 +503,11 @@
document_types,
amount_condition,
account_from_to,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
)
or []
)
@@ -389,15 +516,42 @@
def get_matching_queries(
- bank_account, company, transaction, document_types, amount_condition, account_from_to
+ bank_account,
+ company,
+ transaction,
+ document_types,
+ amount_condition,
+ account_from_to,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
):
queries = []
if "payment_entry" in document_types:
- pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction)
+ pe_amount_matching = get_pe_matching_query(
+ amount_condition,
+ account_from_to,
+ transaction,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+ )
queries.extend([pe_amount_matching])
if "journal_entry" in document_types:
- je_amount_matching = get_je_matching_query(amount_condition, transaction)
+ je_amount_matching = get_je_matching_query(
+ amount_condition,
+ transaction,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+ )
queries.extend([je_amount_matching])
if transaction.deposit > 0 and "sales_invoice" in document_types:
@@ -504,47 +658,81 @@
return vouchers
-def get_pe_matching_query(amount_condition, account_from_to, transaction):
+def get_pe_matching_query(
+ amount_condition,
+ account_from_to,
+ transaction,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+):
# get matching payment entries query
if transaction.deposit > 0:
currency_field = "paid_to_account_currency as currency"
else:
currency_field = "paid_from_account_currency as currency"
+ filter_by_date = f"AND posting_date between '{from_date}' and '{to_date}'"
+ order_by = " posting_date"
+ filter_by_reference_no = ""
+ if cint(filter_by_reference_date):
+ filter_by_date = f"AND reference_date between '{from_reference_date}' and '{to_reference_date}'"
+ order_by = " reference_date"
+ if frappe.flags.auto_reconcile_vouchers == True:
+ filter_by_reference_no = f"AND reference_no = '{transaction.reference_number}'"
return f"""
- SELECT
- (CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
- + CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
- + 1 ) AS rank,
- 'Payment Entry' as doctype,
- name,
- paid_amount,
- reference_no,
- reference_date,
- party,
- party_type,
- posting_date,
- {currency_field}
- FROM
- `tabPayment Entry`
- WHERE
- paid_amount {amount_condition} %(amount)s
- AND docstatus = 1
- AND payment_type IN (%(payment_type)s, 'Internal Transfer')
- AND ifnull(clearance_date, '') = ""
- AND {account_from_to} = %(bank_account)s
+ SELECT
+ (CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
+ + CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ + 1 ) AS rank,
+ 'Payment Entry' as doctype,
+ name,
+ paid_amount,
+ reference_no,
+ reference_date,
+ party,
+ party_type,
+ posting_date,
+ {currency_field}
+ FROM
+ `tabPayment Entry`
+ WHERE
+ paid_amount {amount_condition} %(amount)s
+ AND docstatus = 1
+ AND payment_type IN (%(payment_type)s, 'Internal Transfer')
+ AND ifnull(clearance_date, '') = ""
+ AND {account_from_to} = %(bank_account)s
+ {filter_by_date}
+ {filter_by_reference_no}
+ order by{order_by}
+
"""
-def get_je_matching_query(amount_condition, transaction):
+def get_je_matching_query(
+ amount_condition,
+ transaction,
+ from_date,
+ to_date,
+ filter_by_reference_date,
+ from_reference_date,
+ to_reference_date,
+):
# get matching journal entry query
-
# We have mapping at the bank level
# So one bank could have both types of bank accounts like asset and liability
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
-
+ filter_by_date = f"AND je.posting_date between '{from_date}' and '{to_date}'"
+ order_by = " je.posting_date"
+ filter_by_reference_no = ""
+ if cint(filter_by_reference_date):
+ filter_by_date = f"AND je.cheque_date between '{from_reference_date}' and '{to_reference_date}'"
+ order_by = " je.cheque_date"
+ if frappe.flags.auto_reconcile_vouchers == True:
+ filter_by_reference_no = f"AND je.cheque_no = '{transaction.reference_number}'"
return f"""
-
SELECT
(CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END
+ 1) AS rank ,
@@ -568,6 +756,9 @@
AND jea.account = %(bank_account)s
AND jea.{cr_or_dr}_in_account_currency {amount_condition} %(amount)s
AND je.docstatus = 1
+ {filter_by_date}
+ {filter_by_reference_no}
+ order by {order_by}
"""
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index a5d0413..f900e07 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -5,6 +5,7 @@
import unittest
import frappe
+from frappe import utils
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
@@ -40,7 +41,12 @@
"Bank Transaction",
dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"),
)
- linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
+ linked_payments = get_linked_payments(
+ bank_transaction.name,
+ ["payment_entry", "exact_match"],
+ from_date=bank_transaction.date,
+ to_date=utils.today(),
+ )
self.assertTrue(linked_payments[0][6] == "Conrad Electronic")
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
@@ -81,7 +87,12 @@
"Bank Transaction",
dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"),
)
- linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
+ linked_payments = get_linked_payments(
+ bank_transaction.name,
+ ["payment_entry", "exact_match"],
+ from_date=bank_transaction.date,
+ to_date=utils.today(),
+ )
self.assertTrue(linked_payments[0][3])
# Check error if already reconciled
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 5a4168a..2415aec 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -219,20 +219,16 @@
else:
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
frappe.throw(
- _(
- "Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}"
- ).format(item.idx, item.fg_item, item.item_code)
+ _("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
+ item.idx, item.fg_item
+ )
)
elif not frappe.get_value("Item", item.fg_item, "default_bom"):
frappe.throw(
_("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item)
)
if not item.fg_item_qty:
- frappe.throw(
- _("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format(
- item.idx, item.item_code
- )
- )
+ frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
else:
for item in self.items:
item.set("fg_item", None)
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 335d92f..a9561fe 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -74,24 +74,25 @@
)
if not is_stock_item:
- msg = f"Item {item.item_name} must be a stock item."
- frappe.throw(_(msg))
+ frappe.throw(_("Row {0}: Item {1} must be a stock item.").format(item.idx, item.item_name))
if not is_sub_contracted_item:
- msg = f"Item {item.item_name} must be a subcontracted item."
- frappe.throw(_(msg))
+ frappe.throw(
+ _("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
+ )
if item.bom:
bom = frappe.get_doc("BOM", item.bom)
if not bom.is_active:
- msg = f"Please select an active BOM for Item {item.item_name}."
- frappe.throw(_(msg))
+ frappe.throw(
+ _("Row {0}: Please select an active BOM for Item {1}.").format(item.idx, item.item_name)
+ )
if bom.item != item.item_code:
- msg = f"Please select an valid BOM for Item {item.item_name}."
- frappe.throw(_(msg))
+ frappe.throw(
+ _("Row {0}: Please select an valid BOM for Item {1}.").format(item.idx, item.item_name)
+ )
else:
- msg = f"Please select a BOM for Item {item.item_name}."
- frappe.throw(_(msg))
+ frappe.throw(_("Row {0}: Please select a BOM for Item {1}.").format(item.idx, item.item_name))
def __get_data_before_save(self):
item_dict = {}
diff --git a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
index 9ef8ce6..f7c19a1 100644
--- a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
@@ -5,7 +5,12 @@
Object.assign(this, opts);
this.dialog_manager = new erpnext.accounts.bank_reconciliation.DialogManager(
this.company,
- this.bank_account
+ this.bank_account,
+ this.bank_statement_from_date,
+ this.bank_statement_to_date,
+ this.filter_by_reference_date,
+ this.from_reference_date,
+ this.to_reference_date
);
this.make_dt();
}
@@ -17,6 +22,8 @@
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_bank_transactions",
args: {
bank_account: this.bank_account,
+ from_date: this.bank_statement_from_date,
+ to_date: this.bank_statement_to_date
},
callback: function (response) {
me.format_data(response.message);
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index b5e6ab8..51664f8 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -5,8 +5,12 @@
this.bank_account = bank_account;
this.company = company;
this.make_dialog();
+ this.bank_statement_from_date = bank_statement_from_date;
+ this.bank_statement_to_date = bank_statement_to_date;
+ this.filter_by_reference_date = filter_by_reference_date;
+ this.from_reference_date = from_reference_date;
+ this.to_reference_date = to_reference_date;
}
-
show_dialog(bank_transaction_name, update_dt_cards) {
this.bank_transaction_name = bank_transaction_name;
this.update_dt_cards = update_dt_cards;
@@ -35,13 +39,13 @@
if (r.message) {
this.bank_transaction = r.message;
r.message.payment_entry = 1;
+ r.message.journal_entry = 1;
this.dialog.set_values(r.message);
this.dialog.show();
}
},
});
}
-
get_linked_vouchers(document_types) {
frappe.call({
method:
@@ -49,6 +53,11 @@
args: {
bank_transaction_name: this.bank_transaction_name,
document_types: document_types,
+ from_date: this.bank_statement_from_date,
+ to_date: this.bank_statement_to_date,
+ filter_by_reference_date: this.filter_by_reference_date,
+ from_reference_date:this.from_reference_date,
+ to_reference_date:this.to_reference_date
},
callback: (result) => {
@@ -66,6 +75,7 @@
row[1],
row[2],
reference_date,
+ row[8],
format_currency(row[3], row[9]),
row[6],
row[4],
@@ -102,6 +112,11 @@
width: 120,
},
{
+ name: "Posting Date",
+ editable: false,
+ width: 120,
+ },
+ {
name: __("Amount"),
editable: false,
width: 100,
@@ -578,4 +593,4 @@
}
}
-};
+};
\ 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 d0dabad..134b5ea 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -638,6 +638,7 @@
"width": "70px"
},
{
+ "allow_on_submit": 1,
"fieldname": "ordered_qty",
"fieldtype": "Float",
"label": "Ordered Qty",
@@ -864,7 +865,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-12-25 02:51:10.247569",
+ "modified": "2023-01-12 13:13:28.691585",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 65a792f..9e6aead 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -230,7 +230,8 @@
frappe.throw(_("Qty of Finished Goods Item should be greater than 0."))
def before_print(self, settings=None):
- self.group_similar_items()
+ if self.group_same_items:
+ self.group_similar_items()
def group_similar_items(self):
group_item_qty = defaultdict(float)
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index f552299..71663e8 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -445,6 +445,20 @@
pl.before_print()
self.assertEqual(len(pl.locations), 4)
+ # grouping should not happen if group_same_items is False
+ pl = frappe.get_doc(
+ doctype="Pick List",
+ group_same_items=False,
+ locations=[
+ _dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
+ _dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
+ _dict(item_code="A", warehouse="X", qty=3, picked_qty=2),
+ _dict(item_code="B", warehouse="Y", qty=2, picked_qty=2),
+ ],
+ )
+ pl.before_print()
+ self.assertEqual(len(pl.locations), 4)
+
# grouping should halve the number of items
pl = frappe.get_doc(
doctype="Pick List",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index bce5360..7e1915b 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -57,6 +57,7 @@
def before_validate(self):
super(SubcontractingReceipt, self).before_validate()
+ self.validate_items_qty()
self.set_items_bom()
self.set_items_cost_center()
self.set_items_expense_account()
@@ -157,7 +158,7 @@
total_qty = total_amount = 0
for item in self.items:
- if item.name in rm_supp_cost:
+ if item.qty and item.name in rm_supp_cost:
item.rm_supp_cost = rm_supp_cost[item.name]
item.rm_cost_per_qty = item.rm_supp_cost / item.qty
rm_supp_cost.pop(item.name)
@@ -194,6 +195,13 @@
).format(item.idx)
)
+ def validate_items_qty(self):
+ for item in self.items:
+ if not (item.qty or item.rejected_qty):
+ frappe.throw(
+ _("Row {0}: Accepted Qty and Rejected Qty can't be zero at the same time.").format(item.idx)
+ )
+
def set_items_bom(self):
if self.is_return:
for item in self.items: