Merge branch 'develop' into ar_billed_cur
diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py
index 5d94a08..04dab4c 100644
--- a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py
+++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py
@@ -112,7 +112,8 @@
for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
- names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
+ field = party.lower() + "_name"
+ names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
for field in ["bank_party_name", "description"]:
if not self.get(field):
@@ -131,7 +132,11 @@
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
skip = False
- result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
+ result = process.extract(
+ query=self.get(field),
+ choices={row.get("name"): row.get("party_name") for row in names},
+ scorer=fuzz.token_set_ratio,
+ )
party_name, skip = self.process_fuzzy_result(result)
if not party_name:
@@ -149,14 +154,14 @@
Returns: Result, Skip (whether or not to discontinue matching)
"""
- PARTY, SCORE, CUTOFF = 0, 1, 80
+ SCORE, PARTY_ID, CUTOFF = 1, 2, 80
if not result or not len(result):
return None, False
first_result = result[0]
if len(result) == 1:
- return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
+ return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
second_result = result[1]
if first_result[SCORE] > CUTOFF:
@@ -165,7 +170,7 @@
if first_result[SCORE] == second_result[SCORE]:
return None, True
- return first_result[PARTY], True
+ return first_result[PARTY_ID], True
else:
return None, False
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 0203c45..b3ae627 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -154,7 +154,7 @@
frm.events.set_dynamic_labels(frm);
frm.events.show_general_ledger(frm);
erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm);
- if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) {
+ if((frm.doc.references) && (frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0}))) {
frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() {
frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
}, __('Actions'));
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
index 9cf2ac6..28c9529 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
@@ -30,7 +30,8 @@
{
"fieldname": "posting_date",
"fieldtype": "Date",
- "label": "Posting Date"
+ "label": "Posting Date",
+ "search_index": 1
},
{
"fieldname": "account_type",
@@ -64,7 +65,8 @@
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Voucher Type",
- "options": "DocType"
+ "options": "DocType",
+ "search_index": 1
},
{
"fieldname": "voucher_no",
@@ -72,14 +74,16 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Voucher No",
- "options": "voucher_type"
+ "options": "voucher_type",
+ "search_index": 1
},
{
"fieldname": "against_voucher_type",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Against Voucher Type",
- "options": "DocType"
+ "options": "DocType",
+ "search_index": 1
},
{
"fieldname": "against_voucher_no",
@@ -87,7 +91,8 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Against Voucher No",
- "options": "against_voucher_type"
+ "options": "against_voucher_type",
+ "search_index": 1
},
{
"fieldname": "amount",
@@ -147,13 +152,14 @@
{
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
- "label": "Voucher Detail No"
+ "label": "Voucher Detail No",
+ "search_index": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2023-06-29 12:24:20.500632",
+ "modified": "2023-11-03 16:39:58.904113",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 1626f25..43167be 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -109,6 +109,8 @@
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
)
+ limit = f"limit {self.payment_limit}" if self.payment_limit else " "
+
# nosemgrep
journal_entries = frappe.db.sql(
"""
@@ -132,11 +134,13 @@
ELSE {bank_account_condition}
END)
order by t1.posting_date
+ {limit}
""".format(
**{
"dr_or_cr": dr_or_cr,
"bank_account_condition": bank_account_condition,
"condition": condition,
+ "limit": limit,
}
),
{
@@ -162,7 +166,7 @@
if self.payment_name:
conditions.append(doc.name.like(f"%{self.payment_name}%"))
- self.return_invoices = (
+ self.return_invoices_query = (
qb.from_(doc)
.select(
ConstantColumn(voucher_type).as_("voucher_type"),
@@ -170,8 +174,11 @@
doc.return_against,
)
.where(Criterion.all(conditions))
- .run(as_dict=True)
)
+ if self.payment_limit:
+ self.return_invoices_query = self.return_invoices_query.limit(self.payment_limit)
+
+ self.return_invoices = self.return_invoices_query.run(as_dict=True)
def get_dr_or_cr_notes(self):
diff --git a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json
index 1131a0f..b4ac981 100644
--- a/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json
+++ b/erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json
@@ -110,7 +110,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2023-04-21 17:36:26.642617",
+ "modified": "2023-11-02 11:32:12.254018",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Payment Reconciliation Log",
@@ -125,7 +125,19 @@
"print": 1,
"read": 1,
"report": 1,
- "role": "System Manager",
+ "role": "Accounts Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts User",
"share": 1,
"write": 1
}
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 2d1f445..09bffff 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -384,7 +384,8 @@
"label": "Supplier Invoice No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "search_index": 1
},
{
"fieldname": "column_break_15",
@@ -407,7 +408,8 @@
"no_copy": 1,
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "section_addresses",
@@ -1602,7 +1604,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2023-10-16 16:24:51.886231",
+ "modified": "2023-11-03 15:47:30.319200",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1665,4 +1667,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 13593bc..171cc0c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1783,9 +1783,14 @@
set_advance_flag(company="_Test Company", flag=0, default_account="")
def test_gl_entries_for_standalone_debit_note(self):
- make_purchase_invoice(qty=5, rate=500, update_stock=True)
+ from erpnext.stock.doctype.item.test_item import make_item
- returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
+ item_code = make_item(properties={"is_stock_item": 1})
+ make_purchase_invoice(item_code=item_code, qty=5, rate=500, update_stock=True)
+
+ returned_inv = make_purchase_invoice(
+ item_code=item_code, qty=-5, rate=5, update_stock=True, is_return=True
+ )
# override the rate with valuation rate
sle = frappe.get_all(
@@ -1795,7 +1800,7 @@
)[0]
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
- self.assertAlmostEqual(returned_inv.items[0].rate, rate)
+ self.assertAlmostEqual(rate, 500)
def test_payment_allocation_for_payment_terms(self):
from erpnext.buying.doctype.purchase_order.test_purchase_order import (
@@ -1898,6 +1903,12 @@
disable_dimension()
def test_repost_accounting_entries(self):
+ # update repost settings
+ settings = frappe.get_doc("Repost Accounting Ledger Settings")
+ if not [x for x in settings.allowed_types if x.document_type == "Purchase Invoice"]:
+ settings.append("allowed_types", {"document_type": "Purchase Invoice", "allowed": True})
+ settings.save()
+
pi = make_purchase_invoice(
rate=1000,
price_list_rate=1000,
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js
index 3a87a38..c7b7a14 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.js
@@ -5,9 +5,7 @@
setup: function(frm) {
frm.fields_dict['vouchers'].grid.get_field('voucher_type').get_query = function(doc) {
return {
- filters: {
- name: ['in', ['Purchase Invoice', 'Sales Invoice', 'Payment Entry', 'Journal Entry']],
- }
+ query: "erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.get_repost_allowed_types"
}
}
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index dbb0971..69cfe9f 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -10,9 +10,12 @@
class RepostAccountingLedger(Document):
def __init__(self, *args, **kwargs):
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
- self._allowed_types = set(
- ["Purchase Invoice", "Sales Invoice", "Payment Entry", "Journal Entry"]
- )
+ self._allowed_types = [
+ x.document_type
+ for x in frappe.db.get_all(
+ "Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
+ )
+ ]
def validate(self):
self.validate_vouchers()
@@ -157,7 +160,7 @@
doc.docstatus = 1
doc.make_gl_entries()
- elif doc.doctype in ["Payment Entry", "Journal Entry"]:
+ elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
if not repost_doc.delete_cancelled_entries:
doc.make_gl_entries(1)
doc.make_gl_entries()
@@ -186,3 +189,18 @@
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]))
)
)
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
+ filters = {"allowed": True}
+
+ if txt:
+ filters.update({"document_type": ("like", f"%{txt}%")})
+
+ if allowed_types := frappe.db.get_all(
+ "Repost Allowed Types", filters=filters, fields=["distinct(document_type)"], as_list=1
+ ):
+ return allowed_types
+ return []
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 0e75dd2..dda0ec7 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
@@ -20,10 +20,18 @@
self.create_company()
self.create_customer()
self.create_item()
+ self.update_repost_settings()
def teadDown(self):
frappe.db.rollback()
+ def update_repost_settings(self):
+ allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
+ repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
+ for x in allowed_types:
+ repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
+ repost_settings.save()
+
def test_01_basic_functions(self):
si = create_sales_invoice(
item=self.item,
diff --git a/erpnext/projects/report/employee_billing_summary/__init__.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/__init__.py
similarity index 100%
copy from erpnext/projects/report/employee_billing_summary/__init__.py
copy to erpnext/accounts/doctype/repost_accounting_ledger_settings/__init__.py
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.js b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.js
new file mode 100644
index 0000000..8c83ca5
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.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("Repost Accounting Ledger Settings", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json
new file mode 100644
index 0000000..8aa0a84
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json
@@ -0,0 +1,46 @@
+{
+ "actions": [],
+ "creation": "2023-11-07 09:57:20.619939",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "allowed_types"
+ ],
+ "fields": [
+ {
+ "fieldname": "allowed_types",
+ "fieldtype": "Table",
+ "label": "Allowed Doctypes",
+ "options": "Repost Allowed Types"
+ }
+ ],
+ "in_create": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2023-11-07 14:24:13.321522",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Repost Accounting Ledger Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Administrator",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "read": 1,
+ "role": "System Manager",
+ "select": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py
new file mode 100644
index 0000000..2b8230d
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class RepostAccountingLedgerSettings(Document):
+ pass
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py b/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py
new file mode 100644
index 0000000..ec4e87f
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.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 TestRepostAccountingLedgerSettings(FrappeTestCase):
+ pass
diff --git a/erpnext/projects/report/employee_billing_summary/__init__.py b/erpnext/accounts/doctype/repost_allowed_types/__init__.py
similarity index 100%
rename from erpnext/projects/report/employee_billing_summary/__init__.py
rename to erpnext/accounts/doctype/repost_allowed_types/__init__.py
diff --git a/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json
new file mode 100644
index 0000000..ede12fb
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json
@@ -0,0 +1,45 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-11-07 09:58:03.595382",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "document_type",
+ "column_break_sfzb",
+ "allowed"
+ ],
+ "fields": [
+ {
+ "fieldname": "document_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Doctype",
+ "options": "DocType"
+ },
+ {
+ "default": "0",
+ "fieldname": "allowed",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Allowed"
+ },
+ {
+ "fieldname": "column_break_sfzb",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-11-07 10:01:39.217861",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Repost Allowed Types",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.py b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.py
new file mode 100644
index 0000000..0e4883b
--- /dev/null
+++ b/erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class RepostAllowedTypes(Document):
+ pass
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index e5adeae..cd725b9 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -26,6 +26,7 @@
"is_return",
"return_against",
"update_billed_amount_in_sales_order",
+ "update_billed_amount_in_delivery_note",
"is_debit_note",
"amended_from",
"accounting_dimensions_section",
@@ -2153,6 +2154,13 @@
"fieldname": "use_company_roundoff_cost_center",
"fieldtype": "Check",
"label": "Use Company default Cost Center for Round off"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval: doc.is_return",
+ "fieldname": "update_billed_amount_in_delivery_note",
+ "fieldtype": "Check",
+ "label": "Update Billed Amount in Delivery Note"
}
],
"icon": "fa fa-file-text",
@@ -2165,7 +2173,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2023-07-25 16:02:18.988799",
+ "modified": "2023-11-03 14:39:38.012346",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index f6d9c93..fa95ccd 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -253,6 +253,7 @@
self.update_status_updater_args()
self.update_prevdoc_status()
+
self.update_billing_status_in_dn()
self.clear_unallocated_mode_of_payments()
@@ -1019,7 +1020,7 @@
def make_customer_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
- # because rounded_total had value even before introcution of posting GLE based on rounded total
+ # because rounded_total had value even before introduction of posting GLE based on rounded total
grand_total = (
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
)
@@ -1267,7 +1268,7 @@
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
payment_mode.base_amount -= flt(self.change_amount)
- if payment_mode.amount:
+ if payment_mode.base_amount:
# POS, make payment entries
gl_entries.append(
self.get_gl_dict(
@@ -1429,6 +1430,8 @@
)
def update_billing_status_in_dn(self, update_modified=True):
+ if self.is_return and not self.update_billed_amount_in_delivery_note:
+ return
updated_delivery_notes = []
for d in self.get("items"):
if d.dn_detail:
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 310e412..16e73ea 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -5,7 +5,7 @@
from typing import Optional
import frappe
-from frappe import _, msgprint, scrub
+from frappe import _, msgprint, qb, scrub
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
@@ -480,11 +480,19 @@
def get_party_gle_currency(party_type, party, company):
def generator():
- existing_gle_currency = frappe.db.sql(
- """select account_currency from `tabGL Entry`
- where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
- limit 1""",
- {"company": company, "party_type": party_type, "party": party},
+ gl = qb.DocType("GL Entry")
+ existing_gle_currency = (
+ qb.from_(gl)
+ .select(gl.account_currency)
+ .where(
+ (gl.docstatus == 1)
+ & (gl.company == company)
+ & (gl.party_type == party_type)
+ & (gl.party == party)
+ & (gl.is_cancelled == 0)
+ )
+ .limit(1)
+ .run()
)
return existing_gle_currency[0][0] if existing_gle_currency else None
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 8fa81cd..10362db 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -148,7 +148,13 @@
"fieldname": "in_party_currency",
"label": __("In Party Currency"),
"fieldtype": "Check",
+ },
+ {
+ "fieldname": "ignore_accounts",
+ "label": __("Group by Voucher"),
+ "fieldtype": "Check",
}
+
],
"formatter": function(value, row, column, data, default_formatter) {
@@ -180,4 +186,4 @@
});
});
return options;
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index c49b67a..d06b259 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -177,7 +177,13 @@
"fieldname": "in_party_currency",
"label": __("In Party Currency"),
"fieldtype": "Check",
+ },
+ {
+ "fieldname": "ignore_accounts",
+ "label": __("Group by Voucher"),
+ "fieldtype": "Check",
}
+
],
"formatter": function(value, row, column, data, default_formatter) {
@@ -210,4 +216,4 @@
});
});
return options;
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index e8cf915..e42d325 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -119,7 +119,12 @@
# build all keys, since we want to exclude vouchers beyond the report date
for ple in self.ple_entries:
# get the balance object for voucher_type
- key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
+
+ if self.filters.get("ignore_accounts"):
+ key = (ple.voucher_type, ple.voucher_no, ple.party)
+ else:
+ key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
+
if not key in self.voucher_balance:
self.voucher_balance[key] = frappe._dict(
voucher_type=ple.voucher_type,
@@ -186,7 +191,10 @@
):
return
- key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
+ if self.filters.get("ignore_accounts"):
+ key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
+ else:
+ key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
# If payment is made against credit note
# and credit note is made against a Sales Invoice
@@ -195,13 +203,19 @@
if ple.against_voucher_no in self.return_entries:
return_against = self.return_entries.get(ple.against_voucher_no)
if return_against:
- key = (ple.account, ple.against_voucher_type, return_against, ple.party)
+ if self.filters.get("ignore_accounts"):
+ key = (ple.against_voucher_type, return_against, ple.party)
+ else:
+ key = (ple.account, ple.against_voucher_type, return_against, ple.party)
row = self.voucher_balance.get(key)
if not row:
# no invoice, this is an invoice / stand-alone payment / credit note
- row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
+ if self.filters.get("ignore_accounts"):
+ row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
+ else:
+ row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
row.party_type = ple.party_type
return row
@@ -722,6 +736,7 @@
query = (
qb.from_(ple)
.select(
+ ple.name,
ple.account,
ple.voucher_type,
ple.voucher_no,
@@ -735,13 +750,15 @@
ple.account_currency,
ple.amount,
ple.amount_in_account_currency,
- ple.remarks,
)
.where(ple.delinked == 0)
.where(Criterion.all(self.qb_selection_filter))
.where(Criterion.any(self.or_filters))
)
+ if self.filters.get("show_remarks"):
+ query = query.select(ple.remarks)
+
if self.filters.get("group_by_party"):
query = query.orderby(self.ple.party, self.ple.posting_date)
else:
diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py
index 099884a..696a03b 100644
--- a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py
+++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py
@@ -79,7 +79,9 @@
.select(
gle.company,
gle.account,
+ gle.voucher_type,
gle.voucher_no,
+ gle.party_type,
gle.party,
outstanding,
)
@@ -89,7 +91,9 @@
& (gle.account.isin(val.accounts))
)
.where(Criterion.all(filter_criterion))
- .groupby(gle.company, gle.account, gle.voucher_no, gle.party)
+ .groupby(
+ gle.company, gle.account, gle.voucher_type, gle.voucher_no, gle.party_type, gle.party
+ )
.run()
)
@@ -112,7 +116,13 @@
self.account_types[acc_type].ple = (
qb.from_(ple)
.select(
- ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding")
+ ple.company,
+ ple.account,
+ ple.voucher_type,
+ ple.voucher_no,
+ ple.party_type,
+ ple.party,
+ Sum(ple.amount).as_("outstanding"),
)
.where(
(ple.company == self.filters.company)
@@ -120,7 +130,9 @@
& (ple.account.isin(val.accounts))
)
.where(Criterion.all(filter_criterion))
- .groupby(ple.company, ple.account, ple.voucher_no, ple.party)
+ .groupby(
+ ple.company, ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party
+ )
.run()
)
@@ -138,12 +150,12 @@
self.diff = frappe._dict({})
for x in self.variation_in_payment_ledger:
- self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
+ self.diff[(x[0], x[1], x[2], x[3], x[4], x[5])] = frappe._dict({"gl_balance": x[6]})
for x in self.variation_in_general_ledger:
- self.diff.setdefault((x[0], x[1], x[2], x[3]), frappe._dict({"gl_balance": 0.0})).update(
- frappe._dict({"pl_balance": x[4]})
- )
+ self.diff.setdefault(
+ (x[0], x[1], x[2], x[3], x[4], x[5]), frappe._dict({"gl_balance": 0.0})
+ ).update(frappe._dict({"pl_balance": x[6]}))
def generate_data(self):
self.data = []
@@ -151,8 +163,12 @@
self.data.append(
frappe._dict(
{
- "voucher_no": key[2],
- "party": key[3],
+ "company": key[0],
+ "account": key[1],
+ "voucher_type": key[2],
+ "voucher_no": key[3],
+ "party_type": key[4],
+ "party": key[5],
"gl_balance": val.gl_balance,
"pl_balance": val.pl_balance,
}
@@ -164,10 +180,50 @@
options = None
self.columns.append(
dict(
+ label=_("Company"),
+ fieldname="company",
+ fieldtype="Link",
+ options="Company",
+ width="100",
+ )
+ )
+
+ self.columns.append(
+ dict(
+ label=_("Account"),
+ fieldname="account",
+ fieldtype="Link",
+ options="Account",
+ width="100",
+ )
+ )
+
+ self.columns.append(
+ dict(
+ label=_("Voucher Type"),
+ fieldname="voucher_type",
+ fieldtype="Link",
+ options="DocType",
+ width="100",
+ )
+ )
+
+ self.columns.append(
+ dict(
label=_("Voucher No"),
fieldname="voucher_no",
- fieldtype="Data",
- options=options,
+ fieldtype="Dynamic Link",
+ options="voucher_type",
+ width="100",
+ )
+ )
+
+ self.columns.append(
+ dict(
+ label=_("Party Type"),
+ fieldname="party_type",
+ fieldtype="Link",
+ options="DocType",
width="100",
)
)
@@ -176,8 +232,8 @@
dict(
label=_("Party"),
fieldname="party",
- fieldtype="Data",
- options=options,
+ fieldtype="Dynamic Link",
+ options="party_type",
width="100",
)
)
diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py
index 4b0e99d..59e906b 100644
--- a/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py
+++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py
@@ -50,7 +50,11 @@
self.assertEqual(len(data), 1)
expected = {
+ "company": sinv.company,
+ "account": sinv.debit_to,
+ "voucher_type": sinv.doctype,
"voucher_no": sinv.name,
+ "party_type": "Customer",
"party": sinv.customer,
"gl_balance": sinv.grand_total,
"pl_balance": sinv.grand_total - 1,
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 37d0659..c0b4f59 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -193,7 +193,13 @@
"fieldname": "add_values_in_transaction_currency",
"label": __("Add Columns in Transaction Currency"),
"fieldtype": "Check"
+ },
+ {
+ "fieldname": "show_remarks",
+ "label": __("Show Remarks"),
+ "fieldtype": "Check"
}
+
]
}
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 79bfd78..5e484cf 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -163,6 +163,9 @@
select_fields = """, debit, credit, debit_in_account_currency,
credit_in_account_currency """
+ if filters.get("show_remarks"):
+ select_fields += """,remarks"""
+
order_by_statement = "order by posting_date, account, creation"
if filters.get("include_dimensions"):
@@ -195,7 +198,7 @@
voucher_type, voucher_no, {dimension_fields}
cost_center, project, {transaction_currency_fields}
against_voucher_type, against_voucher, account_currency,
- remarks, against, is_opening, creation {select_fields}
+ against, is_opening, creation {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
{order_by_statement}
@@ -631,8 +634,10 @@
"width": 100,
},
{"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100},
- {"label": _("Remarks"), "fieldname": "remarks", "width": 400},
]
)
+ if filters.get("show_remarks"):
+ columns.extend([{"label": _("Remarks"), "fieldname": "remarks", "width": 400}])
+
return columns
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
index 4a3d9bb..b6bbd97 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js
@@ -32,13 +32,6 @@
"label": __("Accounting Dimension"),
"fieldtype": "Link",
"options": "Accounting Dimension",
- "get_query": () =>{
- return {
- filters: {
- "disabled": 0
- }
- }
- }
},
{
"fieldname": "fiscal_year",
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
index eac5426..e842d2e 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
@@ -47,6 +47,7 @@
out = []
for name, details in gle_map.items():
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
+ bill_no, bill_date = "", ""
tax_withholding_category = tax_category_map.get(name)
rate = tax_rate_map.get(tax_withholding_category)
@@ -70,7 +71,10 @@
if net_total_map.get(name):
if voucher_type == "Journal Entry" and tax_amount and rate:
# back calcalute total amount from rate and tax_amount
- total_amount = grand_total = base_total = tax_amount / (rate / 100)
+ if rate:
+ total_amount = grand_total = base_total = tax_amount / (rate / 100)
+ elif voucher_type == "Purchase Invoice":
+ total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(name)
else:
total_amount, grand_total, base_total = net_total_map.get(name)
else:
@@ -96,7 +100,7 @@
row.update(
{
- "section_code": tax_withholding_category,
+ "section_code": tax_withholding_category or "",
"entity_type": party_map.get(party, {}).get(party_type),
"rate": rate,
"total_amount": total_amount,
@@ -106,10 +110,14 @@
"transaction_date": posting_date,
"transaction_type": voucher_type,
"ref_no": name,
+ "supplier_invoice_no": bill_no,
+ "supplier_invoice_date": bill_date,
}
)
out.append(row)
+ out.sort(key=lambda x: x["section_code"])
+
return out
@@ -157,14 +165,14 @@
def get_columns(filters):
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
columns = [
- {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
{
- "label": _(filters.get("party_type")),
- "fieldname": "party",
- "fieldtype": "Dynamic Link",
- "options": "party_type",
- "width": 180,
+ "label": _("Section Code"),
+ "options": "Tax Withholding Category",
+ "fieldname": "section_code",
+ "fieldtype": "Link",
+ "width": 90,
},
+ {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
]
if filters.naming_series == "Naming Series":
@@ -179,51 +187,60 @@
columns.extend(
[
- {
- "label": _("Date of Transaction"),
- "fieldname": "transaction_date",
- "fieldtype": "Date",
- "width": 100,
- },
- {
- "label": _("Section Code"),
- "options": "Tax Withholding Category",
- "fieldname": "section_code",
- "fieldtype": "Link",
- "width": 90,
- },
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
- {
- "label": _("Total Amount"),
- "fieldname": "total_amount",
- "fieldtype": "Float",
- "width": 90,
- },
+ ]
+ )
+ if filters.party_type == "Supplier":
+ columns.extend(
+ [
+ {
+ "label": _("Supplier Invoice No"),
+ "fieldname": "supplier_invoice_no",
+ "fieldtype": "Data",
+ "width": 120,
+ },
+ {
+ "label": _("Supplier Invoice Date"),
+ "fieldname": "supplier_invoice_date",
+ "fieldtype": "Date",
+ "width": 120,
+ },
+ ]
+ )
+
+ columns.extend(
+ [
{
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
"fieldname": "rate",
"fieldtype": "Percent",
- "width": 90,
+ "width": 60,
},
{
- "label": _("Tax Amount"),
- "fieldname": "tax_amount",
+ "label": _("Total Amount"),
+ "fieldname": "total_amount",
"fieldtype": "Float",
- "width": 90,
- },
- {
- "label": _("Grand Total"),
- "fieldname": "grand_total",
- "fieldtype": "Float",
- "width": 90,
+ "width": 120,
},
{
"label": _("Base Total"),
"fieldname": "base_total",
"fieldtype": "Float",
- "width": 90,
+ "width": 120,
},
- {"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
+ {
+ "label": _("Tax Amount"),
+ "fieldname": "tax_amount",
+ "fieldtype": "Float",
+ "width": 120,
+ },
+ {
+ "label": _("Grand Total"),
+ "fieldname": "grand_total",
+ "fieldtype": "Float",
+ "width": 120,
+ },
+ {"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
{
"label": _("Reference No."),
"fieldname": "ref_no",
@@ -231,6 +248,12 @@
"options": "transaction_type",
"width": 180,
},
+ {
+ "label": _("Date of Transaction"),
+ "fieldname": "transaction_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
]
)
@@ -253,27 +276,7 @@
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
)
- query_filters = {
- "account": ("in", tds_accounts),
- "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
- "is_cancelled": 0,
- "against": ("not in", bank_accounts),
- }
-
- party = frappe.get_all(filters.get("party_type"), pluck="name")
- or_filters.update({"against": ("in", party), "voucher_type": "Journal Entry"})
-
- if filters.get("party"):
- del query_filters["account"]
- del query_filters["against"]
- or_filters = {"against": filters.get("party"), "party": filters.get("party")}
-
- tds_docs = frappe.get_all(
- "GL Entry",
- filters=query_filters,
- or_filters=or_filters,
- fields=["voucher_no", "voucher_type", "against", "party"],
- )
+ tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":
@@ -309,6 +312,47 @@
)
+def get_tds_docs_query(filters, bank_accounts, tds_accounts):
+ if not tds_accounts:
+ frappe.throw(
+ _("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
+ title="Accounts Missing Error",
+ )
+ gle = frappe.qb.DocType("GL Entry")
+ query = (
+ frappe.qb.from_(gle)
+ .select("voucher_no", "voucher_type", "against", "party")
+ .where((gle.is_cancelled == 0))
+ )
+
+ if filters.get("from_date"):
+ query = query.where(gle.posting_date >= filters.get("from_date"))
+ if filters.get("to_date"):
+ query = query.where(gle.posting_date <= filters.get("to_date"))
+
+ if bank_accounts:
+ query = query.where(gle.against.notin(bank_accounts))
+
+ if filters.get("party"):
+ party = [filters.get("party")]
+ query = query.where(
+ ((gle.account.isin(tds_accounts) & gle.against.isin(party)))
+ | ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
+ | gle.party.isin(party)
+ )
+ else:
+ party = frappe.get_all(filters.get("party_type"), pluck="name")
+ query = query.where(
+ ((gle.account.isin(tds_accounts) & gle.against.isin(party)))
+ | (
+ (gle.voucher_type == "Journal Entry")
+ & ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
+ )
+ | gle.party.isin(party)
+ )
+ return query
+
+
def get_journal_entry_party_map(journal_entries):
journal_entry_party_map = {}
for d in frappe.db.get_all(
@@ -335,6 +379,8 @@
"base_tax_withholding_net_total",
"grand_total",
"base_total",
+ "bill_no",
+ "bill_date",
],
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
"Payment Entry": [
@@ -353,7 +399,13 @@
for entry in entries:
tax_category_map.update({entry.name: entry.tax_withholding_category})
if doctype == "Purchase Invoice":
- value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]
+ value = [
+ entry.base_tax_withholding_net_total,
+ entry.grand_total,
+ entry.base_total,
+ entry.bill_no,
+ entry.bill_date,
+ ]
elif doctype == "Sales Invoice":
value = [entry.base_net_total, entry.grand_total, entry.base_total]
elif doctype == "Payment Entry":
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 1c7052f..e0adac4 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -10,7 +10,7 @@
from frappe import _, qb, throw
from frappe.model.meta import get_field_precision
from frappe.query_builder import AliasedQuery, Criterion, Table
-from frappe.query_builder.functions import Sum
+from frappe.query_builder.functions import Round, Sum
from frappe.query_builder.utils import DocType
from frappe.utils import (
cint,
@@ -536,6 +536,8 @@
)
else:
+ precision = frappe.get_precision("Payment Entry", "unallocated_amount")
+
payment_entry = frappe.qb.DocType("Payment Entry")
payment_ref = frappe.qb.DocType("Payment Entry Reference")
@@ -557,7 +559,10 @@
.where(payment_ref.allocated_amount == args.get("unreconciled_amount"))
)
else:
- q = q.where(payment_entry.unallocated_amount == args.get("unreconciled_amount"))
+ q = q.where(
+ Round(payment_entry.unallocated_amount, precision)
+ == Round(args.get("unreconciled_amount"), precision)
+ )
ret = q.run(as_dict=True)
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index f0e4c82..d378fbd 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -9,7 +9,6 @@
frm.set_query("item_code", function() {
return {
"filters": {
- "disabled": 0,
"is_fixed_asset": 1,
"is_stock_item": 0
}
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 9d35634..3c570d1 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -818,7 +818,7 @@
"depreciation_method": d.depreciation_method,
"total_number_of_depreciations": d.total_number_of_depreciations,
"frequency_of_depreciation": d.frequency_of_depreciation,
- "daily_depreciation": d.daily_depreciation,
+ "daily_prorata_based": d.daily_prorata_based,
"salvage_value_percentage": d.salvage_value_percentage,
"expected_value_after_useful_life": flt(gross_purchase_amount)
* flt(d.salvage_value_percentage / 100),
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index e2a4b29..84a428c 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -780,6 +780,15 @@
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
asset_doc = frappe.get_doc("Asset", asset)
+ if asset_doc.available_for_use_date > getdate(disposal_date):
+ frappe.throw(
+ "Disposal date {0} cannot be before available for use date {1} of the asset.".format(
+ disposal_date, asset_doc.available_for_use_date
+ )
+ )
+ elif asset_doc.available_for_use_date == getdate(disposal_date):
+ return flt(asset_doc.gross_purchase_amount - asset_doc.opening_accumulated_depreciation)
+
if not asset_doc.calculate_depreciation:
return flt(asset_doc.value_after_depreciation)
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index d69f5ef..9e3ec6f 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -755,7 +755,9 @@
self.assertEqual(schedules, expected_schedules)
- def test_schedule_for_straight_line_method_with_daily_depreciation(self):
+ def test_schedule_for_straight_line_method_with_daily_prorata_based(
+ self,
+ ):
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2023-01-01",
@@ -764,7 +766,7 @@
depreciation_start_date="2023-01-31",
total_number_of_depreciations=12,
frequency_of_depreciation=1,
- daily_depreciation=1,
+ daily_prorata_based=1,
)
expected_schedules = [
@@ -1760,7 +1762,7 @@
"total_number_of_depreciations": args.total_number_of_depreciations or 5,
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
"depreciation_start_date": args.depreciation_start_date,
- "daily_depreciation": args.daily_depreciation or 0,
+ "daily_prorata_based": args.daily_prorata_based or 0,
},
)
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index 3772ef4..8d8b463 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -19,7 +19,7 @@
"depreciation_method",
"total_number_of_depreciations",
"rate_of_depreciation",
- "daily_depreciation",
+ "daily_prorata_based",
"column_break_8",
"frequency_of_depreciation",
"expected_value_after_useful_life",
@@ -179,9 +179,9 @@
{
"default": "0",
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
- "fieldname": "daily_depreciation",
+ "fieldname": "daily_prorata_based",
"fieldtype": "Check",
- "label": "Daily Depreciation",
+ "label": "Depreciate based on daily pro-rata",
"print_hide": 1,
"read_only": 1
}
@@ -189,7 +189,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-08-10 22:22:09.722968",
+ "modified": "2023-11-03 21:32:15.021796",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Depreciation Schedule",
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 7a88ffc..7305691 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -153,7 +153,7 @@
self.frequency_of_depreciation = row.frequency_of_depreciation
self.rate_of_depreciation = row.rate_of_depreciation
self.expected_value_after_useful_life = row.expected_value_after_useful_life
- self.daily_depreciation = row.daily_depreciation
+ self.daily_prorata_based = row.daily_prorata_based
self.status = "Draft"
def make_depr_schedule(
@@ -573,7 +573,7 @@
)
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
- if row.daily_depreciation:
+ if row.daily_prorata_based:
daily_depr_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / date_diff(
@@ -618,7 +618,7 @@
) / number_of_pending_depreciations
# if the Depreciation Schedule is being prepared for the first time
else:
- if row.daily_depreciation:
+ if row.daily_prorata_based:
daily_depr_amount = (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index 2c27dc9..e597d5f 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -8,7 +8,7 @@
"finance_book",
"depreciation_method",
"total_number_of_depreciations",
- "daily_depreciation",
+ "daily_prorata_based",
"column_break_5",
"frequency_of_depreciation",
"depreciation_start_date",
@@ -87,22 +87,22 @@
"label": "Rate of Depreciation"
},
{
- "default": "0",
- "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
- "fieldname": "daily_depreciation",
- "fieldtype": "Check",
- "label": "Daily Depreciation"
- },
- {
"fieldname": "salvage_value_percentage",
"fieldtype": "Percent",
"label": "Salvage Value Percentage"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
+ "fieldname": "daily_prorata_based",
+ "fieldtype": "Check",
+ "label": "Depreciate based on daily pro-rata"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-09-29 15:39:52.740594",
+ "modified": "2023-11-03 21:30:24.266601",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",
diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
index 23088c9..a33acfd 100644
--- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
@@ -13,25 +13,22 @@
class TestAssetMaintenance(unittest.TestCase):
def setUp(self):
set_depreciation_settings_in_company()
- create_asset_data()
- create_maintenance_team()
-
- def test_create_asset_maintenance(self):
- pr = make_purchase_receipt(
+ self.pr = make_purchase_receipt(
item_code="Photocopier", qty=1, rate=100000.0, location="Test Location"
)
+ self.asset_name = frappe.db.get_value("Asset", {"purchase_receipt": self.pr.name}, "name")
+ self.asset_doc = frappe.get_doc("Asset", self.asset_name)
- asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
- asset_doc = frappe.get_doc("Asset", asset_name)
+ def test_create_asset_maintenance_with_log(self):
month_end_date = get_last_day(nowdate())
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
- asset_doc.available_for_use_date = purchase_date
- asset_doc.purchase_date = purchase_date
+ self.asset_doc.available_for_use_date = purchase_date
+ self.asset_doc.purchase_date = purchase_date
- asset_doc.calculate_depreciation = 1
- asset_doc.append(
+ self.asset_doc.calculate_depreciation = 1
+ self.asset_doc.append(
"finance_books",
{
"expected_value_after_useful_life": 200,
@@ -42,97 +39,40 @@
},
)
- asset_doc.save()
+ self.asset_doc.save()
- if not frappe.db.exists("Asset Maintenance", "Photocopier"):
- asset_maintenance = frappe.get_doc(
- {
- "doctype": "Asset Maintenance",
- "asset_name": "Photocopier",
- "maintenance_team": "Team Awesome",
- "company": "_Test Company",
- "asset_maintenance_tasks": get_maintenance_tasks(),
- }
- ).insert()
+ asset_maintenance = frappe.get_doc(
+ {
+ "doctype": "Asset Maintenance",
+ "asset_name": self.asset_name,
+ "maintenance_team": "Team Awesome",
+ "company": "_Test Company",
+ "asset_maintenance_tasks": get_maintenance_tasks(),
+ }
+ ).insert()
- next_due_date = calculate_next_due_date(nowdate(), "Monthly")
- self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
-
- def test_create_asset_maintenance_log(self):
- if not frappe.db.exists("Asset Maintenance Log", "Photocopier"):
- asset_maintenance_log = frappe.get_doc(
- {
- "doctype": "Asset Maintenance Log",
- "asset_maintenance": "Photocopier",
- "task": "Change Oil",
- "completion_date": add_days(nowdate(), 2),
- "maintenance_status": "Completed",
- }
- ).insert()
- asset_maintenance = frappe.get_doc("Asset Maintenance", "Photocopier")
- next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly")
+ next_due_date = calculate_next_due_date(nowdate(), "Monthly")
self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
+ asset_maintenance_log = frappe.db.get_value(
+ "Asset Maintenance Log",
+ {"asset_maintenance": asset_maintenance.name, "task_name": "Change Oil"},
+ "name",
+ )
-def create_asset_data():
- if not frappe.db.exists("Asset Category", "Equipment"):
- create_asset_category()
-
- if not frappe.db.exists("Location", "Test Location"):
- frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
-
- if not frappe.db.exists("Item", "Photocopier"):
- meta = frappe.get_meta("Asset")
- naming_series = meta.get_field("naming_series").options
- frappe.get_doc(
+ asset_maintenance_log_doc = frappe.get_doc("Asset Maintenance Log", asset_maintenance_log)
+ asset_maintenance_log_doc.update(
{
- "doctype": "Item",
- "item_code": "Photocopier",
- "item_name": "Photocopier",
- "item_group": "All Item Groups",
- "company": "_Test Company",
- "is_fixed_asset": 1,
- "is_stock_item": 0,
- "asset_category": "Equipment",
- "auto_create_assets": 1,
- "asset_naming_series": naming_series,
+ "completion_date": add_days(nowdate(), 2),
+ "maintenance_status": "Completed",
}
- ).insert()
+ )
+ asset_maintenance_log_doc.save()
+ next_due_date = calculate_next_due_date(asset_maintenance_log_doc.completion_date, "Monthly")
-def create_maintenance_team():
- user_list = ["marcus@abc.com", "thalia@abc.com", "mathias@abc.com"]
- if not frappe.db.exists("Role", "Technician"):
- frappe.get_doc({"doctype": "Role", "role_name": "Technician"}).insert()
- for user in user_list:
- if not frappe.db.get_value("User", user):
- frappe.get_doc(
- {
- "doctype": "User",
- "email": user,
- "first_name": user,
- "new_password": "password",
- "roles": [{"doctype": "Has Role", "role": "Technician"}],
- }
- ).insert()
-
- if not frappe.db.exists("Asset Maintenance Team", "Team Awesome"):
- frappe.get_doc(
- {
- "doctype": "Asset Maintenance Team",
- "maintenance_manager": "marcus@abc.com",
- "maintenance_team_name": "Team Awesome",
- "company": "_Test Company",
- "maintenance_team_members": get_maintenance_team(user_list),
- }
- ).insert()
-
-
-def get_maintenance_team(user_list):
- return [
- {"team_member": user, "full_name": user, "maintenance_role": "Technician"}
- for user in user_list[1:]
- ]
+ asset_maintenance.reload()
+ self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
def get_maintenance_tasks():
@@ -156,23 +96,6 @@
]
-def create_asset_category():
- asset_category = frappe.new_doc("Asset Category")
- asset_category.asset_category_name = "Equipment"
- asset_category.total_number_of_depreciations = 3
- asset_category.frequency_of_depreciation = 3
- asset_category.append(
- "accounts",
- {
- "company_name": "_Test Company",
- "fixed_asset_account": "_Test Fixed Asset - _TC",
- "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
- "depreciation_expense_account": "_Test Depreciations - _TC",
- },
- )
- asset_category.insert()
-
-
def set_depreciation_settings_in_company():
company = frappe.get_doc("Company", "_Test Company")
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
diff --git a/erpnext/assets/doctype/asset_maintenance/test_records.json b/erpnext/assets/doctype/asset_maintenance/test_records.json
new file mode 100644
index 0000000..8306fad
--- /dev/null
+++ b/erpnext/assets/doctype/asset_maintenance/test_records.json
@@ -0,0 +1,68 @@
+[
+ {
+ "doctype": "Asset Category",
+ "asset_category_name": "Equipment",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 3,
+ "accounts": [
+ {
+ "company_name": "_Test Company",
+ "fixed_asset_account": "_Test Fixed Asset - _TC",
+ "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
+ "depreciation_expense_account": "_Test Depreciations - _TC"
+ }
+ ]
+ },
+ {
+ "doctype": "Location",
+ "location_name": "Test Location"
+ },
+ {
+ "doctype": "Role",
+ "role_name": "Technician"
+ },
+ {
+ "doctype": "User",
+ "email": "marcus@abc.com",
+ "first_name": "marcus@abc.com",
+ "new_password": "password",
+ "roles": [{"doctype": "Has Role", "role": "Technician"}]
+ },
+ {
+ "doctype": "User",
+ "email": "thalia@abc.com",
+ "first_name": "thalia@abc.com",
+ "new_password": "password",
+ "roles": [{"doctype": "Has Role", "role": "Technician"}]
+ },
+ {
+ "doctype": "User",
+ "email": "mathias@abc.com",
+ "first_name": "mathias@abc.com",
+ "new_password": "password",
+ "roles": [{"doctype": "Has Role", "role": "Technician"}]
+ },
+ {
+ "doctype": "Asset Maintenance Team",
+ "maintenance_manager": "marcus@abc.com",
+ "maintenance_team_name": "Team Awesome",
+ "company": "_Test Company",
+ "maintenance_team_members": [
+ {"team_member": "marcus@abc.com", "full_name": "marcus@abc.com", "maintenance_role": "Technician"},
+ {"team_member": "thalia@abc.com", "full_name": "thalia@abc.com", "maintenance_role": "Technician"},
+ {"team_member": "mathias@abc.com", "full_name": "mathias@abc.com", "maintenance_role": "Technician"}
+ ]
+ },
+ {
+ "doctype": "Item",
+ "item_code": "Photocopier",
+ "item_name": "Photocopier",
+ "item_group": "All Item Groups",
+ "company": "_Test Company",
+ "is_fixed_asset": 1,
+ "is_stock_item": 0,
+ "asset_category": "Equipment",
+ "auto_create_assets": 1,
+ "asset_naming_series": "ABC.###"
+ }
+]
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 b1da97d..2b6ffb7 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -470,6 +470,7 @@
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
+ "mandatory_depends_on": "eval: doc.material_request_item",
"no_copy": 1,
"oldfieldname": "prevdoc_docname",
"oldfieldtype": "Link",
@@ -485,6 +486,7 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Material Request Item",
+ "mandatory_depends_on": "eval: doc.material_request",
"no_copy": 1,
"oldfieldname": "prevdoc_detail_docname",
"oldfieldtype": "Data",
@@ -914,7 +916,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-27 15:50:42.655573",
+ "modified": "2023-11-06 11:00:53.596417",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index f37db5f..60dd54c 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -174,7 +174,7 @@
"fieldname": "supplier_type",
"fieldtype": "Select",
"label": "Supplier Type",
- "options": "Company\nIndividual",
+ "options": "Company\nIndividual\nProprietorship\nPartnership",
"reqd": 1
},
{
@@ -485,7 +485,7 @@
"link_fieldname": "party"
}
],
- "modified": "2023-09-25 12:48:21.869563",
+ "modified": "2023-10-19 16:55:15.148325",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
index fd73b87..579c0a6 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
@@ -44,11 +44,6 @@
}
}
}
- else {
- return {
- filters: { "disabled": 0 }
- }
- }
}
},
{
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 6efe631..38c1e82 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -2269,6 +2269,7 @@
repost_ledger = frappe.new_doc("Repost Accounting Ledger")
repost_ledger.company = self.company
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
+ repost_ledger.flags.ignore_permissions = True
repost_ledger.insert()
repost_ledger.submit()
self.db_set("repost_required", 0)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index a76abe2..ece08d8 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -4,7 +4,7 @@
import frappe
from frappe import ValidationError, _, msgprint
-from frappe.contacts.doctype.address.address import get_address_display
+from frappe.contacts.doctype.address.address import render_address
from frappe.utils import cint, flt, getdate
from frappe.utils.data import nowtime
@@ -105,26 +105,26 @@
def set_rate_for_standalone_debit_note(self):
if self.get("is_return") and self.get("update_stock") and not self.return_against:
for row in self.items:
+ if row.rate <= 0:
+ # override the rate with valuation rate
+ row.rate = get_incoming_rate(
+ {
+ "item_code": row.item_code,
+ "warehouse": row.warehouse,
+ "posting_date": self.get("posting_date"),
+ "posting_time": self.get("posting_time"),
+ "qty": row.qty,
+ "serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ },
+ raise_error_if_no_rate=False,
+ )
- # override the rate with valuation rate
- row.rate = get_incoming_rate(
- {
- "item_code": row.item_code,
- "warehouse": row.warehouse,
- "posting_date": self.get("posting_date"),
- "posting_time": self.get("posting_time"),
- "qty": row.qty,
- "serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
- "company": self.company,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- },
- raise_error_if_no_rate=False,
- )
-
- row.discount_percentage = 0.0
- row.discount_amount = 0.0
- row.margin_rate_or_amount = 0.0
+ row.discount_percentage = 0.0
+ row.discount_amount = 0.0
+ row.margin_rate_or_amount = 0.0
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@@ -246,7 +246,9 @@
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
- self.set(address_display_field, get_address_display(self.get(address_field)))
+ self.set(
+ address_display_field, render_address(self.get(address_field), check_permissions=False)
+ )
def set_total_in_words(self):
from frappe.utils import money_in_words
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 73a248f..d09001c 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -47,15 +47,15 @@
],
[
"To Bill",
- "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
+ "eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
],
[
"To Deliver",
- "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note",
+ "eval:self.per_delivered < 100 and self.per_billed >= 100 and self.docstatus == 1 and not self.skip_delivery_note",
],
[
"Completed",
- "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
+ "eval:(self.per_delivered >= 100 or self.skip_delivery_note) and self.per_billed >= 100 and self.docstatus == 1",
],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index a7330ec..fc45c7a 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -1210,8 +1210,6 @@
repost_entry = frappe.new_doc("Repost Item Valuation")
repost_entry.based_on = "Item and Warehouse"
- repost_entry.voucher_type = voucher_type
- repost_entry.voucher_no = voucher_no
repost_entry.item_code = sle.item_code
repost_entry.warehouse = sle.warehouse
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
index 510317f..dfef223 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
@@ -195,26 +195,6 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "GoCardless Settings",
- "link_count": 0,
- "link_to": "GoCardless Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Mpesa Settings",
- "link_count": 0,
- "link_to": "Mpesa Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
"label": "Plaid Settings",
"link_count": 0,
"link_to": "Plaid Settings",
@@ -223,7 +203,7 @@
"type": "Link"
}
],
- "modified": "2023-08-29 15:48:59.010704",
+ "modified": "2023-10-31 19:57:32.748726",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
index f4877fd..9e32085 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
@@ -10,8 +10,8 @@
},
gantt: {
field_map: {
- "start": "started_time",
- "end": "started_time",
+ "start": "expected_start_date",
+ "end": "expected_end_date",
"id": "name",
"title": "subject",
"color": "color",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_list.js b/erpnext/manufacturing/doctype/job_card/job_card_list.js
index 5d883bf..99fca95 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card_list.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card_list.js
@@ -1,6 +1,6 @@
frappe.listview_settings['Job Card'] = {
has_indicator_for_draft: true,
-
+ add_fields: ["expected_start_date", "expected_end_date"],
get_indicator: function(doc) {
const status_colors = {
"Work In Progress": "orange",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 4a00416..49386c4 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -36,6 +36,7 @@
"prod_plan_references",
"section_break_24",
"combine_sub_items",
+ "sub_assembly_warehouse",
"section_break_ucc4",
"skip_available_sub_assembly_item",
"column_break_igxl",
@@ -416,13 +417,19 @@
{
"fieldname": "column_break_igxl",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "sub_assembly_warehouse",
+ "fieldtype": "Link",
+ "label": "Sub Assembly Warehouse",
+ "options": "Warehouse"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-09-29 11:41:03.246059",
+ "modified": "2023-11-03 14:08:11.928027",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index ddd9375..6b12a29 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -490,6 +490,12 @@
bin = frappe.get_doc("Bin", bin_name, for_update=True)
bin.update_reserved_qty_for_production_plan()
+ for d in self.sub_assembly_items:
+ if d.fg_warehouse and d.type_of_manufacturing == "In House":
+ bin_name = get_or_make_bin(d.production_item, d.fg_warehouse)
+ bin = frappe.get_doc("Bin", bin_name, for_update=True)
+ bin.update_reserved_qty_for_for_sub_assembly()
+
def delete_draft_work_order(self):
for d in frappe.get_all(
"Work Order", fields=["name"], filters={"docstatus": 0, "production_plan": ("=", self.name)}
@@ -809,7 +815,11 @@
bom_data = []
- warehouse = row.warehouse if self.skip_available_sub_assembly_item else None
+ warehouse = (
+ (self.sub_assembly_warehouse or row.warehouse)
+ if self.skip_available_sub_assembly_item
+ else None
+ )
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
@@ -831,7 +841,7 @@
for data in bom_data:
data.qty = data.stock_qty
data.production_plan_item = row.name
- data.fg_warehouse = row.warehouse
+ data.fg_warehouse = self.sub_assembly_warehouse or row.warehouse
data.schedule_date = row.planned_start_date
data.type_of_manufacturing = manufacturing_type or (
"Subcontract" if data.is_sub_contracted_item else "In House"
@@ -1637,8 +1647,8 @@
query = query.run()
- if not query:
- return 0.0
+ if not query or query[0][0] is None:
+ return None
reserved_qty_for_production_plan = flt(query[0][0])
@@ -1735,7 +1745,10 @@
if not item.conversion_factor and item.purchase_uom:
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
- item_details.setdefault(item.get("item_code"), item)
+ if details := item_details.get(item.get("item_code")):
+ details.qty += item.get("qty")
+ else:
+ item_details.setdefault(item.get("item_code"), item)
return item_details
@@ -1777,3 +1790,29 @@
query = query.offset(start)
return query.run()
+
+
+def get_reserved_qty_for_sub_assembly(item_code, warehouse):
+ table = frappe.qb.DocType("Production Plan")
+ child = frappe.qb.DocType("Production Plan Sub Assembly Item")
+
+ query = (
+ frappe.qb.from_(table)
+ .inner_join(child)
+ .on(table.name == child.parent)
+ .select(Sum(child.qty - IfNull(child.wo_produced_qty, 0)))
+ .where(
+ (table.docstatus == 1)
+ & (child.production_item == item_code)
+ & (child.fg_warehouse == warehouse)
+ & (table.status.notin(["Completed", "Closed"]))
+ )
+ )
+
+ query = query.run()
+
+ if not query or query[0][0] is None:
+ return None
+
+ qty = flt(query[0][0])
+ return qty if qty > 0 else 0.0
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 6ab9232..e9c6ee3 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1042,13 +1042,14 @@
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertEqual(after_qty - before_qty, 1)
-
pln = frappe.get_doc("Production Plan", pln.name)
pln.cancel()
bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+ pln.reload()
+ self.assertEqual(pln.docstatus, 2)
self.assertEqual(after_qty, before_qty)
def test_resered_qty_for_production_plan_for_work_order(self):
@@ -1332,6 +1333,120 @@
self.assertTrue(row.warehouse == mrp_warhouse)
self.assertEqual(row.quantity, 12)
+ def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
+ from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+ bom_tree = {
+ "Fininshed Goods2 For SUB Test": {
+ "SubAssembly2 For SUB Test": {"ChildPart2 For SUB Test": {}},
+ "SubAssembly3 For SUB Test": {"ChildPart2 For SUB Test": {}},
+ }
+ }
+
+ parent_bom = create_nested_bom(bom_tree, prefix="")
+ plan = create_production_plan(
+ item_code=parent_bom.item,
+ planned_qty=1,
+ ignore_existing_ordered_qty=1,
+ do_not_submit=1,
+ skip_available_sub_assembly_item=1,
+ warehouse="_Test Warehouse - _TC",
+ )
+
+ plan.get_sub_assembly_items()
+ plan.make_material_request()
+
+ for row in plan.mr_items:
+ if row.item_code == "ChildPart2 For SUB Test":
+ self.assertEqual(row.quantity, 2)
+
+ def test_reserve_sub_assembly_items(self):
+ from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ bom_tree = {
+ "Fininshed Goods Bicycle": {
+ "Frame Assembly": {"Frame": {}},
+ "Chain Assembly": {"Chain": {}},
+ }
+ }
+ parent_bom = create_nested_bom(bom_tree, prefix="")
+
+ warehouse = "_Test Warehouse - _TC"
+ company = "_Test Company"
+
+ sub_assembly_warehouse = create_warehouse("SUB ASSEMBLY WH", company=company)
+
+ for item_code in ["Frame", "Chain"]:
+ make_stock_entry(item_code=item_code, target=warehouse, qty=2, basic_rate=100)
+
+ before_qty = flt(
+ frappe.db.get_value(
+ "Bin",
+ {"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse},
+ "reserved_qty_for_production_plan",
+ )
+ )
+
+ plan = create_production_plan(
+ item_code=parent_bom.item,
+ planned_qty=2,
+ ignore_existing_ordered_qty=1,
+ do_not_submit=1,
+ skip_available_sub_assembly_item=1,
+ warehouse=warehouse,
+ sub_assembly_warehouse=sub_assembly_warehouse,
+ )
+
+ plan.get_sub_assembly_items()
+ plan.submit()
+
+ after_qty = flt(
+ frappe.db.get_value(
+ "Bin",
+ {"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse},
+ "reserved_qty_for_production_plan",
+ )
+ )
+
+ self.assertEqual(after_qty, before_qty + 2)
+
+ plan.make_work_order()
+ work_orders = frappe.get_all(
+ "Work Order",
+ fields=["name", "production_item"],
+ filters={"production_plan": plan.name},
+ order_by="creation desc",
+ )
+
+ for d in work_orders:
+ wo_doc = frappe.get_doc("Work Order", d.name)
+ wo_doc.skip_transfer = 1
+ wo_doc.from_wip_warehouse = 1
+
+ wo_doc.wip_warehouse = (
+ warehouse
+ if d.production_item in ["Frame Assembly", "Chain Assembly"]
+ else sub_assembly_warehouse
+ )
+
+ wo_doc.submit()
+
+ if d.production_item == "Frame Assembly":
+ self.assertEqual(wo_doc.fg_warehouse, sub_assembly_warehouse)
+ se_doc = frappe.get_doc(make_se_from_wo(wo_doc.name, "Manufacture", 2))
+ se_doc.submit()
+
+ after_qty = flt(
+ frappe.db.get_value(
+ "Bin",
+ {"item_code": "Frame Assembly", "warehouse": sub_assembly_warehouse},
+ "reserved_qty_for_production_plan",
+ )
+ )
+
+ self.assertEqual(after_qty, before_qty)
+
def create_production_plan(**args):
"""
@@ -1352,6 +1467,7 @@
"ignore_existing_ordered_qty": args.ignore_existing_ordered_qty or 0,
"get_items_from": "Sales Order",
"skip_available_sub_assembly_item": args.skip_available_sub_assembly_item or 0,
+ "sub_assembly_warehouse": args.sub_assembly_warehouse,
}
)
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
index fde0404..aff740b 100644
--- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
@@ -17,11 +17,10 @@
"type_of_manufacturing",
"supplier",
"work_order_details_section",
- "work_order",
+ "wo_produced_qty",
"purchase_order",
"production_plan_item",
"column_break_7",
- "produced_qty",
"received_qty",
"indent",
"section_break_19",
@@ -53,13 +52,6 @@
"label": "Reference"
},
{
- "fieldname": "work_order",
- "fieldtype": "Link",
- "label": "Work Order",
- "options": "Work Order",
- "read_only": 1
- },
- {
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
@@ -81,7 +73,8 @@
{
"fieldname": "received_qty",
"fieldtype": "Float",
- "label": "Received Qty"
+ "label": "Received Qty",
+ "read_only": 1
},
{
"fieldname": "bom_no",
@@ -162,12 +155,6 @@
"options": "Warehouse"
},
{
- "fieldname": "produced_qty",
- "fieldtype": "Data",
- "label": "Produced Quantity",
- "read_only": 1
- },
- {
"default": "In House",
"fieldname": "type_of_manufacturing",
"fieldtype": "Select",
@@ -209,12 +196,18 @@
"label": "Projected Qty",
"no_copy": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "wo_produced_qty",
+ "fieldtype": "Float",
+ "label": "Produced Qty",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-05-22 17:52:34.708879",
+ "modified": "2023-11-03 13:33:42.959387",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index c828c87..0ae7657 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -494,6 +494,7 @@
"from_time": row.from_time,
"to_time": row.to_time,
"time_in_mins": row.time_in_mins,
+ "completed_qty": 0,
},
)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 58945bb..d9cc212 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -710,7 +710,7 @@
return new Promise((resolve, reject) => {
frappe.prompt({
fieldtype: 'Float',
- label: __('Qty for {0}', [purpose]),
+ label: __('Qty for {0}', [__(purpose)]),
fieldname: 'qty',
description: __('Max: {0}', [max]),
default: max
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index f9fddcb..36a0cae 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -293,6 +293,7 @@
update_produced_qty_in_so_item(self.sales_order, self.sales_order_item)
if self.production_plan:
+ self.set_produced_qty_for_sub_assembly_item()
self.update_production_plan_status()
def get_transferred_or_manufactured_qty(self, purpose):
@@ -569,16 +570,49 @@
)
def update_planned_qty(self):
+ from erpnext.manufacturing.doctype.production_plan.production_plan import (
+ get_reserved_qty_for_sub_assembly,
+ )
+
+ qty_dict = {"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)}
+
+ if self.production_plan_sub_assembly_item and self.production_plan:
+ qty_dict["reserved_qty_for_production_plan"] = get_reserved_qty_for_sub_assembly(
+ self.production_item, self.fg_warehouse
+ )
+
update_bin_qty(
self.production_item,
self.fg_warehouse,
- {"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)},
+ qty_dict,
)
if self.material_request:
mr_obj = frappe.get_doc("Material Request", self.material_request)
mr_obj.update_requested_qty([self.material_request_item])
+ def set_produced_qty_for_sub_assembly_item(self):
+ table = frappe.qb.DocType("Work Order")
+
+ query = (
+ frappe.qb.from_(table)
+ .select(Sum(table.produced_qty))
+ .where(
+ (table.production_plan == self.production_plan)
+ & (table.production_plan_sub_assembly_item == self.production_plan_sub_assembly_item)
+ & (table.docstatus == 1)
+ )
+ ).run()
+
+ produced_qty = flt(query[0][0]) if query else 0
+
+ frappe.db.set_value(
+ "Production Plan Sub Assembly Item",
+ self.production_plan_sub_assembly_item,
+ "wo_produced_qty",
+ produced_qty,
+ )
+
def update_ordered_qty(self):
if (
self.production_plan
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
index 34edb9d..8729775 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
@@ -12,7 +12,7 @@
"options": "Item",
"get_query": () =>{
return {
- filters: { "disabled": 0, "is_stock_item": 1 }
+ filters: { "is_stock_item": 1 }
}
}
},
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d7f33ad..d394db6 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -343,5 +343,11 @@
erpnext.patches.v15_0.update_sre_from_voucher_details
erpnext.patches.v14_0.rename_over_order_allowance_field
erpnext.patches.v14_0.migrate_delivery_stop_lock_field
+execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50)
+execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50)
+erpnext.patches.v14_0.add_default_for_repost_settings
+erpnext.patches.v15_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month
+erpnext.patches.v15_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based
+erpnext.patches.v15_0.set_reserved_stock_in_bin
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v14_0/add_default_for_repost_settings.py b/erpnext/patches/v14_0/add_default_for_repost_settings.py
new file mode 100644
index 0000000..6cafc66
--- /dev/null
+++ b/erpnext/patches/v14_0/add_default_for_repost_settings.py
@@ -0,0 +1,12 @@
+import frappe
+
+
+def execute():
+ """
+ Update Repost Accounting Ledger Settings with default values
+ """
+ allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
+ repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
+ for x in allowed_types:
+ repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
+ repost_settings.save()
diff --git a/erpnext/patches/v15_0/rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month.py b/erpnext/patches/v15_0/rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month.py
new file mode 100644
index 0000000..63dc0e0
--- /dev/null
+++ b/erpnext/patches/v15_0/rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+
+from frappe.model.utils.rename_field import rename_field
+
+
+def execute():
+ try:
+ rename_field(
+ "Asset Finance Book", "daily_depreciation", "depreciation_amount_based_on_num_days_in_month"
+ )
+ rename_field(
+ "Asset Depreciation Schedule",
+ "daily_depreciation",
+ "depreciation_amount_based_on_num_days_in_month",
+ )
+
+ except Exception as e:
+ if e.args[0] != 1054:
+ raise
diff --git a/erpnext/patches/v15_0/rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based.py b/erpnext/patches/v15_0/rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based.py
new file mode 100644
index 0000000..2c03c23
--- /dev/null
+++ b/erpnext/patches/v15_0/rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+
+from frappe.model.utils.rename_field import rename_field
+
+
+def execute():
+ try:
+ rename_field(
+ "Asset Finance Book", "depreciation_amount_based_on_num_days_in_month", "daily_prorata_based"
+ )
+ rename_field(
+ "Asset Depreciation Schedule",
+ "depreciation_amount_based_on_num_days_in_month",
+ "daily_prorata_based",
+ )
+
+ except Exception as e:
+ if e.args[0] != 1054:
+ raise
diff --git a/erpnext/patches/v15_0/set_reserved_stock_in_bin.py b/erpnext/patches/v15_0/set_reserved_stock_in_bin.py
new file mode 100644
index 0000000..fd0a233
--- /dev/null
+++ b/erpnext/patches/v15_0/set_reserved_stock_in_bin.py
@@ -0,0 +1,24 @@
+import frappe
+from frappe.query_builder.functions import Sum
+
+
+def execute():
+ sre = frappe.qb.DocType("Stock Reservation Entry")
+ query = (
+ frappe.qb.from_(sre)
+ .select(
+ sre.item_code,
+ sre.warehouse,
+ Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_stock"),
+ )
+ .where((sre.docstatus == 1) & (sre.status.notin(["Delivered", "Cancelled"])))
+ .groupby(sre.item_code, sre.warehouse)
+ )
+
+ for d in query.run(as_dict=True):
+ frappe.db.set_value(
+ "Bin",
+ {"item_code": d.item_code, "warehouse": d.warehouse},
+ "reserved_stock",
+ d.reserved_stock,
+ )
diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py
deleted file mode 100644
index ac1524a..0000000
--- a/erpnext/projects/report/billing_summary.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.utils import flt, time_diff_in_hours
-
-
-def get_columns():
- return [
- {
- "label": _("Employee ID"),
- "fieldtype": "Link",
- "fieldname": "employee",
- "options": "Employee",
- "width": 300,
- },
- {
- "label": _("Employee Name"),
- "fieldtype": "data",
- "fieldname": "employee_name",
- "hidden": 1,
- "width": 200,
- },
- {
- "label": _("Timesheet"),
- "fieldtype": "Link",
- "fieldname": "timesheet",
- "options": "Timesheet",
- "width": 150,
- },
- {"label": _("Working Hours"), "fieldtype": "Float", "fieldname": "total_hours", "width": 150},
- {
- "label": _("Billable Hours"),
- "fieldtype": "Float",
- "fieldname": "total_billable_hours",
- "width": 150,
- },
- {"label": _("Billing Amount"), "fieldtype": "Currency", "fieldname": "amount", "width": 150},
- ]
-
-
-def get_data(filters):
- data = []
- if filters.from_date > filters.to_date:
- frappe.msgprint(_("From Date can not be greater than To Date"))
- return data
-
- timesheets = get_timesheets(filters)
-
- filters.from_date = frappe.utils.get_datetime(filters.from_date)
- filters.to_date = frappe.utils.add_to_date(
- frappe.utils.get_datetime(filters.to_date), days=1, seconds=-1
- )
-
- timesheet_details = get_timesheet_details(filters, timesheets.keys())
-
- for ts, ts_details in timesheet_details.items():
- total_hours = 0
- total_billing_hours = 0
- total_amount = 0
-
- for row in ts_details:
- from_time, to_time = filters.from_date, filters.to_date
-
- if row.to_time < from_time or row.from_time > to_time:
- continue
-
- if row.from_time > from_time:
- from_time = row.from_time
-
- if row.to_time < to_time:
- to_time = row.to_time
-
- activity_duration, billing_duration = get_billable_and_total_duration(row, from_time, to_time)
-
- total_hours += activity_duration
- total_billing_hours += billing_duration
- total_amount += billing_duration * flt(row.billing_rate)
-
- if total_hours:
- data.append(
- {
- "employee": timesheets.get(ts).employee,
- "employee_name": timesheets.get(ts).employee_name,
- "timesheet": ts,
- "total_billable_hours": total_billing_hours,
- "total_hours": total_hours,
- "amount": total_amount,
- }
- )
-
- return data
-
-
-def get_timesheets(filters):
- record_filters = [
- ["start_date", "<=", filters.to_date],
- ["end_date", ">=", filters.from_date],
- ]
- if not filters.get("include_draft_timesheets"):
- record_filters.append(["docstatus", "=", 1])
- else:
- record_filters.append(["docstatus", "!=", 2])
- if "employee" in filters:
- record_filters.append(["employee", "=", filters.employee])
-
- timesheets = frappe.get_all(
- "Timesheet", filters=record_filters, fields=["employee", "employee_name", "name"]
- )
- timesheet_map = frappe._dict()
- for d in timesheets:
- timesheet_map.setdefault(d.name, d)
-
- return timesheet_map
-
-
-def get_timesheet_details(filters, timesheet_list):
- timesheet_details_filter = {"parent": ["in", timesheet_list]}
-
- if "project" in filters:
- timesheet_details_filter["project"] = filters.project
-
- timesheet_details = frappe.get_all(
- "Timesheet Detail",
- filters=timesheet_details_filter,
- fields=[
- "from_time",
- "to_time",
- "hours",
- "is_billable",
- "billing_hours",
- "billing_rate",
- "parent",
- ],
- )
-
- timesheet_details_map = frappe._dict()
- for d in timesheet_details:
- timesheet_details_map.setdefault(d.parent, []).append(d)
-
- return timesheet_details_map
-
-
-def get_billable_and_total_duration(activity, start_time, end_time):
- precision = frappe.get_precision("Timesheet Detail", "hours")
- activity_duration = time_diff_in_hours(end_time, start_time)
- billing_duration = 0.0
- if activity.is_billable:
- billing_duration = activity.billing_hours
- if activity_duration != activity.billing_hours:
- billing_duration = activity_duration * activity.billing_hours / activity.hours
-
- return flt(activity_duration, precision), flt(billing_duration, precision)
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
deleted file mode 100644
index 2c25465..0000000
--- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-
-frappe.query_reports["Employee Billing Summary"] = {
- "filters": [
- {
- fieldname: "employee",
- label: __("Employee"),
- fieldtype: "Link",
- options: "Employee",
- reqd: 1
- },
- {
- fieldname:"from_date",
- label: __("From Date"),
- fieldtype: "Date",
- default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
- reqd: 1
- },
- {
- fieldname:"to_date",
- label: __("To Date"),
- fieldtype: "Date",
- default: frappe.datetime.add_days(frappe.datetime.month_start(), -1),
- reqd: 1
- },
- {
- fieldname:"include_draft_timesheets",
- label: __("Include Timesheets in Draft Status"),
- fieldtype: "Check",
- },
- ]
-}
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py
deleted file mode 100644
index a2f7378..0000000
--- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-
-from erpnext.projects.report.billing_summary import get_columns, get_data
-
-
-def execute(filters=None):
- filters = frappe._dict(filters or {})
- columns = get_columns()
-
- data = get_data(filters)
- return columns, data
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js
deleted file mode 100644
index fce0c68..0000000
--- a/erpnext/projects/report/project_billing_summary/project_billing_summary.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-
-frappe.query_reports["Project Billing Summary"] = {
- "filters": [
- {
- fieldname: "project",
- label: __("Project"),
- fieldtype: "Link",
- options: "Project",
- reqd: 1
- },
- {
- fieldname:"from_date",
- label: __("From Date"),
- fieldtype: "Date",
- default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
- reqd: 1
- },
- {
- fieldname:"to_date",
- label: __("To Date"),
- fieldtype: "Date",
- default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
- reqd: 1
- },
- {
- fieldname:"include_draft_timesheets",
- label: __("Include Timesheets in Draft Status"),
- fieldtype: "Check",
- },
- ]
-}
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.json b/erpnext/projects/report/project_billing_summary/project_billing_summary.json
deleted file mode 100644
index 817d0cd..0000000
--- a/erpnext/projects/report/project_billing_summary/project_billing_summary.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "add_total_row": 1,
- "creation": "2019-03-11 16:22:39.460524",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2019-06-13 15:54:55.255947",
- "modified_by": "Administrator",
- "module": "Projects",
- "name": "Project Billing Summary",
- "owner": "Administrator",
- "prepared_report": 0,
- "ref_doctype": "Timesheet",
- "report_name": "Project Billing Summary",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Projects User"
- },
- {
- "role": "HR User"
- },
- {
- "role": "Manufacturing User"
- },
- {
- "role": "Employee"
- },
- {
- "role": "Accounts User"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.py b/erpnext/projects/report/project_billing_summary/project_billing_summary.py
deleted file mode 100644
index a2f7378..0000000
--- a/erpnext/projects/report/project_billing_summary/project_billing_summary.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-
-from erpnext.projects.report.billing_summary import get_columns, get_data
-
-
-def execute(filters=None):
- filters = frappe._dict(filters or {})
- columns = get_columns()
-
- data = get_data(filters)
- return columns, data
diff --git a/erpnext/projects/report/project_billing_summary/__init__.py b/erpnext/projects/report/timesheet_billing_summary/__init__.py
similarity index 100%
rename from erpnext/projects/report/project_billing_summary/__init__.py
rename to erpnext/projects/report/timesheet_billing_summary/__init__.py
diff --git a/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.js b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.js
new file mode 100644
index 0000000..1efd0c6
--- /dev/null
+++ b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.js
@@ -0,0 +1,67 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Timesheet Billing Summary"] = {
+ tree: true,
+ initial_depth: 0,
+ filters: [
+ {
+ fieldname: "employee",
+ label: __("Employee"),
+ fieldtype: "Link",
+ options: "Employee",
+ on_change: function (report) {
+ unset_group_by(report, "employee");
+ },
+ },
+ {
+ fieldname: "project",
+ label: __("Project"),
+ fieldtype: "Link",
+ options: "Project",
+ on_change: function (report) {
+ unset_group_by(report, "project");
+ },
+ },
+ {
+ fieldname: "from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(
+ frappe.datetime.month_start(),
+ -1
+ ),
+ },
+ {
+ fieldname: "to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_days(
+ frappe.datetime.month_start(),
+ -1
+ ),
+ },
+ { // NOTE: `update_group_by_options` expects this filter to be the fifth in the list
+ fieldname: "group_by",
+ label: __("Group By"),
+ fieldtype: "Select",
+ options: [
+ "",
+ { value: "employee", label: __("Employee") },
+ { value: "project", label: __("Project") },
+ { value: "date", label: __("Start Date") },
+ ],
+ },
+ {
+ fieldname: "include_draft_timesheets",
+ label: __("Include Timesheets in Draft Status"),
+ fieldtype: "Check",
+ },
+ ],
+};
+
+function unset_group_by(report, fieldname) {
+ if (report.get_filter_value(fieldname) && report.get_filter_value("group_by") == fieldname) {
+ report.set_filter_value("group_by", "");
+ }
+}
diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json
similarity index 61%
rename from erpnext/projects/report/employee_billing_summary/employee_billing_summary.json
rename to erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json
index e5626a0..0f070cb 100644
--- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json
+++ b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json
@@ -1,36 +1,42 @@
{
"add_total_row": 1,
- "creation": "2019-03-08 15:08:19.929728",
- "disable_prepared_report": 0,
+ "columns": [],
+ "creation": "2023-10-10 23:53:43.692067",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
+ "filters": [],
"idx": 0,
"is_standard": "Yes",
- "modified": "2019-06-13 15:54:49.213973",
+ "letter_head": "ALYF GmbH",
+ "letterhead": null,
+ "modified": "2023-10-11 00:58:30.639078",
"modified_by": "Administrator",
"module": "Projects",
- "name": "Employee Billing Summary",
+ "name": "Timesheet Billing Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Timesheet",
- "report_name": "Employee Billing Summary",
+ "report_name": "Timesheet Billing Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Projects User"
},
{
- "role": "HR User"
+ "role": "Employee"
+ },
+ {
+ "role": "Accounts User"
},
{
"role": "Manufacturing User"
},
{
- "role": "Employee"
+ "role": "HR User"
},
{
- "role": "Accounts User"
+ "role": "Employee Self Service"
}
]
}
\ No newline at end of file
diff --git a/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py
new file mode 100644
index 0000000..a6e7150
--- /dev/null
+++ b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py
@@ -0,0 +1,146 @@
+import frappe
+from frappe import _
+from frappe.model.docstatus import DocStatus
+
+
+def execute(filters=None):
+ group_fieldname = filters.pop("group_by", None)
+
+ filters = frappe._dict(filters or {})
+ columns = get_columns(filters, group_fieldname)
+
+ data = get_data(filters, group_fieldname)
+ return columns, data
+
+
+def get_columns(filters, group_fieldname=None):
+ group_columns = {
+ "date": {
+ "label": _("Date"),
+ "fieldtype": "Date",
+ "fieldname": "date",
+ "width": 150,
+ },
+ "project": {
+ "label": _("Project"),
+ "fieldtype": "Link",
+ "fieldname": "project",
+ "options": "Project",
+ "width": 200,
+ "hidden": int(bool(filters.get("project"))),
+ },
+ "employee": {
+ "label": _("Employee ID"),
+ "fieldtype": "Link",
+ "fieldname": "employee",
+ "options": "Employee",
+ "width": 200,
+ "hidden": int(bool(filters.get("employee"))),
+ },
+ }
+ columns = []
+ if group_fieldname:
+ columns.append(group_columns.get(group_fieldname))
+ columns.extend(
+ column for column in group_columns.values() if column.get("fieldname") != group_fieldname
+ )
+ else:
+ columns.extend(group_columns.values())
+
+ columns.extend(
+ [
+ {
+ "label": _("Employee Name"),
+ "fieldtype": "data",
+ "fieldname": "employee_name",
+ "hidden": 1,
+ },
+ {
+ "label": _("Timesheet"),
+ "fieldtype": "Link",
+ "fieldname": "timesheet",
+ "options": "Timesheet",
+ "width": 150,
+ },
+ {"label": _("Working Hours"), "fieldtype": "Float", "fieldname": "hours", "width": 150},
+ {
+ "label": _("Billing Hours"),
+ "fieldtype": "Float",
+ "fieldname": "billing_hours",
+ "width": 150,
+ },
+ {
+ "label": _("Billing Amount"),
+ "fieldtype": "Currency",
+ "fieldname": "billing_amount",
+ "width": 150,
+ },
+ ]
+ )
+
+ return columns
+
+
+def get_data(filters, group_fieldname=None):
+ _filters = []
+ if filters.get("employee"):
+ _filters.append(("employee", "=", filters.get("employee")))
+ if filters.get("project"):
+ _filters.append(("Timesheet Detail", "project", "=", filters.get("project")))
+ if filters.get("from_date"):
+ _filters.append(("Timesheet Detail", "from_time", ">=", filters.get("from_date")))
+ if filters.get("to_date"):
+ _filters.append(("Timesheet Detail", "to_time", "<=", filters.get("to_date")))
+ if not filters.get("include_draft_timesheets"):
+ _filters.append(("docstatus", "=", DocStatus.submitted()))
+ else:
+ _filters.append(("docstatus", "in", (DocStatus.submitted(), DocStatus.draft())))
+
+ data = frappe.get_list(
+ "Timesheet",
+ fields=[
+ "name as timesheet",
+ "`tabTimesheet`.employee",
+ "`tabTimesheet`.employee_name",
+ "`tabTimesheet Detail`.from_time as date",
+ "`tabTimesheet Detail`.project",
+ "`tabTimesheet Detail`.hours",
+ "`tabTimesheet Detail`.billing_hours",
+ "`tabTimesheet Detail`.billing_amount",
+ ],
+ filters=_filters,
+ order_by="`tabTimesheet Detail`.from_time",
+ )
+
+ return group_by(data, group_fieldname) if group_fieldname else data
+
+
+def group_by(data, fieldname):
+ groups = {row.get(fieldname) for row in data}
+ grouped_data = []
+ for group in sorted(groups):
+ group_row = {
+ fieldname: group,
+ "hours": sum(row.get("hours") for row in data if row.get(fieldname) == group),
+ "billing_hours": sum(row.get("billing_hours") for row in data if row.get(fieldname) == group),
+ "billing_amount": sum(row.get("billing_amount") for row in data if row.get(fieldname) == group),
+ "indent": 0,
+ "is_group": 1,
+ }
+ if fieldname == "employee":
+ group_row["employee_name"] = next(
+ row.get("employee_name") for row in data if row.get(fieldname) == group
+ )
+
+ grouped_data.append(group_row)
+ for row in data:
+ if row.get(fieldname) != group:
+ continue
+
+ _row = row.copy()
+ _row[fieldname] = None
+ _row["indent"] = 1
+ _row["is_group"] = 0
+ grouped_data.append(_row)
+
+ return grouped_data
diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json
index 94ae9c0..e6bead9 100644
--- a/erpnext/projects/workspace/projects/projects.json
+++ b/erpnext/projects/workspace/projects/projects.json
@@ -155,9 +155,9 @@
"dependencies": "Project",
"hidden": 0,
"is_query_report": 1,
- "label": "Project Billing Summary",
+ "label": "Timesheet Billing Summary",
"link_count": 0,
- "link_to": "Project Billing Summary",
+ "link_to": "Timesheet Billing Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
@@ -192,7 +192,7 @@
"type": "Link"
}
],
- "modified": "2023-07-04 14:39:08.935853",
+ "modified": "2023-10-10 23:54:33.082108",
"modified_by": "Administrator",
"module": "Projects",
"name": "Projects",
@@ -234,8 +234,8 @@
"type": "DocType"
},
{
- "label": "Project Billing Summary",
- "link_to": "Project Billing Summary",
+ "label": "Timesheet Billing Summary",
+ "link_to": "Timesheet Billing Summary",
"type": "Report"
},
{
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index 3545521..7879173 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -30,7 +30,6 @@
filters: {
"account_type": account_type,
"company": doc.company,
- "disabled": 0
}
}
});
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
index fd2b6a4..79fd2eb 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
@@ -3,10 +3,10 @@
frappe.ui.form.on('Quality Procedure', {
refresh: function(frm) {
- frm.set_query("procedure","processes", (frm) =>{
+ frm.set_query('procedure', 'processes', (frm) =>{
return {
filters: {
- name: ["not in", [frm.parent_quality_procedure, frm.name]]
+ name: ['not in', [frm.parent_quality_procedure, frm.name]]
}
};
});
@@ -14,7 +14,8 @@
frm.set_query('parent_quality_procedure', function(){
return {
filters: {
- is_group: 1
+ is_group: 1,
+ name: ['!=', frm.doc.name]
}
};
});
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
index e860408..6834abc 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
@@ -16,16 +16,13 @@
def on_update(self):
NestedSet.on_update(self)
self.set_parent()
+ self.remove_parent_from_old_child()
+ self.add_child_to_parent()
+ self.remove_child_from_old_parent()
def after_insert(self):
self.set_parent()
-
- # add child to parent if missing
- if self.parent_quality_procedure:
- parent = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
- if not [d for d in parent.processes if d.procedure == self.name]:
- parent.append("processes", {"procedure": self.name, "process_description": self.name})
- parent.save()
+ self.add_child_to_parent()
def on_trash(self):
# clear from child table (sub procedures)
@@ -36,15 +33,6 @@
)
NestedSet.on_trash(self, allow_root_deletion=True)
- def set_parent(self):
- for process in self.processes:
- # Set parent for only those children who don't have a parent
- has_parent = frappe.db.get_value(
- "Quality Procedure", process.procedure, "parent_quality_procedure"
- )
- if not has_parent and process.procedure:
- frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name)
-
def check_for_incorrect_child(self):
for process in self.processes:
if process.procedure:
@@ -61,6 +49,48 @@
title=_("Invalid Child Procedure"),
)
+ def set_parent(self):
+ """Set `Parent Procedure` in `Child Procedures`"""
+
+ for process in self.processes:
+ if process.procedure:
+ if not frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure"):
+ frappe.db.set_value(
+ "Quality Procedure", process.procedure, "parent_quality_procedure", self.name
+ )
+
+ def remove_parent_from_old_child(self):
+ """Remove `Parent Procedure` from `Old Child Procedures`"""
+
+ if old_doc := self.get_doc_before_save():
+ if old_child_procedures := set([d.procedure for d in old_doc.processes if d.procedure]):
+ current_child_procedures = set([d.procedure for d in self.processes if d.procedure])
+
+ if removed_child_procedures := list(old_child_procedures.difference(current_child_procedures)):
+ for child_procedure in removed_child_procedures:
+ frappe.db.set_value("Quality Procedure", child_procedure, "parent_quality_procedure", None)
+
+ def add_child_to_parent(self):
+ """Add `Child Procedure` to `Parent Procedure`"""
+
+ if self.parent_quality_procedure:
+ parent = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
+ if not [d for d in parent.processes if d.procedure == self.name]:
+ parent.append("processes", {"procedure": self.name, "process_description": self.name})
+ parent.save()
+
+ def remove_child_from_old_parent(self):
+ """Remove `Child Procedure` from `Old Parent Procedure`"""
+
+ if old_doc := self.get_doc_before_save():
+ if old_parent := old_doc.parent_quality_procedure:
+ if self.parent_quality_procedure != old_parent:
+ parent = frappe.get_doc("Quality Procedure", old_parent)
+ for process in parent.processes:
+ if process.procedure == self.name:
+ parent.remove(process)
+ parent.save()
+
@frappe.whitelist()
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
diff --git a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py
index 04e8211..467186d 100644
--- a/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py
+++ b/erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py
@@ -1,56 +1,107 @@
# Copyright (c) 2018, Frappe and Contributors
# See license.txt
-import unittest
-
import frappe
+from frappe.tests.utils import FrappeTestCase
from .quality_procedure import add_node
-class TestQualityProcedure(unittest.TestCase):
+class TestQualityProcedure(FrappeTestCase):
def test_add_node(self):
- try:
- procedure = frappe.get_doc(
- dict(
- doctype="Quality Procedure",
- quality_procedure_name="Test Procedure 1",
- processes=[dict(process_description="Test Step 1")],
- )
- ).insert()
-
- frappe.local.form_dict = frappe._dict(
- doctype="Quality Procedure",
- quality_procedure_name="Test Child 1",
- parent_quality_procedure=procedure.name,
- cmd="test",
- is_root="false",
- )
- node = add_node()
-
- procedure.reload()
-
- self.assertEqual(procedure.is_group, 1)
-
- # child row created
- self.assertTrue([d for d in procedure.processes if d.procedure == node.name])
-
- node.delete()
- procedure.reload()
-
- # child unset
- self.assertFalse([d for d in procedure.processes if d.name == node.name])
-
- finally:
- procedure.delete()
-
-
-def create_procedure():
- return frappe.get_doc(
- dict(
- doctype="Quality Procedure",
- quality_procedure_name="Test Procedure 1",
- is_group=1,
- processes=[dict(process_description="Test Step 1")],
+ procedure = create_procedure(
+ {
+ "quality_procedure_name": "Test Procedure 1",
+ "is_group": 1,
+ "processes": [dict(process_description="Test Step 1")],
+ }
)
- ).insert()
+
+ frappe.local.form_dict = frappe._dict(
+ doctype="Quality Procedure",
+ quality_procedure_name="Test Child 1",
+ parent_quality_procedure=procedure.name,
+ cmd="test",
+ is_root="false",
+ )
+ node = add_node()
+
+ procedure.reload()
+
+ self.assertEqual(procedure.is_group, 1)
+
+ # child row created
+ self.assertTrue([d for d in procedure.processes if d.procedure == node.name])
+
+ node.delete()
+ procedure.reload()
+
+ # child unset
+ self.assertFalse([d for d in procedure.processes if d.name == node.name])
+
+ def test_remove_parent_from_old_child(self):
+ child_qp = create_procedure(
+ {
+ "quality_procedure_name": "Test Child 1",
+ "is_group": 0,
+ }
+ )
+ group_qp = create_procedure(
+ {
+ "quality_procedure_name": "Test Group",
+ "is_group": 1,
+ "processes": [dict(procedure=child_qp.name)],
+ }
+ )
+
+ child_qp.reload()
+ self.assertEqual(child_qp.parent_quality_procedure, group_qp.name)
+
+ group_qp.reload()
+ del group_qp.processes[0]
+ group_qp.save()
+
+ child_qp.reload()
+ self.assertEqual(child_qp.parent_quality_procedure, None)
+
+ def remove_child_from_old_parent(self):
+ child_qp = create_procedure(
+ {
+ "quality_procedure_name": "Test Child 1",
+ "is_group": 0,
+ }
+ )
+ group_qp = create_procedure(
+ {
+ "quality_procedure_name": "Test Group",
+ "is_group": 1,
+ "processes": [dict(procedure=child_qp.name)],
+ }
+ )
+
+ group_qp.reload()
+ self.assertTrue([d for d in group_qp.processes if d.procedure == child_qp.name])
+
+ child_qp.reload()
+ self.assertEqual(child_qp.parent_quality_procedure, group_qp.name)
+
+ child_qp.parent_quality_procedure = None
+ child_qp.save()
+
+ group_qp.reload()
+ self.assertFalse([d for d in group_qp.processes if d.procedure == child_qp.name])
+
+
+def create_procedure(kwargs=None):
+ kwargs = frappe._dict(kwargs or {})
+
+ doc = frappe.new_doc("Quality Procedure")
+ doc.quality_procedure_name = kwargs.quality_procedure_name or "_Test Procedure"
+ doc.is_group = kwargs.is_group or 0
+
+ for process in kwargs.processes or []:
+ doc.append("processes", process)
+
+ doc.insert()
+
+ return doc
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 40cab9f..3b97123 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -134,7 +134,7 @@
"label": "Customer Type",
"oldfieldname": "customer_type",
"oldfieldtype": "Select",
- "options": "Company\nIndividual",
+ "options": "Company\nIndividual\nProprietorship\nPartnership",
"reqd": 1
},
{
@@ -584,7 +584,7 @@
"link_fieldname": "party"
}
],
- "modified": "2023-09-21 12:23:20.706020",
+ "modified": "2023-10-19 16:56:27.327035",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 94f9d6e..e4f1a28 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -217,7 +217,15 @@
def validate_with_previous_doc(self):
super(SalesOrder, self).validate_with_previous_doc(
- {"Quotation": {"ref_dn_field": "prevdoc_docname", "compare_fields": [["company", "="]]}}
+ {
+ "Quotation": {"ref_dn_field": "prevdoc_docname", "compare_fields": [["company", "="]]},
+ "Quotation Item": {
+ "ref_dn_field": "quotation_item",
+ "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
+ "is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
+ },
+ }
)
if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")):
@@ -759,6 +767,8 @@
if target.company_address:
target.update(get_fetch_values("Delivery Note", "company_address", target.company_address))
+ # set target items names to ensure proper linking with packed_items
+ target.set_new_name()
make_packing_list(target)
def condition(doc):
@@ -831,6 +841,7 @@
"postprocess": update_dn_item,
}
},
+ ignore_permissions=True,
)
dn_item.qty = flt(sre.reserved_qty) * flt(dn_item.get("conversion_factor", 1))
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 23b93dc..1bd469b 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -40,7 +40,7 @@
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
- 'company': frm.doc.company
+ 'company': frm.doc.company_name
}
};
});
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index a115727..312470d 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -5,20 +5,24 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
- "warehouse",
"item_code",
- "reserved_qty",
+ "column_break_yreo",
+ "warehouse",
+ "section_break_stag",
"actual_qty",
- "ordered_qty",
- "indented_qty",
"planned_qty",
+ "indented_qty",
+ "ordered_qty",
"projected_qty",
+ "column_break_xn5j",
+ "reserved_qty",
"reserved_qty_for_production",
"reserved_qty_for_sub_contract",
"reserved_qty_for_production_plan",
- "ma_rate",
+ "reserved_stock",
+ "section_break_pmrs",
"stock_uom",
- "fcfs_rate",
+ "column_break_0slj",
"valuation_rate",
"stock_value"
],
@@ -56,7 +60,7 @@
"fieldname": "reserved_qty",
"fieldtype": "Float",
"in_list_view": 1,
- "label": "Reserved Quantity",
+ "label": "Reserved Qty",
"oldfieldname": "reserved_qty",
"oldfieldtype": "Currency",
"read_only": 1
@@ -67,7 +71,7 @@
"fieldtype": "Float",
"in_filter": 1,
"in_list_view": 1,
- "label": "Actual Quantity",
+ "label": "Actual Qty",
"oldfieldname": "actual_qty",
"oldfieldtype": "Currency",
"read_only": 1
@@ -77,7 +81,7 @@
"fieldname": "ordered_qty",
"fieldtype": "Float",
"in_list_view": 1,
- "label": "Ordered Quantity",
+ "label": "Ordered Qty",
"oldfieldname": "ordered_qty",
"oldfieldtype": "Currency",
"read_only": 1
@@ -86,7 +90,7 @@
"default": "0.00",
"fieldname": "indented_qty",
"fieldtype": "Float",
- "label": "Requested Quantity",
+ "label": "Requested Qty",
"oldfieldname": "indented_qty",
"oldfieldtype": "Currency",
"read_only": 1
@@ -116,21 +120,10 @@
{
"fieldname": "reserved_qty_for_sub_contract",
"fieldtype": "Float",
- "label": "Reserved Qty for sub contract",
+ "label": "Reserved Qty for Subcontract",
"read_only": 1
},
{
- "fieldname": "ma_rate",
- "fieldtype": "Float",
- "hidden": 1,
- "label": "Moving Average Rate",
- "oldfieldname": "ma_rate",
- "oldfieldtype": "Currency",
- "print_hide": 1,
- "read_only": 1,
- "report_hide": 1
- },
- {
"fieldname": "stock_uom",
"fieldtype": "Link",
"in_filter": 1,
@@ -141,17 +134,6 @@
"read_only": 1
},
{
- "fieldname": "fcfs_rate",
- "fieldtype": "Float",
- "hidden": 1,
- "label": "FCFS Rate",
- "oldfieldname": "fcfs_rate",
- "oldfieldtype": "Currency",
- "print_hide": 1,
- "read_only": 1,
- "report_hide": 1
- },
- {
"fieldname": "valuation_rate",
"fieldtype": "Float",
"label": "Valuation Rate",
@@ -172,13 +154,40 @@
"fieldtype": "Float",
"label": "Reserved Qty for Production Plan",
"read_only": 1
+ },
+ {
+ "fieldname": "section_break_stag",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_yreo",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_xn5j",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_pmrs",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_0slj",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "reserved_stock",
+ "fieldtype": "Float",
+ "label": "Reserved Stock",
+ "read_only": 1
}
],
"hide_toolbar": 1,
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2023-05-02 23:26:21.806965",
+ "modified": "2023-11-01 16:51:17.079107",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 5abea9e..8b2e5cf 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -34,10 +34,15 @@
get_reserved_qty_for_production_plan,
)
- self.reserved_qty_for_production_plan = get_reserved_qty_for_production_plan(
+ reserved_qty_for_production_plan = get_reserved_qty_for_production_plan(
self.item_code, self.warehouse
)
+ if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan:
+ return
+
+ self.reserved_qty_for_production_plan = flt(reserved_qty_for_production_plan)
+
self.db_set(
"reserved_qty_for_production_plan",
flt(self.reserved_qty_for_production_plan),
@@ -48,6 +53,29 @@
self.set_projected_qty()
self.db_set("projected_qty", self.projected_qty, update_modified=True)
+ def update_reserved_qty_for_for_sub_assembly(self):
+ from erpnext.manufacturing.doctype.production_plan.production_plan import (
+ get_reserved_qty_for_sub_assembly,
+ )
+
+ reserved_qty_for_production_plan = get_reserved_qty_for_sub_assembly(
+ self.item_code, self.warehouse
+ )
+
+ if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan:
+ return
+
+ self.reserved_qty_for_production_plan = flt(reserved_qty_for_production_plan)
+ self.set_projected_qty()
+
+ self.db_set(
+ {
+ "projected_qty": self.projected_qty,
+ "reserved_qty_for_production_plan": flt(self.reserved_qty_for_production_plan),
+ },
+ update_modified=True,
+ )
+
def update_reserved_qty_for_production(self):
"""Update qty reserved for production from Production Item tables
in open work orders"""
@@ -148,6 +176,17 @@
self.set_projected_qty()
self.db_set("projected_qty", self.projected_qty, update_modified=True)
+ def update_reserved_stock(self):
+ """Update `Reserved Stock` on change in Reserved Qty of Stock Reservation Entry"""
+
+ from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+ get_sre_reserved_qty_for_item_and_warehouse,
+ )
+
+ reserved_stock = get_sre_reserved_qty_for_item_and_warehouse(self.item_code, self.warehouse)
+
+ self.db_set("reserved_stock", flt(reserved_stock), update_modified=True)
+
def on_doctype_update():
frappe.db.add_unique("Bin", ["item_code", "warehouse"], constraint_name="unique_item_warehouse")
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 190575e..66dd33a 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -365,6 +365,9 @@
# Update Stock Reservation Entry `Status` based on `Delivered Qty`.
sre_doc.update_status()
+ # Update Reserved Stock in Bin.
+ sre_doc.update_reserved_stock_in_bin()
+
qty_to_deliver -= qty_can_be_deliver
if self._action == "cancel":
@@ -427,6 +430,9 @@
# Update Stock Reservation Entry `Status` based on `Delivered Qty`.
sre_doc.update_status()
+ # Update Reserved Stock in Bin.
+ sre_doc.update_reserved_stock_in_bin()
+
qty_to_undelivered -= qty_can_be_undelivered
def validate_against_stock_reservation_entries(self):
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 1eecf6d..137c352 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1029,6 +1029,7 @@
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-3)
si1 = make_sales_invoice(dn1.name)
+ si1.update_billed_amount_in_delivery_note = True
si1.insert()
si1.submit()
dn1.reload()
@@ -1037,6 +1038,7 @@
dn2 = create_delivery_note(is_return=1, return_against=dn.name, qty=-4)
si2 = make_sales_invoice(dn2.name)
+ si2.update_billed_amount_in_delivery_note = True
si2.insert()
si2.submit()
dn2.reload()
diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js
index ce489ff..8a4b4ee 100644
--- a/erpnext/stock/doctype/item_price/item_price.js
+++ b/erpnext/stock/doctype/item_price/item_price.js
@@ -6,7 +6,6 @@
frm.set_query("item_code", function() {
return {
filters: {
- "disabled": 0,
"has_variants": 0
}
};
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 7f0dc2d..8bbc660 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -205,7 +205,11 @@
)
docs = frappe.db.get_all(
"Asset",
- filters={receipt_document_type: item.receipt_document, "item_code": item.item_code},
+ filters={
+ receipt_document_type: item.receipt_document,
+ "item_code": item.item_code,
+ "docstatus": ["!=", 2],
+ },
fields=["name", "docstatus"],
)
if not docs or len(docs) != item.qty:
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 2a4b6f3..cbc1693 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -600,11 +600,10 @@
make_rate_difference_entry(d)
make_sub_contracting_gl_entries(d)
make_divisional_loss_gl_entry(d, outgoing_amount)
- elif (
- d.warehouse not in warehouse_with_no_account
- or d.rejected_warehouse not in warehouse_with_no_account
+ elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or (
+ d.rejected_warehouse and d.rejected_warehouse not in warehouse_with_no_account
):
- warehouse_with_no_account.append(d.warehouse)
+ warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse)
if d.is_fixed_asset:
self.update_assets(d, d.valuation_rate)
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 f5240a6..718f007 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -900,7 +900,8 @@
"label": "Delivery Note Item",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"collapsible": 1,
@@ -1089,7 +1090,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-10-19 10:50:58.071735",
+ "modified": "2023-10-30 17:32:24.560337",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection_template/test_records.json b/erpnext/stock/doctype/quality_inspection_template/test_records.json
index 980f49a..2da99f6 100644
--- a/erpnext/stock/doctype/quality_inspection_template/test_records.json
+++ b/erpnext/stock/doctype/quality_inspection_template/test_records.json
@@ -1,5 +1,9 @@
[
{
+ "doctype": "Quality Inspection Parameter",
+ "parameter" : "_Test Param"
+ },
+ {
"quality_inspection_template_name" : "_Test Quality Inspection Template",
"doctype": "Quality Inspection Template",
"item_quality_inspection_parameter" : [
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
index 1853f45..5b76e44 100644
--- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -5,7 +5,7 @@
from unittest.mock import MagicMock, call
import frappe
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, add_to_date, now, nowdate, today
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -137,8 +137,6 @@
item_code="_Test Item",
warehouse="_Test Warehouse - _TC",
based_on="Item and Warehouse",
- voucher_type="Sales Invoice",
- voucher_no="SI-1",
posting_date="2021-01-02",
posting_time="00:01:00",
)
@@ -148,8 +146,6 @@
riv1.flags.dont_run_in_test = True
riv1.submit()
_assert_status(riv1, "Queued")
- self.assertEqual(riv1.voucher_type, "Sales Invoice") # traceability
- self.assertEqual(riv1.voucher_no, "SI-1")
# newer than existing duplicate - riv1
riv2 = frappe.get_doc(riv_args.update({"posting_date": "2021-01-03"}))
@@ -200,6 +196,7 @@
riv.set_status("Skipped")
+ @change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_prevention_of_cancelled_transaction_riv(self):
frappe.flags.dont_execute_stock_reposts = True
@@ -377,6 +374,7 @@
accounts_settings.acc_frozen_upto = ""
accounts_settings.save()
+ @change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_create_repost_entry_for_cancelled_document(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
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 f96c184..f2bbf2b 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
@@ -121,7 +121,7 @@
def throw_error_message(self, message, exception=frappe.ValidationError):
frappe.throw(_(message), exception, title=_("Error"))
- def set_incoming_rate(self, row=None, save=False):
+ def set_incoming_rate(self, row=None, save=False, allow_negative_stock=False):
if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [
"Installation Note",
"Job Card",
@@ -131,7 +131,9 @@
return
if self.type_of_transaction == "Outward":
- self.set_incoming_rate_for_outward_transaction(row, save)
+ self.set_incoming_rate_for_outward_transaction(
+ row, save, allow_negative_stock=allow_negative_stock
+ )
else:
self.set_incoming_rate_for_inward_transaction(row, save)
@@ -152,7 +154,9 @@
def get_serial_nos(self):
return [d.serial_no for d in self.entries if d.serial_no]
- def set_incoming_rate_for_outward_transaction(self, row=None, save=False):
+ def set_incoming_rate_for_outward_transaction(
+ self, row=None, save=False, allow_negative_stock=False
+ ):
sle = self.get_sle_for_outward_transaction()
if self.has_serial_no:
@@ -181,7 +185,8 @@
if self.docstatus == 1:
available_qty += flt(d.qty)
- self.validate_negative_batch(d.batch_no, available_qty)
+ if not allow_negative_stock:
+ self.validate_negative_batch(d.batch_no, available_qty)
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index c41349f..b06de2e 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -161,7 +161,7 @@
if self.is_enqueue_action():
frappe.msgprint(
_(
- "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"
+ "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Draft stage"
)
)
self.queue_action("submit", timeout=2000)
@@ -172,7 +172,7 @@
if self.is_enqueue_action():
frappe.msgprint(
_(
- "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"
+ "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Submitted stage"
)
)
self.queue_action("cancel", timeout=2000)
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 3e0610e..b640983 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1467,6 +1467,7 @@
self.assertEqual(se.items[0].item_name, item.item_name)
self.assertEqual(se.items[0].stock_uom, item.stock_uom)
+ @change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_reposting_for_depedent_warehouse(self):
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 5452692..b3998b7 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -123,13 +123,6 @@
fieldname: "item_code",
fieldtype: "Link",
options: "Item",
- "get_query": function() {
- return {
- "filters": {
- "disabled": 0,
- }
- };
- }
},
{
label: __("Ignore Empty Stock"),
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 98b4ffd..323ad4f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _, bold, msgprint
from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, cstr, flt
+from frappe.utils import add_to_date, cint, cstr, flt
import erpnext
from erpnext.accounts.utils import get_company_default
@@ -88,9 +88,12 @@
self.repost_future_sle_and_gle()
self.delete_auto_created_batches()
- def set_current_serial_and_batch_bundle(self):
+ def set_current_serial_and_batch_bundle(self, voucher_detail_no=None, save=False) -> None:
"""Set Serial and Batch Bundle for each item"""
for item in self.items:
+ if voucher_detail_no and voucher_detail_no != item.name:
+ continue
+
item_details = frappe.get_cached_value(
"Item", item.item_code, ["has_serial_no", "has_batch_no"], as_dict=1
)
@@ -148,6 +151,7 @@
"warehouse": item.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
+ "ignore_voucher_nos": [self.name],
}
)
)
@@ -163,11 +167,36 @@
)
if not serial_and_batch_bundle.entries:
+ if voucher_detail_no:
+ return
+
continue
- item.current_serial_and_batch_bundle = serial_and_batch_bundle.save().name
+ serial_and_batch_bundle.save()
+ item.current_serial_and_batch_bundle = serial_and_batch_bundle.name
item.current_qty = abs(serial_and_batch_bundle.total_qty)
item.current_valuation_rate = abs(serial_and_batch_bundle.avg_rate)
+ if save:
+ sle_creation = frappe.db.get_value(
+ "Serial and Batch Bundle", item.serial_and_batch_bundle, "creation"
+ )
+ creation = add_to_date(sle_creation, seconds=-1)
+ item.db_set(
+ {
+ "current_serial_and_batch_bundle": item.current_serial_and_batch_bundle,
+ "current_qty": item.current_qty,
+ "current_valuation_rate": item.current_valuation_rate,
+ "creation": creation,
+ }
+ )
+
+ serial_and_batch_bundle.db_set(
+ {
+ "creation": creation,
+ "voucher_no": self.name,
+ "voucher_detail_no": voucher_detail_no,
+ }
+ )
def set_new_serial_and_batch_bundle(self):
for item in self.items:
@@ -689,56 +718,84 @@
else:
self._cancel()
- def recalculate_current_qty(self, item_code, batch_no):
+ def recalculate_current_qty(self, voucher_detail_no, sle_creation, add_new_sle=False):
from erpnext.stock.stock_ledger import get_valuation_rate
sl_entries = []
+
for row in self.items:
- if (
- not (row.item_code == item_code and row.batch_no == batch_no)
- and not row.serial_and_batch_bundle
- ):
+ if voucher_detail_no != row.name:
continue
+ current_qty = 0.0
if row.current_serial_and_batch_bundle:
- self.recalculate_qty_for_serial_and_batch_bundle(row)
- continue
-
- current_qty = get_batch_qty_for_stock_reco(
- item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name
- )
+ current_qty = self.get_qty_for_serial_and_batch_bundle(row)
+ elif row.batch_no:
+ current_qty = get_batch_qty_for_stock_reco(
+ row.item_code, row.warehouse, row.batch_no, self.posting_date, self.posting_time, self.name
+ )
precesion = row.precision("current_qty")
- if flt(current_qty, precesion) == flt(row.current_qty, precesion):
- continue
+ if flt(current_qty, precesion) != flt(row.current_qty, precesion):
+ val_rate = get_valuation_rate(
+ row.item_code,
+ row.warehouse,
+ self.doctype,
+ self.name,
+ company=self.company,
+ batch_no=row.batch_no,
+ serial_and_batch_bundle=row.current_serial_and_batch_bundle,
+ )
- val_rate = get_valuation_rate(
- item_code, row.warehouse, self.doctype, self.name, company=self.company, batch_no=batch_no
- )
+ row.current_valuation_rate = val_rate
+ row.current_qty = current_qty
+ row.db_set(
+ {
+ "current_qty": row.current_qty,
+ "current_valuation_rate": row.current_valuation_rate,
+ "current_amount": flt(row.current_qty * row.current_valuation_rate),
+ }
+ )
- row.current_valuation_rate = val_rate
- if not row.current_qty and current_qty:
- sle = self.get_sle_for_items(row)
- sle.actual_qty = current_qty * -1
- sle.valuation_rate = val_rate
- sl_entries.append(sle)
+ if (
+ add_new_sle
+ and not frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
+ "name",
+ )
+ and (not row.current_serial_and_batch_bundle and not row.batch_no)
+ ):
+ self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True)
+ row.reload()
- row.current_qty = current_qty
- row.db_set(
- {
- "current_qty": row.current_qty,
- "current_valuation_rate": row.current_valuation_rate,
- "current_amount": flt(row.current_qty * row.current_valuation_rate),
- }
- )
+ if row.current_qty > 0 and row.current_serial_and_batch_bundle:
+ new_sle = self.get_sle_for_items(row)
+ new_sle.actual_qty = row.current_qty * -1
+ new_sle.valuation_rate = row.current_valuation_rate
+ new_sle.creation_time = add_to_date(sle_creation, seconds=-1)
+ new_sle.serial_and_batch_bundle = row.current_serial_and_batch_bundle
+ new_sle.qty_after_transaction = 0.0
+ sl_entries.append(new_sle)
if sl_entries:
- self.make_sl_entries(sl_entries, allow_negative_stock=True)
+ self.make_sl_entries(sl_entries, allow_negative_stock=self.has_negative_stock_allowed())
+ if not frappe.db.exists("Repost Item Valuation", {"voucher_no": self.name, "status": "Queued"}):
+ self.repost_future_sle_and_gle(force=True)
- def recalculate_qty_for_serial_and_batch_bundle(self, row):
+ def has_negative_stock_allowed(self):
+ allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
+
+ if all(d.serial_and_batch_bundle and flt(d.qty) == flt(d.current_qty) for d in self.items):
+ allow_negative_stock = True
+
+ return allow_negative_stock
+
+ def get_qty_for_serial_and_batch_bundle(self, row):
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
precision = doc.entries[0].precision("qty")
+ current_qty = 0
for d in doc.entries:
qty = (
get_batch_qty(
@@ -751,10 +808,12 @@
or 0
) * -1
- if flt(d.qty, precision) == flt(qty, precision):
- continue
+ if flt(d.qty, precision) != flt(qty, precision):
+ d.db_set("qty", qty)
- d.db_set("qty", qty)
+ current_qty += qty
+
+ return abs(current_qty)
def get_batch_qty_for_stock_reco(
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 4817c8d..1ec99bf 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -674,6 +674,7 @@
self.assertEqual(flt(sl_entry.actual_qty), 1.0)
self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0)
+ @change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_backdated_stock_reco_entry(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -741,13 +742,6 @@
se2.cancel()
- self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name}))
-
- self.assertEqual(
- frappe.db.get_value("Repost Item Valuation", {"voucher_no": stock_reco.name}, "status"),
- "Completed",
- )
-
sle = frappe.get_all(
"Stock Ledger Entry",
filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0},
@@ -765,6 +759,68 @@
self.assertEqual(flt(sle[0].actual_qty), flt(-100.0))
+ def test_backdated_stock_reco_entry_with_batch(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+ item_code = self.make_item(
+ "Test New Batch Item ABCVSD",
+ {
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "batch_number_series": "BNS9.####",
+ "create_new_batch": 1,
+ },
+ ).name
+
+ warehouse = "_Test Warehouse - _TC"
+
+ # Stock Reco for 100, Balace Qty 100
+ stock_reco = create_stock_reconciliation(
+ item_code=item_code,
+ posting_date=nowdate(),
+ posting_time="11:00:00",
+ warehouse=warehouse,
+ qty=100,
+ rate=100,
+ )
+
+ sles = frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["actual_qty"],
+ filters={"voucher_no": stock_reco.name, "is_cancelled": 0},
+ )
+
+ self.assertEqual(len(sles), 1)
+
+ stock_reco.reload()
+ batch_no = get_batch_from_bundle(stock_reco.items[0].serial_and_batch_bundle)
+
+ # Stock Reco for 100, Balace Qty 100
+ stock_reco1 = create_stock_reconciliation(
+ item_code=item_code,
+ posting_date=add_days(nowdate(), -1),
+ posting_time="11:00:00",
+ batch_no=batch_no,
+ warehouse=warehouse,
+ qty=60,
+ rate=100,
+ )
+
+ sles = frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["actual_qty"],
+ filters={"voucher_no": stock_reco.name, "is_cancelled": 0},
+ )
+
+ stock_reco1.reload()
+ new_batch_no = get_batch_from_bundle(stock_reco1.items[0].serial_and_batch_bundle)
+
+ self.assertEqual(len(sles), 2)
+
+ for row in sles:
+ if row.actual_qty < 0:
+ self.assertEqual(row.actual_qty, -60)
+
def test_update_stock_reconciliation_while_reposting(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index ca19bbb..d9cbf95 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -205,6 +205,7 @@
"fieldname": "current_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Current Serial / Batch Bundle",
+ "no_copy": 1,
"options": "Serial and Batch Bundle",
"read_only": 1
},
@@ -216,7 +217,7 @@
],
"istable": 1,
"links": [],
- "modified": "2023-07-26 12:54:34.011915",
+ "modified": "2023-11-02 15:47:07.929550",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json
index 7c712ce..68afd99 100644
--- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json
+++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json
@@ -50,7 +50,7 @@
"label": "Limit timeslot for Stock Reposting"
},
{
- "default": "0",
+ "default": "1",
"fieldname": "item_based_reposting",
"fieldtype": "Check",
"label": "Use Item based reposting"
@@ -70,7 +70,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2023-05-04 16:14:29.080697",
+ "modified": "2023-11-01 16:14:29.080697",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reposting Settings",
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
index 6b39965..0954282 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -9,6 +9,8 @@
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt
+from erpnext.stock.utils import get_or_make_bin
+
class StockReservationEntry(Document):
def validate(self) -> None:
@@ -31,6 +33,7 @@
self.update_reserved_qty_in_voucher()
self.update_reserved_qty_in_pick_list()
self.update_status()
+ self.update_reserved_stock_in_bin()
def on_update_after_submit(self) -> None:
self.can_be_updated()
@@ -40,12 +43,14 @@
self.validate_reservation_based_on_serial_and_batch()
self.update_reserved_qty_in_voucher()
self.update_status()
+ self.update_reserved_stock_in_bin()
self.reload()
def on_cancel(self) -> None:
self.update_reserved_qty_in_voucher()
self.update_reserved_qty_in_pick_list()
self.update_status()
+ self.update_reserved_stock_in_bin()
def validate_amended_doc(self) -> None:
"""Raises an exception if document is amended."""
@@ -341,6 +346,13 @@
update_modified=update_modified,
)
+ def update_reserved_stock_in_bin(self) -> None:
+ """Updates `Reserved Stock` in Bin."""
+
+ bin_name = get_or_make_bin(self.item_code, self.warehouse)
+ bin_doc = frappe.get_cached_doc("Bin", bin_name)
+ bin_doc.update_reserved_stock()
+
def update_status(self, status: str = None, update_modified: bool = True) -> None:
"""Updates status based on Voucher Qty, Reserved Qty and Delivered Qty."""
@@ -681,6 +693,68 @@
return flt(reserved_qty[0][0])
+def get_sre_reserved_serial_nos_details(
+ item_code: str, warehouse: str, serial_nos: list = None
+) -> dict:
+ """Returns a dict of `Serial No` reserved in Stock Reservation Entry. The dict is like {serial_no: sre_name, ...}"""
+
+ sre = frappe.qb.DocType("Stock Reservation Entry")
+ sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+ query = (
+ frappe.qb.from_(sre)
+ .inner_join(sb_entry)
+ .on(sre.name == sb_entry.parent)
+ .select(sb_entry.serial_no, sre.name)
+ .where(
+ (sre.docstatus == 1)
+ & (sre.item_code == item_code)
+ & (sre.warehouse == warehouse)
+ & (sre.reserved_qty > sre.delivered_qty)
+ & (sre.status.notin(["Delivered", "Cancelled"]))
+ & (sre.reservation_based_on == "Serial and Batch")
+ )
+ .orderby(sb_entry.creation)
+ )
+
+ if serial_nos:
+ query = query.where(sb_entry.serial_no.isin(serial_nos))
+
+ return frappe._dict(query.run())
+
+
+def get_sre_reserved_batch_nos_details(
+ item_code: str, warehouse: str, batch_nos: list = None
+) -> dict:
+ """Returns a dict of `Batch Qty` reserved in Stock Reservation Entry. The dict is like {batch_no: qty, ...}"""
+
+ sre = frappe.qb.DocType("Stock Reservation Entry")
+ sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+ query = (
+ frappe.qb.from_(sre)
+ .inner_join(sb_entry)
+ .on(sre.name == sb_entry.parent)
+ .select(
+ sb_entry.batch_no,
+ Sum(sb_entry.qty - sb_entry.delivered_qty),
+ )
+ .where(
+ (sre.docstatus == 1)
+ & (sre.item_code == item_code)
+ & (sre.warehouse == warehouse)
+ & ((sre.reserved_qty - sre.delivered_qty) > 0)
+ & (sre.status.notin(["Delivered", "Cancelled"]))
+ & (sre.reservation_based_on == "Serial and Batch")
+ )
+ .groupby(sb_entry.batch_no)
+ .orderby(sb_entry.creation)
+ )
+
+ if batch_nos:
+ query = query.where(sb_entry.batch_no.isin(batch_nos))
+
+ return frappe._dict(query.run())
+
+
def get_sre_details_for_voucher(voucher_type: str, voucher_no: str) -> list[dict]:
"""Returns a list of SREs for the provided voucher."""
diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
index f4c74a8..dd023e2 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
@@ -286,6 +286,7 @@
self.assertEqual(item.stock_reserved_qty, sre_details.reserved_qty)
self.assertEqual(sre_details.status, "Partially Reserved")
+ cancel_stock_reservation_entries("Sales Order", so.name)
se.cancel()
# Test - 3: Stock should be fully Reserved if the Available Qty to Reserve is greater than the Un-reserved Qty.
@@ -493,7 +494,7 @@
"pick_serial_and_batch_based_on": "FIFO",
},
)
- def test_stock_reservation_from_pick_list(self):
+ def test_stock_reservation_from_pick_list(self) -> None:
items_details = create_items()
create_material_receipt(items_details, self.warehouse, qty=100)
@@ -575,7 +576,7 @@
"auto_reserve_stock_for_sales_order_on_purchase": 1,
},
)
- def test_stock_reservation_from_purchase_receipt(self):
+ def test_stock_reservation_from_purchase_receipt(self) -> None:
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.selling.doctype.sales_order.sales_order import make_material_request
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
@@ -645,6 +646,40 @@
# Test - 3: Reserved Serial/Batch Nos should be equal to PR Item Serial/Batch Nos.
self.assertEqual(set(sb_details), set(reserved_sb_details))
+ @change_settings(
+ "Stock Settings",
+ {
+ "allow_negative_stock": 0,
+ "enable_stock_reservation": 1,
+ "auto_reserve_serial_and_batch": 1,
+ "pick_serial_and_batch_based_on": "FIFO",
+ },
+ )
+ def test_consider_reserved_stock_while_cancelling_an_inward_transaction(self) -> None:
+ items_details = create_items()
+ se = create_material_receipt(items_details, self.warehouse, qty=100)
+
+ item_list = []
+ for item_code, properties in items_details.items():
+ item_list.append(
+ {
+ "item_code": item_code,
+ "warehouse": self.warehouse,
+ "qty": randint(11, 100),
+ "uom": properties.stock_uom,
+ "rate": randint(10, 400),
+ }
+ )
+
+ so = make_sales_order(
+ item_list=item_list,
+ warehouse=self.warehouse,
+ )
+ so.create_stock_reservation_entries()
+
+ # Test - 1: ValidationError should be thrown as the inwarded stock is reserved.
+ self.assertRaises(frappe.ValidationError, se.cancel)
+
def tearDown(self) -> None:
cancel_all_stock_reservation_entries()
return super().tearDown()
diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js
index f00dd3e..90b8d45 100644
--- a/erpnext/stock/page/stock_balance/stock_balance.js
+++ b/erpnext/stock/page/stock_balance/stock_balance.js
@@ -11,6 +11,7 @@
label: __('Warehouse'),
fieldtype:'Link',
options:'Warehouse',
+ default: frappe.route_options && frappe.route_options.warehouse,
change: function() {
page.item_dashboard.start = 0;
page.item_dashboard.refresh();
@@ -22,6 +23,7 @@
label: __('Item'),
fieldtype:'Link',
options:'Item',
+ default: frappe.route_options && frappe.route_options.item_code,
change: function() {
page.item_dashboard.start = 0;
page.item_dashboard.refresh();
@@ -33,6 +35,7 @@
label: __('Item Group'),
fieldtype:'Link',
options:'Item Group',
+ default: frappe.route_options && frappe.route_options.item_group,
change: function() {
page.item_dashboard.start = 0;
page.item_dashboard.refresh();
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index eeef396..e59f2fe 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -249,6 +249,13 @@
"options": "Serial No",
"width": 100,
},
+ {
+ "label": _("Serial and Batch Bundle"),
+ "fieldname": "serial_and_batch_bundle",
+ "fieldtype": "Link",
+ "options": "Serial and Batch Bundle",
+ "width": 100,
+ },
{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
{
"label": _("Project"),
@@ -287,6 +294,7 @@
sle.voucher_type,
sle.qty_after_transaction,
sle.stock_value_difference,
+ sle.serial_and_batch_bundle,
sle.voucher_no,
sle.stock_value,
sle.batch_no,
diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
index ca15afe..fb392f7 100644
--- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
+++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
@@ -24,6 +24,7 @@
"stock_value_difference",
"valuation_rate",
"voucher_detail_no",
+ "serial_and_batch_bundle",
)
@@ -64,7 +65,11 @@
balance_qty += sle.actual_qty
balance_stock_value += sle.stock_value_difference
- if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
+ if (
+ sle.voucher_type == "Stock Reconciliation"
+ and not sle.batch_no
+ and not sle.serial_and_batch_bundle
+ ):
balance_qty = frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "qty")
if balance_qty is None:
balance_qty = sle.qty_after_transaction
@@ -144,6 +149,12 @@
"options": "Batch",
},
{
+ "fieldname": "serial_and_batch_bundle",
+ "fieldtype": "Link",
+ "label": _("Serial and Batch Bundle"),
+ "options": "Serial and Batch Bundle",
+ },
+ {
"fieldname": "use_batchwise_valuation",
"fieldtype": "Check",
"label": _("Batchwise Valuation"),
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index b950f18..e9381d4 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -11,17 +11,22 @@
from frappe.model.meta import get_field_precision
from frappe.query_builder import Case
from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, parse_json
+from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, nowtime, parse_json
import erpnext
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
+from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+ get_available_batches,
+)
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
- get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock,
+ get_sre_reserved_batch_nos_details,
+ get_sre_reserved_serial_nos_details,
)
from erpnext.stock.utils import (
get_incoming_outgoing_rate_for_cancel,
get_or_make_bin,
+ get_stock_balance,
get_valuation_method,
)
from erpnext.stock.valuation import FIFOValuation, LIFOValuation, round_off_if_near_zero
@@ -88,6 +93,7 @@
is_stock_item = frappe.get_cached_value("Item", args.get("item_code"), "is_stock_item")
if is_stock_item:
bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
+ args.reserved_stock = flt(frappe.db.get_value("Bin", bin_name, "reserved_stock"))
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
update_bin_qty(bin_name, args)
else:
@@ -114,6 +120,7 @@
"voucher_no": args.get("voucher_no"),
"sle_id": args.get("name"),
"creation": args.get("creation"),
+ "reserved_stock": args.get("reserved_stock"),
},
allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher,
@@ -203,6 +210,11 @@
sle.allow_negative_stock = allow_negative_stock
sle.via_landed_cost_voucher = via_landed_cost_voucher
sle.submit()
+
+ # Added to handle the case when the stock ledger entry is created from the repostig
+ if args.get("creation_time") and args.get("voucher_type") == "Stock Reconciliation":
+ sle.db_set("creation", args.get("creation_time"))
+
return sle
@@ -506,7 +518,7 @@
self.new_items_found = False
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
self.affected_transactions: Set[Tuple[str, str]] = set()
- self.reserved_stock = get_reserved_stock(self.args.item_code, self.args.warehouse)
+ self.reserved_stock = flt(self.args.reserved_stock)
self.data = frappe._dict()
self.initialize_previous_data(self.args)
@@ -689,9 +701,11 @@
if (
sle.voucher_type == "Stock Reconciliation"
- and (sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle))
+ and (
+ sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle and not sle.has_serial_no)
+ )
and sle.voucher_detail_no
- and sle.actual_qty < 0
+ and not self.args.get("sle_id")
):
self.reset_actual_qty_for_stock_reco(sle)
@@ -754,27 +768,22 @@
self.update_outgoing_rate_on_transaction(sle)
def reset_actual_qty_for_stock_reco(self, sle):
- if sle.serial_and_batch_bundle:
- current_qty = frappe.get_cached_value(
- "Serial and Batch Bundle", sle.serial_and_batch_bundle, "total_qty"
+ doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no)
+ doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0)
+
+ if sle.actual_qty < 0:
+ sle.actual_qty = (
+ flt(frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"))
+ * -1
)
- if current_qty is not None:
- current_qty = abs(current_qty)
- else:
- current_qty = frappe.get_cached_value(
- "Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"
- )
-
- if current_qty:
- sle.actual_qty = current_qty * -1
- elif current_qty == 0:
- sle.is_cancelled = 1
+ if abs(sle.actual_qty) == 0.0:
+ sle.is_cancelled = 1
def calculate_valuation_for_serial_batch_bundle(self, sle):
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
- doc.set_incoming_rate(save=True)
+ doc.set_incoming_rate(save=True, allow_negative_stock=self.allow_negative_stock)
doc.calculate_qty_and_amount(save=True)
self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + doc.total_amount)
@@ -1461,6 +1470,7 @@
currency=None,
company=None,
raise_error_if_no_rate=True,
+ batch_no=None,
serial_and_batch_bundle=None,
):
@@ -1469,6 +1479,25 @@
if not company:
company = frappe.get_cached_value("Warehouse", warehouse, "company")
+ if warehouse and batch_no and frappe.db.get_value("Batch", batch_no, "use_batchwise_valuation"):
+ table = frappe.qb.DocType("Stock Ledger Entry")
+ query = (
+ frappe.qb.from_(table)
+ .select(Sum(table.stock_value_difference) / Sum(table.actual_qty))
+ .where(
+ (table.item_code == item_code)
+ & (table.warehouse == warehouse)
+ & (table.batch_no == batch_no)
+ & (table.is_cancelled == 0)
+ & (table.voucher_no != voucher_no)
+ & (table.voucher_type != voucher_type)
+ )
+ )
+
+ last_valuation_rate = query.run()
+ if last_valuation_rate:
+ return flt(last_valuation_rate[0][0])
+
# Get moving average rate of a specific batch number
if warehouse and serial_and_batch_bundle:
batch_obj = BatchNoValuation(
@@ -1563,8 +1592,6 @@
next_stock_reco_detail = get_next_stock_reco(args)
if next_stock_reco_detail:
detail = next_stock_reco_detail[0]
- if detail.batch_no or (detail.serial_and_batch_bundle and detail.has_batch_no):
- regenerate_sle_for_batch_stock_reco(detail)
# add condition to update SLEs before this date & time
datetime_limit_condition = get_datetime_limit_condition(detail)
@@ -1593,16 +1620,6 @@
validate_negative_qty_in_future_sle(args, allow_negative_stock)
-def regenerate_sle_for_batch_stock_reco(detail):
- doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no)
- doc.recalculate_current_qty(detail.item_code, detail.batch_no)
-
- if not frappe.db.exists(
- "Repost Item Valuation", {"voucher_no": doc.name, "status": "Queued", "docstatus": "1"}
- ):
- doc.repost_future_sle_and_gle(force=True)
-
-
def get_stock_reco_qty_shift(args):
stock_reco_qty_shift = 0
if args.get("is_cancelled"):
@@ -1709,22 +1726,23 @@
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock"))
- if not args.batch_no:
- return
+ if args.batch_no:
+ neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
+ if is_negative_with_precision(neg_batch_sle, is_batch=True):
+ message = _(
+ "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
+ ).format(
+ abs(neg_batch_sle[0]["cumulative_total"]),
+ frappe.get_desk_link("Batch", args.batch_no),
+ frappe.get_desk_link("Warehouse", args.warehouse),
+ neg_batch_sle[0]["posting_date"],
+ neg_batch_sle[0]["posting_time"],
+ frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]),
+ )
+ frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
- neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
- if is_negative_with_precision(neg_batch_sle, is_batch=True):
- message = _(
- "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
- ).format(
- abs(neg_batch_sle[0]["cumulative_total"]),
- frappe.get_desk_link("Batch", args.batch_no),
- frappe.get_desk_link("Warehouse", args.warehouse),
- neg_batch_sle[0]["posting_date"],
- neg_batch_sle[0]["posting_time"],
- frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]),
- )
- frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
+ if args.reserved_stock:
+ validate_reserved_stock(args)
def is_negative_with_precision(neg_sle, is_batch=False):
@@ -1791,6 +1809,96 @@
)
+def validate_reserved_stock(kwargs):
+ if kwargs.serial_no:
+ serial_nos = kwargs.serial_no.split("\n")
+ validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
+
+ elif kwargs.batch_no:
+ validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, [kwargs.batch_no])
+
+ elif kwargs.serial_and_batch_bundle:
+ sbb_entries = frappe.db.get_all(
+ "Serial and Batch Entry",
+ {
+ "parenttype": "Serial and Batch Bundle",
+ "parent": kwargs.serial_and_batch_bundle,
+ "docstatus": 1,
+ },
+ ["batch_no", "serial_no"],
+ )
+
+ if serial_nos := [entry.serial_no for entry in sbb_entries if entry.serial_no]:
+ validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
+ elif batch_nos := [entry.batch_no for entry in sbb_entries if entry.batch_no]:
+ validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, batch_nos)
+
+ # Qty based validation for non-serial-batch items OR SRE with Reservation Based On Qty.
+ precision = cint(frappe.db.get_default("float_precision")) or 2
+ balance_qty = get_stock_balance(kwargs.item_code, kwargs.warehouse)
+
+ diff = flt(balance_qty - kwargs.get("reserved_stock", 0), precision)
+ if diff < 0 and abs(diff) > 0.0001:
+ msg = _("{0} units of {1} needed in {2} on {3} {4} to complete this transaction.").format(
+ abs(diff),
+ frappe.get_desk_link("Item", kwargs.item_code),
+ frappe.get_desk_link("Warehouse", kwargs.warehouse),
+ nowdate(),
+ nowtime(),
+ )
+ frappe.throw(msg, title=_("Reserved Stock"))
+
+
+def validate_reserved_serial_nos(item_code, warehouse, serial_nos):
+ if reserved_serial_nos_details := get_sre_reserved_serial_nos_details(
+ item_code, warehouse, serial_nos
+ ):
+ if common_serial_nos := list(
+ set(serial_nos).intersection(set(reserved_serial_nos_details.keys()))
+ ):
+ msg = _(
+ "Serial Nos are reserved in Stock Reservation Entries, you need to unreserve them before proceeding."
+ )
+ msg += "<br />"
+ msg += _("Example: Serial No {0} reserved in {1}.").format(
+ frappe.bold(common_serial_nos[0]),
+ frappe.get_desk_link(
+ "Stock Reservation Entry", reserved_serial_nos_details[common_serial_nos[0]]
+ ),
+ )
+ frappe.throw(msg, title=_("Reserved Serial No."))
+
+
+def validate_reserved_batch_nos(item_code, warehouse, batch_nos):
+ if reserved_batches_map := get_sre_reserved_batch_nos_details(item_code, warehouse, batch_nos):
+ available_batches = get_available_batches(
+ frappe._dict(
+ {
+ "item_code": item_code,
+ "warehouse": warehouse,
+ "posting_date": nowdate(),
+ "posting_time": nowtime(),
+ }
+ )
+ )
+ available_batches_map = {row.batch_no: row.qty for row in available_batches}
+ precision = cint(frappe.db.get_default("float_precision")) or 2
+
+ for batch_no in batch_nos:
+ diff = flt(
+ available_batches_map.get(batch_no, 0) - reserved_batches_map.get(batch_no, 0), precision
+ )
+ if diff < 0 and abs(diff) > 0.0001:
+ msg = _("{0} units of {1} needed in {2} on {3} {4} to complete this transaction.").format(
+ abs(diff),
+ frappe.get_desk_link("Batch", batch_no),
+ frappe.get_desk_link("Warehouse", warehouse),
+ nowdate(),
+ nowtime(),
+ )
+ frappe.throw(msg, title=_("Reserved Stock for Batch"))
+
+
def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)):
return True
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index d4daacd..f96823b 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -1,13 +1,6 @@
frappe.ui.form.on("Issue", {
onload: function(frm) {
frm.email_field = "raised_by";
- frm.set_query("customer", function () {
- return {
- filters: {
- "disabled": 0
- }
- };
- });
frappe.db.get_value("Support Settings", {name: "Support Settings"},
["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html
index 9fe4338..3b8698f 100644
--- a/erpnext/templates/pages/projects.html
+++ b/erpnext/templates/pages/projects.html
@@ -14,18 +14,16 @@
{% block style %}
<style>
- {
- % include "templates/includes/projects.css"%
- }
+ {% include "templates/includes/projects.css" %}
</style>
{% endblock %}
{% block page_content %}
<div class="web-list-item transaction-list-item">
<div class="row align-items-center">
- <div class="col-sm-4 "><b>Status: {{ doc.status }}</b></div>
- <div class="col-sm-4 "><b>Progress: {{ doc.percent_complete }}%</b></div>
- <div class="col-sm-4 "><b>Hours Spent: {{ doc.actual_time | round }}</b></div>
+ <div class="col-sm-4 "><b>{{ _("Status") }}: {{ _(doc.status) }}</b></div>
+ <div class="col-sm-4 "><b>{{ _("Progress") }}: {{ doc.get_formatted("percent_complete") }}</b></div>
+ <div class="col-sm-4 "><b>{{ _("Hours Spent") }}: {{ doc.get_formatted("actual_time") }}</b></div>
</div>
</div>
@@ -34,7 +32,7 @@
<hr>
<div class="row align-items-center">
- <div class="col-sm-6 my-account-header"> <h4>Tasks</h4></div>
+ <div class="col-sm-6 my-account-header"> <h4>{{ _("Tasks") }}</h4></div>
<div class="col-sm-6 text-right">
<a class="btn btn-secondary btn-light btn-sm" href='/tasks/new?project={{ doc.project_name }}'>{{ _("New task") }}</a>
</div>
@@ -44,39 +42,39 @@
<div class="result">
<div class="web-list-item transaction-list-item">
<div class="row align-items-center">
- <div class="col-sm-4"><b>Tasks</b></div>
- <div class="col-sm-2"><b>Status</b></div>
- <div class="col-sm-2"><b>End Date</b></div>
- <div class="col-sm-2"><b>Assignment</b></div>
- <div class="col-sm-2"><b>Modified On</b></div>
+ <div class="col-sm-4"><b>{{ _("Tasks") }}</b></div>
+ <div class="col-sm-2"><b>{{ _("Status") }}</b></div>
+ <div class="col-sm-2"><b>{{ _("End Date") }}</b></div>
+ <div class="col-sm-2"><b>{{ _("Assignment") }}</b></div>
+ <div class="col-sm-2"><b>{{ _("Modified On") }}</b></div>
</div>
</div>
{% include "erpnext/templates/includes/projects/project_tasks.html" %}
</div>
</div>
{% else %}
- {{ empty_state('Task')}}
+ {{ empty_state(_("Task")) }}
{% endif %}
- <h4 class="my-account-header">Timesheets</h4>
+ <h4 class="my-account-header">{{ _("Timesheets") }}</h4>
{% if doc.timesheets %}
<div class="website-list">
<div class="result">
<div class="web-list-item transaction-list-item">
<div class="row align-items-center">
- <div class="col-xs-2"><b>Timesheet</b></div>
- <div class="col-xs-2"><b>Status</b></div>
- <div class="col-xs-2"><b>From</b></div>
- <div class="col-xs-2"><b>To</b></div>
- <div class="col-xs-2"><b>Modified By</b></div>
- <div class="col-xs-2"><b>Modified On</b></div>
+ <div class="col-xs-2"><b>{{ _("Timesheet") }}</b></div>
+ <div class="col-xs-2"><b>{{ _("Status") }}</b></div>
+ <div class="col-xs-2"><b>{{ _("From") }}</b></div>
+ <div class="col-xs-2"><b>{{ _("To") }}</b></div>
+ <div class="col-xs-2"><b>{{ _("Modified By") }}</b></div>
+ <div class="col-xs-2"><b>{{ _("Modified On") }}</b></div>
</div>
</div>
{% include "erpnext/templates/includes/projects/project_timesheets.html" %}
</div>
</div>
{% else %}
- {{ empty_state('Timesheet')}}
+ {{ empty_state(_("Timesheet")) }}
{% endif %}
{% if doc.attachments %}
@@ -113,7 +111,7 @@
{% macro progress_bar(percent_complete) %}
{% if percent_complete %}
- <span class="small py-2">Project Progress:</span>
+ <span class="small py-2">{{ _("Project Progress:") }}</span>
<div class="progress progress-hg" style="height: 15px;">
<div
class="progress-bar progress-bar-{{ 'warning' if percent_complete|round < 100 else 'success' }} active"\
@@ -133,7 +131,7 @@
<div>
<img src="/assets/frappe/images/ui-states/list-empty-state.svg" alt="Generic Empty State" class="null-state">
</div>
- <p>You haven't created a {{ section_name }} yet</p>
+ <p>{{ _("You haven't created a {0} yet").format(section_name) }}</p>
</div>
</div>
</div>
diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv
index d4b823d..f457314 100644
--- a/erpnext/translations/af.csv
+++ b/erpnext/translations/af.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantie Periode (in dae),
Auto re-order,Outo herbestel,
Reorder level based on Warehouse,Herbestel vlak gebaseer op Warehouse,
-Will also apply for variants unless overrridden,Sal ook aansoek doen vir variante tensy dit oortree word,
+Will also apply for variants unless overridden,Sal ook aansoek doen vir variante tensy dit oortree word,
Units of Measure,Eenhede van maatreël,
Will also apply for variants,Sal ook aansoek doen vir variante,
Serial Nos and Batches,Serial Nos and Batches,
diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv
index 764868d..0453d5d 100644
--- a/erpnext/translations/am.csv
+++ b/erpnext/translations/am.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(ቀናት ውስጥ) የዋስትና ክፍለ ጊዜ,
Auto re-order,ራስ-ዳግም-ትዕዛዝ,
Reorder level based on Warehouse,መጋዘን ላይ የተመሠረተ አስይዝ ደረጃ,
-Will also apply for variants unless overrridden,overrridden በስተቀር ደግሞ ተለዋጮች ማመልከት ይሆን,
+Will also apply for variants unless overridden,overridden በስተቀር ደግሞ ተለዋጮች ማመልከት ይሆን,
Units of Measure,ይለኩ አሃዶች,
Will also apply for variants,በተጨማሪም ተለዋጮች ማመልከት ይሆን,
Serial Nos and Batches,ተከታታይ ቁጥሮች እና ቡድኖች,
diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv
index 4e03a18..67b409e 100644
--- a/erpnext/translations/ar.csv
+++ b/erpnext/translations/ar.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),فترة الضمان (بالأيام),
Auto re-order,إعادة ترتيب تلقائي,
Reorder level based on Warehouse,مستوى إعادة الطلب بناء على مستودع,
-Will also apply for variants unless overrridden,سوف تطبق أيضا على المتغيرات الا اذا تم التغير فوقها,
+Will also apply for variants unless overridden,سوف تطبق أيضا على المتغيرات الا اذا تم التغير فوقها,
Units of Measure,وحدات القياس,
Will also apply for variants,سوف تطبق أيضا على المتغيرات,
Serial Nos and Batches,الرقم التسلسلي ودفعات,
diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv
index 8dff755..787f81e 100644
--- a/erpnext/translations/bg.csv
+++ b/erpnext/translations/bg.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Гаранционен срок (в дни),
Auto re-order,Автоматична повторна поръчка,
Reorder level based on Warehouse,Пренареждане равнище въз основа на Warehouse,
-Will also apply for variants unless overrridden,"Ще се прилага и за варианти, освен ако overrridden",
+Will also apply for variants unless overridden,"Ще се прилага и за варианти, освен ако overridden",
Units of Measure,Мерни единици за измерване,
Will also apply for variants,Ще се прилага и за варианти,
Serial Nos and Batches,Серийни номера и партиди,
diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv
index 8a698df..69fd08c 100644
--- a/erpnext/translations/bn.csv
+++ b/erpnext/translations/bn.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(দিন) ওয়্যারেন্টি সময়কাল,
Auto re-order,অটো পুনরায় আদেশ,
Reorder level based on Warehouse,গুদাম উপর ভিত্তি রেকর্ডার স্তর,
-Will also apply for variants unless overrridden,Overrridden তবে এছাড়াও ভিন্নতা জন্য আবেদন করতে হবে,
+Will also apply for variants unless overridden,Overrridden তবে এছাড়াও ভিন্নতা জন্য আবেদন করতে হবে,
Units of Measure,পরিমাপ ইউনিট,
Will also apply for variants,এছাড়াও ভিন্নতা জন্য আবেদন করতে হবে,
Serial Nos and Batches,সিরিয়াল আমরা এবং ব্যাচ,
diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv
index 7ba4a88..ef680a3 100644
--- a/erpnext/translations/bs.csv
+++ b/erpnext/translations/bs.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Jamstveni period (u danima),
Auto re-order,Autorefiniš reda,
Reorder level based on Warehouse,Nivo Ponovno red zasnovan na Skladište,
-Will also apply for variants unless overrridden,Primjenjivat će se i za varijante osim overrridden,
+Will also apply for variants unless overridden,Primjenjivat će se i za varijante osim overridden,
Units of Measure,Jedinice mjere,
Will also apply for variants,Primjenjivat će se i za varijante,
Serial Nos and Batches,Serijski brojevi i Paketi,
diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv
index cce1f3a..fa545a4 100644
--- a/erpnext/translations/ca.csv
+++ b/erpnext/translations/ca.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Període de garantia (en dies),
Auto re-order,Acte reordenar,
Reorder level based on Warehouse,Nivell de comanda basat en Magatzem,
-Will also apply for variants unless overrridden,També s'aplicarà per a les variants menys overrridden,
+Will also apply for variants unless overridden,També s'aplicarà per a les variants menys overridden,
Units of Measure,Unitats de mesura,
Will also apply for variants,També s'aplicarà per a les variants,
Serial Nos and Batches,Nº de sèrie i lots,
diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv
index 1072ed3..7fb1679 100644
--- a/erpnext/translations/cs.csv
+++ b/erpnext/translations/cs.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Záruční doba (ve dnech),
Auto re-order,Automatické znovuobjednání,
Reorder level based on Warehouse,Úroveň Změna pořadí na základě Warehouse,
-Will also apply for variants unless overrridden,"Bude platit i pro varianty, pokud nebude přepsáno",
+Will also apply for variants unless overridden,"Bude platit i pro varianty, pokud nebude přepsáno",
Units of Measure,Jednotky měření,
Will also apply for variants,Bude platit i pro varianty,
Serial Nos and Batches,Sériové čísla a dávky,
diff --git a/erpnext/translations/cz.csv b/erpnext/translations/cz.csv
index 270a710..96de062 100644
--- a/erpnext/translations/cz.csv
+++ b/erpnext/translations/cz.csv
@@ -1991,7 +1991,7 @@
Actual End Date,Skutečné datum ukončen$1,
Applicable To (Role),Vztahující se na (Role)
Purpose,Účel,
-Will also apply for variants unless overrridden,"Bude platit i pro varianty, pokud nebude přepsáno"
+Will also apply for variants unless overridden,"Bude platit i pro varianty, pokud nebude přepsáno"
Advances,Zálohy,
Approving User cannot be same as user the rule is Applicable To,Schválení Uživatel nemůže být stejná jako uživatel pravidlo se vztahuje na,
No of Requested SMS,Počet žádaným SMS,
diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv
index 138da5d..4eb3960 100644
--- a/erpnext/translations/da.csv
+++ b/erpnext/translations/da.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantiperiode (i dage),
Auto re-order,Auto genbestil,
Reorder level based on Warehouse,Genbestil niveau baseret på Warehouse,
-Will also apply for variants unless overrridden,"Vil også gælde for varianter, medmindre overrridden",
+Will also apply for variants unless overridden,"Vil også gælde for varianter, medmindre overridden",
Units of Measure,Måleenheder,
Will also apply for variants,Vil også gælde for varianter,
Serial Nos and Batches,Serienummer og partier,
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 79b9574..c627f81 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -3340,7 +3340,7 @@
Cannot Optimize Route as Driver Address is Missing.,"Route kann nicht optimiert werden, da die Fahreradresse fehlt.",
Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.,"Aufgabe {0} kann nicht abgeschlossen werden, da die abhängige Aufgabe {1} nicht abgeschlossen / abgebrochen wurde.",
Cannot find a matching Item. Please select some other value for {0}.,Ein passender Artikel kann nicht gefunden werden. Bitte einen anderen Wert für {0} auswählen.,
-"Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings","Artikel {0} in Zeile {1} kann nicht mehr als {2} in Rechnung gestellt werden. Um eine Überberechnung zuzulassen, legen Sie die Überberechnung in den Kontoeinstellungen fest",
+"Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings","Für Artikel {0} in Zeile {1} kann nicht mehr als {2} zusätzlich in Rechnung gestellt werden. Um diese Überfakturierung zuzulassen, passen Sie bitte die Grenzwerte in den Buchhaltungseinstellungen an.",
"Capacity Planning Error, planned start time can not be same as end time","Kapazitätsplanungsfehler, die geplante Startzeit darf nicht mit der Endzeit übereinstimmen",
Categories,Kategorien,
Changes in {0},Änderungen in {0},
@@ -3746,7 +3746,7 @@
This page keeps track of your items in which buyers have showed some interest.,"Diese Seite verfolgt Ihre Artikel, an denen Käufer Interesse gezeigt haben.",
Thursday,Donnerstag,
Title,Bezeichnung,
-"To allow over billing, update ""Over Billing Allowance"" in Accounts Settings or the Item.","Aktualisieren Sie "Over Billing Allowance" in den Kontoeinstellungen oder im Artikel, um eine Überberechnung zuzulassen.",
+"To allow over billing, update ""Over Billing Allowance"" in Accounts Settings or the Item.","Aktualisieren Sie "Over Billing Allowance" in den Buchhaltungseinstellungen oder im Artikel, um eine Überberechnung zuzulassen.",
"To allow over receipt / delivery, update ""Over Receipt/Delivery Allowance"" in Stock Settings or the Item.","Um eine Überbestätigung / Überlieferung zu ermöglichen, aktualisieren Sie "Überbestätigung / Überlieferung" in den Lagereinstellungen oder im Artikel.",
Total,Summe,
Total Payment Request amount cannot be greater than {0} amount,Der Gesamtbetrag der Zahlungsanforderung darf nicht größer als {0} sein,
@@ -4113,8 +4113,8 @@
Accounting Period,Abrechnungszeitraum,
Period Name,Zeitraumname,
Closed Documents,Geschlossene Dokumente,
-Accounts Settings,Konteneinstellungen,
-Settings for Accounts,Konteneinstellungen,
+Accounts Settings,Buchhaltungseinstellungen,
+Settings for Accounts,Einstellungen für die Buchhaltung,
Make Accounting Entry For Every Stock Movement,Eine Buchung für jede Lagerbewegung erstellen,
Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,Benutzer mit dieser Rolle sind berechtigt Konten zu sperren und Buchungen zu gesperrten Konten zu erstellen/verändern,
Determine Address Tax Category From,Adresssteuerkategorie bestimmen von,
@@ -7074,7 +7074,7 @@
Warranty Period (in days),Garantiefrist (in Tagen),
Auto re-order,Automatische Nachbestellung,
Reorder level based on Warehouse,Meldebestand auf Basis des Lagers,
-Will also apply for variants unless overrridden,"Gilt auch für Varianten, sofern nicht außer Kraft gesetzt",
+Will also apply for variants unless overridden,"Gilt auch für Varianten, sofern nicht außer Kraft gesetzt",
Units of Measure,Maßeinheiten,
Will also apply for variants,Gilt auch für Varianten,
Serial Nos and Batches,Seriennummern und Chargen,
@@ -7617,7 +7617,7 @@
Journal Entry Type,Buchungssatz-Typ,
Journal Entry Template Account,Buchungssatzvorlagenkonto,
Process Deferred Accounting,Aufgeschobene Buchhaltung verarbeiten,
-Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again,Manuelle Eingabe kann nicht erstellt werden! Deaktivieren Sie die automatische Eingabe für die verzögerte Buchhaltung in den Konteneinstellungen und versuchen Sie es erneut,
+Manual entry cannot be created! Disable automatic entry for deferred accounting in accounts settings and try again,Manuelle Eingabe kann nicht erstellt werden! Deaktivieren Sie die automatische Eingabe für die verzögerte Buchhaltung in den Buchhaltungseinstellungen und versuchen Sie es erneut,
End date cannot be before start date,Das Enddatum darf nicht vor dem Startdatum liegen,
Total Counts Targeted,Gesamtzahl der anvisierten Zählungen,
Total Counts Completed,Gesamtzahl der abgeschlossenen Zählungen,
@@ -8826,5 +8826,32 @@
Is Mandatory,Ist obligatorisch,
WhatsApp,WhatsApp,
Make a call,Einen Anruf tätigen,
+Enable Automatic Party Matching,Automatisches Zuordnen von Parteien aktivieren,
+Auto match and set the Party in Bank Transactions,"Partei automatisch anhand der Kontonummer bzw. IBAN zuordnen",
+Enable Fuzzy Matching,Fuzzy Matching aktivieren,
+Approximately match the description/party name against parties,"Partei automatisch anhand grober Übereinstimmung des Namens zuordnen"
+Accounts Closing,Kontenabschluss,
+Period Closing Settings,Periodenabschlusseinstellungen,
+Ignore Account Closing Balance,Saldo des Kontos zum Periodenabschluss ignorieren,
+Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing),"Finanzberichte werden anhand des Hauptbuchs erstellt (sollte aktiviert sein, wenn Periodenabschlüsse fehlen oder nicht sequentiell für alle Jahre gebucht werden)",
+Asset Settings,Vermögenswerteinstellungen,
+POS Setting,POS-Einstellungen,
+Create Ledger Entries for Change Amount,Buchungssätze für Wechselgeld erstellen,
+"If enabled, ledger entries will be posted for change amount in POS transactions","Wenn aktiviert, werden Buchungssätze für Wechselgeld in POS-Transaktionen erstellt",
+Credit Limit Settings,Kreditlimit-Einstellungen,
+Role Allowed to Over Bill,
+Users with this role are allowed to over bill above the allowance percentage,"Roll, die mehr als den erlaubten Prozentsatz zusätzlich abrechnen darf",
+Role allowed to bypass Credit Limit,"Rolle, die das Kreditlimit umgehen darf",
+Invoice Cancellation,Rechnungsstornierung,
+Delete Accounting and Stock Ledger Entries on deletion of Transaction,Beim Löschen einer Transaktion auch die entsprechenden Buchungs- und Lagerbuchungssätze löschen,
+Enable Common Party Accounting,Verknüpfung von Kunden und Liefeanten erlauben,
+Allow multi-currency invoices against single party account,Rechnungsbeträge in Fremdwährungen dürfen umgerechnet und in der Hauptwährung gebucht werden,
+Enabling this will allow creation of multi-currency invoices against single party account in company currency,Bei Aktivierung können Rechnungen in Fremdwährungen gegen ein Konto in der Hauptwährung gebucht werden,
+Payment Terms from orders will be fetched into the invoices as is,Zahlungsbedingungen aus Aufträgen werden eins zu eins in Rechnungen übernommen,
+Automatically Fetch Payment Terms from Order,Zahlungsbedingungen aus Auftrag in die Rechnung übernehmen,
+Enable Custom Cash Flow Format,Individuelles Cashflow-Format aktivieren,
+Tax Settings,Umsatzsteuer-Einstellungen,
+Book Tax Loss on Early Payment Discount,Umsatzsteueranteil bei Skonto berücksichtigen,
+Split Early Payment Discount Loss into Income and Tax Loss,"Skontobetrag in Aufwand und Umsatzsteuerkorrektur aufteilen",
Approve,Genehmigen,
Reject,Ablehnen,
diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv
index 7a83cc5..21fb435 100644
--- a/erpnext/translations/el.csv
+++ b/erpnext/translations/el.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Περίοδος εγγύησης (σε ημέρες),
Auto re-order,Αυτόματη εκ νέου προκειμένου,
Reorder level based on Warehouse,Αναδιάταξη επίπεδο με βάση Αποθήκης,
-Will also apply for variants unless overrridden,Θα ισχύουν επίσης για τις παραλλαγές εκτός αν υπάρχει υπέρβαση,
+Will also apply for variants unless overridden,Θα ισχύουν επίσης για τις παραλλαγές εκτός αν υπάρχει υπέρβαση,
Units of Measure,Μονάδες μέτρησης,
Will also apply for variants,Θα ισχύουν επίσης για τις παραλλαγές,
Serial Nos and Batches,Σειριακοί αριθμοί και παρτίδες,
diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv
index fadf7a7..2abe418 100644
--- a/erpnext/translations/es.csv
+++ b/erpnext/translations/es.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Período de garantía (en días),
Auto re-order,Ordenar Automáticamente,
Reorder level based on Warehouse,Nivel de reabastecimiento basado en almacén,
-Will also apply for variants unless overrridden,También se aplicará para las variantes menos que se sobre escriba,
+Will also apply for variants unless overridden,También se aplicará para las variantes menos que se sobre escriba,
Units of Measure,Unidades de medida,
Will also apply for variants,También se aplicará para las variantes,
Serial Nos and Batches,Números de serie y lotes,
diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv
index 4e26a82..a4a8736 100644
--- a/erpnext/translations/et.csv
+++ b/erpnext/translations/et.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantii Periood (päeva),
Auto re-order,Auto ümber korraldada,
Reorder level based on Warehouse,Reorder tasandil põhineb Warehouse,
-Will also apply for variants unless overrridden,"Kehtib ka variante, kui overrridden",
+Will also apply for variants unless overridden,"Kehtib ka variante, kui overridden",
Units of Measure,Mõõtühikud,
Will also apply for variants,Kehtib ka variandid,
Serial Nos and Batches,Serial Nos ning partiid,
diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv
index 530965d..bd40c8b 100644
--- a/erpnext/translations/fa.csv
+++ b/erpnext/translations/fa.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),دوره گارانتی (در روز),
Auto re-order,خودکار دوباره سفارش,
Reorder level based on Warehouse,سطح تغییر مجدد ترتیب بر اساس انبار,
-Will also apply for variants unless overrridden,همچنین برای انواع اعمال می شود مگر اینکه overrridden,
+Will also apply for variants unless overridden,همچنین برای انواع اعمال می شود مگر اینکه overridden,
Units of Measure,واحدهای اندازه گیری,
Will also apply for variants,همچنین برای انواع اعمال می شود,
Serial Nos and Batches,سریال شماره و دسته,
diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv
index 6e9380c..33cf157 100644
--- a/erpnext/translations/fi.csv
+++ b/erpnext/translations/fi.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Takuuaika (päivinä),
Auto re-order,Auto re-order,
Reorder level based on Warehouse,Varastoon perustuva täydennystilaustaso,
-Will also apply for variants unless overrridden,"Sovelletaan myös tuotemalleissa, ellei kumota",
+Will also apply for variants unless overridden,"Sovelletaan myös tuotemalleissa, ellei kumota",
Units of Measure,Mittayksiköt,
Will also apply for variants,Sovelletaan myös tuotemalleissa,
Serial Nos and Batches,Sarjanumerot ja Erät,
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index d3875c1..d15af74 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -6650,7 +6650,7 @@
Warranty Period (in days),Période de Garantie (en jours),
Auto re-order,Re-commande auto,
Reorder level based on Warehouse,Niveau de réapprovisionnement basé sur l’Entrepôt,
-Will also apply for variants unless overrridden,S'appliquera également pour des variantes sauf si remplacé,
+Will also apply for variants unless overridden,S'appliquera également pour des variantes sauf si remplacé,
Units of Measure,Unités de Mesure,
Will also apply for variants,S'appliquera également pour les variantes,
Serial Nos and Batches,N° de Série et Lots,
diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv
index e2de8ce..06a3cc6 100644
--- a/erpnext/translations/gu.csv
+++ b/erpnext/translations/gu.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(દિવસોમાં) વોરંટી સમયગાળા,
Auto re-order,ઓટો ફરી ઓર્ડર,
Reorder level based on Warehouse,વેરહાઉસ પર આધારિત પુનઃક્રમાંકિત કરો સ્તર,
-Will also apply for variants unless overrridden,Overrridden સિવાય પણ ચલો માટે લાગુ પડશે,
+Will also apply for variants unless overridden,Overrridden સિવાય પણ ચલો માટે લાગુ પડશે,
Units of Measure,માપવા એકમો,
Will also apply for variants,પણ ચલો માટે લાગુ પડશે,
Serial Nos and Batches,સીરીયલ સંખ્યા અને બૅચેસ,
diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv
index 6cd900a..d5fcab6 100644
--- a/erpnext/translations/he.csv
+++ b/erpnext/translations/he.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),תקופת אחריות (בימים),
Auto re-order,רכב מחדש כדי,
Reorder level based on Warehouse,רמת הזמנה חוזרת המבוסס על מחסן,
-Will also apply for variants unless overrridden,תחול גם לגרסות אלא אם overrridden,
+Will also apply for variants unless overridden,תחול גם לגרסות אלא אם overridden,
Units of Measure,יחידות מידה,
Will also apply for variants,תחול גם לגרסות,
Serial Nos and Batches,מספרים וסידורים סדרתיים,
diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv
index 388b502..a5caa66 100644
--- a/erpnext/translations/hi.csv
+++ b/erpnext/translations/hi.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),वारंटी अवधि (दिनों में),
Auto re-order,ऑटो पुनः आदेश,
Reorder level based on Warehouse,गोदाम के आधार पर पुन: व्यवस्थित स्तर,
-Will also apply for variants unless overrridden,Overrridden जब तक भी वेरिएंट के लिए लागू होगी,
+Will also apply for variants unless overridden,Overrridden जब तक भी वेरिएंट के लिए लागू होगी,
Units of Measure,मापन की इकाई,
Will also apply for variants,यह भी वेरिएंट के लिए लागू होगी,
Serial Nos and Batches,सीरियल नंबर और बैचों,
diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv
index b44babc..2834602 100644
--- a/erpnext/translations/hr.csv
+++ b/erpnext/translations/hr.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Jamstveni period (u danima),
Auto re-order,Automatski reorganiziraj,
Reorder level based on Warehouse,Razina redoslijeda na temelju Skladište,
-Will also apply for variants unless overrridden,Također će zatražiti varijante osim overrridden,
+Will also apply for variants unless overridden,Također će zatražiti varijante osim overridden,
Units of Measure,Mjerne jedinice,
Will also apply for variants,Također će podnijeti zahtjev za varijante,
Serial Nos and Batches,Serijski brojevi i serije,
diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv
index 4ea5b9a..a262c8a 100644
--- a/erpnext/translations/hu.csv
+++ b/erpnext/translations/hu.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garancia hossza (napokban),
Auto re-order,Auto újra-rendelés,
Reorder level based on Warehouse,Raktárkészleten alapuló újrerendelési szint,
-Will also apply for variants unless overrridden,"Változatokra is alkalmazni fogja, hacsak nem kerül fellülírásra",
+Will also apply for variants unless overridden,"Változatokra is alkalmazni fogja, hacsak nem kerül fellülírásra",
Units of Measure,Mértékegységek,
Will also apply for variants,Változatokra is alkalmazni fogja,
Serial Nos and Batches,Sorszámok és kötegek,
diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv
index d84c3fd..c4e50fd 100644
--- a/erpnext/translations/id.csv
+++ b/erpnext/translations/id.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Masa Garansi (dalam hari),
Auto re-order,Auto re-order,
Reorder level based on Warehouse,Tingkat Re-Order berdasarkan Gudang,
-Will also apply for variants unless overrridden,Juga akan berlaku untuk varian kecuali tertimpa,
+Will also apply for variants unless overridden,Juga akan berlaku untuk varian kecuali tertimpa,
Units of Measure,Satuan ukur,
Will also apply for variants,Juga akan berlaku untuk varian,
Serial Nos and Batches,Nomor Seri dan Partai,
diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv
index 7687e4a..50c06ec 100644
--- a/erpnext/translations/is.csv
+++ b/erpnext/translations/is.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Ábyrgðartímabilið (í dögum),
Auto re-order,Auto endurraða,
Reorder level based on Warehouse,Uppröðun stigi byggist á Lager,
-Will also apply for variants unless overrridden,Mun einnig gilda um afbrigði nema overrridden,
+Will also apply for variants unless overridden,Mun einnig gilda um afbrigði nema overridden,
Units of Measure,Mælieiningar,
Will also apply for variants,Mun einnig gilda fyrir afbrigði,
Serial Nos and Batches,Raðnúmer og lotur,
diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv
index b88cada..3760895 100644
--- a/erpnext/translations/it.csv
+++ b/erpnext/translations/it.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Periodo di garanzia (in giorni),
Auto re-order,Auto riordino,
Reorder level based on Warehouse,Livello di riordino sulla base di Magazzino,
-Will also apply for variants unless overrridden,Si applica anche per le varianti meno overrridden,
+Will also apply for variants unless overridden,Si applica anche per le varianti meno overridden,
Units of Measure,Unità di misura,
Will also apply for variants,Si applica anche per le varianti,
Serial Nos and Batches,Numero e lotti seriali,
diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv
index 11455bd..888ec80 100644
--- a/erpnext/translations/ja.csv
+++ b/erpnext/translations/ja.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),保証期間(日数),
Auto re-order,自動再注文,
Reorder level based on Warehouse,倉庫ごとの再注文レベル,
-Will also apply for variants unless overrridden,上書きされない限り、バリエーションについても適用されます,
+Will also apply for variants unless overridden,上書きされない限り、バリエーションについても適用されます,
Units of Measure,測定の単位,
Will also apply for variants,バリエーションについても適用されます,
Serial Nos and Batches,シリアル番号とバッチ,
diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv
index 46dcaba..d2003c0 100644
--- a/erpnext/translations/km.csv
+++ b/erpnext/translations/km.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),ការធានារយៈពេល (នៅក្នុងថ្ងៃ),
Auto re-order,ការបញ្ជាទិញជាថ្មីម្តងទៀតដោយស្វ័យប្រវត្តិ,
Reorder level based on Warehouse,កម្រិតនៃការរៀបចំដែលមានមូលដ្ឋានលើឃ្លាំង,
-Will also apply for variants unless overrridden,ក៏នឹងអនុវត្តសម្រាប់វ៉ារ្យ៉ង់បានទេលុះត្រាតែ overrridden,
+Will also apply for variants unless overridden,ក៏នឹងអនុវត្តសម្រាប់វ៉ារ្យ៉ង់បានទេលុះត្រាតែ overridden,
Units of Measure,ឯកតារង្វាស់,
Will also apply for variants,ក៏នឹងអនុវត្តសម្រាប់វ៉ារ្យ៉ង់,
Serial Nos and Batches,សៀរៀល nos និងជំនាន់,
diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv
index 18e44a1..7206671 100644
--- a/erpnext/translations/kn.csv
+++ b/erpnext/translations/kn.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),( ದಿನಗಳಲ್ಲಿ ) ಖಾತರಿ ಅವಧಿಯ,
Auto re-order,ಆಟೋ ಪುನಃ ಸಲುವಾಗಿ,
Reorder level based on Warehouse,ವೇರ್ಹೌಸ್ ಆಧರಿಸಿ ಮರುಕ್ರಮಗೊಳಿಸಿ ಮಟ್ಟದ,
-Will also apply for variants unless overrridden,Overrridden ಹೊರತು ಸಹ ರೂಪಾಂತರಗಳು ಅನ್ವಯವಾಗುವುದು,
+Will also apply for variants unless overridden,Overrridden ಹೊರತು ಸಹ ರೂಪಾಂತರಗಳು ಅನ್ವಯವಾಗುವುದು,
Units of Measure,ಮಾಪನದ ಘಟಕಗಳಿಗೆ,
Will also apply for variants,ಸಹ ರೂಪಾಂತರಗಳು ಅನ್ವಯವಾಗುವುದು,
Serial Nos and Batches,ಸೀರಿಯಲ್ ಸೂಲ ಮತ್ತು ಬ್ಯಾಚ್,
diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv
index 788655c..9911925 100644
--- a/erpnext/translations/ko.csv
+++ b/erpnext/translations/ko.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(일) 보증 기간,
Auto re-order,자동 재 주문,
Reorder level based on Warehouse,웨어 하우스를 기반으로 재정렬 수준,
-Will also apply for variants unless overrridden,overrridden가 아니면 변형 적용됩니다,
+Will also apply for variants unless overridden,overridden가 아니면 변형 적용됩니다,
Units of Measure,측정 단위,
Will also apply for variants,또한 변형 적용됩니다,
Serial Nos and Batches,일련 번호 및 배치,
diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv
index a7fcf4e..8fec059 100644
--- a/erpnext/translations/ku.csv
+++ b/erpnext/translations/ku.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Period Warranty (di rojên),
Auto re-order,Auto re-da,
Reorder level based on Warehouse,asta DIRTYHERTZ li ser Warehouse,
-Will also apply for variants unless overrridden,jî wê ji bo Guhertoyên serî heta overrridden,
+Will also apply for variants unless overridden,jî wê ji bo Guhertoyên serî heta overridden,
Units of Measure,Yekîneyên Measure,
Will also apply for variants,jî wê ji bo Guhertoyên serî,
Serial Nos and Batches,Serial Nos û lekerên,
diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv
index 9b74f65..0831788 100644
--- a/erpnext/translations/lo.csv
+++ b/erpnext/translations/lo.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),ໄລຍະເວລາຮັບປະກັນ (ໃນວັນເວລາ),
Auto re-order,Auto Re: ຄໍາສັ່ງ,
Reorder level based on Warehouse,ລະດັບລໍາດັບຂຶ້ນຢູ່ກັບຄັງສິນຄ້າ,
-Will also apply for variants unless overrridden,ຍັງຈະນໍາໃຊ້ສໍາລັບການ variants ເວັ້ນເສຍແຕ່ວ່າ overrridden,
+Will also apply for variants unless overridden,ຍັງຈະນໍາໃຊ້ສໍາລັບການ variants ເວັ້ນເສຍແຕ່ວ່າ overridden,
Units of Measure,ຫົວຫນ່ວຍວັດແທກ,
Will also apply for variants,ຍັງຈະນໍາໃຊ້ສໍາລັບການ variants,
Serial Nos and Batches,Serial Nos ແລະສໍາຫລັບຂະບວນ,
diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv
index 2c87256..8215275 100644
--- a/erpnext/translations/lt.csv
+++ b/erpnext/translations/lt.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantinis laikotarpis (dienomis),
Auto re-order,Auto naujo užsakymas,
Reorder level based on Warehouse,Pertvarkyti lygį remiantis Warehouse,
-Will also apply for variants unless overrridden,Bus taikoma variantų nebent overrridden,
+Will also apply for variants unless overridden,Bus taikoma variantų nebent overridden,
Units of Measure,Matavimo vienetai,
Will also apply for variants,Bus taikoma variantų,
Serial Nos and Batches,Eilės Nr ir Partijos,
diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv
index 2cfa130..8c4526c 100644
--- a/erpnext/translations/lv.csv
+++ b/erpnext/translations/lv.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantijas periods (dienās),
Auto re-order,Auto re-pasūtīt,
Reorder level based on Warehouse,Pārkārtot līmenis balstās uz Noliktava,
-Will also apply for variants unless overrridden,"Attieksies arī uz variantiem, ja vien overrridden",
+Will also apply for variants unless overridden,"Attieksies arī uz variantiem, ja vien overridden",
Units of Measure,Mērvienību,
Will also apply for variants,Attieksies arī uz variantiem,
Serial Nos and Batches,Sērijas Nr un Partijām,
diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv
index f01b1b0..a622524 100644
--- a/erpnext/translations/mk.csv
+++ b/erpnext/translations/mk.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Гарантниот период (во денови),
Auto re-order,Автоматско повторно цел,
Reorder level based on Warehouse,Ниво врз основа на промените редоследот Магацински,
-Will also apply for variants unless overrridden,"Ќе се казни и варијанти, освен ако overrridden",
+Will also apply for variants unless overridden,"Ќе се казни и варијанти, освен ако overridden",
Units of Measure,На мерните единици,
Will also apply for variants,Ќе се применуваат и за варијанти,
Serial Nos and Batches,Сериски броеви и Пакетите,
diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv
index 59d3160..777d5c6 100644
--- a/erpnext/translations/ml.csv
+++ b/erpnext/translations/ml.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(ദിവസങ്ങളിൽ) വാറന്റി കാലാവധി,
Auto re-order,ഓട്ടോ റീ-ഓർഡർ,
Reorder level based on Warehouse,വെയർഹൗസ് അടിസ്ഥാനമാക്കിയുള്ള പുനഃക്രമീകരിക്കുക തലത്തിൽ,
-Will also apply for variants unless overrridden,കൂടാതെ overrridden അവയൊഴിച്ച് മോഡലുകൾക്കാണ് ബാധകമാകും,
+Will also apply for variants unless overridden,കൂടാതെ overridden അവയൊഴിച്ച് മോഡലുകൾക്കാണ് ബാധകമാകും,
Units of Measure,അളവിന്റെ യൂണിറ്റുകൾ,
Will also apply for variants,കൂടാതെ മോഡലുകൾക്കാണ് ബാധകമാകും,
Serial Nos and Batches,സീരിയൽ എണ്ണം ബാച്ചുകളും,
diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv
index ff339b6..624f1ab 100644
--- a/erpnext/translations/mr.csv
+++ b/erpnext/translations/mr.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(दिवस मध्ये) वॉरंटी कालावधी,
Auto re-order,ऑटो पुन्हा आदेश,
Reorder level based on Warehouse,वखारवर आधारित पुन्हा क्रमवारी लावा पातळी,
-Will also apply for variants unless overrridden,Overrridden आहेत तोपर्यंत देखील रूपे लागू राहील,
+Will also apply for variants unless overridden,Overrridden आहेत तोपर्यंत देखील रूपे लागू राहील,
Units of Measure,माप युनिट,
Will also apply for variants,तसेच रूपे लागू राहील,
Serial Nos and Batches,सिरियल क्र आणि बॅच,
diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv
index 2258a18..75e150a 100644
--- a/erpnext/translations/ms.csv
+++ b/erpnext/translations/ms.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Tempoh jaminan (dalam hari),
Auto re-order,Auto semula perintah,
Reorder level based on Warehouse,Tahap pesanan semula berdasarkan Warehouse,
-Will also apply for variants unless overrridden,Juga akan memohon varian kecuali overrridden,
+Will also apply for variants unless overridden,Juga akan memohon varian kecuali overridden,
Units of Measure,Unit ukuran,
Will also apply for variants,Juga akan memohon varian,
Serial Nos and Batches,Serial Nos dan Kelompok,
diff --git a/erpnext/translations/my.csv b/erpnext/translations/my.csv
index dc5ab12..36cd874 100644
--- a/erpnext/translations/my.csv
+++ b/erpnext/translations/my.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(ရက်) ကိုအာမခံကာလ,
Auto re-order,မော်တော်ကားပြန်လည်အမိန့်,
Reorder level based on Warehouse,ဂိုဒေါင်အပေါ်အခြေခံပြီး reorder level ကို,
-Will also apply for variants unless overrridden,စ overrridden မဟုတ်လျှင်မျိုးကွဲလျှောက်ထားလိမ့်မည်ဟု,
+Will also apply for variants unless overridden,စ overridden မဟုတ်လျှင်မျိုးကွဲလျှောက်ထားလိမ့်မည်ဟု,
Units of Measure,တိုင်း၏ယူနစ်,
Will also apply for variants,စမျိုးကွဲလျှောက်ထားလိမ့်မည်ဟု,
Serial Nos and Batches,serial Nos နှင့် batch,
diff --git a/erpnext/translations/nl.csv b/erpnext/translations/nl.csv
index ad11eae..5859833 100644
--- a/erpnext/translations/nl.csv
+++ b/erpnext/translations/nl.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantieperiode (in dagen),
Auto re-order,Auto re-order,
Reorder level based on Warehouse,Bestelniveau gebaseerd op Warehouse,
-Will also apply for variants unless overrridden,Geldt ook voor varianten tenzij overrridden,
+Will also apply for variants unless overridden,Geldt ook voor varianten tenzij overridden,
Units of Measure,Meeteenheden,
Will also apply for variants,Geldt ook voor varianten,
Serial Nos and Batches,Serienummers en batches,
diff --git a/erpnext/translations/no.csv b/erpnext/translations/no.csv
index b136e97..a3236ac 100644
--- a/erpnext/translations/no.csv
+++ b/erpnext/translations/no.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantiperioden (i dager),
Auto re-order,Auto re-order,
Reorder level based on Warehouse,Omgjøre nivå basert på Warehouse,
-Will also apply for variants unless overrridden,Vil også gjelde for varianter med mindre overrridden,
+Will also apply for variants unless overridden,Vil også gjelde for varianter med mindre overridden,
Units of Measure,Måleenheter,
Will also apply for variants,Vil også gjelde for varianter,
Serial Nos and Batches,Serienummer og partier,
diff --git a/erpnext/translations/pl.csv b/erpnext/translations/pl.csv
index 9be56f3..df41e39 100644
--- a/erpnext/translations/pl.csv
+++ b/erpnext/translations/pl.csv
@@ -6987,7 +6987,7 @@
Warranty Period (in days),Okres gwarancji (w dniach),
Auto re-order,Automatyczne ponowne zamówienie,
Reorder level based on Warehouse,Zmiana kolejności w oparciu o poziom Magazynu,
-Will also apply for variants unless overrridden,"Również zostanie zastosowany do wariantów, chyba że zostanie nadpisany",
+Will also apply for variants unless overridden,"Również zostanie zastosowany do wariantów, chyba że zostanie nadpisany",
Units of Measure,Jednostki miary,
Will also apply for variants,Również zastosowanie do wariantów,
Serial Nos and Batches,Numery seryjne i partie,
diff --git a/erpnext/translations/ps.csv b/erpnext/translations/ps.csv
index 8033752..5a0b2a5 100644
--- a/erpnext/translations/ps.csv
+++ b/erpnext/translations/ps.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),ګرنټی د دورې (په ورځو),
Auto re-order,د موټرونو د بيا نظم,
Reorder level based on Warehouse,ترمیمي په کچه د پر بنسټ د ګدام,
-Will also apply for variants unless overrridden,مګر overrridden به د بېرغونو هم تر غوږو,
+Will also apply for variants unless overridden,مګر overridden به د بېرغونو هم تر غوږو,
Units of Measure,د اندازه کولو واحدونه,
Will also apply for variants,به هم د بېرغونو درخواست,
Serial Nos and Batches,سریال وځيري او دستو,
diff --git a/erpnext/translations/pt-BR.csv b/erpnext/translations/pt-BR.csv
index 9823470..bc5b616 100644
--- a/erpnext/translations/pt-BR.csv
+++ b/erpnext/translations/pt-BR.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Período de Garantia (em dias),
Auto re-order,Reposição Automática,
Reorder level based on Warehouse,Nível de reposição baseado no Armazén,
-Will also apply for variants unless overrridden,Também se aplica a variantes a não ser que seja sobrescrito,
+Will also apply for variants unless overridden,Também se aplica a variantes a não ser que seja sobrescrito,
Units of Measure,Unidades de Medida,
Will also apply for variants,Também se aplica às variantes,
Serial Nos and Batches,Números de Série e Lotes,
diff --git a/erpnext/translations/pt.csv b/erpnext/translations/pt.csv
index c2afe32..e6846c6 100644
--- a/erpnext/translations/pt.csv
+++ b/erpnext/translations/pt.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Período de Garantia (em dias),
Auto re-order,Voltar a Pedir Autom.,
Reorder level based on Warehouse,Nível de reencomenda no Armazém,
-Will also apply for variants unless overrridden,Também se aplica para as variantes a menos que seja anulado,
+Will also apply for variants unless overridden,Também se aplica para as variantes a menos que seja anulado,
Units of Measure,Unidades de medida,
Will also apply for variants,Também se aplicará para as variantes,
Serial Nos and Batches,Números de série e lotes,
diff --git a/erpnext/translations/ro.csv b/erpnext/translations/ro.csv
index 0cb3f84..ac7e598 100644
--- a/erpnext/translations/ro.csv
+++ b/erpnext/translations/ro.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Perioada de garanție (în zile),
Auto re-order,Re-comandă automată,
Reorder level based on Warehouse,Nivel pentru re-comanda bazat pe Magazie,
-Will also apply for variants unless overrridden,Se va aplica și pentru variantele cu excepția cazului în overrridden,
+Will also apply for variants unless overridden,Se va aplica și pentru variantele cu excepția cazului în overridden,
Units of Measure,Unitati de masura,
Will also apply for variants,"Va aplică, de asemenea pentru variante",
Serial Nos and Batches,Numere și loturi seriale,
diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv
index da4e1be..52c2998 100644
--- a/erpnext/translations/ru.csv
+++ b/erpnext/translations/ru.csv
@@ -6985,7 +6985,7 @@
Warranty Period (in days),Гарантийный период (дней),
Auto re-order,Автоматический перезаказ,
Reorder level based on Warehouse,Уровень переупорядочивания на основе склада,
-Will also apply for variants unless overrridden,"Будет также применяться для модификаций, если не отменено",
+Will also apply for variants unless overridden,"Будет также применяться для модификаций, если не отменено",
Units of Measure,Единицы измерения,
Will also apply for variants,Также применять к модификациям,
Serial Nos and Batches,Серийные номера и партии,
diff --git a/erpnext/translations/rw.csv b/erpnext/translations/rw.csv
index ae0cb3a..f035d57 100644
--- a/erpnext/translations/rw.csv
+++ b/erpnext/translations/rw.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Igihe cya garanti (muminsi),
Auto re-order,Ongera utumire,
Reorder level based on Warehouse,Urwego rwo kwisubiramo rushingiye kububiko,
-Will also apply for variants unless overrridden,Uzasaba kandi kubitandukanye keretse birenze,
+Will also apply for variants unless overridden,Uzasaba kandi kubitandukanye keretse birenze,
Units of Measure,Ibipimo,
Will also apply for variants,Uzasaba kandi kubitandukanye,
Serial Nos and Batches,Urutonde Nomero,
diff --git a/erpnext/translations/si.csv b/erpnext/translations/si.csv
index fa6fbf0..4047263 100644
--- a/erpnext/translations/si.csv
+++ b/erpnext/translations/si.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),වගකීම් කාලය (දින තුළ),
Auto re-order,වාහන නැවත අනුපිළිවෙලට,
Reorder level based on Warehouse,ගබඩාව මත පදනම් මොහොත මට්ටමේ,
-Will also apply for variants unless overrridden,ද overrridden මිස ප්රභේද සඳහා අයදුම් කරනු ඇත,
+Will also apply for variants unless overridden,ද overridden මිස ප්රභේද සඳහා අයදුම් කරනු ඇත,
Units of Measure,නු ඒකක,
Will also apply for variants,ද ප්රභේද සඳහා අයදුම් කරනු ඇත,
Serial Nos and Batches,අනුක්රමික අංක සහ කාණ්ඩ,
diff --git a/erpnext/translations/sk.csv b/erpnext/translations/sk.csv
index 0e51158..98e1663 100644
--- a/erpnext/translations/sk.csv
+++ b/erpnext/translations/sk.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Záruční doba (ve dnech),
Auto re-order,Auto re-order,
Reorder level based on Warehouse,Úroveň Zmena poradia na základe Warehouse,
-Will also apply for variants unless overrridden,"Bude platiť aj pre varianty, pokiaľ nebude prepísané",
+Will also apply for variants unless overridden,"Bude platiť aj pre varianty, pokiaľ nebude prepísané",
Units of Measure,merné jednotky,
Will also apply for variants,Bude platiť aj pre varianty,
Serial Nos and Batches,Sériové čísla a dávky,
diff --git a/erpnext/translations/sl.csv b/erpnext/translations/sl.csv
index 7c4e11b..5380714 100644
--- a/erpnext/translations/sl.csv
+++ b/erpnext/translations/sl.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garancijski rok (v dnevih),
Auto re-order,Auto re-order,
Reorder level based on Warehouse,Raven Preureditev temelji na Warehouse,
-Will also apply for variants unless overrridden,Bo veljalo tudi za variante razen overrridden,
+Will also apply for variants unless overridden,Bo veljalo tudi za variante razen overridden,
Units of Measure,Merske enote,
Will also apply for variants,Bo veljalo tudi za variante,
Serial Nos and Batches,Serijska št in Serije,
diff --git a/erpnext/translations/sq.csv b/erpnext/translations/sq.csv
index 0f10795..2a893d2 100644
--- a/erpnext/translations/sq.csv
+++ b/erpnext/translations/sq.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garanci Periudha (në ditë),
Auto re-order,Auto ri-qëllim,
Reorder level based on Warehouse,Niveli Reorder bazuar në Magazina,
-Will also apply for variants unless overrridden,Gjithashtu do të aplikojë për variantet nëse overrridden,
+Will also apply for variants unless overridden,Gjithashtu do të aplikojë për variantet nëse overridden,
Units of Measure,Njësitë e masës,
Will also apply for variants,Gjithashtu do të aplikojë për variantet,
Serial Nos and Batches,Serial Nr dhe Batches,
diff --git a/erpnext/translations/sr.csv b/erpnext/translations/sr.csv
index 38116ec..c1e5eb0 100644
--- a/erpnext/translations/sr.csv
+++ b/erpnext/translations/sr.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Гарантни период (у данима),
Auto re-order,Ауто поново реда,
Reorder level based on Warehouse,Промени редослед ниво на основу Варехоусе,
-Will also apply for variants unless overrridden,Ће конкурисати и за варијанте осим оверрридден,
+Will also apply for variants unless overridden,Ће конкурисати и за варијанте осим оверрридден,
Units of Measure,Мерних јединица,
Will also apply for variants,Ће конкурисати и за варијанте,
Serial Nos and Batches,Сериал Нос анд Пакети,
diff --git a/erpnext/translations/sv.csv b/erpnext/translations/sv.csv
index c4d46ea..8b4ab06 100644
--- a/erpnext/translations/sv.csv
+++ b/erpnext/translations/sv.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Garantitiden (i dagar),
Auto re-order,Auto återbeställning,
Reorder level based on Warehouse,Beställningsnivå baserat på Warehouse,
-Will also apply for variants unless overrridden,Kommer också att ansöka om varianter såvida inte överskriden,
+Will also apply for variants unless overridden,Kommer också att ansöka om varianter såvida inte överskriden,
Units of Measure,Måttenheter,
Will also apply for variants,Kommer också att ansöka om varianter,
Serial Nos and Batches,Serienummer och partier,
diff --git a/erpnext/translations/sw.csv b/erpnext/translations/sw.csv
index ad144f0..fa2287c 100644
--- a/erpnext/translations/sw.csv
+++ b/erpnext/translations/sw.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Kipindi cha udhamini (katika siku),
Auto re-order,Rejesha upya,
Reorder level based on Warehouse,Weka upya ngazi kulingana na Ghala,
-Will also apply for variants unless overrridden,Pia itatumika kwa vipengee isipokuwa imeingizwa,
+Will also apply for variants unless overridden,Pia itatumika kwa vipengee isipokuwa imeingizwa,
Units of Measure,Units of Measure,
Will also apply for variants,Pia itatumika kwa vipengee,
Serial Nos and Batches,Serial Nos na Batches,
diff --git a/erpnext/translations/ta.csv b/erpnext/translations/ta.csv
index 5ccabc0..6eaae34 100644
--- a/erpnext/translations/ta.csv
+++ b/erpnext/translations/ta.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),உத்தரவாதத்தை காலம் (நாட்கள்),
Auto re-order,வாகன மறு ஒழுங்கு,
Reorder level based on Warehouse,கிடங்கில் அடிப்படையில் மறுவரிசைப்படுத்துக நிலை,
-Will also apply for variants unless overrridden,Overrridden வரை கூட வகைகளில் விண்ணப்பிக்க,
+Will also apply for variants unless overridden,Overrridden வரை கூட வகைகளில் விண்ணப்பிக்க,
Units of Measure,அளவின் அலகுகள்,
Will also apply for variants,கூட வகைகளில் விண்ணப்பிக்க,
Serial Nos and Batches,சீரியல் எண்கள் மற்றும் தொகுப்புகளும்,
diff --git a/erpnext/translations/te.csv b/erpnext/translations/te.csv
index 8472163..d3f739a 100644
--- a/erpnext/translations/te.csv
+++ b/erpnext/translations/te.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(రోజుల్లో) వారంటీ వ్యవధి,
Auto re-order,ఆటో క్రమాన్ని,
Reorder level based on Warehouse,వేర్హౌస్ ఆధారంగా క్రమాన్ని స్థాయి,
-Will also apply for variants unless overrridden,Overrridden తప్ప కూడా రూపాంతరాలు వర్తిస్తాయని,
+Will also apply for variants unless overridden,Overrridden తప్ప కూడా రూపాంతరాలు వర్తిస్తాయని,
Units of Measure,యూనిట్స్ ఆఫ్ మెజర్,
Will also apply for variants,కూడా రూపాంతరాలు వర్తిస్తాయని,
Serial Nos and Batches,సీరియల్ Nos మరియు ఇస్తున్న,
diff --git a/erpnext/translations/th.csv b/erpnext/translations/th.csv
index dcd632b..a065595 100644
--- a/erpnext/translations/th.csv
+++ b/erpnext/translations/th.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),ระยะเวลารับประกัน (วัน),
Auto re-order,Auto สั่งซื้อใหม่,
Reorder level based on Warehouse,ระดับสั่งซื้อใหม่บนพื้นฐานของคลังสินค้า,
-Will also apply for variants unless overrridden,นอกจากนี้ยังจะใช้สำหรับสายพันธุ์เว้นแต่ overrridden,
+Will also apply for variants unless overridden,นอกจากนี้ยังจะใช้สำหรับสายพันธุ์เว้นแต่ overridden,
Units of Measure,หน่วยวัด,
Will also apply for variants,นอกจากนี้ยังจะใช้สำหรับสายพันธุ์,
Serial Nos and Batches,หมายเลขและชุดเลขที่ผลิตภัณฑ์,
diff --git a/erpnext/translations/tr.csv b/erpnext/translations/tr.csv
index 3708246..9e916f0 100644
--- a/erpnext/translations/tr.csv
+++ b/erpnext/translations/tr.csv
@@ -7051,7 +7051,7 @@
Warranty Period (in days),Garanti Süresi (gün),
Auto re-order,Otomatik Yeniden Sipariş,
Reorder level based on Warehouse,Depo bazlı Yeniden sipariş seviyesi,
-Will also apply for variants unless overrridden,Geçersiz kılınmadığı sürece varyantlar için de geçerli olacaktır.,
+Will also apply for variants unless overridden,Geçersiz kılınmadığı sürece varyantlar için de geçerli olacaktır.,
Units of Measure,Ölçü Birimleri,
Will also apply for variants,Varyantlar için de geçerli olacak,
Serial Nos and Batches,Seri No ve Batches (Parti),
diff --git a/erpnext/translations/uk.csv b/erpnext/translations/uk.csv
index 1752330..8d1fb04 100644
--- a/erpnext/translations/uk.csv
+++ b/erpnext/translations/uk.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Гарантійний термін (в днях),
Auto re-order,Авто-дозамовлення,
Reorder level based on Warehouse,Рівень перезамовлення по складу,
-Will also apply for variants unless overrridden,"Буде також застосовуватися для варіантів, якщо не перевказано",
+Will also apply for variants unless overridden,"Буде також застосовуватися для варіантів, якщо не перевказано",
Units of Measure,одиниці виміру,
Will also apply for variants,Буде також застосовуватися для варіантів,
Serial Nos and Batches,Серійні номери і Порції,
diff --git a/erpnext/translations/ur.csv b/erpnext/translations/ur.csv
index 504cc8d..649c1c7 100644
--- a/erpnext/translations/ur.csv
+++ b/erpnext/translations/ur.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),(دن میں) وارنٹی مدت,
Auto re-order,آٹو دوبارہ آرڈر,
Reorder level based on Warehouse,گودام کی بنیاد پر ترتیب کی سطح کو منتخب,
-Will also apply for variants unless overrridden,overrridden جب تک بھی مختلف حالتوں کے لئے لاگو ہوں گے,
+Will also apply for variants unless overridden,overridden جب تک بھی مختلف حالتوں کے لئے لاگو ہوں گے,
Units of Measure,پیمائش کی اکائیوں,
Will also apply for variants,بھی مختلف حالتوں کے لئے لاگو ہوں گے,
Serial Nos and Batches,سیریل نمبر اور بیچوں,
diff --git a/erpnext/translations/uz.csv b/erpnext/translations/uz.csv
index 6b25e7b..5ca51cc 100644
--- a/erpnext/translations/uz.csv
+++ b/erpnext/translations/uz.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Kafolat muddati (kunlar),
Auto re-order,Avtomatik buyurtma,
Reorder level based on Warehouse,Qoidalarga asoslangan qayta tartiblash,
-Will also apply for variants unless overrridden,"Variantlar uchun ham qo'llanilmaydi, agar bekor qilinsa",
+Will also apply for variants unless overridden,"Variantlar uchun ham qo'llanilmaydi, agar bekor qilinsa",
Units of Measure,O'lchov birliklari,
Will also apply for variants,"Shuningdek, variantlar uchun ham qo'llaniladi",
Serial Nos and Batches,Seriya nos va paketlar,
diff --git a/erpnext/translations/vi.csv b/erpnext/translations/vi.csv
index e576ef7..7a005fa 100644
--- a/erpnext/translations/vi.csv
+++ b/erpnext/translations/vi.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),Thời gian bảo hành (trong...ngày),
Auto re-order,Auto lại trật tự,
Reorder level based on Warehouse,mức đèn đỏ mua vật tư (phải bổ xung hoặc đặt mua thêm),
-Will also apply for variants unless overrridden,Cũng sẽ được áp dụng cho các biến thể trừ phần bị ghi đèn,
+Will also apply for variants unless overridden,Cũng sẽ được áp dụng cho các biến thể trừ phần bị ghi đèn,
Units of Measure,Đơn vị đo lường,
Will also apply for variants,Cũng sẽ được áp dụng cho các biến thể,
Serial Nos and Batches,Số hàng loạt và hàng loạt,
diff --git a/erpnext/translations/zh-TW.csv b/erpnext/translations/zh-TW.csv
index 699d802..0f76e97 100644
--- a/erpnext/translations/zh-TW.csv
+++ b/erpnext/translations/zh-TW.csv
@@ -2964,7 +2964,7 @@
Code {0} already exist,代碼{0}已經存在
Sales orders are not available for production,銷售訂單不可用於生產
Fixed Asset Depreciation Settings,固定資產折舊設置
-Will also apply for variants unless overrridden,同時將申請變種,除非overrridden,
+Will also apply for variants unless overridden,同時將申請變種,除非overridden,
Advances,進展
Manufacture against Material Request,對製造材料要求
Assessment Group: ,評估組:
diff --git a/erpnext/translations/zh.csv b/erpnext/translations/zh.csv
index 70ec81a..cf89dc6 100644
--- a/erpnext/translations/zh.csv
+++ b/erpnext/translations/zh.csv
@@ -7052,7 +7052,7 @@
Warranty Period (in days),保修期限(天数),
Auto re-order,自动重订货,
Reorder level based on Warehouse,根据仓库订货点水平,
-Will also apply for variants unless overrridden,除非手动指定,否则会同时应用于变体,
+Will also apply for variants unless overridden,除非手动指定,否则会同时应用于变体,
Units of Measure,计量单位,
Will also apply for variants,会同时应用于变体,
Serial Nos and Batches,序列号和批号,
@@ -8743,3 +8743,4 @@
Make a call,打个电话,
Approve,同意,
Reject,拒绝,
+Stock,库存,
diff --git a/erpnext/translations/zh_tw.csv b/erpnext/translations/zh_tw.csv
index 75157f0..8ecbaaa 100644
--- a/erpnext/translations/zh_tw.csv
+++ b/erpnext/translations/zh_tw.csv
@@ -7485,7 +7485,7 @@
Warranty Period (in days),保修期限(天數),
Auto re-order,自動重新排序,
Reorder level based on Warehouse,根據倉庫訂貨點水平,
-Will also apply for variants unless overrridden,同時將申請變種,除非overrridden,
+Will also apply for variants unless overridden,同時將申請變種,除非overridden,
Units of Measure,測量的單位,
Will also apply for variants,同時將申請變種,
Serial Nos and Batches,序列號和批號,
diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py
index 5e57b31..fcee265 100644
--- a/erpnext/utilities/bulk_transaction.py
+++ b/erpnext/utilities/bulk_transaction.py
@@ -192,9 +192,7 @@
record = 0
for d in log_doc.get("logger_data"):
if d.transaction_name == doc_name and d.transaction_status == "Failed":
- d.retried = 1
+ frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1)
record = record + 1
- log_doc.save()
-
return record