diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py index 5d94a08f2f..04dab4c28a 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 @@ class AutoMatchbyPartyNameDescription: 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 @@ class AutoMatchbyPartyNameDescription: 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 @@ class AutoMatchbyPartyNameDescription: 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 @@ class AutoMatchbyPartyNameDescription: 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 0203c45058..b3ae627c6e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -154,7 +154,7 @@ frappe.ui.form.on('Payment Entry', { 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 9cf2ac6c2a..28c9529995 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 1626f25f3e..43167be15a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -109,6 +109,8 @@ class PaymentReconciliation(Document): "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 @@ class PaymentReconciliation(Document): 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 @@ class PaymentReconciliation(Document): 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 @@ class PaymentReconciliation(Document): 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 1131a0fca6..b4ac9812cb 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 2d1f4451b6..09bffff6da 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 13593bcf9b..171cc0ccdf 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 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): 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 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): )[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 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): 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 3a87a380d1..c7b7a148cf 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 @@ frappe.ui.form.on("Repost Accounting Ledger", { 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 dbb0971fde..69cfe9fcd7 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 @@ from frappe.utils.data import comma_and 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 @@ def start_repost(account_repost_doc=str) -> None: 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 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs): 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 0e75dd2e3e..dda0ec778f 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 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): 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% rename from erpnext/projects/report/employee_billing_summary/__init__.py rename 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 0000000000..8c83ca5043 --- /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 0000000000..8aa0a840c7 --- /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 0000000000..2b8230df86 --- /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 0000000000..ec4e87ffc0 --- /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/project_billing_summary/__init__.py b/erpnext/accounts/doctype/repost_allowed_types/__init__.py similarity index 100% rename from erpnext/projects/report/project_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 0000000000..ede12fbc18 --- /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 0000000000..0e4883b0c9 --- /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 e5adeae501..cd725b9862 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 f6d9c93261..fa95ccdc57 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -253,6 +253,7 @@ class SalesInvoice(SellingController): 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 @@ class SalesInvoice(SellingController): 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 @@ class SalesInvoice(SellingController): 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 @@ class SalesInvoice(SellingController): ) 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 310e41208f..16e73ea52f 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_account_currency(party_type, party, company): 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 8fa81cd9dc..10362dba3d 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -148,7 +148,13 @@ frappe.query_reports["Accounts Payable"] = { "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 @@ function get_party_type_options() { }); }); 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 c49b67a727..d06b259474 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -177,7 +177,13 @@ frappe.query_reports["Accounts Receivable"] = { "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 @@ function get_party_type_options() { }); }); 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 e8cf91507b..e42d325bf1 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -119,7 +119,12 @@ class ReceivablePayableReport(object): # 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 @@ class ReceivablePayableReport(object): ): 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 @@ class ReceivablePayableReport(object): 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 @@ class ReceivablePayableReport(object): query = ( qb.from_(ple) .select( + ple.name, ple.account, ple.voucher_type, ple.voucher_no, @@ -735,13 +750,15 @@ class ReceivablePayableReport(object): 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 099884a48e..696a03b0a7 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 @@ class General_Payment_Ledger_Comparison(object): .select( gle.company, gle.account, + gle.voucher_type, gle.voucher_no, + gle.party_type, gle.party, outstanding, ) @@ -89,7 +91,9 @@ class General_Payment_Ledger_Comparison(object): & (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 @@ class General_Payment_Ledger_Comparison(object): 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 @@ class General_Payment_Ledger_Comparison(object): & (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 @@ class General_Payment_Ledger_Comparison(object): 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 @@ class General_Payment_Ledger_Comparison(object): 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, } @@ -162,12 +178,52 @@ class General_Payment_Ledger_Comparison(object): def get_columns(self): self.columns = [] 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 @@ class General_Payment_Ledger_Comparison(object): 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 4b0e99d712..59e906ba33 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 @@ class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin): 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 37d0659acf..c0b4f59579 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -193,7 +193,13 @@ frappe.query_reports["General Ledger"] = { "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 79bfd7833a..5e484cf558 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -163,6 +163,9 @@ def get_gl_entries(filters, accounting_dimensions): 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 @@ def get_gl_entries(filters, accounting_dimensions): 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 @@ def get_columns(filters): "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 4a3d9bb479..b6bbd979ed 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -32,13 +32,6 @@ frappe.query_reports["Profitability Analysis"] = { "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 eac5426b8b..e842d2e8dc 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 @@ def get_result( 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 @@ def get_result( 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 @@ def get_result( 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 @@ def get_result( "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_gle_map(documents): 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 @@ def get_columns(filters): 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 @@ def get_columns(filters): "options": "transaction_type", "width": 180, }, + { + "label": _("Date of Transaction"), + "fieldname": "transaction_date", + "fieldtype": "Date", + "width": 100, + }, ] ) @@ -253,27 +276,7 @@ def get_tds_docs(filters): "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(filters): ) +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 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): "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 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): 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 1c7052f8ff..e0adac412b 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -10,7 +10,7 @@ import frappe.defaults 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 @@ def check_if_advance_entry_modified(args): ) 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 @@ def check_if_advance_entry_modified(args): .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 f0e4c82048..d378fbd26a 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', { 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 9d35634933..3c570d1af0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -818,7 +818,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount): "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 e2a4b2909a..84a428ca54 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -780,6 +780,15 @@ def get_disposal_account_and_cost_center(company): 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 d69f5ef0b7..9e3ec6faa8 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -755,7 +755,9 @@ class TestDepreciationMethods(AssetSetup): 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 @@ class TestDepreciationMethods(AssetSetup): 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 @@ def create_asset(**args): "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 3772ef4d68..8d8b46321f 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 7a88ffc5b7..7305691f97 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 @@ class AssetDepreciationSchedule(Document): 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 @@ def get_straight_line_or_manual_depr_amount( ) # 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 @@ def get_straight_line_or_manual_depr_amount( ) / 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 2c27dc9aca..e597d5fe31 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", @@ -86,23 +86,23 @@ "fieldtype": "Percent", "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 23088c9ccf..a33acfd833 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 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu 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 @@ class TestAssetMaintenance(unittest.TestCase): }, ) - 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 get_maintenance_tasks(): ] -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 0000000000..8306fad6cb --- /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 b1da97d634..2b6ffb752f 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 f37db5f115..60dd54c238 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 fd73b870c5..579c0a65ad 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 @@ frappe.query_reports["Supplier Quotation Comparison"] = { } } } - else { - return { - filters: { "disabled": 0 } - } - } } }, { diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6efe631a29..38c1e820d2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2269,6 +2269,7 @@ class AccountsController(TransactionBase): 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 a76abe2154..ece08d83f9 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 @@ class BuyingController(SubcontractingController): 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 @@ class BuyingController(SubcontractingController): 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 73a248fb53..d09001c8fc 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -47,15 +47,15 @@ status_map = { ], [ "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 a7330ec63c..fc45c7ad52 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1210,8 +1210,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa 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 510317f5c2..dfef223c43 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -192,26 +192,6 @@ "onboard": 0, "type": "Card Break" }, - { - "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, @@ -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 f4877fdca0..9e32085351 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 @@ frappe.views.calendar["Job Card"] = { }, 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 5d883bf9fa..99fca9570f 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 4a0041662b..49386c4ebc 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 ddd9375211..6b12a29b50 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -490,6 +490,12 @@ class ProductionPlan(Document): 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 @@ class ProductionPlan(Document): 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 @@ class ProductionPlan(Document): 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 @@ def get_reserved_qty_for_production_plan(item_code, warehouse): 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 @@ def get_raw_materials_of_sub_assembly_items( 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 @@ def sales_order_query( 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 6ab9232788..e9c6ee3af2 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 @@ class TestProductionPlan(FrappeTestCase): 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 @@ class TestProductionPlan(FrappeTestCase): 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 @@ def create_production_plan(**args): "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 fde0404c01..aff740b732 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", @@ -52,13 +51,6 @@ "fieldtype": "Section Break", "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", @@ -161,12 +154,6 @@ "label": "Target Warehouse", "options": "Warehouse" }, - { - "fieldname": "produced_qty", - "fieldtype": "Data", - "label": "Produced Quantity", - "read_only": 1 - }, { "default": "In House", "fieldname": "type_of_manufacturing", @@ -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 c828c878eb..0ae7657c42 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 @@ class TestWorkOrder(FrappeTestCase): "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 58945bba77..d9cc212e8c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -710,7 +710,7 @@ erpnext.work_order = { 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 f9fddcbb5e..36a0cae5cc 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -293,6 +293,7 @@ class WorkOrder(Document): 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 @@ class WorkOrder(Document): ) 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 34edb9d538..8729775dc2 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 @@ frappe.query_reports["BOM Operations Time"] = { "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 d7f33adeea..d394db6d96 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -343,5 +343,11 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item 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 0000000000..6cafc66aab --- /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 0000000000..63dc0e09bc --- /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 0000000000..2c03c23d87 --- /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 0000000000..fd0a23333e --- /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 ac1524a49d..0000000000 --- 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 2c25465a61..0000000000 --- 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.json b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json deleted file mode 100644 index e5626a0206..0000000000 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "add_total_row": 1, - "creation": "2019-03-08 15:08:19.929728", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2019-06-13 15:54:49.213973", - "modified_by": "Administrator", - "module": "Projects", - "name": "Employee Billing Summary", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Timesheet", - "report_name": "Employee 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/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py deleted file mode 100644 index a2f7378d1b..0000000000 --- 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 fce0c68f11..0000000000 --- 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.py b/erpnext/projects/report/project_billing_summary/project_billing_summary.py deleted file mode 100644 index a2f7378d1b..0000000000 --- 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/timesheet_billing_summary/__init__.py b/erpnext/projects/report/timesheet_billing_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 0000000000..1efd0c6733 --- /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/project_billing_summary/project_billing_summary.json b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json similarity index 61% rename from erpnext/projects/report/project_billing_summary/project_billing_summary.json rename to erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.json index 817d0cdb66..0f070cb457 100644 --- a/erpnext/projects/report/project_billing_summary/project_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-11 16:22:39.460524", - "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:55.255947", + "letter_head": "ALYF GmbH", + "letterhead": null, + "modified": "2023-10-11 00:58:30.639078", "modified_by": "Administrator", "module": "Projects", - "name": "Project Billing Summary", + "name": "Timesheet Billing Summary", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "Timesheet", - "report_name": "Project 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 0000000000..a6e7150e41 --- /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 94ae9c04a4..e6bead9ff4 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 354552137b..7879173cd1 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -30,7 +30,6 @@ erpnext.accounts.taxes = { 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 fd2b6a4eaa..79fd2ebdbe 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 @@ frappe.ui.form.on('Quality Procedure', { 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 e8604080fb..6834abc9d4 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 @@ class QualityProcedure(NestedSet): 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 @@ class QualityProcedure(NestedSet): ) 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 @@ class QualityProcedure(NestedSet): 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 04e8211214..467186debd 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 40cab9f330..3b97123113 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 94f9d6e37c..e4f1a28316 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -217,7 +217,15 @@ class SalesOrder(SellingController): 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 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): 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 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): "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 23b93dc161..1bd469b956 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -40,7 +40,7 @@ frappe.ui.form.on("Company", { 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 a11572776a..312470d50e 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,20 +120,9 @@ { "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", @@ -140,17 +133,6 @@ "options": "UOM", "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", @@ -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 5abea9e69f..8b2e5cf9ec 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -34,10 +34,15 @@ class Bin(Document): 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 @@ class Bin(Document): 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 @@ class Bin(Document): 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 190575eb94..66dd33a400 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -365,6 +365,9 @@ class DeliveryNote(SellingController): # 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 @@ class DeliveryNote(SellingController): # 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 1eecf6dc2a..137c352e99 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 @@ class TestDeliveryNote(FrappeTestCase): 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 @@ class TestDeliveryNote(FrappeTestCase): 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 ce489ff52b..8a4b4eef0a 100644 --- a/erpnext/stock/doctype/item_price/item_price.js +++ b/erpnext/stock/doctype/item_price/item_price.js @@ -6,7 +6,6 @@ frappe.ui.form.on("Item Price", { 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 7f0dc2df9f..8bbc660899 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 @@ class LandedCostVoucher(Document): ) 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 2a4b6f34b5..cbc1693eaa 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -600,11 +600,10 @@ class PurchaseReceipt(BuyingController): 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 f5240a6094..718f007577 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 980f49a80a..2da99f616a 100644 --- a/erpnext/stock/doctype/quality_inspection_template/test_records.json +++ b/erpnext/stock/doctype/quality_inspection_template/test_records.json @@ -1,4 +1,8 @@ [ + { + "doctype": "Quality Inspection Parameter", + "parameter" : "_Test Param" + }, { "quality_inspection_template_name" : "_Test Quality Inspection Template", "doctype": "Quality Inspection Template", 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 1853f45f58..5b76e442f4 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 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): 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 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): 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 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): 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 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): 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 f96c184c88..f2bbf2b211 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 @@ class SerialandBatchBundle(Document): 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 @@ class SerialandBatchBundle(Document): 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 @@ class SerialandBatchBundle(Document): 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 @@ class SerialandBatchBundle(Document): 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 c41349fcfb..b06de2e2fc 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -161,7 +161,7 @@ class StockEntry(StockController): 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 @@ class StockEntry(StockController): 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 3e0610ef6e..b640983a09 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 @@ class TestStockEntry(FrappeTestCase): 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 5452692a24..b3998b7c7e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -123,13 +123,6 @@ frappe.ui.form.on("Stock Reconciliation", { 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 98b4ffdfcf..323ad4f57d 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -6,7 +6,7 @@ from typing import Optional 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 @@ class StockReconciliation(StockController): 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 @@ class StockReconciliation(StockController): "warehouse": item.warehouse, "posting_date": self.posting_date, "posting_time": self.posting_time, + "ignore_voucher_nos": [self.name], } ) ) @@ -163,11 +167,36 @@ class StockReconciliation(StockController): ) 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 @@ class StockReconciliation(StockController): 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 @@ class StockReconciliation(StockController): 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 4817c8d8dc..1ec99bf9a5 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 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): 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 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): 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 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): 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 ca19bbb96a..d9cbf95710 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 7c712ce225..68afd996b4 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 6b39965f9b..09542826f3 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.model.document import Document 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 @@ class StockReservationEntry(Document): 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 @@ class StockReservationEntry(Document): 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 @@ class StockReservationEntry(Document): 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 @@ def get_sre_reserved_qty_for_voucher_detail_no( 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 f4c74a8aac..dd023e2080 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 @@ class TestStockReservationEntry(FrappeTestCase): 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 @@ class TestStockReservationEntry(FrappeTestCase): "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 @@ class TestStockReservationEntry(FrappeTestCase): "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 @@ class TestStockReservationEntry(FrappeTestCase): # 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 f00dd3e791..90b8d45342 100644 --- a/erpnext/stock/page/stock_balance/stock_balance.js +++ b/erpnext/stock/page/stock_balance/stock_balance.js @@ -11,6 +11,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { 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 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { 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 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) { 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 eeef39641b..e59f2fe644 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -249,6 +249,13 @@ def get_columns(filters): "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 @@ def get_stock_ledger_entries(filters, items): 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 ca15afe444..fb392f7e36 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 @@ SLE_FIELDS = ( "stock_value_difference", "valuation_rate", "voucher_detail_no", + "serial_and_batch_bundle", ) @@ -64,7 +65,11 @@ def add_invariant_check_fields(sles): 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 @@ -143,6 +148,12 @@ def get_columns(): "label": _("Batch"), "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", diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b950f18810..e9381d42b9 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -11,17 +11,22 @@ from frappe import _, scrub 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 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc 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 @@ def repost_current_voucher(args, allow_negative_stock=False, via_landed_cost_vou "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 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): 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 @@ class update_entries_after(object): 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 @@ class update_entries_after(object): 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 @@ class update_entries_after(object): 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 @@ def get_valuation_rate( currency=None, company=None, raise_error_if_no_rate=True, + batch_no=None, serial_and_batch_bundle=None, ): @@ -1469,6 +1479,25 @@ def get_valuation_rate( 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 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): 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 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): 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 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): 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 get_future_sle_with_negative_batch_qty(args): ) +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 += "
" + 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 d4daacd4ea..f96823b290 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 9fe4338477..3b8698f4ab 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -14,18 +14,16 @@ {% block style %} {% endblock %} {% block page_content %}
-
Status: {{ doc.status }}
-
Progress: {{ doc.percent_complete }}%
-
Hours Spent: {{ doc.actual_time | round }}
+
{{ _("Status") }}: {{ _(doc.status) }}
+
{{ _("Progress") }}: {{ doc.get_formatted("percent_complete") }}
+
{{ _("Hours Spent") }}: {{ doc.get_formatted("actual_time") }}
@@ -34,7 +32,7 @@
-

Tasks

+

{{ _("Tasks") }}

{{ _("New task") }}
@@ -44,39 +42,39 @@
-
Tasks
-
Status
-
End Date
-
Assignment
-
Modified On
+
{{ _("Tasks") }}
+
{{ _("Status") }}
+
{{ _("End Date") }}
+
{{ _("Assignment") }}
+
{{ _("Modified On") }}
{% include "erpnext/templates/includes/projects/project_tasks.html" %}
{% else %} - {{ empty_state('Task')}} + {{ empty_state(_("Task")) }} {% endif %} -

Timesheets

+

{{ _("Timesheets") }}

{% if doc.timesheets %}
-
Timesheet
-
Status
-
From
-
To
-
Modified By
-
Modified On
+
{{ _("Timesheet") }}
+
{{ _("Status") }}
+
{{ _("From") }}
+
{{ _("To") }}
+
{{ _("Modified By") }}
+
{{ _("Modified On") }}
{% include "erpnext/templates/includes/projects/project_timesheets.html" %}
{% else %} - {{ empty_state('Timesheet')}} + {{ empty_state(_("Timesheet")) }} {% endif %} {% if doc.attachments %} @@ -113,7 +111,7 @@ {% macro progress_bar(percent_complete) %} {% if percent_complete %} - Project Progress: + {{ _("Project Progress:") }}
Generic Empty State
-

You haven't created a {{ section_name }} yet

+

{{ _("You haven't created a {0} yet").format(section_name) }}

diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv index d4b823d995..f45731468d 100644 --- a/erpnext/translations/af.csv +++ b/erpnext/translations/af.csv @@ -7052,7 +7052,7 @@ Moving Average,Beweeg gemiddeld, 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 764868d8dc..0453d5d685 100644 --- a/erpnext/translations/am.csv +++ b/erpnext/translations/am.csv @@ -7052,7 +7052,7 @@ Moving Average,በመውሰድ ላይ አማካኝ, 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 4e03a18a5c..67b409e7ef 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -7052,7 +7052,7 @@ Moving Average,المتوسط المتحرك, 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 8dff755941..787f81ee7f 100644 --- a/erpnext/translations/bg.csv +++ b/erpnext/translations/bg.csv @@ -7052,7 +7052,7 @@ Moving Average,Пълзяща средна стойност, 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 8a698dfca4..69fd08cb51 100644 --- a/erpnext/translations/bn.csv +++ b/erpnext/translations/bn.csv @@ -7052,7 +7052,7 @@ Moving Average,চলন্ত গড়, 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 7ba4a883cb..ef680a36ad 100644 --- a/erpnext/translations/bs.csv +++ b/erpnext/translations/bs.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 cce1f3a13b..fa545a4c19 100644 --- a/erpnext/translations/ca.csv +++ b/erpnext/translations/ca.csv @@ -7052,7 +7052,7 @@ Moving Average,Mitjana Mòbil, 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 1072ed31fc..7fb1679f9c 100644 --- a/erpnext/translations/cs.csv +++ b/erpnext/translations/cs.csv @@ -7052,7 +7052,7 @@ Moving Average,Klouzavý průměr, 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 270a7104ba..96de062059 100644 --- a/erpnext/translations/cz.csv +++ b/erpnext/translations/cz.csv @@ -1991,7 +1991,7 @@ Maintenance start date can not be before delivery date for Serial No {0},Datum z 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 138da5d1f2..4eb39609f2 100644 --- a/erpnext/translations/da.csv +++ b/erpnext/translations/da.csv @@ -7052,7 +7052,7 @@ Moving Average,Glidende gennemsnit, 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 79b9574239..c627f81984 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -3340,7 +3340,7 @@ Cannot Calculate Arrival Time as Driver Address is Missing.,"Die Ankunftszeit ka 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 items you want to buy from sellers.,"Auf dieser Seite w 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 @@ Mandatory For Profit and Loss Account,Obligatorisch für Gewinn- und Verlustrech 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 @@ Moving Average,Gleitender Durchschnitt, 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 @@ Template Title,Vorlagentitel, 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 @@ Global Defaults,Allgemeine Voreinstellungen, 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 7a83cc5dcd..21fb435901 100644 --- a/erpnext/translations/el.csv +++ b/erpnext/translations/el.csv @@ -7052,7 +7052,7 @@ Moving Average,Κινητός μέσος, 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 fadf7a78e2..2abe418707 100644 --- a/erpnext/translations/es.csv +++ b/erpnext/translations/es.csv @@ -7052,7 +7052,7 @@ Moving Average,Precio medio variable, 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 4e26a82f85..a4a8736006 100644 --- a/erpnext/translations/et.csv +++ b/erpnext/translations/et.csv @@ -7052,7 +7052,7 @@ Moving Average,Libisev keskmine, 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 530965d8cf..bd40c8b396 100644 --- a/erpnext/translations/fa.csv +++ b/erpnext/translations/fa.csv @@ -7052,7 +7052,7 @@ Moving Average,میانگین متحرک, 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 6e9380cf74..33cf1574ae 100644 --- a/erpnext/translations/fi.csv +++ b/erpnext/translations/fi.csv @@ -7052,7 +7052,7 @@ Moving Average,Liukuva keskiarvo, 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 d3875c1132..d15af74d47 100644 --- a/erpnext/translations/fr.csv +++ b/erpnext/translations/fr.csv @@ -6650,7 +6650,7 @@ Moving Average,Moyenne Mobile, 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 e2de8ce96d..06a3cc64af 100644 --- a/erpnext/translations/gu.csv +++ b/erpnext/translations/gu.csv @@ -7052,7 +7052,7 @@ Moving Average,ખસેડવું સરેરાશ, 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 6cd900a7cf..d5fcab6454 100644 --- a/erpnext/translations/he.csv +++ b/erpnext/translations/he.csv @@ -7052,7 +7052,7 @@ Moving Average,ממוצע נע, 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 388b502c11..a5caa666c2 100644 --- a/erpnext/translations/hi.csv +++ b/erpnext/translations/hi.csv @@ -7052,7 +7052,7 @@ Moving Average,चलायमान औसत, 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 b44babc7eb..2834602248 100644 --- a/erpnext/translations/hr.csv +++ b/erpnext/translations/hr.csv @@ -7052,7 +7052,7 @@ Moving Average,Prosječna ponderirana cijena, 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 4ea5b9a714..a262c8a994 100644 --- a/erpnext/translations/hu.csv +++ b/erpnext/translations/hu.csv @@ -7052,7 +7052,7 @@ Moving Average,Mozgóátlag, 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 d84c3fdcd6..c4e50fdfe5 100644 --- a/erpnext/translations/id.csv +++ b/erpnext/translations/id.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 7687e4a680..50c06ecfbb 100644 --- a/erpnext/translations/is.csv +++ b/erpnext/translations/is.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 b88cadad36..37608958c3 100644 --- a/erpnext/translations/it.csv +++ b/erpnext/translations/it.csv @@ -7052,7 +7052,7 @@ Moving Average,Media Mobile, 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 11455bdf7d..888ec800c9 100644 --- a/erpnext/translations/ja.csv +++ b/erpnext/translations/ja.csv @@ -7052,7 +7052,7 @@ Moving Average,移動平均, 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 46dcabaa86..d2003c004e 100644 --- a/erpnext/translations/km.csv +++ b/erpnext/translations/km.csv @@ -7052,7 +7052,7 @@ Moving Average,ជាមធ្យមការផ្លាស់ប្តូរ, 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 18e44a1bb2..72066711ca 100644 --- a/erpnext/translations/kn.csv +++ b/erpnext/translations/kn.csv @@ -7052,7 +7052,7 @@ Moving Average,ಸರಾಸರಿ ಮೂವಿಂಗ್, 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 788655c044..99119256c1 100644 --- a/erpnext/translations/ko.csv +++ b/erpnext/translations/ko.csv @@ -7052,7 +7052,7 @@ Moving Average,움직임 평균, 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 a7fcf4e1ed..8fec05993d 100644 --- a/erpnext/translations/ku.csv +++ b/erpnext/translations/ku.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 9b74f655b2..0831788651 100644 --- a/erpnext/translations/lo.csv +++ b/erpnext/translations/lo.csv @@ -7052,7 +7052,7 @@ Moving Average,ການເຄື່ອນຍ້າຍໂດຍສະເລ່ 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 2c87256d2a..82152754e8 100644 --- a/erpnext/translations/lt.csv +++ b/erpnext/translations/lt.csv @@ -7052,7 +7052,7 @@ Moving Average,slenkamasis vidurkis, 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 2cfa1306f1..8c4526ca52 100644 --- a/erpnext/translations/lv.csv +++ b/erpnext/translations/lv.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 f01b1b0cf2..a622524bf1 100644 --- a/erpnext/translations/mk.csv +++ b/erpnext/translations/mk.csv @@ -7052,7 +7052,7 @@ Moving Average,Се движат просек, 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 59d3160d39..777d5c64ff 100644 --- a/erpnext/translations/ml.csv +++ b/erpnext/translations/ml.csv @@ -7052,7 +7052,7 @@ Moving Average,ശരാശരി നീക്കുന്നു, 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 ff339b6d9f..624f1ab481 100644 --- a/erpnext/translations/mr.csv +++ b/erpnext/translations/mr.csv @@ -7052,7 +7052,7 @@ Moving Average,हलवित/Moving सरासरी, 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 2258a1804f..75e150a88d 100644 --- a/erpnext/translations/ms.csv +++ b/erpnext/translations/ms.csv @@ -7052,7 +7052,7 @@ Moving Average,Purata bergerak, 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 dc5ab12f43..36cd8740d7 100644 --- a/erpnext/translations/my.csv +++ b/erpnext/translations/my.csv @@ -7052,7 +7052,7 @@ Moving Average,ပျမ်းမျှ Moving, 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 ad11eae08b..5859833861 100644 --- a/erpnext/translations/nl.csv +++ b/erpnext/translations/nl.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 b136e97954..a3236ac084 100644 --- a/erpnext/translations/no.csv +++ b/erpnext/translations/no.csv @@ -7052,7 +7052,7 @@ Moving Average,Glidende gjennomsnitt, 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 9be56f36f7..df41e39862 100644 --- a/erpnext/translations/pl.csv +++ b/erpnext/translations/pl.csv @@ -6987,7 +6987,7 @@ Moving Average,Średnia Ruchoma, 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 8033752f78..5a0b2a50cb 100644 --- a/erpnext/translations/ps.csv +++ b/erpnext/translations/ps.csv @@ -7052,7 +7052,7 @@ Moving Average,حرکت اوسط, 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 9823470afb..bc5b616080 100644 --- a/erpnext/translations/pt-BR.csv +++ b/erpnext/translations/pt-BR.csv @@ -7052,7 +7052,7 @@ Moving Average,Média Móvel, 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 c2afe3216d..e6846c6a37 100644 --- a/erpnext/translations/pt.csv +++ b/erpnext/translations/pt.csv @@ -7052,7 +7052,7 @@ Moving Average,Média Móvel, 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 0cb3f84278..ac7e598e2d 100644 --- a/erpnext/translations/ro.csv +++ b/erpnext/translations/ro.csv @@ -7052,7 +7052,7 @@ Moving Average,Mutarea medie, 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 da4e1bef82..52c29982f3 100644 --- a/erpnext/translations/ru.csv +++ b/erpnext/translations/ru.csv @@ -6985,7 +6985,7 @@ Moving Average,Скользящее среднее, 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 ae0cb3a908..f035d57985 100644 --- a/erpnext/translations/rw.csv +++ b/erpnext/translations/rw.csv @@ -7052,7 +7052,7 @@ Moving Average,Impuzandengo, 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 fa6fbf090a..4047263492 100644 --- a/erpnext/translations/si.csv +++ b/erpnext/translations/si.csv @@ -7052,7 +7052,7 @@ Moving Average,වෙනස්වන සාමාන්යය, 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 0e51158b27..98e1663f1c 100644 --- a/erpnext/translations/sk.csv +++ b/erpnext/translations/sk.csv @@ -7052,7 +7052,7 @@ Moving Average,Klouzavý průměr, 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 7c4e11b63f..5380714bdc 100644 --- a/erpnext/translations/sl.csv +++ b/erpnext/translations/sl.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 0f10795113..2a893d272b 100644 --- a/erpnext/translations/sq.csv +++ b/erpnext/translations/sq.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Mesatare, 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 38116ecc9e..c1e5eb0eea 100644 --- a/erpnext/translations/sr.csv +++ b/erpnext/translations/sr.csv @@ -7052,7 +7052,7 @@ Moving Average,Мовинг Авераге, 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 c4d46ea130..8b4ab068eb 100644 --- a/erpnext/translations/sv.csv +++ b/erpnext/translations/sv.csv @@ -7052,7 +7052,7 @@ Moving Average,Rörligt medelvärde, 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 ad144f04d6..fa2287c3bb 100644 --- a/erpnext/translations/sw.csv +++ b/erpnext/translations/sw.csv @@ -7052,7 +7052,7 @@ Moving Average,Kusonga Wastani, 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 5ccabc044d..6eaae34a6e 100644 --- a/erpnext/translations/ta.csv +++ b/erpnext/translations/ta.csv @@ -7052,7 +7052,7 @@ Moving Average,சராசரியாக நகர்கிறது, 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 8472163af5..d3f739af8b 100644 --- a/erpnext/translations/te.csv +++ b/erpnext/translations/te.csv @@ -7052,7 +7052,7 @@ Moving Average,మూవింగ్ సగటు, 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 dcd632bb3c..a065595898 100644 --- a/erpnext/translations/th.csv +++ b/erpnext/translations/th.csv @@ -7052,7 +7052,7 @@ Moving Average,ค่าเฉลี่ยเคลื่อนที่, 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 3708246a9d..9e916f0315 100644 --- a/erpnext/translations/tr.csv +++ b/erpnext/translations/tr.csv @@ -7051,7 +7051,7 @@ Moving Average,Hareketli Ortalama, 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 1752330754..8d1fb04fdb 100644 --- a/erpnext/translations/uk.csv +++ b/erpnext/translations/uk.csv @@ -7052,7 +7052,7 @@ Moving Average,Moving Average, 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 504cc8de0c..649c1c7759 100644 --- a/erpnext/translations/ur.csv +++ b/erpnext/translations/ur.csv @@ -7052,7 +7052,7 @@ Moving Average,حرکت پذیری اوسط, 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 6b25e7b152..5ca51ccb0f 100644 --- a/erpnext/translations/uz.csv +++ b/erpnext/translations/uz.csv @@ -7052,7 +7052,7 @@ Moving Average,O'rtacha harakatlanuvchi, 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 e576ef7119..7a005fa414 100644 --- a/erpnext/translations/vi.csv +++ b/erpnext/translations/vi.csv @@ -7052,7 +7052,7 @@ Moving Average,Di chuyển trung bình, 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 699d802206..0f76e97f97 100644 --- a/erpnext/translations/zh-TW.csv +++ b/erpnext/translations/zh-TW.csv @@ -2964,7 +2964,7 @@ Replace BOM,更換BOM, 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 70ec81af26..cf89dc6852 100644 --- a/erpnext/translations/zh.csv +++ b/erpnext/translations/zh.csv @@ -7052,7 +7052,7 @@ Moving Average,移动平均, 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 @@ WhatsApp,WhatsApp的, Make a call,打个电话, Approve,同意, Reject,拒绝, +Stock,库存, diff --git a/erpnext/translations/zh_tw.csv b/erpnext/translations/zh_tw.csv index 75157f02fc..8ecbaaa9c5 100644 --- a/erpnext/translations/zh_tw.csv +++ b/erpnext/translations/zh_tw.csv @@ -7485,7 +7485,7 @@ Moving Average,移動平均線, 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 5e57b31793..fcee265644 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -192,9 +192,7 @@ def mark_retrired_transaction(log_doc, doc_name): record = 0 for d in log_doc.get("logger_data"): if d.transaction_name == doc_name and d.transaction_status == "Failed": - d.retried = 1 + frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1) record = record + 1 - log_doc.save() - return record