diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index ddca68a57b9..d4513c6a686 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -84,20 +84,12 @@ class POSInvoiceMergeLog(Document): sales_invoice.set_posting_time = 1 sales_invoice.posting_date = getdate(self.posting_date) sales_invoice.save() - self.write_off_fractional_amount(sales_invoice, data) sales_invoice.submit() self.consolidated_invoice = sales_invoice.name return sales_invoice.name - def write_off_fractional_amount(self, invoice, data): - pos_invoice_grand_total = sum(d.grand_total for d in data) - - if abs(pos_invoice_grand_total - invoice.grand_total) < 1: - invoice.write_off_amount += -1 * (pos_invoice_grand_total - invoice.grand_total) - invoice.save() - def process_merging_into_credit_note(self, data): credit_note = self.get_new_sales_invoice() credit_note.is_return = 1 @@ -110,7 +102,6 @@ class POSInvoiceMergeLog(Document): # TODO: return could be against multiple sales invoice which could also have been consolidated? # credit_note.return_against = self.consolidated_invoice credit_note.save() - self.write_off_fractional_amount(credit_note, data) credit_note.submit() self.consolidated_credit_note = credit_note.name diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py index 5930aa097f7..89f7f18b42c 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -5,6 +5,7 @@ import json import unittest import frappe +from frappe.tests.utils import change_settings from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return @@ -280,3 +281,100 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Invoice`") + + @change_settings("System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}) + def test_consolidation_round_off_error_3(self): + frappe.db.sql("delete from `tabPOS Invoice`") + + try: + make_stock_entry( + to_warehouse="_Test Warehouse - _TC", + item_code="_Test Item", + rate=8000, + qty=10, + ) + init_user_and_profile() + + item_rates = [69, 59, 29] + for i in [1, 2]: + inv = create_pos_invoice(is_return=1, do_not_save=1) + inv.items = [] + for rate in item_rates: + inv.append("items", { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "qty": -1, + "rate": rate, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + inv.append("taxes", { + "account_head": "_Test Account VAT - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "VAT", + "doctype": "Sales Taxes and Charges", + "rate": 15, + "included_in_print_rate": 1 + }) + inv.payments = [] + inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -157 + }) + inv.paid_amount = -157 + inv.save() + inv.submit() + + consolidate_pos_invoices() + + inv.load_from_db() + consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice) + self.assertEqual(consolidated_invoice.status, 'Return') + self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001) + + finally: + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") + + def test_consolidation_rounding_adjustment(self): + ''' + Test if the rounding adjustment is calculated correctly + ''' + frappe.db.sql("delete from `tabPOS Invoice`") + + try: + make_stock_entry( + to_warehouse="_Test Warehouse - _TC", + item_code="_Test Item", + rate=8000, + qty=10, + ) + + init_user_and_profile() + + inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True) + inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 70 + }) + inv.insert() + inv.submit() + + inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True) + inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60 + }) + inv2.insert() + inv2.submit() + + consolidate_pos_invoices() + + inv.load_from_db() + consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice) + self.assertEqual(consolidated_invoice.rounding_adjustment, 1) + + finally: + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index f8d191cc3f8..82705a9cea4 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -64,10 +64,10 @@ {{ frappe.format(row.account, {fieldtype: "Link"}) or " " }} - {{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }} + {{ row.get('account', '') and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }} - {{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }} + {{ row.get('account', '') and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }} {% endif %} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js index 088c190f451..29f2e98e779 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js @@ -51,6 +51,13 @@ frappe.ui.form.on('Process Statement Of Accounts', { } } }); + frm.set_query("account", function() { + return { + filters: { + 'company': frm.doc.company + } + }; + }); if(frm.doc.__islocal){ frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1)); frm.set_value('to_date', frappe.datetime.get_today()); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 5062c1c807a..80b95db8868 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2,7 +2,7 @@ "actions": [], "allow_import": 1, "autoname": "naming_series:", - "creation": "2013-05-24 19:29:05", + "creation": "2022-01-25 10:29:57.771398", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -651,7 +651,6 @@ "hide_seconds": 1, "label": "Ignore Pricing Rule", "no_copy": 1, - "permlevel": 0, "print_hide": 1 }, { @@ -1974,9 +1973,10 @@ }, { "default": "0", + "description": "Issue a debit note with 0 qty against an existing Sales Invoice", "fieldname": "is_debit_note", "fieldtype": "Check", - "label": "Is Debit Note" + "label": "Is Rate Adjustment Entry (Debit Note)" }, { "default": "0", @@ -2038,7 +2038,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-12-23 20:19:38.667508", + "modified": "2022-03-08 16:08:53.517903", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2089,8 +2089,9 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "customer", "title_field": "title", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index b894f90c7e1..573da276a2a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -263,6 +263,9 @@ class SalesInvoice(SellingController): self.process_common_party_accounting() def validate_pos_return(self): + if self.is_consolidated: + # pos return is already validated in pos invoice + return if self.is_pos and self.is_return: total_amount_in_payments = 0 diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py index 00d08473d66..c81b611a418 100644 --- a/erpnext/assets/doctype/asset/asset_dashboard.py +++ b/erpnext/assets/doctype/asset/asset_dashboard.py @@ -1,3 +1,6 @@ +from frappe import _ + + def get_data(): return { 'non_standard_fieldnames': { @@ -5,7 +8,7 @@ def get_data(): }, 'transactions': [ { - 'label': ['Movement'], + 'label': _('Movement'), 'items': ['Asset Movement'] } ] diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 874fb630f87..6e042422101 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -23,7 +23,7 @@ def post_depreciation_entries(date=None): frappe.db.commit() def get_depreciable_assets(date): - return frappe.db.sql_list("""select a.name + return frappe.db.sql_list("""select distinct a.name from tabAsset a, `tabDepreciation Schedule` ds where a.name = ds.parent and a.docstatus=1 and ds.schedule_date<=%s and a.calculate_depreciation = 1 and a.status in ('Submitted', 'Partially Depreciated') diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js index 52996e93475..5c03b98873b 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js @@ -48,7 +48,7 @@ frappe.ui.form.on('Asset Maintenance', {
- ${d.maintenance_status} ${d.count} + ${__(d.maintenance_status)} ${d.count}
`).appendTo(rows); diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index d554d52a718..3fe6b2d0d5d 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -68,6 +68,28 @@ frappe.ui.form.on('Asset Repair', { }); frappe.ui.form.on('Asset Repair Consumed Item', { + item_code: function(frm, cdt, cdn) { + var item = locals[cdt][cdn]; + + let item_args = { + 'item_code': item.item_code, + 'warehouse': frm.doc.warehouse, + 'qty': item.consumed_quantity, + 'serial_no': item.serial_no, + 'company': frm.doc.company + }; + + frappe.call({ + method: 'erpnext.stock.utils.get_incoming_rate', + args: { + args: item_args + }, + callback: function(r) { + frappe.model.set_value(cdt, cdn, 'valuation_rate', r.message); + } + }); + }, + consumed_quantity: function(frm, cdt, cdn) { var row = locals[cdt][cdn]; frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate); diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json index f63add12356..4685a09db63 100644 --- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json +++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json @@ -13,12 +13,10 @@ ], "fields": [ { - "fetch_from": "item.valuation_rate", "fieldname": "valuation_rate", "fieldtype": "Currency", "in_list_view": 1, - "label": "Valuation Rate", - "read_only": 1 + "label": "Valuation Rate" }, { "fieldname": "consumed_quantity", @@ -49,7 +47,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-11-11 18:23:00.492483", + "modified": "2022-02-08 17:37:20.028290", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair Consumed Item", diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index a739cc37306..0073170a855 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -3,15 +3,11 @@ frappe.ui.form.on('Bulk Transaction Log', { - before_load: function(frm) { - query(frm); - }, - refresh: function(frm) { frm.disable_save(); frm.add_custom_button(__('Retry Failed Transactions'), ()=>{ frappe.confirm(__("Retry Failing Transactions ?"), ()=>{ - query(frm); + query(frm, 1); } ); }); @@ -25,8 +21,8 @@ function query(frm) { log_date: frm.doc.log_date } }).then((r) => { - if (r.message) { - frm.remove_custom_button("Retry Failed Transactions"); + if (r.message === "No Failed Records") { + frappe.show_alert(__(r.message), 5); } else { frappe.show_alert(__("Retrying Failed Transactions"), 5); } diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index de7cde5a6d3..92f37f56678 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -15,6 +15,8 @@ class BulkTransactionLog(Document): @frappe.whitelist() def retry_failing_transaction(log_date=None): + if not log_date: + log_date = str(date.today()) btp = frappe.qb.DocType("Bulk Transaction Log Detail") data = ( frappe.qb.from_(btp) @@ -25,9 +27,7 @@ def retry_failing_transaction(log_date=None): .where(btp.date == log_date) ).run(as_dict=True) - if data: - if not log_date: - log_date = str(date.today()) + if data : if len(data) > 10: frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date) else: diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index dc58e1181c2..a1bb6670c42 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -277,7 +277,8 @@ class calculate_taxes_and_totals(object): shipping_rule.apply(self.doc) def calculate_taxes(self): - if not self.doc.get('is_consolidated'): + rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment') + if not rounding_adjustment_computed: self.doc.rounding_adjustment = 0 # maintain actual tax rate based on idx @@ -333,7 +334,7 @@ class calculate_taxes_and_totals(object): if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ and self.doc.discount_amount \ and self.doc.apply_discount_on == "Grand Total" \ - and not self.doc.get('is_consolidated'): + and not rounding_adjustment_computed: self.doc.rounding_adjustment = flt(self.doc.grand_total - flt(self.doc.discount_amount) - tax.total, self.doc.precision("rounding_adjustment")) @@ -472,20 +473,22 @@ class calculate_taxes_and_totals(object): self.doc.total_net_weight += d.total_weight def set_rounded_total(self): - if not self.doc.get('is_consolidated'): - if self.doc.meta.get_field("rounded_total"): - if self.doc.is_rounded_total_disabled(): - self.doc.rounded_total = self.doc.base_rounded_total = 0 - return + if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'): + return - self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, - self.doc.currency, self.doc.precision("rounded_total")) + if self.doc.meta.get_field("rounded_total"): + if self.doc.is_rounded_total_disabled(): + self.doc.rounded_total = self.doc.base_rounded_total = 0 + return - #if print_in_rate is set, we would have already calculated rounding adjustment - self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, - self.doc.precision("rounding_adjustment")) + self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, + self.doc.currency, self.doc.precision("rounded_total")) - self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) + #if print_in_rate is set, we would have already calculated rounding adjustment + self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, + self.doc.precision("rounding_adjustment")) + + self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) def _cleanup(self): if not self.doc.get('is_consolidated'): diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py index 43cb36ca2e2..84554ae7d0f 100644 --- a/erpnext/e_commerce/api.py +++ b/erpnext/e_commerce/api.py @@ -48,7 +48,6 @@ def get_product_filter_data(query_args=None): sub_categories = [] if item_group: - field_filters['item_group'] = item_group sub_categories = get_child_groups_for_website(item_group, immediate=True) engine = ProductQuery() diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py index c4a3cb9fbef..89d4ffdff48 100644 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ b/erpnext/e_commerce/product_data_engine/filters.py @@ -14,6 +14,8 @@ class ProductFiltersBuilder: self.item_group = item_group def get_field_filters(self): + from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website + if not self.item_group and not self.doc.enable_field_filters: return @@ -25,18 +27,26 @@ class ProductFiltersBuilder: fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)] for df in fields: - item_filters, item_or_filters = {}, [] + item_filters, item_or_filters = {"published_in_website": 1}, [] link_doctype_values = self.get_filtered_link_doctype_records(df) if df.fieldtype == "Link": if self.item_group: - item_or_filters.extend([ - ["item_group", "=", self.item_group], - ["Website Item Group", "item_group", "=", self.item_group] # consider website item groups - ]) + include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants") + if include_child: + include_groups = get_child_groups_for_website(self.item_group, include_self=True) + include_groups = [x.name for x in include_groups] + item_or_filters.extend([ + ["item_group", "in", include_groups], + ["Website Item Group", "item_group", "=", self.item_group] # consider website item groups + ]) + else: + item_or_filters.extend([ + ["item_group", "=", self.item_group], + ["Website Item Group", "item_group", "=", self.item_group] # consider website item groups + ]) # Get link field values attached to published items - item_filters['published_in_website'] = 1 item_values = frappe.get_all( "Item", fields=[df.fieldname], diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py index cfc3c7b357c..5c92e3d98ad 100644 --- a/erpnext/e_commerce/product_data_engine/query.py +++ b/erpnext/e_commerce/product_data_engine/query.py @@ -46,10 +46,10 @@ class ProductQuery: self.filter_with_discount = bool(fields.get("discount")) result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0 - website_item_groups = self.get_website_item_group_results(item_group, website_item_groups) - if fields: self.build_fields_filters(fields) + if item_group: + self.build_item_group_filters(item_group) if search_term: self.build_search_filters(search_term) if self.settings.hide_variants: @@ -61,8 +61,6 @@ class ProductQuery: else: result, count = self.query_items(start=start) - result = self.combine_web_item_group_results(item_group, result, website_item_groups) - # sort combined results by ranking result = sorted(result, key=lambda x: x.get("ranking"), reverse=True) @@ -167,6 +165,25 @@ class ProductQuery: # `=` will be faster than `IN` for most cases self.filters.append([field, "=", values]) + def build_item_group_filters(self, item_group): + "Add filters for Item group page and include Website Item Groups." + from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website + item_group_filters = [] + + item_group_filters.append(["Website Item", "item_group", "=", item_group]) + # Consider Website Item Groups + item_group_filters.append(["Website Item Group", "item_group", "=", item_group]) + + if frappe.db.get_value("Item Group", item_group, "include_descendants"): + # include child item group's items as well + # eg. Group Node A, will show items of child 1 and child 2 as well + # on it's web page + include_groups = get_child_groups_for_website(item_group, include_self=True) + include_groups = [x.name for x in include_groups] + item_group_filters.append(["Website Item", "item_group", "in", include_groups]) + + self.or_filters.extend(item_group_filters) + def build_search_filters(self, search_term): """Query search term in specified fields @@ -190,19 +207,6 @@ class ProductQuery: for field in search_fields: self.or_filters.append([field, "like", search]) - def get_website_item_group_results(self, item_group, website_item_groups): - """Get Web Items for Item Group Page via Website Item Groups.""" - if item_group: - website_item_groups = frappe.db.get_all( - "Website Item", - fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"], - filters=[ - ["Website Item Group", "item_group", "=", item_group], - ["published", "=", 1] - ] - ) - return website_item_groups - def add_display_details(self, result, discount_list, cart_items): """Add price and availability details in result.""" for item in result: @@ -278,16 +282,6 @@ class ProductQuery: return [] - def combine_web_item_group_results(self, item_group, result, website_item_groups): - """Combine results with context of website item groups into item results.""" - if item_group and website_item_groups: - items_list = {row.name for row in result} - for row in website_item_groups: - if row.wig_parent not in items_list: - result.append(row) - - return result - def filter_results_by_discount(self, fields, result): if fields and fields.get("discount"): discount_percent = frappe.utils.flt(fields["discount"][0]) diff --git a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py index f0f7918d00e..6549ba692af 100644 --- a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py +++ b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py @@ -13,8 +13,7 @@ test_dependencies = ["Item", "Item Group"] class TestItemGroupProductDataEngine(unittest.TestCase): "Test Products & Sub-Category Querying for Product Listing on Item Group Page." - @classmethod - def setUpClass(cls): + def setUp(self): item_codes = [ ("Test Mobile A", "_Test Item Group B"), ("Test Mobile B", "_Test Item Group B"), @@ -28,8 +27,10 @@ class TestItemGroupProductDataEngine(unittest.TestCase): if not frappe.db.exists("Website Item", {"item_code": item_code}): create_regular_web_item(item_code, item_args=item_args) - @classmethod - def tearDownClass(cls): + frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1) + frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1) + + def tearDown(self): frappe.db.rollback() def test_product_listing_in_item_group(self): @@ -87,7 +88,6 @@ class TestItemGroupProductDataEngine(unittest.TestCase): def test_item_group_with_sub_groups(self): "Test Valid Sub Item Groups in Item Group Page." - frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1) frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0) result = get_product_filter_data(query_args={ @@ -114,4 +114,45 @@ class TestItemGroupProductDataEngine(unittest.TestCase): # check if child group is fetched if shown in website self.assertIn("_Test Item Group B - 1", child_groups) - self.assertIn("_Test Item Group B - 2", child_groups) \ No newline at end of file + self.assertIn("_Test Item Group B - 2", child_groups) + + def test_item_group_page_with_descendants_included(self): + """ + Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3). + > _Test Item Group B [Level 1] + > _Test Item Group B - 1 [Level 2] + > _Test Item Group B - 1 - 1 [Level 3] + """ + frappe.get_doc({ # create Level 3 nested child group + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B - 1 - 1", + "parent_item_group": "_Test Item Group B - 1" + }).insert() + + create_regular_web_item( # create an item belonging to level 3 item group + "Test Mobile F", + item_args={"item_group": "_Test Item Group B - 1 - 1"} + ) + + frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1) + + # enable 'include descendants' in Level 1 + frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1) + + result = get_product_filter_data(query_args={ + "field_filters": {}, + "attribute_filters": {}, + "start": 0, + "item_group": "_Test Item Group B" + }) + + items = result.get("items") + item_codes = [item.get("item_code") for item in items] + + # check if all sub groups' items are pulled + self.assertEqual(len(items), 6) + self.assertIn("Test Mobile A", item_codes) + self.assertIn("Test Mobile C", item_codes) + self.assertIn("Test Mobile E", item_codes) + self.assertIn("Test Mobile F", item_codes) \ No newline at end of file diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py index 118cc987efb..c74967d213e 100644 --- a/erpnext/hr/doctype/attendance/test_attendance.py +++ b/erpnext/hr/doctype/attendance/test_attendance.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -import unittest - import frappe -from frappe.utils import add_days, get_first_day, getdate, nowdate +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_days, get_first_day, getdate, now_datetime, nowdate from erpnext.hr.doctype.attendance.attendance import ( get_month_map, @@ -16,7 +15,7 @@ from erpnext.hr.doctype.leave_application.test_leave_application import get_firs test_records = frappe.get_test_records('Attendance') -class TestAttendance(unittest.TestCase): +class TestAttendance(FrappeTestCase): def test_mark_absent(self): employee = make_employee("test_mark_absent@example.com") date = nowdate() @@ -74,12 +73,14 @@ class TestAttendance(unittest.TestCase): self.assertNotIn(first_sunday, unmarked_days) def test_unmarked_days_as_per_joining_and_relieving_dates(self): - first_day = get_first_day(getdate()) + now = now_datetime() + previous_month = now.month - 1 + first_day = now.replace(day=1).replace(month=previous_month).date() doj = add_days(first_day, 1) relieving_date = add_days(first_day, 5) employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj, - date_of_relieving=relieving_date) + relieving_date=relieving_date) frappe.db.delete('Attendance', {'employee': employee}) attendance_date = add_days(first_day, 2) @@ -104,4 +105,4 @@ def get_month_name(date): month_number = date.month for month, number in get_month_map().items(): if number == month_number: - return month \ No newline at end of file + return month diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 70250f5bcf8..345d8dc3700 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Max, Min, Sum from frappe.utils import ( add_days, cint, @@ -494,7 +495,6 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day number_of_days = date_diff(to_date, from_date) + .5 else: number_of_days = date_diff(to_date, from_date) + 1 - else: number_of_days = date_diff(to_date, from_date) + 1 @@ -567,28 +567,39 @@ def get_leave_balance_on(employee, leave_type, date, to_date=None, consider_all_ return get_remaining_leaves(allocation, leaves_taken, date, expiry) def get_leave_allocation_records(employee, date, leave_type=None): - ''' returns the total allocated leaves and carry forwarded leaves based on ledger entries ''' + """Returns the total allocated leaves and carry forwarded leaves based on ledger entries""" + Ledger = frappe.qb.DocType("Leave Ledger Entry") - conditions = ("and leave_type='%s'" % leave_type) if leave_type else "" - allocation_details = frappe.db.sql(""" - SELECT - SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves, - SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves, - MIN(from_date) as from_date, - MAX(to_date) as to_date, - leave_type - FROM `tabLeave Ledger Entry` - WHERE - from_date <= %(date)s - AND to_date >= %(date)s - AND docstatus=1 - AND transaction_type="Leave Allocation" - AND employee=%(employee)s - AND is_expired=0 - AND is_lwp=0 - {0} - GROUP BY employee, leave_type - """.format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec + cf_leave_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0) + sum_cf_leaves = Sum(cf_leave_case).as_("cf_leaves") + + new_leaves_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0) + sum_new_leaves = Sum(new_leaves_case).as_("new_leaves") + + query = ( + frappe.qb.from_(Ledger) + .select( + sum_cf_leaves, + sum_new_leaves, + Min(Ledger.from_date).as_("from_date"), + Max(Ledger.to_date).as_("to_date"), + Ledger.leave_type + ).where( + (Ledger.from_date <= date) + & (Ledger.to_date >= date) + & (Ledger.docstatus == 1) + & (Ledger.transaction_type == "Leave Allocation") + & (Ledger.employee == employee) + & (Ledger.is_expired == 0) + & (Ledger.is_lwp == 0) + ) + ) + + if leave_type: + query = query.where((Ledger.leave_type == leave_type)) + query = query.groupby(Ledger.employee, Ledger.leave_type) + + allocation_details = query.run(as_dict=True) allocated_leaves = frappe._dict() for d in allocation_details: diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 6d27f4abef1..7b3aa497298 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -792,4 +792,4 @@ def get_first_sunday(holiday_list): order by holiday_date """, (holiday_list, month_start_date, month_end_date))[0][0] - return first_sunday \ No newline at end of file + return first_sunday diff --git a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py new file mode 100644 index 00000000000..952af8117e2 --- /dev/null +++ b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py @@ -0,0 +1,44 @@ +import frappe +from dateutil.relativedelta import relativedelta +from frappe.tests.utils import FrappeTestCase +from frappe.utils import now_datetime + +from erpnext.hr.doctype.attendance.attendance import mark_attendance +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.report.monthly_attendance_sheet.monthly_attendance_sheet import execute + + +class TestMonthlyAttendanceSheet(FrappeTestCase): + def setUp(self): + self.employee = make_employee("test_employee@example.com") + frappe.db.delete('Attendance', {'employee': self.employee}) + + def test_monthly_attendance_sheet_report(self): + now = now_datetime() + previous_month = now.month - 1 + previous_month_first = now.replace(day=1).replace(month=previous_month).date() + + company = frappe.db.get_value('Employee', self.employee, 'company') + + # mark different attendance status on first 3 days of previous month + mark_attendance(self.employee, previous_month_first, 'Absent') + mark_attendance(self.employee, previous_month_first + relativedelta(days=1), 'Present') + mark_attendance(self.employee, previous_month_first + relativedelta(days=2), 'On Leave') + + filters = frappe._dict({ + 'month': previous_month, + 'year': now.year, + 'company': company, + }) + report = execute(filters=filters) + employees = report[1][0] + datasets = report[3]['data']['datasets'] + absent = datasets[0]['values'] + present = datasets[1]['values'] + leaves = datasets[2]['values'] + + # ensure correct attendance is reflect on the report + self.assertIn(self.employee, employees) + self.assertEqual(absent[0], 1) + self.assertEqual(present[1], 1) + self.assertEqual(leaves[2], 1) diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py index b3ee3404642..7ae4c42cecf 100644 --- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py +++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py @@ -5,10 +5,13 @@ def execute(): frappe.reload_doc('accounts', 'doctype', 'bank', force=1) if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'): - frappe.db.sql(""" - UPDATE `tabBank` b, `tabBank Account` ba - SET b.swift_number = ba.swift_number WHERE b.name = ba.bank - """) + try: + frappe.db.sql(""" + UPDATE `tabBank` b, `tabBank Account` ba + SET b.swift_number = ba.swift_number WHERE b.name = ba.bank + """) + except Exception as e: + frappe.log_error(e, title="Patch Migration Failed") frappe.reload_doc('accounts', 'doctype', 'bank_account') frappe.reload_doc('accounts', 'doctype', 'payment_request') diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 9520cd17be2..23c3b82c6dc 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -1,14 +1,12 @@ # 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 def execute(filters=None): - columns, data = [], [] data = get_data(filters) columns = get_columns() charts = get_chart_data(data) diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 1eb3d0d717b..3ca28c1ce0f 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -1,6 +1,5 @@ -import unittest - import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate from erpnext.hr.doctype.employee.test_employee import make_employee @@ -12,7 +11,7 @@ from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_ from erpnext.projects.report.project_profitability.project_profitability import execute -class TestProjectProfitability(unittest.TestCase): +class TestProjectProfitability(FrappeTestCase): def setUp(self): frappe.db.sql('delete from `tabTimesheet`') emp = make_employee('test_employee_9@salary.com', company='_Test Company') @@ -67,6 +66,3 @@ class TestProjectProfitability(unittest.TestCase): fractional_cost = self.salary_slip.base_gross_pay * utilization self.assertEqual(fractional_cost, row.fractional_cost) - - def tearDown(self): - frappe.db.rollback() diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index beac7ed65cd..92a10c288f8 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -343,8 +343,7 @@ def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1): /* against number or, if empty, party against number */ %(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)', - /* disable automatic VAT deduction */ - '40' as 'BU-Schlüssel', + '' as 'BU-Schlüssel', gl.posting_date as 'Belegdatum', gl.voucher_no as 'Belegfeld 1', diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py index b1a5d109621..147a59fb012 100644 --- a/erpnext/regional/report/irs_1099/irs_1099.py +++ b/erpnext/regional/report/irs_1099/irs_1099.py @@ -30,7 +30,6 @@ def execute(filters=None): if region != 'United States': return [], [] - data = [] columns = get_columns() conditions = "" if filters.supplier_group: diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 41336873ac9..464939f39e0 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -118,8 +118,7 @@ def make_customer(): "customer_type": "Company", }) customer.insert() - else: - customer = frappe.get_doc("Customer", "_Test UAE Customer") + def make_supplier(): if not frappe.db.exists("Supplier", "_Test UAE Supplier"): diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 7742f26ad11..634c4819e6e 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -218,7 +218,9 @@ class Customer(TransactionBase): else: company_record.append(limit.company) - outstanding_amt = get_customer_outstanding(self.name, limit.company) + outstanding_amt = get_customer_outstanding( + self.name, limit.company, ignore_outstanding_sales_order=limit.bypass_credit_limit_check + ) if flt(limit.credit_limit) < outstanding_amt: frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt)) diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 4a245e1f778..56e1eb57b81 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -156,24 +156,24 @@ def get_data(filters): customer_record = customer_details.get(record.customer) item_record = item_details.get(record.item_code) row = { - "item_code": record.item_code, - "item_name": item_record.item_name, - "item_group": item_record.item_group, - "description": record.description, - "quantity": record.qty, - "uom": record.uom, - "rate": record.base_rate, - "amount": record.base_amount, - "sales_order": record.name, - "transaction_date": record.transaction_date, - "customer": record.customer, - "customer_name": customer_record.customer_name, - "customer_group": customer_record.customer_group, - "territory": record.territory, - "project": record.project, - "delivered_quantity": flt(record.delivered_qty), - "billed_amount": flt(record.billed_amt), - "company": record.company + "item_code": record.get('item_code'), + "item_name": item_record.get('item_name'), + "item_group": item_record.get('item_group'), + "description": record.get('description'), + "quantity": record.get('qty'), + "uom": record.get('uom'), + "rate": record.get('base_rate'), + "amount": record.get('base_amount'), + "sales_order": record.get('name'), + "transaction_date": record.get('transaction_date'), + "customer": record.get('customer'), + "customer_name": customer_record.get('customer_name'), + "customer_group": customer_record.get('customer_group'), + "territory": record.get('territory'), + "project": record.get('project'), + "delivered_quantity": flt(record.get('delivered_qty')), + "billed_amount": flt(record.get('billed_amt')), + "company": record.get('company') } data.append(row) diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index 885d874720d..f570c2faec6 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -14,6 +14,16 @@ frappe.ui.form.on("Item Group", { ] } } + frm.fields_dict['item_group_defaults'].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + return { + filters: { + 'report_type': 'Profit and Loss', + 'company': row.company, + "is_group": 0 + } + }; + } frm.fields_dict["item_group_defaults"].grid.get_field("expense_account").get_query = function(doc, cdt, cdn) { const row = locals[cdt][cdn]; return { diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json index 3e0680f4f51..50f923d87e0 100644 --- a/erpnext/setup/doctype/item_group/item_group.json +++ b/erpnext/setup/doctype/item_group/item_group.json @@ -20,12 +20,14 @@ "sec_break_taxes", "taxes", "sb9", - "show_in_website", "route", - "weightage", - "slideshow", "website_title", "description", + "show_in_website", + "include_descendants", + "column_break_16", + "weightage", + "slideshow", "website_specifications", "website_filters_section", "filter_fields", @@ -111,7 +113,7 @@ }, { "default": "0", - "description": "Check this if you want to show in website", + "description": "Make Item Group visible in website", "fieldname": "show_in_website", "fieldtype": "Check", "label": "Show in Website" @@ -124,6 +126,7 @@ "unique": 1 }, { + "depends_on": "show_in_website", "fieldname": "weightage", "fieldtype": "Int", "label": "Weightage" @@ -186,6 +189,8 @@ "report_hide": 1 }, { + "collapsible": 1, + "depends_on": "show_in_website", "fieldname": "website_filters_section", "fieldtype": "Section Break", "label": "Website Filters" @@ -203,9 +208,22 @@ "options": "Website Attribute" }, { + "depends_on": "show_in_website", "fieldname": "website_title", "fieldtype": "Data", "label": "Title" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "show_in_website", + "description": "Include Website Items belonging to child Item Groups", + "fieldname": "include_descendants", + "fieldtype": "Check", + "label": "Include Descendants" } ], "icon": "fa fa-sitemap", @@ -214,11 +232,12 @@ "is_tree": 1, "links": [], "max_attachments": 3, - "modified": "2021-02-18 13:40:30.049650", + "modified": "2022-03-09 12:27:11.055782", "modified_by": "Administrator", "module": "Setup", "name": "Item Group", "name_case": "Title Case", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_item_group", "owner": "Administrator", "permissions": [ @@ -285,5 +304,6 @@ "search_fields": "parent_item_group", "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 4f92240c84f..5c7194baf68 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -111,7 +111,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): from erpnext.stock.doctype.item.item import validate_item_default_company_links validate_item_default_company_links(self.item_group_defaults) -def get_child_groups_for_website(item_group_name, immediate=False): +def get_child_groups_for_website(item_group_name, immediate=False, include_self=False): """Returns child item groups *excluding* passed group.""" item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) filters = { @@ -123,6 +123,12 @@ def get_child_groups_for_website(item_group_name, immediate=False): if immediate: filters["parent_item_group"] = item_group_name + if include_self: + filters.update({ + "lft": [">=", item_group.lft], + "rgt": ["<=", item_group.rgt] + }) + return frappe.get_all( "Item Group", filters=filters, diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index ffea9c2d6e0..9e8b3bd4637 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -165,21 +165,21 @@ frappe.ui.form.on("Item", { frm.set_value('has_batch_no', 0); frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); - frm.call({ - method: "set_asset_naming_series", - doc: frm.doc, - callback: function() { + frappe.call({ + method: "erpnext.stock.doctype.item.item.get_asset_naming_series", + callback: function(r) { frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1); - frm.trigger("set_asset_naming_series"); + frm.events.set_asset_naming_series(frm, r.message); } }); frm.trigger('auto_create_assets'); }, - set_asset_naming_series: function(frm) { - if (frm.doc.__onload && frm.doc.__onload.asset_naming_series) { - frm.set_df_property("asset_naming_series", "options", frm.doc.__onload.asset_naming_series); + set_asset_naming_series: function(frm, asset_naming_series) { + if ((frm.doc.__onload && frm.doc.__onload.asset_naming_series) || asset_naming_series) { + let naming_series = (frm.doc.__onload && frm.doc.__onload.asset_naming_series) || asset_naming_series; + frm.set_df_property("asset_naming_series", "options", naming_series); } }, diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 494fb3b8bb2..32c72fd2f64 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -50,15 +50,7 @@ class DataValidationError(frappe.ValidationError): class Item(Document): def onload(self): self.set_onload('stock_exists', self.stock_ledger_created()) - self.set_asset_naming_series() - - @frappe.whitelist() - def set_asset_naming_series(self): - if not hasattr(self, '_asset_naming_series'): - from erpnext.assets.doctype.asset.asset import get_asset_naming_series - self._asset_naming_series = get_asset_naming_series() - - self.set_onload('asset_naming_series', self._asset_naming_series) + self.set_onload('asset_naming_series', get_asset_naming_series()) def autoname(self): if frappe.db.get_default("item_naming_by") == "Naming Series": @@ -999,7 +991,7 @@ def get_uom_conv_factor(uom, stock_uom): if uom == stock_uom: return 1.0 - from_uom, to_uom = uom, stock_uom # renaming for readability + from_uom, to_uom = uom, stock_uom # renaming for readability exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1) if exact_match: @@ -1011,9 +1003,9 @@ def get_uom_conv_factor(uom, stock_uom): # This attempts to try and get conversion from intermediate UOM. # case: - # g -> mg = 1000 - # g -> kg = 0.001 - # therefore kg -> mg = 1000 / 0.001 = 1,000,000 + # g -> mg = 1000 + # g -> kg = 0.001 + # therefore kg -> mg = 1000 / 0.001 = 1,000,000 intermediate_match = frappe.db.sql(""" select (first.value / second.value) as value from `tabUOM Conversion Factor` first @@ -1072,3 +1064,11 @@ def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> Non frappe.bold(item_default.company), frappe.bold(frappe.unscrub(field)) ), title=_("Invalid Item Defaults")) + + +@frappe.whitelist() +def get_asset_naming_series(): + from erpnext.assets.doctype.asset.asset import get_asset_naming_series + + return get_asset_naming_series() + diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 5484a117ddb..b2eaecb5868 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -272,9 +272,9 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s {warehouse_condition} GROUP BY - `warehouse`, - `batch_no`, - `item_code` + sle.`warehouse`, + sle.`batch_no`, + sle.`item_code` HAVING `qty` > 0 ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation` """.format(warehouse_condition=warehouse_condition), { #nosec diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 9bec5f74940..9bb41b9dbbe 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -367,7 +367,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): if not out[d[1]]: out[d[1]] = frappe.get_cached_value('Company', args.company, d[2]) if d[2] else None - for fieldname in ("item_name", "item_group", "barcodes", "brand", "stock_uom"): + for fieldname in ("item_name", "item_group", "brand", "stock_uom"): out[fieldname] = item.get(fieldname) if args.get("manufacturer"): diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index b4f43a7fef1..24f47c19468 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -18,7 +18,6 @@ def execute(filters=None): is_reposting_item_valuation_in_progress() if not filters: filters = {} - from_date = filters.get('from_date') to_date = filters.get('to_date') if filters.get("company"): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 69755529e5a..ba1081f4dce 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -829,7 +829,7 @@ class update_entries_after(object): if msg_list: message = "\n\n".join(msg_list) if self.verbose: - frappe.throw(message, NegativeStockError, title='Insufficient Stock') + frappe.throw(message, NegativeStockError, title=_('Insufficient Stock')) else: raise NegativeStockError(message) @@ -1157,7 +1157,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): neg_sle[0]["posting_date"], neg_sle[0]["posting_time"], frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"])) - frappe.throw(message, NegativeStockError, title='Insufficient Stock') + frappe.throw(message, NegativeStockError, title=_('Insufficient Stock')) if not args.batch_no: @@ -1171,7 +1171,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): 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") + frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch")) def get_future_sle_with_negative_qty(args): diff --git a/erpnext/templates/pages/non_profit/__init__.py b/erpnext/templates/pages/non_profit/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 95ba8f7df28..dd3c23a0145 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -39,16 +39,13 @@ frappe.call(opts).then(res => { let success_dialog = new frappe.ui.Dialog({ title: __('Success'), - primary_action_label: __('View Program Content'), + primary_action_label: __('OK'), primary_action: function() { window.location.reload(); - }, - secondary_action: function() { - window.location.reload(); } }) success_dialog.show(); - success_dialog.set_message(__('You have successfully enrolled for the program ')); + success_dialog.set_message(__('You have successfully enrolled for the program.')); }) }