diff --git a/README.md b/README.md index 96093531d3..c26660c5a2 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@

[![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml) +[![UI](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml/badge.svg?branch=develop&event=schedule)](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) [![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext) [![docker pulls](https://img.shields.io/docker/pulls/frappe/erpnext-worker.svg)](https://hub.docker.com/r/frappe/erpnext-worker) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index a3ef38465e..8ae90ceb38 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -160,7 +160,7 @@ frappe.treeview_settings["Account"] = { let root_company = treeview.page.fields_dict.root_company.get_value(); if(root_company) { - frappe.throw(__("Please add the account to root level Company - ") + root_company); + frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]); } else { treeview.new_node(); } diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 990d6d9c8d..a964965c26 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -200,7 +200,7 @@ frappe.ui.form.on("Bank Statement Import", { }) .then((result) => { if (result.length > 0) { - frm.add_custom_button("Report Error", () => { + frm.add_custom_button(__("Report Error"), () => { let fake_xhr = { responseText: JSON.stringify({ exc: result[0].error, diff --git a/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py b/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py index 3bce4d51c7..402469fc1c 100644 --- a/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py +++ b/erpnext/accounts/doctype/cash_flow_mapping/cash_flow_mapping.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ from frappe.model.document import Document @@ -16,6 +17,6 @@ class CashFlowMapping(Document): ] if len(checked_fields) > 1: frappe.throw( - frappe._("You can only select a maximum of one option from the list of check boxes."), - title="Error", + _("You can only select a maximum of one option from the list of check boxes."), + title=_("Error"), ) diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py index 04a8e8ea92..edea37dcfd 100644 --- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py +++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py @@ -68,9 +68,8 @@ class CurrencyExchangeSettings(Document): str(key.key).format(transaction_date=nowdate(), to_currency="INR", from_currency="USD") ] except Exception: - frappe.throw("Invalid result key. Response: " + response.text) + frappe.throw(_("Invalid result key. Response:") + " " + response.text) if not isinstance(value, (int, float)): frappe.throw(_("Returned exchange rate is neither integer not float.")) self.url = response.url - frappe.msgprint("Exchange rate of USD to INR is " + str(value)) diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py index d0373021a6..ed35d1e094 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py @@ -42,12 +42,7 @@ class ModeofPayment(Document): pos_profiles = list(map(lambda x: x[0], pos_profiles)) if pos_profiles: - message = ( - "POS Profile " - + frappe.bold(", ".join(pos_profiles)) - + " contains \ - Mode of Payment " - + frappe.bold(str(self.name)) - + ". Please remove them to disable this mode." - ) - frappe.throw(_(message), title="Not Allowed") + message = _( + "POS Profile {} contains Mode of Payment {}. Please remove them to disable this mode." + ).format(frappe.bold(", ".join(pos_profiles)), frappe.bold(str(self.name))) + frappe.throw(message, title=_("Not Allowed")) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 65fd4af350..e83dc0f11e 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -61,13 +61,13 @@ class POSProfile(Document): if len(item_groups) != len(set(item_groups)): frappe.throw( - _("Duplicate item group found in the item group table"), title="Duplicate Item Group" + _("Duplicate item group found in the item group table"), title=_("Duplicate Item Group") ) if len(customer_groups) != len(set(customer_groups)): frappe.throw( _("Duplicate customer group found in the cutomer group table"), - title="Duplicate Customer Group", + title=_("Duplicate Customer Group"), ) def validate_payment_methods(self): 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 29f2e98e77..7dd77fbb3c 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 @@ -8,7 +8,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { }, refresh: function(frm){ if(!frm.doc.__islocal) { - frm.add_custom_button('Send Emails',function(){ + frm.add_custom_button(__('Send Emails'), function(){ frappe.call({ method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails", args: { @@ -24,7 +24,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { } }); }); - frm.add_custom_button('Download',function(){ + frm.add_custom_button(__('Download'), function(){ var url = frappe.urllib.get_full_url( '/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?' + 'document_name='+encodeURIComponent(frm.doc.name)) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 5f6e61090b..ee29d2a744 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -141,7 +141,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. }) }, __("Get Items From")); } - this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes"); + this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted); if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) { frappe.model.with_doc("Supplier", me.frm.doc.supplier, function() { @@ -571,10 +571,10 @@ frappe.ui.form.on("Purchase Invoice", { }, is_subcontracted: function(frm) { - if (frm.doc.is_subcontracted === "Yes") { + if (frm.doc.is_subcontracted) { erpnext.buying.get_default_bom(frm); } - frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); + frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); }, update_stock: function(frm) { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index bd0116443f..9f87c5ab54 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -543,11 +543,10 @@ "fieldtype": "Column Break" }, { - "default": "No", + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", - "label": "Raw Materials Supplied", - "options": "No\nYes", + "fieldtype": "Check", + "label": "Is Subcontracted", "print_hide": 1 }, { @@ -1366,7 +1365,7 @@ "width": "50px" }, { - "depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"", + "depends_on": "eval:doc.update_stock && doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 843f66d546..73390dd6f4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -901,7 +901,7 @@ class TestPurchaseInvoice(unittest.TestCase): ) pi = make_purchase_invoice( - item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted="Yes" + item_code="_Test FG Item", qty=10, rate=500, update_stock=1, is_subcontracted=1 ) self.assertEqual(len(pi.get("supplied_items")), 2) @@ -1611,7 +1611,7 @@ def make_purchase_invoice(**args): pi.conversion_rate = args.conversion_rate or 1 pi.is_return = args.is_return pi.return_against = args.return_against - pi.is_subcontracted = args.is_subcontracted or "No" + pi.is_subcontracted = args.is_subcontracted or 0 pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pi.cost_center = args.parent_cost_center @@ -1674,7 +1674,7 @@ def make_purchase_invoice_against_cost_center(**args): pi.is_return = args.is_return pi.is_return = args.is_return pi.credit_to = args.return_against or "Creditors - _TC" - pi.is_subcontracted = args.is_subcontracted or "No" + pi.is_subcontracted = args.is_subcontracted or 0 if args.supplier_warehouse: pi.supplier_warehouse = "_Test Warehouse 1 - _TC" diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index f9b2efd053..6651195e5f 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -623,7 +623,7 @@ }, { "default": "0", - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "include_exploded_items", "fieldtype": "Check", "label": "Include Exploded Items", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 7d98c22033..1efd3dca0d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1412,7 +1412,7 @@ class SalesInvoice(SellingController): ) ) else: - frappe.throw(_("Select change amount account"), title="Mandatory Field") + frappe.throw(_("Select change amount account"), title=_("Mandatory Field")) def make_write_off_gl_entry(self, gl_entries): # write off entries, applicable if only pos diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 35c2f8494f..a519d8be73 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -34,7 +34,9 @@ class TaxWithholdingCategory(Document): def validate_thresholds(self): for d in self.get("rates"): - if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold: + if ( + d.cumulative_threshold and d.single_threshold and d.cumulative_threshold < d.single_threshold + ): frappe.throw( _("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format( d.idx diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index e658049309..605ce8383e 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -1,7 +1,8 @@ {%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%} -{%- set einvoice = json.loads(doc.signed_einvoice) -%}
+ {% if doc.signed_einvoice %} + {%- set einvoice = json.loads(doc.signed_einvoice) -%}
{% if letter_head and not no_letterhead %}
{{ letter_head }}
@@ -170,4 +171,10 @@
+ {% else %} +
+ You must generate IRN before you can preview GST E-Invoice. +
+ {% endif %}
+ diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 7b1e979326..07552e311c 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -201,17 +201,17 @@ def get_report_summary( net_provisional_profit_loss += provisional_profit_loss.get(key) return [ - {"value": net_asset, "label": "Total Asset", "datatype": "Currency", "currency": currency}, + {"value": net_asset, "label": _("Total Asset"), "datatype": "Currency", "currency": currency}, { "value": net_liability, - "label": "Total Liability", + "label": _("Total Liability"), "datatype": "Currency", "currency": currency, }, - {"value": net_equity, "label": "Total Equity", "datatype": "Currency", "currency": currency}, + {"value": net_equity, "label": _("Total Equity"), "datatype": "Currency", "currency": currency}, { "value": net_provisional_profit_loss, - "label": "Provisional Profit / Loss (Credit)", + "label": _("Provisional Profit / Loss (Credit)"), "indicator": "Green" if net_provisional_profit_loss > 0 else "Red", "datatype": "Currency", "currency": currency, diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index ca341f4993..7b774ba740 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -97,8 +97,8 @@ def get_columns(filters): if filters["period"] == "Yearly": labels = [ _("Budget") + " " + str(year[0]), - _("Actual ") + " " + str(year[0]), - _("Variance ") + " " + str(year[0]), + _("Actual") + " " + str(year[0]), + _("Variance") + " " + str(year[0]), ] for label in labels: columns.append( diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py index 8e8465cee9..ecad9f104f 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -230,7 +230,7 @@ def get_columns(dimension_list): columns.append( { "fieldname": "total", - "label": "Total", + "label": _("Total"), "fieldtype": "Currency", "options": "currency", "width": 150, diff --git a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py index 8db72de22f..1a003993aa 100644 --- a/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py +++ b/erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py @@ -29,7 +29,7 @@ def get_columns(): "options": "Item Group", "width": 150, }, - {"fieldname": "item", "fieldtype": "Link", "options": "Item", "label": "Item", "width": 150}, + {"fieldname": "item", "fieldtype": "Link", "options": "Item", "label": _("Item"), "width": 150}, {"fieldname": "item_name", "fieldtype": "Data", "label": _("Item Name"), "width": 150}, { "fieldname": "customer", diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 00f5948a1b..3f178f4715 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -115,9 +115,9 @@ def get_columns(filters): {"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140}, {"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200}, {"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50}, - {"fieldname": "range1", "label": "0-30", "fieldtype": "Currency", "width": 140}, - {"fieldname": "range2", "label": "30-60", "fieldtype": "Currency", "width": 140}, - {"fieldname": "range3", "label": "60-90", "fieldtype": "Currency", "width": 140}, + {"fieldname": "range1", "label": _("0-30"), "fieldtype": "Currency", "width": 140}, + {"fieldname": "range2", "label": _("30-60"), "fieldtype": "Currency", "width": 140}, + {"fieldname": "range3", "label": _("60-90"), "fieldtype": "Currency", "width": 140}, {"fieldname": "range4", "label": _("90 Above"), "fieldtype": "Currency", "width": 140}, { "fieldname": "delay_in_payment", diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.json b/erpnext/accounts/report/tax_detail/test_tax_detail.json index 3a4b175455..e4903167cb 100644 --- a/erpnext/accounts/report/tax_detail/test_tax_detail.json +++ b/erpnext/accounts/report/tax_detail/test_tax_detail.json @@ -302,7 +302,7 @@ "is_opening": "No", "is_paid": 0, "is_return": 0, - "is_subcontracted": "No", + "is_subcontracted": 0, "items": [ { "allow_zero_valuation_rate": 0, diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 7291daf2b3..a4d2c82845 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -87,7 +87,7 @@ class AssetCategory(Document): missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name)) if missing_cwip_accounts_for_company: - msg = _("""To enable Capital Work in Progress Accounting, """) + msg = _("""To enable Capital Work in Progress Accounting,""") + " " msg += _("""you must select Capital Work in Progress Account in accounts table""") msg += "

" msg += _("You can also set default CWIP account in Company {}").format( diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index e61efadb12..143f215db2 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -46,10 +46,9 @@ class AssetMovement(Document): if d.target_location: frappe.throw( _( - "Issuing cannot be done to a location. \ - Please enter employee who has issued Asset {0}" + "Issuing cannot be done to a location. Please enter employee who has issued Asset {0}" ).format(d.asset), - title="Incorrect Movement Purpose", + title=_("Incorrect Movement Purpose"), ) if not d.to_employee: frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset)) @@ -58,10 +57,9 @@ class AssetMovement(Document): if d.to_employee: frappe.throw( _( - "Transferring cannot be done to an Employee. \ - Please enter location where Asset {0} has to be transferred" + "Transferring cannot be done to an Employee. Please enter location where Asset {0} has to be transferred" ).format(d.asset), - title="Incorrect Movement Purpose", + title=_("Incorrect Movement Purpose"), ) if not d.target_location: frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset)) @@ -89,8 +87,7 @@ class AssetMovement(Document): if d.to_employee and d.target_location: frappe.throw( _( - "Asset {0} cannot be received at a location and \ - given to employee in a single movement" + "Asset {0} cannot be received at a location and given to employee in a single movement" ).format(d.asset) ) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js index 3fe6b2d0d5..f5e4e723b4 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.js +++ b/erpnext/assets/doctype/asset_repair/asset_repair.js @@ -32,7 +32,7 @@ frappe.ui.form.on('Asset Repair', { refresh: function(frm) { if (frm.doc.docstatus) { - frm.add_custom_button("View General Ledger", function() { + frm.add_custom_button(__("View General Ledger"), function() { frappe.route_options = { "voucher_no": frm.doc.name }; diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 9953c61a81..20865e8ddc 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -37,7 +37,7 @@ class AssetValueAdjustment(Document): _("Asset Value Adjustment cannot be posted before Asset's purchase date {0}.").format( formatdate(asset_purchase_date) ), - title="Incorrect Date", + title=_("Incorrect Date"), ) def set_difference_amount(self): diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 2005dac37d..c9e67987c6 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -179,7 +179,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e if (doc.status != "On Hold") { if(flt(doc.per_received) < 100 && allow_receipt) { cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create')); - if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) { + if(doc.is_subcontracted && me.has_unsupplied_items()) { cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer")); } @@ -636,7 +636,7 @@ function set_schedule_date(frm) { frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) { - if (frm.doc.is_subcontracted === "Yes") { + if (frm.doc.is_subcontracted) { erpnext.buying.get_default_bom(frm); } }); diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 896208f25e..9a1f9d1995 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -457,16 +457,15 @@ "fieldtype": "Column Break" }, { - "default": "No", + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", + "fieldtype": "Check", "in_standard_filter": 1, - "label": "Supply Raw Materials", - "options": "No\nYes", + "label": "Is Subcontracted", "print_hide": 1 }, { - "depends_on": "eval:doc.is_subcontracted==\"Yes\"", + "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 582bd8d1db..5860c4c8ae 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -194,7 +194,7 @@ class PurchaseOrder(BuyingController): ) def validate_bom_for_subcontracting_items(self): - if self.is_subcontracted == "Yes": + if self.is_subcontracted: for item in self.items: if not item.bom: frappe.throw( @@ -294,7 +294,7 @@ class PurchaseOrder(BuyingController): self.set_status(update=True, status=status) self.update_requested_qty() self.update_ordered_qty() - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.update_reserved_qty_for_subcontract() self.notify_update() @@ -311,7 +311,7 @@ class PurchaseOrder(BuyingController): self.update_ordered_qty() self.validate_budget() - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.update_reserved_qty_for_subcontract() frappe.get_doc("Authorization Control").validate_approving_authority( @@ -331,7 +331,7 @@ class PurchaseOrder(BuyingController): if self.has_drop_ship_item(): self.update_delivered_qty_in_sales_order() - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.update_reserved_qty_for_subcontract() self.check_on_hold_or_closed_status() diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index e4fb970c3f..1a7f2dd5d9 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -390,7 +390,7 @@ class TestPurchaseOrder(FrappeTestCase): frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete() def test_update_child_uom_conv_factor_change(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")]) trans_item = json.dumps( @@ -573,7 +573,7 @@ class TestPurchaseOrder(FrappeTestCase): automatically_fetch_payment_terms(enable=0) def test_subcontracting(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) self.assertEqual(len(po.get("supplied_items")), 2) def test_warehouse_company_validation(self): @@ -617,7 +617,7 @@ class TestPurchaseOrder(FrappeTestCase): "doctype": "Purchase Order", "company": "_Test Company", "supplier": "_Test Supplier", - "is_subcontracted": "No", + "is_subcontracted": 0, "schedule_date": add_days(nowdate(), 1), "currency": frappe.get_cached_value("Company", "_Test Company", "default_currency"), "conversion_factor": 1, @@ -764,7 +764,7 @@ class TestPurchaseOrder(FrappeTestCase): ) # Submit PO - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) bin2 = frappe.db.get_value( "Bin", @@ -919,7 +919,7 @@ class TestPurchaseOrder(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=1, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1, ) @@ -936,7 +936,7 @@ class TestPurchaseOrder(FrappeTestCase): po1 = create_purchase_order( item_code=item_code, qty=1, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0, ) @@ -957,7 +957,7 @@ class TestPurchaseOrder(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=order_qty, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", ) @@ -1050,7 +1050,7 @@ class TestPurchaseOrder(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=order_qty, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", do_not_save=True, ) @@ -1283,7 +1283,7 @@ def create_purchase_order(**args): po.schedule_date = add_days(nowdate(), 1) po.company = args.company or "_Test Company" po.supplier = args.supplier or "_Test Supplier" - po.is_subcontracted = args.is_subcontracted or "No" + po.is_subcontracted = args.is_subcontracted or 0 po.currency = args.currency or frappe.get_cached_value("Company", po.company, "default_currency") po.conversion_factor = args.conversion_factor or 1 po.supplier_warehouse = args.supplier_warehouse or None @@ -1309,7 +1309,7 @@ def create_purchase_order(**args): if not args.do_not_save: po.insert() if not args.do_not_submit: - if po.is_subcontracted == "Yes": + if po.is_subcontracted: supp_items = po.get("supplied_items") for d in supp_items: if not d.reserve_warehouse: diff --git a/erpnext/buying/doctype/purchase_order/test_records.json b/erpnext/buying/doctype/purchase_order/test_records.json index 74b8f1b429..896050ce43 100644 --- a/erpnext/buying/doctype/purchase_order/test_records.json +++ b/erpnext/buying/doctype/purchase_order/test_records.json @@ -8,7 +8,7 @@ "doctype": "Purchase Order", "base_grand_total": 5000.0, "grand_total": 5000.0, - "is_subcontracted": "Yes", + "is_subcontracted": 1, "naming_series": "_T-Purchase Order-", "base_net_total": 5000.0, "items": [ @@ -42,7 +42,7 @@ "doctype": "Purchase Order", "base_grand_total": 5000.0, "grand_total": 5000.0, - "is_subcontracted": "No", + "is_subcontracted": 0, "naming_series": "_T-Purchase Order-", "base_net_total": 5000.0, "items": [ 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 a18c527644..f72c598840 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -572,7 +572,7 @@ "read_only": 1 }, { - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "bom", "fieldtype": "Link", "label": "BOM", @@ -581,7 +581,7 @@ }, { "default": "0", - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "include_exploded_items", "fieldtype": "Check", "label": "Include Exploded Items", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 567e41fb61..8d1939a101 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -773,11 +773,10 @@ "fieldtype": "Column Break" }, { - "default": "No", + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", + "fieldtype": "Check", "label": "Is Subcontracted", - "options": "\nYes\nNo", "print_hide": 1 }, { diff --git a/erpnext/buying/doctype/supplier_quotation/test_records.json b/erpnext/buying/doctype/supplier_quotation/test_records.json index 0f835d2a40..8acac3210d 100644 --- a/erpnext/buying/doctype/supplier_quotation/test_records.json +++ b/erpnext/buying/doctype/supplier_quotation/test_records.json @@ -7,7 +7,7 @@ "doctype": "Supplier Quotation", "base_grand_total": 5000.0, "grand_total": 5000.0, - "is_subcontracted": "No", + "is_subcontracted": 0, "naming_series": "_T-Supplier Quotation-", "base_net_total": 5000.0, "items": [ diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py index 992bc805a5..486bf23e90 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py @@ -213,7 +213,8 @@ def make_all_scorecards(docname): end_date = get_scorecard_date(sc.period, start_date) if scp_count > 0: frappe.msgprint( - _("Created {0} scorecards for {1} between: ").format(scp_count, sc.supplier) + _("Created {0} scorecards for {1} between:").format(scp_count, sc.supplier) + + " " + str(first_start_date) + " - " + str(last_end_date) diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py index 130adc97d4..ab7d4879c4 100644 --- a/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py +++ b/erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.py @@ -80,6 +80,6 @@ def _get_variables(criteria): )[0] my_variables.append(var) except Exception: - frappe.throw(_("Unable to find variable: ") + str(match.group(1)), InvalidFormulaVariable) + frappe.throw(_("Unable to find variable:") + " " + str(match.group(1)), InvalidFormulaVariable) return my_variables diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py index 11a74491a4..dbdc62e9ec 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py @@ -48,7 +48,7 @@ def get_chart_data(data, conditions, filters): "data": { "labels": labels, "datasets": [ - {"name": _("{0}").format(filters.get("period")) + _(" Purchase Value"), "values": datapoints} + {"name": _(filters.get("period")) + " " + _("Purchase Value"), "values": datapoints} ], }, "type": "line", diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js index 5ba52f1b21..6889322fb9 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js @@ -35,7 +35,7 @@ frappe.query_reports["Subcontract Order Summary"] = { return { filters: { docstatus: 1, - is_subcontracted: 'Yes', + is_subcontracted: 1, company: frappe.query_report.get_filter_value('company') } } diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py index 1b2705a7be..3d66637576 100644 --- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py +++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py @@ -45,7 +45,7 @@ def get_subcontracted_orders(report_filters): def get_filters(report_filters): filters = [ ["Purchase Order", "docstatus", "=", 1], - ["Purchase Order", "is_subcontracted", "=", "Yes"], + ["Purchase Order", "is_subcontracted", "=", 1], [ "Purchase Order", "transaction_date", diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py index 004657b6e8..2e90de66ef 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.py @@ -78,7 +78,7 @@ def get_data(data, filters): def get_po(filters): record_filters = [ - ["is_subcontracted", "=", "Yes"], + ["is_subcontracted", "=", 1], ["supplier", "=", filters.supplier], ["transaction_date", "<=", filters.to_date], ["transaction_date", ">=", filters.from_date], diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py index 26e4243eee..57f8741b5b 100644 --- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py +++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py @@ -17,7 +17,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry class TestSubcontractedItemToBeReceived(FrappeTestCase): def test_pending_and_received_qty(self): - po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1) transfer_param = [] make_stock_entry( item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100 diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py index 98b18da4ac..6b8a3b140a 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py @@ -72,7 +72,7 @@ def get_po_items_to_supply(filters): ], filters=[ ["Purchase Order", "per_received", "<", "100"], - ["Purchase Order", "is_subcontracted", "=", "Yes"], + ["Purchase Order", "is_subcontracted", "=", 1], ["Purchase Order", "supplier", "=", filters.supplier], ["Purchase Order", "transaction_date", "<=", filters.to_date], ["Purchase Order", "transaction_date", ">=", filters.from_date], diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py index 401176d5ce..2791a26db7 100644 --- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py +++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py @@ -19,7 +19,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry class TestSubcontractedItemToBeTransferred(FrappeTestCase): def test_pending_and_transferred_qty(self): po = create_purchase_order( - item_code="_Test FG Item", is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + item_code="_Test FG Item", is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) # Material Receipt of RMs diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3a20d3f232..8a9318e184 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2586,7 +2586,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil parent.update_ordered_qty() parent.update_ordered_and_reserved_qty() parent.update_receiving_percentage() - if parent.is_subcontracted == "Yes": + if parent.is_subcontracted: parent.update_reserved_qty_for_subcontract() parent.create_raw_materials_supplied("supplied_items") parent.save() diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 47892073f3..eda36868b9 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -167,7 +167,7 @@ class BuyingController(StockController, Subcontracting): _("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx) ) - if item.get("from_warehouse") and self.get("is_subcontracted") == "Yes": + if item.get("from_warehouse") and self.get("is_subcontracted"): frappe.throw( _( "Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor" @@ -339,10 +339,7 @@ class BuyingController(StockController, Subcontracting): return supplied_items_cost def validate_for_subcontracting(self): - if not self.is_subcontracted and self.sub_contracted_items: - frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No")) - - if self.is_subcontracted == "Yes": + if self.is_subcontracted: if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse: frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype)) @@ -363,14 +360,14 @@ class BuyingController(StockController, Subcontracting): item.bom = None def create_raw_materials_supplied(self, raw_material_table): - if self.is_subcontracted == "Yes": + if self.is_subcontracted: self.set_materials_for_subcontracted_items(raw_material_table) elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]: for item in self.get("items"): item.rm_supp_cost = 0.0 - if self.is_subcontracted == "No" and self.get("supplied_items"): + if not self.is_subcontracted and self.get("supplied_items"): self.set("supplied_items", []) @property @@ -466,7 +463,10 @@ class BuyingController(StockController, Subcontracting): stock_items = self.get_stock_items() for d in self.get("items"): - if d.item_code in stock_items and d.warehouse: + if d.item_code not in stock_items: + continue + + if d.warehouse: pr_qty = flt(d.qty) * flt(d.conversion_factor) if pr_qty: @@ -491,6 +491,7 @@ class BuyingController(StockController, Subcontracting): sle = self.get_sl_entries( d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()} ) + if self.is_return: outgoing_rate = get_rate_for_return( self.doctype, self.name, d.item_code, self.return_against, item_row=d @@ -520,18 +521,18 @@ class BuyingController(StockController, Subcontracting): sl_entries.append(from_warehouse_sle) - if flt(d.rejected_qty) != 0: - sl_entries.append( - self.get_sl_entries( - d, - { - "warehouse": d.rejected_warehouse, - "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), - "serial_no": cstr(d.rejected_serial_no).strip(), - "incoming_rate": 0.0, - }, - ) + if flt(d.rejected_qty) != 0: + sl_entries.append( + self.get_sl_entries( + d, + { + "warehouse": d.rejected_warehouse, + "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), + "serial_no": cstr(d.rejected_serial_no).strip(), + "incoming_rate": 0.0, + }, ) + ) self.make_sl_entries_for_supplier_warehouse(sl_entries) self.make_sl_entries( @@ -803,7 +804,7 @@ class BuyingController(StockController, Subcontracting): if self.doctype == "Material Request": return - if hasattr(self, "is_subcontracted") and self.is_subcontracted == "Yes": + if hasattr(self, "is_subcontracted") and self.is_subcontracted: validate_item_type(self, "is_sub_contracted_item", "subcontracted") else: validate_item_type(self, "is_purchase_item", "purchase") diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index 70830882ef..4bce06ff9b 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -407,7 +407,7 @@ class Subcontracting: def set_consumed_qty_in_po(self): # Update consumed qty back in the purchase order - if self.is_subcontracted != "Yes": + if not self.is_subcontracted: return self.__get_purchase_orders() diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js index 7aa0b77759..d532236b7d 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js @@ -37,7 +37,7 @@ frappe.ui.form.on('LinkedIn Settings', { let msg,color; if (days>0){ - msg = __("Your Session will be expire in ") + days + __(" days."); + msg = __("Your Session will be expire in {0} days.", [days]); color = "green"; } else { diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 6874caac71..d4ac0bad16 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -86,7 +86,7 @@ frappe.ui.form.on('Social Media Post', { frm.trigger('add_post_btn'); } if (frm.doc.post_status !='Deleted') { - frm.add_custom_button(('Delete Post'), function() { + frm.add_custom_button(__('Delete Post'), function() { frappe.confirm(__('Are you sure want to delete the Post from Social Media platforms?'), function() { frappe.call({ diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py index 9dae1d50f6..db36581cec 100644 --- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py +++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py @@ -3,11 +3,12 @@ import frappe +from frappe import _ def execute(filters=None): columns = [ - {"fieldname": "creation_date", "label": "Date", "fieldtype": "Date", "width": 300}, + {"fieldname": "creation_date", "label": _("Date"), "fieldtype": "Date", "width": 300}, { "fieldname": "first_response_time", "fieldtype": "Duration", diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json index d5fb9697f8..e6f08f708a 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json @@ -47,7 +47,7 @@ "item_search_settings_section", "redisearch_warning", "search_index_fields", - "show_categories_in_search_autocomplete", + "is_redisearch_enabled", "is_redisearch_loaded", "shop_by_category_section", "slideshow", @@ -293,6 +293,7 @@ "fieldname": "search_index_fields", "fieldtype": "Small Text", "label": "Search Index Fields", + "mandatory_depends_on": "is_redisearch_enabled", "read_only_depends_on": "eval:!doc.is_redisearch_loaded" }, { @@ -301,13 +302,6 @@ "fieldtype": "Section Break", "label": "Item Search Settings" }, - { - "default": "1", - "fieldname": "show_categories_in_search_autocomplete", - "fieldtype": "Check", - "label": "Show Categories in Search Autocomplete", - "read_only_depends_on": "eval:!doc.is_redisearch_loaded" - }, { "default": "0", "fieldname": "is_redisearch_loaded", @@ -365,12 +359,19 @@ "fieldname": "show_price_in_quotation", "fieldtype": "Check", "label": "Show Price in Quotation" + }, + { + "default": "0", + "fieldname": "is_redisearch_enabled", + "fieldtype": "Check", + "label": "Enable Redisearch", + "read_only_depends_on": "eval:!doc.is_redisearch_loaded" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-09-02 14:02:44.785824", + "modified": "2022-04-01 18:35:56.106756", "modified_by": "Administrator", "module": "E-commerce", "name": "E Commerce Settings", @@ -389,5 +390,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py index 881d833eb0..f85667e1b2 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py @@ -9,6 +9,7 @@ from frappe.utils import comma_and, flt, unique from erpnext.e_commerce.redisearch_utils import ( create_website_items_index, + define_autocomplete_dictionary, get_indexable_web_fields, is_search_module_loaded, ) @@ -21,6 +22,8 @@ class ShoppingCartSetupError(frappe.ValidationError): class ECommerceSettings(Document): def onload(self): self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series") + + # flag >> if redisearch is installed and loaded self.is_redisearch_loaded = is_search_module_loaded() def validate(self): @@ -34,6 +37,20 @@ class ECommerceSettings(Document): frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings") + self.is_redisearch_enabled_pre_save = frappe.db.get_single_value( + "E Commerce Settings", "is_redisearch_enabled" + ) + + def after_save(self): + self.create_redisearch_indexes() + + def create_redisearch_indexes(self): + # if redisearch is enabled (value changed) create indexes and dictionary + value_changed = self.is_redisearch_enabled != self.is_redisearch_enabled_pre_save + if self.is_redisearch_loaded and self.is_redisearch_enabled and value_changed: + define_autocomplete_dictionary() + create_website_items_index() + def validate_field_filters(self): if not (self.enable_field_filters and self.filter_fields): return diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js index 7108cabfb3..7295e4b56a 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.js +++ b/erpnext/e_commerce/doctype/website_item/website_item.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Website Item', { - onload: function(frm) { + onload: (frm) => { // should never check Private frm.fields_dict["website_image"].df.is_private = 0; @@ -13,18 +13,35 @@ frappe.ui.form.on('Website Item', { }); }, - image: function() { + refresh: (frm) => { + frm.add_custom_button(__("Prices"), function() { + frappe.set_route("List", "Item Price", {"item_code": frm.doc.item_code}); + }, __("View")); + + frm.add_custom_button(__("Stock"), function() { + frappe.route_options = { + "item_code": frm.doc.item_code + }; + frappe.set_route("query-report", "Stock Balance"); + }, __("View")); + + frm.add_custom_button(__("E Commerce Settings"), function() { + frappe.set_route("Form", "E Commerce Settings"); + }, __("View")); + }, + + image: () => { refresh_field("image_view"); }, - copy_from_item_group: function(frm) { + copy_from_item_group: (frm) => { return frm.call({ doc: frm.doc, method: "copy_specification_from_item_group" }); }, - set_meta_tags(frm) { + set_meta_tags: (frm) => { frappe.utils.set_meta_tag(frm.doc.route); } }); diff --git a/erpnext/e_commerce/redisearch_utils.py b/erpnext/e_commerce/redisearch_utils.py index 82829bf1ef..f2dd796f2c 100644 --- a/erpnext/e_commerce/redisearch_utils.py +++ b/erpnext/e_commerce/redisearch_utils.py @@ -1,8 +1,12 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +import json + import frappe +from frappe import _ from frappe.utils.redis_wrapper import RedisWrapper +from redis import ResponseError from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField WEBSITE_ITEM_INDEX = "website_items_index" @@ -22,6 +26,12 @@ def get_indexable_web_fields(): return [df.fieldname for df in valid_fields] +def is_redisearch_enabled(): + "Return True only if redisearch is loaded and enabled." + is_redisearch_enabled = frappe.db.get_single_value("E Commerce Settings", "is_redisearch_enabled") + return is_search_module_loaded() and is_redisearch_enabled + + def is_search_module_loaded(): try: cache = frappe.cache() @@ -32,14 +42,14 @@ def is_search_module_loaded(): ) return "search" in parsed_output except Exception: - return False + return False # handling older redis versions -def if_redisearch_loaded(function): - "Decorator to check if Redisearch is loaded." +def if_redisearch_enabled(function): + "Decorator to check if Redisearch is enabled." def wrapper(*args, **kwargs): - if is_search_module_loaded(): + if is_redisearch_enabled(): func = function(*args, **kwargs) return func return @@ -51,22 +61,25 @@ def make_key(key): return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8") -@if_redisearch_loaded +@if_redisearch_enabled def create_website_items_index(): "Creates Index Definition." # CREATE index client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache()) - # DROP if already exists try: - client.drop_index() - except Exception: + client.drop_index() # drop if already exists + except ResponseError: + # will most likely raise a ResponseError if index does not exist + # ignore and create index pass + except Exception: + raise_redisearch_error() idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)]) - # Based on e-commerce settings + # Index fields mentioned in e-commerce settings idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields") idx_fields = idx_fields.split(",") if idx_fields else [] @@ -91,20 +104,20 @@ def to_search_field(field): return TextField(field) -@if_redisearch_loaded +@if_redisearch_enabled def insert_item_to_index(website_item_doc): # Insert item to index key = get_cache_key(website_item_doc.name) cache = frappe.cache() web_item = create_web_item_map(website_item_doc) - for k, v in web_item.items(): - super(RedisWrapper, cache).hset(make_key(key), k, v) + for field, value in web_item.items(): + super(RedisWrapper, cache).hset(make_key(key), field, value) insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name) -@if_redisearch_loaded +@if_redisearch_enabled def insert_to_name_ac(web_name, doc_name): ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache()) ac.add_suggestions(Suggestion(web_name, payload=doc_name)) @@ -114,20 +127,20 @@ def create_web_item_map(website_item_doc): fields_to_index = get_fields_indexed() web_item = {} - for f in fields_to_index: - web_item[f] = website_item_doc.get(f) or "" + for field in fields_to_index: + web_item[field] = website_item_doc.get(field) or "" return web_item -@if_redisearch_loaded +@if_redisearch_enabled def update_index_for_item(website_item_doc): # Reinsert to Cache insert_item_to_index(website_item_doc) define_autocomplete_dictionary() -@if_redisearch_loaded +@if_redisearch_enabled def delete_item_from_index(website_item_doc): cache = frappe.cache() key = get_cache_key(website_item_doc.name) @@ -135,13 +148,13 @@ def delete_item_from_index(website_item_doc): try: cache.delete(key) except Exception: - return False + raise_redisearch_error() delete_from_ac_dict(website_item_doc) return True -@if_redisearch_loaded +@if_redisearch_enabled def delete_from_ac_dict(website_item_doc): """Removes this items's name from autocomplete dictionary""" cache = frappe.cache() @@ -149,40 +162,60 @@ def delete_from_ac_dict(website_item_doc): name_ac.delete(website_item_doc.web_item_name) -@if_redisearch_loaded +@if_redisearch_enabled def define_autocomplete_dictionary(): - """Creates an autocomplete search dictionary for `name`. - Also creats autocomplete dictionary for `categories` if - checked in E Commerce Settings""" + """ + Defines/Redefines an autocomplete search dictionary for Website Item Name. + Also creats autocomplete dictionary for Published Item Groups. + """ cache = frappe.cache() - name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache) - cat_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache) - - ac_categories = frappe.db.get_single_value( - "E Commerce Settings", "show_categories_in_search_autocomplete" - ) + item_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache) + item_group_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache) # Delete both autocomplete dicts try: cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE)) cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE)) except Exception: - return False + raise_redisearch_error() + create_items_autocomplete_dict(autocompleter=item_ac) + create_item_groups_autocomplete_dict(autocompleter=item_group_ac) + + +@if_redisearch_enabled +def create_items_autocomplete_dict(autocompleter): + "Add items as suggestions in Autocompleter." items = frappe.get_all( "Website Item", fields=["web_item_name", "item_group"], filters={"published": 1} ) for item in items: - name_ac.add_suggestions(Suggestion(item.web_item_name)) - if ac_categories and item.item_group: - cat_ac.add_suggestions(Suggestion(item.item_group)) - - return True + autocompleter.add_suggestions(Suggestion(item.web_item_name)) -@if_redisearch_loaded +@if_redisearch_enabled +def create_item_groups_autocomplete_dict(autocompleter): + "Add item groups with weightage as suggestions in Autocompleter." + published_item_groups = frappe.get_all( + "Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1} + ) + if not published_item_groups: + return + + for item_group in published_item_groups: + payload = json.dumps({"name": item_group.name, "route": item_group.route}) + autocompleter.add_suggestions( + Suggestion( + string=item_group.name, + score=frappe.utils.flt(item_group.weightage) or 1.0, + payload=payload, # additional info that can be retrieved later + ) + ) + + +@if_redisearch_enabled def reindex_all_web_items(): items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True}) @@ -191,8 +224,8 @@ def reindex_all_web_items(): web_item = create_web_item_map(item) key = make_key(get_cache_key(item.name)) - for k, v in web_item.items(): - super(RedisWrapper, cache).hset(key, k, v) + for field, value in web_item.items(): + super(RedisWrapper, cache).hset(key, field, value) def get_cache_key(name): @@ -210,7 +243,12 @@ def get_fields_indexed(): return fields_to_index -# TODO: Remove later -# # Figure out a way to run this at startup -define_autocomplete_dictionary() -create_website_items_index() +def raise_redisearch_error(): + "Create an Error Log and raise error." + traceback = frappe.get_traceback() + log = frappe.log_error(traceback, frappe._("Redisearch Error")) + log_link = frappe.utils.get_link_to_form("Error Log", log.name) + + frappe.throw( + msg=_("Something went wrong. Check {0}").format(log_link), title=_("Redisearch Error") + ) diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py index 4db6f981fc..b3072c20b6 100644 --- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py +++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py @@ -41,10 +41,8 @@ class CourseSchedulingTool(Document): if self.day == calendar.day_name[getdate(date).weekday()]: course_schedule = self.make_course_schedule(date) try: - print("pass") course_schedule.save() except OverlapError: - print("fail") course_schedules_errors.append(date) else: course_schedules.append(course_schedule) diff --git a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py index 0fb255077f..bbeb654bfc 100644 --- a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py +++ b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py @@ -69,13 +69,13 @@ class StudentGroupCreationTool(Document): l = len(self.courses) for d in self.courses: if not d.student_group_name: - frappe.throw(_("""Student Group Name is mandatory in row {0}""".format(d.idx))) + frappe.throw(_("Student Group Name is mandatory in row {0}").format(d.idx)) if d.group_based_on == "Course" and not d.course: - frappe.throw(_("""Course is mandatory in row {0}""".format(d.idx))) + frappe.throw(_("Course is mandatory in row {0}").format(d.idx)) if d.group_based_on == "Batch" and not d.batch: - frappe.throw(_("""Batch is mandatory in row {0}""".format(d.idx))) + frappe.throw(_("Batch is mandatory in row {0}").format(d.idx)) frappe.publish_realtime( "student_group_creation_progress", {"progress": [d.idx, l]}, user=frappe.session.user diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index d849900ef2..87bdddd6ff 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -87,7 +87,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): field_name, frappe.bold(employee.employee_name) ) if department_list: - error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department)) + error_msg += " " + _("or for Department: {0}").format(frappe.bold(employee_department)) frappe.throw(error_msg, title=_(field_name + " Missing")) return set(tuple(approver) for approver in approvers) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 98408afab6..27479a5e81 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -39,11 +39,15 @@ class LeaveAllocation(Document): def validate(self): self.validate_period() self.validate_allocation_overlap() - self.validate_back_dated_allocation() - self.set_total_leaves_allocated() - self.validate_total_leaves_allocated() self.validate_lwp() set_employee_name(self) + self.set_total_leaves_allocated() + self.validate_leave_days_and_dates() + + def validate_leave_days_and_dates(self): + # all validations that should run on save as well as on update after submit + self.validate_back_dated_allocation() + self.validate_total_leaves_allocated() self.validate_leave_allocation_days() def validate_leave_allocation_days(self): @@ -56,14 +60,19 @@ class LeaveAllocation(Document): leave_allocated = 0 if leave_period: leave_allocated = get_leave_allocation_for_period( - self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date + self.employee, + self.leave_type, + leave_period[0].from_date, + leave_period[0].to_date, + exclude_allocation=self.name, ) leave_allocated += flt(self.new_leaves_allocated) if leave_allocated > max_leaves_allowed: frappe.throw( _( - "Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period" - ).format(self.leave_type, self.employee) + "Total allocated leaves are more than maximum allocation allowed for {0} leave type for employee {1} in the period" + ).format(self.leave_type, self.employee), + OverAllocationError, ) def on_submit(self): @@ -84,6 +93,12 @@ class LeaveAllocation(Document): def on_update_after_submit(self): if self.has_value_changed("new_leaves_allocated"): self.validate_against_leave_applications() + + # recalculate total leaves allocated + self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated) + # run required validations again since total leaves are being updated + self.validate_leave_days_and_dates() + leaves_to_be_added = self.new_leaves_allocated - self.get_existing_leave_count() args = { "leaves": leaves_to_be_added, @@ -92,6 +107,7 @@ class LeaveAllocation(Document): "is_carry_forward": 0, } create_leave_ledger_entry(self, args, True) + self.db_update() def get_existing_leave_count(self): ledger_entries = frappe.get_all( @@ -279,27 +295,27 @@ def get_previous_allocation(from_date, leave_type, employee): ) -def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): - leave_allocated = 0 - leave_allocations = frappe.db.sql( - """ - select employee, leave_type, from_date, to_date, total_leaves_allocated - from `tabLeave Allocation` - where employee=%(employee)s and leave_type=%(leave_type)s - and docstatus=1 - and (from_date between %(from_date)s and %(to_date)s - or to_date between %(from_date)s and %(to_date)s - or (from_date < %(from_date)s and to_date > %(to_date)s)) - """, - {"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type}, - as_dict=1, - ) +def get_leave_allocation_for_period( + employee, leave_type, from_date, to_date, exclude_allocation=None +): + from frappe.query_builder.functions import Sum - if leave_allocations: - for leave_alloc in leave_allocations: - leave_allocated += leave_alloc.total_leaves_allocated - - return leave_allocated + Allocation = frappe.qb.DocType("Leave Allocation") + return ( + frappe.qb.from_(Allocation) + .select(Sum(Allocation.total_leaves_allocated).as_("total_allocated_leaves")) + .where( + (Allocation.employee == employee) + & (Allocation.leave_type == leave_type) + & (Allocation.docstatus == 1) + & (Allocation.name != exclude_allocation) + & ( + (Allocation.from_date.between(from_date, to_date)) + | (Allocation.to_date.between(from_date, to_date)) + | ((Allocation.from_date < from_date) & (Allocation.to_date > to_date)) + ) + ) + ).run()[0][0] or 0.0 @frappe.whitelist() diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index a53d4a82ba..dde52d7ad8 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -1,24 +1,26 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, add_months, getdate, nowdate import erpnext from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.leave_allocation.leave_allocation import ( + BackDatedAllocationError, + OverAllocationError, +) from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type -class TestLeaveAllocation(unittest.TestCase): - @classmethod - def setUpClass(cls): - frappe.db.sql("delete from `tabLeave Period`") +class TestLeaveAllocation(FrappeTestCase): + def setUp(self): + frappe.db.delete("Leave Period") + frappe.db.delete("Leave Allocation") - emp_id = make_employee("test_emp_leave_allocation@salary.com") - cls.employee = frappe.get_doc("Employee", emp_id) - - def tearDown(self): - frappe.db.rollback() + emp_id = make_employee("test_emp_leave_allocation@salary.com", company="_Test Company") + self.employee = frappe.get_doc("Employee", emp_id) def test_overlapping_allocation(self): leaves = [ @@ -65,7 +67,7 @@ class TestLeaveAllocation(unittest.TestCase): # invalid period self.assertRaises(frappe.ValidationError, doc.save) - def test_allocated_leave_days_over_period(self): + def test_validation_for_over_allocation(self): doc = frappe.get_doc( { "doctype": "Leave Allocation", @@ -80,7 +82,135 @@ class TestLeaveAllocation(unittest.TestCase): ) # allocated leave more than period - self.assertRaises(frappe.ValidationError, doc.save) + self.assertRaises(OverAllocationError, doc.save) + + def test_validation_for_over_allocation_post_submission(self): + allocation = frappe.get_doc( + { + "doctype": "Leave Allocation", + "__islocal": 1, + "employee": self.employee.name, + "employee_name": self.employee.employee_name, + "leave_type": "_Test Leave Type", + "from_date": getdate("2015-09-1"), + "to_date": getdate("2015-09-30"), + "new_leaves_allocated": 15, + } + ).submit() + allocation.reload() + # allocated leaves more than period after submission + allocation.new_leaves_allocated = 35 + self.assertRaises(OverAllocationError, allocation.save) + + def test_validation_for_over_allocation_based_on_leave_setup(self): + frappe.delete_doc_if_exists("Leave Period", "Test Allocation Period") + leave_period = frappe.get_doc( + dict( + name="Test Allocation Period", + doctype="Leave Period", + from_date=add_months(nowdate(), -6), + to_date=add_months(nowdate(), 6), + company="_Test Company", + is_active=1, + ) + ).insert() + + leave_type = create_leave_type(leave_type_name="_Test Allocation Validation", is_carry_forward=1) + leave_type.max_leaves_allowed = 25 + leave_type.save() + + # 15 leaves allocated in this period + allocation = create_leave_allocation( + leave_type=leave_type.name, + employee=self.employee.name, + employee_name=self.employee.employee_name, + from_date=leave_period.from_date, + to_date=nowdate(), + ) + allocation.submit() + + # trying to allocate additional 15 leaves + allocation = create_leave_allocation( + leave_type=leave_type.name, + employee=self.employee.name, + employee_name=self.employee.employee_name, + from_date=add_days(nowdate(), 1), + to_date=leave_period.to_date, + ) + self.assertRaises(OverAllocationError, allocation.save) + + def test_validation_for_over_allocation_based_on_leave_setup_post_submission(self): + frappe.delete_doc_if_exists("Leave Period", "Test Allocation Period") + leave_period = frappe.get_doc( + dict( + name="Test Allocation Period", + doctype="Leave Period", + from_date=add_months(nowdate(), -6), + to_date=add_months(nowdate(), 6), + company="_Test Company", + is_active=1, + ) + ).insert() + + leave_type = create_leave_type(leave_type_name="_Test Allocation Validation", is_carry_forward=1) + leave_type.max_leaves_allowed = 30 + leave_type.save() + + # 15 leaves allocated + allocation = create_leave_allocation( + leave_type=leave_type.name, + employee=self.employee.name, + employee_name=self.employee.employee_name, + from_date=leave_period.from_date, + to_date=nowdate(), + ) + allocation.submit() + allocation.reload() + + # allocate additional 15 leaves + allocation = create_leave_allocation( + leave_type=leave_type.name, + employee=self.employee.name, + employee_name=self.employee.employee_name, + from_date=add_days(nowdate(), 1), + to_date=leave_period.to_date, + ) + allocation.submit() + allocation.reload() + + # trying to allocate 25 leaves in 2nd alloc within leave period + # total leaves = 40 which is more than `max_leaves_allowed` setting i.e. 30 + allocation.new_leaves_allocated = 25 + self.assertRaises(OverAllocationError, allocation.save) + + def test_validate_back_dated_allocation_update(self): + leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1) + leave_type.save() + + # initial leave allocation = 15 + leave_allocation = create_leave_allocation( + employee=self.employee.name, + employee_name=self.employee.employee_name, + leave_type="_Test_CF_leave", + from_date=add_months(nowdate(), -12), + to_date=add_months(nowdate(), -1), + carry_forward=0, + ) + leave_allocation.submit() + + # new_leaves = 15, carry_forwarded = 10 + leave_allocation_1 = create_leave_allocation( + employee=self.employee.name, + employee_name=self.employee.employee_name, + leave_type="_Test_CF_leave", + carry_forward=1, + ) + leave_allocation_1.submit() + + # try updating initial leave allocation + leave_allocation.reload() + leave_allocation.new_leaves_allocated = 20 + self.assertRaises(BackDatedAllocationError, leave_allocation.save) def test_carry_forward_calculation(self): leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1) @@ -108,8 +238,10 @@ class TestLeaveAllocation(unittest.TestCase): carry_forward=1, ) leave_allocation_1.submit() + leave_allocation_1.reload() self.assertEqual(leave_allocation_1.unused_leaves, 10) + self.assertEqual(leave_allocation_1.total_leaves_allocated, 25) leave_allocation_1.cancel() @@ -197,9 +329,12 @@ class TestLeaveAllocation(unittest.TestCase): employee=self.employee.name, employee_name=self.employee.employee_name ) leave_allocation.submit() + leave_allocation.reload() self.assertTrue(leave_allocation.total_leaves_allocated, 15) + leave_allocation.new_leaves_allocated = 40 leave_allocation.submit() + leave_allocation.reload() self.assertTrue(leave_allocation.total_leaves_allocated, 40) def test_leave_subtraction_after_submit(self): @@ -207,9 +342,12 @@ class TestLeaveAllocation(unittest.TestCase): employee=self.employee.name, employee_name=self.employee.employee_name ) leave_allocation.submit() + leave_allocation.reload() self.assertTrue(leave_allocation.total_leaves_allocated, 15) + leave_allocation.new_leaves_allocated = 10 leave_allocation.submit() + leave_allocation.reload() self.assertTrue(leave_allocation.total_leaves_allocated, 10) def test_validation_against_leave_application_after_submit(self): diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 5a1248698c..f6bd15951d 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -73,10 +73,10 @@ class ShiftAssignment(Document): frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name) ) if shift_details.start_date: - msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) + msg += " " + _("from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) title = "Ongoing Shift" if shift_details.end_date: - msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y")) + msg += " " + _("to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y")) title = "Active Shift" if msg: frappe.throw(msg, title=title) diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 1e3e8ff646..b5beef7a99 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -109,7 +109,7 @@ class ShiftRequest(Document): self.throw_overlap_error(date_overlap) def throw_overlap_error(self, d): - msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format( + msg = _("Employee {0} has already applied for {1} between {2} and {3}").format( self.employee, d["shift_type"], formatdate(d["from_date"]), formatdate(d["to_date"]) - ) + """ {0}""".format(d["name"]) + ) + """ : {0}""".format(d["name"]) frappe.throw(msg, OverlapError) diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 93a493c9d2..ce7e50f7f4 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -91,8 +91,7 @@ class StaffingPlan(Document): ) > flt(parent_plan_details[0].total_estimated_cost): frappe.throw( _( - "You can only plan for upto {0} vacancies and budget {1} \ - for {2} as per staffing plan {3} for parent company {4}." + "You can only plan for upto {0} vacancies and budget {1} for {2} as per staffing plan {3} for parent company {4}." ).format( cint(parent_plan_details[0].vacancies), parent_plan_details[0].total_estimated_cost, @@ -128,8 +127,7 @@ class StaffingPlan(Document): ): frappe.throw( _( - "{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \ - You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}." + "{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}." ).format( cint(all_sibling_details.vacancies), all_sibling_details.total_estimated_cost, @@ -162,8 +160,7 @@ class StaffingPlan(Document): ): frappe.throw( _( - "Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \ - Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies" + "Subsidiary companies have already planned for {1} vacancies at a budget of {2}. Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies" ).format( self.company, cint(children_details.vacancies), diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 460a514baa..2535180092 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -387,13 +387,13 @@ class LoanRepayment(AccountsController): gle_map = [] if self.shortfall_amount and self.amount_paid > self.shortfall_amount: - remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format( + remarks = _("Shortfall Repayment of {0}.
Repayment against Loan: {1}").format( self.shortfall_amount, self.against_loan ) elif self.shortfall_amount: remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount) else: - remarks = _("Repayment against Loan: ") + self.against_loan + remarks = _("Repayment against Loan:") + " " + self.against_loan if self.repay_from_salary: payment_account = self.payroll_payable_account @@ -746,6 +746,8 @@ def calculate_amounts(against_loan, posting_date, payment_type=""): if payment_type == "Loan Closure": amounts["payable_principal_amount"] = amounts["pending_principal_amount"] amounts["interest_amount"] += amounts["unaccrued_interest"] - amounts["payable_amount"] = amounts["payable_principal_amount"] + amounts["interest_amount"] + amounts["payable_amount"] = ( + amounts["payable_principal_amount"] + amounts["interest_amount"] + amounts["penalty_amount"] + ) return amounts diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 256f66071f..9a23c07106 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -250,7 +250,7 @@ class MaintenanceSchedule(TransactionBase): _("Serial No {0} does not belong to Item {1}").format( frappe.bold(serial_no), frappe.bold(item_code) ), - title="Invalid", + title=_("Invalid"), ) if sr_details.warranty_expiry_date and getdate(sr_details.warranty_expiry_date) >= getdate( diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 29a17849fd..66f4426a0b 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -20,7 +20,7 @@ class MaintenanceVisit(TransactionBase): def validate_purpose_table(self): if not self.purposes: - frappe.throw(_("Add Items in the Purpose Table"), title="Purposes Required") + frappe.throw(_("Add Items in the Purpose Table"), title=_("Purposes Required")) def validate_maintenance_date(self): if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 524f45bfc2..62fc0724e0 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -251,7 +251,7 @@ class TestBOM(FrappeTestCase): self.assertEqual(bom.items[2].rate, 0) # test in Purchase Order sourced_by_supplier is not added to Supplied Item po = create_purchase_order( - item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + item_code=item_code, qty=1, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 89f9ca6d83..60b32b84b0 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -501,7 +501,7 @@ class ProductionPlan(Document): po = frappe.new_doc("Purchase Order") po.supplier = supplier po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate() - po.is_subcontracted = "Yes" + po.is_subcontracted = 1 for row in po_list: po_data = { "item_code": row.production_item, diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index c0affd9cad..ac2f61c5de 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ def execute(filters=None): @@ -46,17 +47,22 @@ def get_exploded_items(bom, data, indent=0, qty=1): def get_columns(): return [ { - "label": "Item Code", + "label": _("Item Code"), "fieldtype": "Link", "fieldname": "item_code", "width": 300, "options": "Item", }, - {"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100}, - {"label": "BOM", "fieldtype": "Link", "fieldname": "bom", "width": 150, "options": "BOM"}, - {"label": "Qty", "fieldtype": "data", "fieldname": "qty", "width": 100}, - {"label": "UOM", "fieldtype": "data", "fieldname": "uom", "width": 100}, - {"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100}, - {"label": "Standard Description", "fieldtype": "data", "fieldname": "description", "width": 150}, - {"label": "Scrap", "fieldtype": "data", "fieldname": "scrap", "width": 100}, + {"label": _("Item Name"), "fieldtype": "data", "fieldname": "item_name", "width": 100}, + {"label": _("BOM"), "fieldtype": "Link", "fieldname": "bom", "width": 150, "options": "BOM"}, + {"label": _("Qty"), "fieldtype": "data", "fieldname": "qty", "width": 100}, + {"label": _("UOM"), "fieldtype": "data", "fieldname": "uom", "width": 100}, + {"label": _("BOM Level"), "fieldtype": "Int", "fieldname": "bom_level", "width": 100}, + { + "label": _("Standard Description"), + "fieldtype": "data", + "fieldname": "description", + "width": 150, + }, + {"label": _("Scrap"), "fieldtype": "data", "fieldname": "scrap", "width": 100}, ] diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py index 17f7f5e51f..2c8f82f2cc 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ from frappe.utils import flt @@ -114,28 +115,28 @@ def get_purchase_order_details(filters, order_details): def get_column(filters): return [ { - "label": "Finished Good", + "label": _("Finished Good"), "fieldtype": "Link", "fieldname": "item_code", "width": 300, "options": "Item", }, - {"label": "Item Name", "fieldtype": "data", "fieldname": "item_name", "width": 100}, + {"label": _("Item Name"), "fieldtype": "data", "fieldname": "item_name", "width": 100}, { - "label": "Document Type", + "label": _("Document Type"), "fieldtype": "Link", "fieldname": "document_type", "width": 150, "options": "DocType", }, { - "label": "Document Name", + "label": _("Document Name"), "fieldtype": "Dynamic Link", "fieldname": "document_name", "width": 150, }, - {"label": "BOM Level", "fieldtype": "Int", "fieldname": "bom_level", "width": 100}, - {"label": "Order Qty", "fieldtype": "Float", "fieldname": "qty", "width": 120}, - {"label": "Received Qty", "fieldtype": "Float", "fieldname": "produced_qty", "width": 160}, - {"label": "Pending Qty", "fieldtype": "Float", "fieldname": "pending_qty", "width": 110}, + {"label": _("BOM Level"), "fieldtype": "Int", "fieldname": "bom_level", "width": 100}, + {"label": _("Order Qty"), "fieldtype": "Float", "fieldname": "qty", "width": 120}, + {"label": _("Received Qty"), "fieldtype": "Float", "fieldname": "produced_qty", "width": 160}, + {"label": _("Pending Qty"), "fieldtype": "Float", "fieldname": "pending_qty", "width": 110}, ] diff --git a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py index c6b7e58d65..063ebba059 100644 --- a/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py +++ b/erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ from frappe.utils import cint @@ -99,59 +100,65 @@ def get_columns(): columns = [ { "fieldname": "work_order", - "label": "Work Order", + "label": _("Work Order"), "fieldtype": "Link", "options": "Work Order", "width": 110, }, - {"fieldname": "bom_no", "label": "BOM", "fieldtype": "Link", "options": "BOM", "width": 120}, + {"fieldname": "bom_no", "label": _("BOM"), "fieldtype": "Link", "options": "BOM", "width": 120}, { "fieldname": "description", - "label": "Description", + "label": _("Description"), "fieldtype": "Data", "options": "", "width": 230, }, { "fieldname": "item_code", - "label": "Item Code", + "label": _("Item Code"), "fieldtype": "Link", "options": "Item", "width": 110, }, { "fieldname": "source_warehouse", - "label": "Source Warehouse", + "label": _("Source Warehouse"), "fieldtype": "Link", "options": "Warehouse", "width": 110, }, - {"fieldname": "qty", "label": "Qty to Build", "fieldtype": "Data", "options": "", "width": 110}, - {"fieldname": "status", "label": "Status", "fieldtype": "Data", "options": "", "width": 100}, + { + "fieldname": "qty", + "label": _("Qty to Build"), + "fieldtype": "Data", + "options": "", + "width": 110, + }, + {"fieldname": "status", "label": _("Status"), "fieldtype": "Data", "options": "", "width": 100}, { "fieldname": "req_items", - "label": "# Req'd Items", + "label": _("# Req'd Items"), "fieldtype": "Data", "options": "", "width": 105, }, { "fieldname": "instock", - "label": "# In Stock", + "label": _("# In Stock"), "fieldtype": "Data", "options": "", "width": 105, }, { "fieldname": "buildable_qty", - "label": "Buildable Qty", + "label": _("Buildable Qty"), "fieldtype": "Data", "options": "", "width": 100, }, { "fieldname": "ready_to_build", - "label": "Build All?", + "label": _("Build All?"), "fieldtype": "Data", "options": "", "width": 90, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 028834a0ec..a3bf78b532 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -4,6 +4,7 @@ erpnext.patches.v11_0.rename_production_order_to_work_order erpnext.patches.v13_0.add_bin_unique_constraint erpnext.patches.v11_0.refactor_naming_series erpnext.patches.v11_0.refactor_autoname_naming +erpnext.patches.v14_0.change_is_subcontracted_fieldtype execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28 execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16 #2020-07-24 erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31 diff --git a/erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py b/erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py new file mode 100644 index 0000000000..ba919a756a --- /dev/null +++ b/erpnext/patches/v14_0/change_is_subcontracted_fieldtype.py @@ -0,0 +1,26 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + + +def execute(): + for doctype in ["Purchase Order", "Purchase Receipt", "Purchase Invoice", "Supplier Quotation"]: + frappe.db.sql( + """ + UPDATE `tab{doctype}` + SET is_subcontracted = 0 + where is_subcontracted in ('', NULL, 'No')""".format( + doctype=doctype + ) + ) + frappe.db.sql( + """ + UPDATE `tab{doctype}` + SET is_subcontracted = 1 + where is_subcontracted = 'Yes'""".format( + doctype=doctype + ) + ) + + frappe.reload_doc(frappe.get_meta(doctype).module, "doctype", frappe.scrub(doctype)) diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py index 31f26b25e7..6ec34b9e71 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -44,8 +44,7 @@ class EmployeeBenefitClaim(Document): if max_benefits < claimed_amount: frappe.throw( _( - "Maximum benefit of employee {0} exceeds {1} by the sum {2} of previous claimed\ - amount" + "Maximum benefit of employee {0} exceeds {1} by the sum {2} of previous claimed amount" ).format(self.employee, max_benefits, claimed_amount - max_benefits) ) @@ -84,8 +83,7 @@ class EmployeeBenefitClaim(Document): if max_benefits < pro_rata_amount + claimed_amount: frappe.throw( _( - "Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component\ - amount and previous claimed amount" + "Maximum benefit of employee {0} exceeds {1} by the sum {2} of benefit application pro-rata component amount and previous claimed amount" ).format( self.employee, max_benefits, pro_rata_amount + claimed_amount - max_benefits ) diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js index 014a121c96..7290a9eafa 100644 --- a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -34,7 +34,7 @@ frappe.ui.form.on('Gratuity Rule Slab', { to_year(frm, cdt, cdn) { let row = locals[cdt][cdn]; if (row.to_year <= row.from_year && row.to_year === 0) { - frappe.throw(__("To(Year) year can not be less than From(year) ")); + frappe.throw(__("To(Year) year can not be less than From(year)")); } } }); diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 496c37b2fa..62e183e59c 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -112,7 +112,7 @@ frappe.ui.form.on('Payroll Entry', { }, callback: function (r) { if (r.message && !r.message.submitted) { - frm.add_custom_button("Make Bank Entry", function () { + frm.add_custom_button(__("Make Bank Entry"), function () { make_bank_entry(frm); }).addClass("btn-primary"); } diff --git a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py index e5348df886..4223f9d4fb 100644 --- a/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py +++ b/erpnext/payroll/report/salary_payments_based_on_payment_mode/salary_payments_based_on_payment_mode.py @@ -142,21 +142,21 @@ def get_report_summary(gross_pay, total_deductions, net_pay, currency): return [ { "value": gross_pay, - "label": "Total Gross Pay", + "label": _("Total Gross Pay"), "indicator": "Green", "datatype": "Currency", "currency": currency, }, { "value": total_deductions, - "label": "Total Deduction", + "label": _("Total Deduction"), "datatype": "Currency", "indicator": "Red", "currency": currency, }, { "value": net_pay, - "label": "Total Net Pay", + "label": _("Total Net Pay"), "datatype": "Currency", "indicator": "Blue", "currency": currency, diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py index 5c3dc2da11..17e3155e28 100644 --- a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ from frappe.utils import date_diff, nowdate @@ -83,19 +84,24 @@ def get_chart_data(data): def get_columns(): columns = [ - {"fieldname": "name", "fieldtype": "Link", "label": "Task", "options": "Task", "width": 150}, - {"fieldname": "subject", "fieldtype": "Data", "label": "Subject", "width": 200}, - {"fieldname": "status", "fieldtype": "Data", "label": "Status", "width": 100}, - {"fieldname": "priority", "fieldtype": "Data", "label": "Priority", "width": 80}, - {"fieldname": "progress", "fieldtype": "Data", "label": "Progress (%)", "width": 120}, + {"fieldname": "name", "fieldtype": "Link", "label": _("Task"), "options": "Task", "width": 150}, + {"fieldname": "subject", "fieldtype": "Data", "label": _("Subject"), "width": 200}, + {"fieldname": "status", "fieldtype": "Data", "label": _("Status"), "width": 100}, + {"fieldname": "priority", "fieldtype": "Data", "label": _("Priority"), "width": 80}, + {"fieldname": "progress", "fieldtype": "Data", "label": _("Progress (%)"), "width": 120}, { "fieldname": "exp_start_date", "fieldtype": "Date", - "label": "Expected Start Date", + "label": _("Expected Start Date"), "width": 150, }, - {"fieldname": "exp_end_date", "fieldtype": "Date", "label": "Expected End Date", "width": 150}, - {"fieldname": "completed_on", "fieldtype": "Date", "label": "Actual End Date", "width": 130}, - {"fieldname": "delay", "fieldtype": "Data", "label": "Delay (In Days)", "width": 120}, + { + "fieldname": "exp_end_date", + "fieldtype": "Date", + "label": _("Expected End Date"), + "width": 150, + }, + {"fieldname": "completed_on", "fieldtype": "Date", "label": _("Actual End Date"), "width": 130}, + {"fieldname": "delay", "fieldtype": "Data", "label": _("Delay (In Days)"), "width": 120}, ] return columns diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 54e5daa6bd..bbf1ff6736 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -81,7 +81,7 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac } this.frm.set_query("item_code", "items", function() { - if (me.frm.doc.is_subcontracted == "Yes") { + if (me.frm.doc.is_subcontracted) { return{ query: "erpnext.controllers.queries.item_query", filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a4492e8bdd..fa41e1bf0a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -239,7 +239,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe () => set_value('currency', currency), () => set_value('price_list_currency', currency), () => set_value('status', 'Draft'), - () => set_value('is_subcontracted', 'No'), + () => set_value('is_subcontracted', 0), () => { if(this.frm.doc.company && !this.frm.doc.amended_from) { this.frm.trigger("company"); diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 9339c5d998..eded16529c 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -483,7 +483,7 @@ erpnext.utils.update_child_items = function(opts) { if (frm.doc.doctype == 'Sales Order') { filters = {"is_sales_item": 1}; } else if (frm.doc.doctype == 'Purchase Order') { - if (frm.doc.is_subcontracted == "Yes") { + if (frm.doc.is_subcontracted) { filters = {"is_sub_contracted_item": 1}; } else { filters = {"is_purchase_item": 1}; diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.js b/erpnext/regional/doctype/datev_settings/datev_settings.js index f04705929f..3c365494c4 100644 --- a/erpnext/regional/doctype/datev_settings/datev_settings.js +++ b/erpnext/regional/doctype/datev_settings/datev_settings.js @@ -3,6 +3,6 @@ frappe.ui.form.on('DATEV Settings', { refresh: function(frm) { - frm.add_custom_button('Show Report', () => frappe.set_route('query-report', 'DATEV'), "fa fa-table"); + frm.add_custom_button(__('Show Report'), () => frappe.set_route('query-report', 'DATEV'), "fa fa-table"); } }); diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 348f0c6fee..17b018c65b 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -105,6 +105,30 @@ erpnext.setup_einvoice_actions = (doctype) => { }, primary_action_label: __('Submit') }); + d.fields_dict.transporter.df.onchange = function () { + const transporter = d.fields_dict.transporter.value; + if (transporter) { + frappe.db.get_value('Supplier', transporter, ['gst_transporter_id', 'supplier_name']) + .then(({ message }) => { + d.set_value('gst_transporter_id', message.gst_transporter_id); + d.set_value('transporter_name', message.supplier_name); + }); + } else { + d.set_value('gst_transporter_id', ''); + d.set_value('transporter_name', ''); + } + }; + d.fields_dict.driver.df.onchange = function () { + const driver = d.fields_dict.driver.value; + if (driver) { + frappe.db.get_value('Driver', driver, ['full_name']) + .then(({ message }) => { + d.set_value('driver_name', message.full_name); + }); + } else { + d.set_value('driver_name', ''); + } + }; d.show(); }; @@ -153,7 +177,6 @@ const get_ewaybill_fields = (frm) => { 'fieldname': 'gst_transporter_id', 'label': 'GST Transporter ID', 'fieldtype': 'Data', - 'fetch_from': 'transporter.gst_transporter_id', 'default': frm.doc.gst_transporter_id }, { @@ -189,9 +212,9 @@ const get_ewaybill_fields = (frm) => { 'fieldname': 'transporter_name', 'label': 'Transporter Name', 'fieldtype': 'Data', - 'fetch_from': 'transporter.name', 'read_only': 1, - 'default': frm.doc.transporter_name + 'default': frm.doc.transporter_name, + 'depends_on': 'transporter' }, { 'fieldname': 'mode_of_transport', @@ -206,7 +229,8 @@ const get_ewaybill_fields = (frm) => { 'fieldtype': 'Data', 'fetch_from': 'driver.full_name', 'read_only': 1, - 'default': frm.doc.driver_name + 'default': frm.doc.driver_name, + 'depends_on': 'driver' }, { 'fieldname': 'lr_date', diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index cbdec564ba..8fd9c1c43d 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -387,7 +387,7 @@ def update_other_charges( def get_payment_details(invoice): payee_name = invoice.company - mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments]) + mode_of_payment = "" paid_amount = invoice.base_paid_amount outstanding_amount = invoice.outstanding_amount diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 87f277f65c..0b48f70eab 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -727,7 +727,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex args: { reference_doctype: me.frm.doctype, reference_name: me.frm.docname, - content: __('Reason for hold: ')+data.reason_for_hold, + content: __('Reason for hold:') + ' ' + data.reason_for_hold, comment_email: frappe.session.user, comment_by: frappe.session.user_fullname }, diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 1177615aee..b62b27bc4b 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -243,7 +243,7 @@ erpnext.PointOfSale.ItemSelector = class { value: "+1", item: { item_code, batch_no, serial_no, uom, rate } }); - me.set_search_value(''); + me.search_field.set_focus(); }); this.search_field.$input.on('input', (e) => { @@ -328,6 +328,7 @@ erpnext.PointOfSale.ItemSelector = class { add_filtered_item_to_cart() { this.$items_container.find(".item-wrapper").click(); + this.set_search_value(''); } resize_selector(minimize) { diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index 33badc37f8..3e4bfb2ef7 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -102,7 +102,7 @@ def get_data_by_time(filters, common_columns): def get_data_by_territory(filters, common_columns): columns = [ { - "label": "Territory", + "label": _("Territory"), "fieldname": "territory", "fieldtype": "Link", "options": "Territory", diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py index 1c10a374b6..98633cb719 100644 --- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py +++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py @@ -65,7 +65,7 @@ def get_columns(customer_naming_type): _("Credit Limit") + ":Currency:120", _("Outstanding Amt") + ":Currency:100", _("Credit Balance") + ":Currency:120", - _("Bypass credit check at Sales Order ") + ":Check:80", + _("Bypass credit check at Sales Order") + ":Check:80", _("Is Frozen") + ":Check:80", _("Disabled") + ":Check:80", ] 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 12ca7b3ff8..091c20c917 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 @@ -235,7 +235,7 @@ def get_chart_data(data): return { "data": { "labels": labels[:30], # show max of 30 items in chart - "datasets": [{"name": _(" Total Sales Amount"), "values": datapoints[:30]}], + "datasets": [{"name": _("Total Sales Amount"), "values": datapoints[:30]}], }, "type": "bar", } diff --git a/erpnext/selling/report/quotation_trends/quotation_trends.py b/erpnext/selling/report/quotation_trends/quotation_trends.py index dfcec22cca..4e0758d7cd 100644 --- a/erpnext/selling/report/quotation_trends/quotation_trends.py +++ b/erpnext/selling/report/quotation_trends/quotation_trends.py @@ -49,7 +49,7 @@ def get_chart_data(data, conditions, filters): "data": { "labels": labels, "datasets": [ - {"name": _("{0}").format(filters.get("period")) + _(" Quoted Amount"), "values": datapoints} + {"name": _(filters.get("period")) + " " + _("Quoted Amount"), "values": datapoints} ], }, "type": "line", diff --git a/erpnext/selling/report/sales_order_trends/sales_order_trends.py b/erpnext/selling/report/sales_order_trends/sales_order_trends.py index 93707bd46d..719f1c5274 100644 --- a/erpnext/selling/report/sales_order_trends/sales_order_trends.py +++ b/erpnext/selling/report/sales_order_trends/sales_order_trends.py @@ -47,9 +47,7 @@ def get_chart_data(data, conditions, filters): return { "data": { "labels": labels, - "datasets": [ - {"name": _("{0}").format(filters.get("period")) + _(" Sales Value"), "values": datapoints} - ], + "datasets": [{"name": _(filters.get("period")) + " " + _("Sales Value"), "values": datapoints}], }, "type": "line", "lineOptions": {"regionFill": 1}, diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 6cb9f7e479..6ea4525606 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -54,7 +54,7 @@ class Bin(Document): (supplied_item.rm_item_code == self.item_code) & (po.name == supplied_item.parent) & (po.docstatus == 1) - & (po.is_subcontracted == "Yes") + & (po.is_subcontracted) & (po.status != "Closed") & (po.per_received < 100) & (supplied_item.reserve_warehouse == self.warehouse) @@ -79,7 +79,7 @@ class Bin(Document): & (se.name == se_item.parent) & (po.name == se.purchase_order) & (po.docstatus == 1) - & (po.is_subcontracted == "Yes") + & (po.is_subcontracted == 1) & (po.status != "Closed") & (po.per_received < 100) ) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 9e8b3bd463..23301a6a78 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -55,10 +55,15 @@ frappe.ui.form.on("Item", { if (frm.doc.has_variants) { frm.set_intro(__("This Item is a Template and cannot be used in transactions. Item attributes will be copied over into the variants unless 'No Copy' is set"), true); + frm.add_custom_button(__("Show Variants"), function() { frappe.set_route("List", "Item", {"variant_of": frm.doc.name}); }, __("View")); + frm.add_custom_button(__("Item Variant Settings"), function() { + frappe.set_route("Form", "Item Variant Settings"); + }, __("View")); + frm.add_custom_button(__("Variant Details Report"), function() { frappe.set_route("query-report", "Item Variant Details", {"item": frm.doc.name}); }, __("View")); @@ -110,6 +115,13 @@ frappe.ui.form.on("Item", { } }); }, __('Actions')); + } else { + frm.add_custom_button(__("Website Item"), function() { + frappe.db.get_value("Website Item", {item_code: frm.doc.name}, "name", (d) => { + if (!d.name) frappe.throw(__("Website Item not found")); + frappe.set_route("Form", "Website Item", d.name); + }); + }, __("View")); } erpnext.item.edit_prices_button(frm); @@ -131,12 +143,6 @@ frappe.ui.form.on("Item", { frappe.set_route('Form', 'Item', new_item.name); }); - if(frm.doc.has_variants) { - frm.add_custom_button(__("Item Variant Settings"), function() { - frappe.set_route("Form", "Item Variant Settings"); - }, __("View")); - } - const stock_exists = (frm.doc.__onload && frm.doc.__onload.stock_exists) ? 1 : 0; diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 9d7c22fc8e..535f565209 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -464,7 +464,7 @@ class Item(Document): frappe.msgprint( _("It can take upto few hours for accurate stock values to be visible after merging items."), indicator="orange", - title="Note", + title=_("Note"), ) if self.published_in_website: diff --git a/erpnext/stock/doctype/item/item_dashboard.py b/erpnext/stock/doctype/item/item_dashboard.py index 33acf4bfd8..3caed02d69 100644 --- a/erpnext/stock/doctype/item/item_dashboard.py +++ b/erpnext/stock/doctype/item/item_dashboard.py @@ -32,5 +32,6 @@ def get_data(): {"label": _("Manufacture"), "items": ["Production Plan", "Work Order", "Item Manufacturer"]}, {"label": _("Traceability"), "items": ["Serial No", "Batch"]}, {"label": _("Move"), "items": ["Stock Entry"]}, + {"label": _("E-commerce"), "items": ["Website Item"]}, ], } diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index d829b2cbf3..32c58c5ae1 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -41,7 +41,7 @@ class TestItemAlternative(FrappeTestCase): supplier_warehouse = "Test Supplier Warehouse - _TC" po = create_purchase_order( item="Test Finished Goods - A", - is_subcontracted="Yes", + is_subcontracted=1, qty=5, rate=3000, supplier_warehouse=supplier_warehouse, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 4524914f5c..a70ff171a9 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -209,16 +209,14 @@ class MaterialRequest(BuyingController): if d.ordered_qty and d.ordered_qty > allowed_qty: frappe.throw( _( - "The total Issue / Transfer quantity {0} in Material Request {1} \ - cannot be greater than allowed requested quantity {2} for Item {3}" + "The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than allowed requested quantity {2} for Item {3}" ).format(d.ordered_qty, d.parent, allowed_qty, d.item_code) ) elif d.ordered_qty and d.ordered_qty > d.stock_qty: frappe.throw( _( - "The total Issue / Transfer quantity {0} in Material Request {1} \ - cannot be greater than requested quantity {2} for Item {3}" + "The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than requested quantity {2} for Item {3}" ).format(d.ordered_qty, d.parent, d.qty, d.item_code) ) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index d3476a88f0..33d7745c62 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -33,7 +33,9 @@ class PickList(Document): location.sales_order and frappe.db.get_value("Sales Order", location.sales_order, "per_picked") == 100 ): - frappe.throw("Row " + str(location.idx) + " has been picked already!") + frappe.throw( + _("Row #{}: item {} has been picked already.").format(location.idx, location.item_code) + ) def before_submit(self): for item in self.locations: @@ -82,10 +84,9 @@ class PickList(Document): 100 + flt(frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")) ): frappe.throw( - "You are picking more than required quantity for " - + item_code - + ". Check if there is any other pick list created for " - + so_doc.name + _( + "You are picking more than required quantity for {}. Check if there is any other pick list created for {}" + ).format(item_code, so_doc.name) ) frappe.db.set_value("Sales Order Item", so_item, "picked_qty", already_picked + picked_qty) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 0182ed55a1..51ec598f72 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -200,7 +200,7 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend cur_frm.add_custom_button(__('Reopen'), this.reopen_purchase_receipt, __("Status")) } - this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes"); + this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted); } make_purchase_invoice() { @@ -298,10 +298,10 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt frappe.provide("erpnext.buying"); frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) { - if (frm.doc.is_subcontracted === "Yes") { + if (frm.doc.is_subcontracted) { erpnext.buying.get_default_bom(frm); } - frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes"); + frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); }); frappe.ui.form.on('Purchase Receipt Item', { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 6d4b4a19bd..6e5f6f5b52 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -437,17 +437,16 @@ "fieldtype": "Column Break" }, { - "default": "No", + "default": "0", "fieldname": "is_subcontracted", - "fieldtype": "Select", - "label": "Raw Materials Consumed", + "fieldtype": "Check", + "label": "Is Subcontracted", "oldfieldname": "is_subcontracted", "oldfieldtype": "Select", - "options": "No\nYes", "print_hide": 1 }, { - "depends_on": "eval:doc.is_subcontracted==\"Yes\"", + "depends_on": "eval:doc.is_subcontracted", "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index a6f82b08dc..f3faba4f8d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -327,7 +327,7 @@ class TestPurchaseReceipt(FrappeTestCase): target="_Test Warehouse 1 - _TC", basic_rate=100, ) - pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes") + pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted=1) self.assertEqual(len(pr.get("supplied_items")), 2) rm_supp_cost = sum(d.amount for d in pr.get("supplied_items")) @@ -362,7 +362,7 @@ class TestPurchaseReceipt(FrappeTestCase): item_code="_Test FG Item", qty=10, rate=0, - is_subcontracted="Yes", + is_subcontracted=1, company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", supplier_warehouse="Work In Progress - TCP1", @@ -401,7 +401,7 @@ class TestPurchaseReceipt(FrappeTestCase): item_code=item_code, qty=1, include_exploded_items=0, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", ) @@ -647,6 +647,45 @@ class TestPurchaseReceipt(FrappeTestCase): return_pr.cancel() pr.cancel() + def test_purchase_receipt_for_rejected_gle_without_accepted_warehouse(self): + from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse + + rejected_warehouse = "_Test Rejected Warehouse - TCP1" + if not frappe.db.exists("Warehouse", rejected_warehouse): + get_warehouse( + company="_Test Company with perpetual inventory", + abbr=" - TCP1", + warehouse_name="_Test Rejected Warehouse", + ).name + + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + received_qty=2, + rejected_qty=2, + rejected_warehouse=rejected_warehouse, + do_not_save=True, + ) + + pr.items[0].qty = 0.0 + pr.items[0].warehouse = "" + pr.submit() + + actual_qty = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": pr.name, + "warehouse": pr.items[0].rejected_warehouse, + "is_cancelled": 0, + }, + "actual_qty", + ) + + self.assertEqual(actual_qty, 2) + self.assertFalse(pr.items[0].warehouse) + pr.cancel() + def test_purchase_return_for_serialized_items(self): def _check_serial_no_values(serial_no, field_values): serial_no = frappe.get_doc("Serial No", serial_no) @@ -1122,7 +1161,7 @@ class TestPurchaseReceipt(FrappeTestCase): po = create_purchase_order( item_code=item_code, qty=order_qty, - is_subcontracted="Yes", + is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC", ) @@ -1465,7 +1504,7 @@ def make_purchase_receipt(**args): pr.set_posting_time = 1 pr.company = args.company or "_Test Company" pr.supplier = args.supplier or "_Test Supplier" - pr.is_subcontracted = args.is_subcontracted or "No" + pr.is_subcontracted = args.is_subcontracted or 0 pr.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pr.currency = args.currency or "INR" pr.is_return = args.is_return diff --git a/erpnext/stock/doctype/purchase_receipt/test_records.json b/erpnext/stock/doctype/purchase_receipt/test_records.json index 724e3d729a..990ad12b30 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_records.json +++ b/erpnext/stock/doctype/purchase_receipt/test_records.json @@ -92,7 +92,7 @@ "currency": "INR", "doctype": "Purchase Receipt", "base_grand_total": 5000.0, - "is_subcontracted": "Yes", + "is_subcontracted": 1, "base_net_total": 5000.0, "items": [ { 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 e5994b2dd4..03a4201ce5 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -648,7 +648,7 @@ }, { "default": "0", - "depends_on": "eval:parent.is_subcontracted == 'Yes'", + "depends_on": "eval:parent.is_subcontracted", "fieldname": "include_exploded_items", "fieldtype": "Check", "label": "Include Exploded Items", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 7564bb266d..1df56ef7b4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -214,7 +214,7 @@ frappe.ui.form.on('Stock Entry', { if (frm.doc.docstatus === 1) { if (frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer' && frm.doc.per_transferred < 100) { - frm.add_custom_button('End Transit', function() { + frm.add_custom_button(__('End Transit'), function() { frappe.model.open_mapped_doc({ method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry", frm: frm @@ -633,7 +633,7 @@ frappe.ui.form.on('Stock Entry Detail', { // set allow_zero_valuation_rate to 0 if s_warehouse is selected. let item = frappe.get_doc(cdt, cdn); if (item.s_warehouse) { - item.allow_zero_valuation_rate = 0; + frappe.model.set_value(cdt, cdn, "allow_zero_valuation_rate", 0); } }, @@ -778,7 +778,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle return { "filters": { "docstatus": 1, - "is_subcontracted": "Yes", + "is_subcontracted": 1, "company": me.frm.doc.company } }; diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 42956a129b..6561362c3a 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -436,7 +436,7 @@ class TestStockLedgerEntry(FrappeTestCase): item_code=subcontracted_item, qty=10, rate=20, - is_subcontracted="Yes", + is_subcontracted=1, ) self.assertEqual(pr1.items[0].valuation_rate, 120) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 4438acf811..05dd105d99 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -55,6 +55,25 @@ frappe.ui.form.on("Stock Reconciliation", { } }, + scan_barcode: function(frm) { + const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:frm}); + barcode_scanner.process_scan(); + }, + + scan_mode: function(frm) { + if (frm.doc.scan_mode) { + frappe.show_alert({ + message: __("Scan mode enabled, existing quantity will not be fetched."), + indicator: "green" + }); + } + }, + + set_warehouse: function(frm) { + let transaction_controller = new erpnext.TransactionController({frm:frm}); + transaction_controller.autofill_warehouse(frm.doc.items, "warehouse", frm.doc.set_warehouse); + }, + get_items: function(frm) { let fields = [ { @@ -148,15 +167,18 @@ frappe.ui.form.on("Stock Reconciliation", { batch_no: d.batch_no }, callback: function(r) { - frappe.model.set_value(cdt, cdn, "qty", r.message.qty); + const row = frappe.model.get_doc(cdt, cdn); + if (!frm.doc.scan_mode) { + frappe.model.set_value(cdt, cdn, "qty", r.message.qty); + } frappe.model.set_value(cdt, cdn, "valuation_rate", r.message.rate); frappe.model.set_value(cdt, cdn, "current_qty", r.message.qty); frappe.model.set_value(cdt, cdn, "current_valuation_rate", r.message.rate); frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty); - frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty); + frappe.model.set_value(cdt, cdn, "amount", row.qty * row.valuation_rate); frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos); - if (frm.doc.purpose == "Stock Reconciliation") { + if (frm.doc.purpose == "Stock Reconciliation" && !frm.doc.scan_mode) { frappe.model.set_value(cdt, cdn, "serial_no", r.message.serial_nos); } } @@ -204,7 +226,7 @@ frappe.ui.form.on("Stock Reconciliation Item", { warehouse: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; - if (child.batch_no) { + if (child.batch_no && !frm.doc.scan_mode) { frappe.model.set_value(child.cdt, child.cdn, "batch_no", ""); } @@ -213,7 +235,7 @@ frappe.ui.form.on("Stock Reconciliation Item", { item_code: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; - if (child.batch_no) { + if (child.batch_no && !frm.doc.scan_mode) { frappe.model.set_value(cdt, cdn, "batch_no", ""); } @@ -239,7 +261,14 @@ frappe.ui.form.on("Stock Reconciliation Item", { const serial_nos = child.serial_no.trim().split('\n'); frappe.model.set_value(cdt, cdn, "qty", serial_nos.length); } - } + }, + + items_add: function(frm, cdt, cdn) { + var item = frappe.get_doc(cdt, cdn); + if (!item.warehouse && frm.doc.set_warehouse) { + frappe.model.set_value(cdt, cdn, "warehouse", frm.doc.set_warehouse); + } + }, }); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index a882a61e5a..e545b8ea5c 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -14,6 +14,12 @@ "posting_date", "posting_time", "set_posting_time", + "section_break_8", + "set_warehouse", + "section_break_22", + "scan_barcode", + "column_break_12", + "scan_mode", "sb9", "items", "section_break_9", @@ -139,13 +145,44 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, + { + "fieldname": "scan_barcode", + "fieldtype": "Data", + "label": "Scan Barcode", + "options": "Barcode" + }, + { + "default": "0", + "description": "Disables auto-fetching of existing quantity", + "fieldname": "scan_mode", + "fieldtype": "Check", + "label": "Scan Mode" + }, + { + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Default Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "section_break_22", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" } ], "icon": "fa fa-upload-alt", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2022-02-06 14:28:19.043905", + "modified": "2022-03-27 08:57:47.161959", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 07a8566d4a..5d5a27f1ea 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +from typing import Optional import frappe from frappe import _, msgprint @@ -706,29 +707,43 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): @frappe.whitelist() def get_stock_balance_for( - item_code, warehouse, posting_date, posting_time, batch_no=None, with_valuation_rate=True + item_code: str, + warehouse: str, + posting_date: str, + posting_time: str, + batch_no: Optional[str] = None, + with_valuation_rate: bool = True, ): frappe.has_permission("Stock Reconciliation", "write", throw=True) - item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) + item_dict = frappe.get_cached_value( + "Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1 + ) if not item_dict: # In cases of data upload to Items table msg = _("Item {} does not exist.").format(item_code) frappe.throw(msg, title=_("Missing")) - serial_nos = "" - with_serial_no = True if item_dict.get("has_serial_no") else False + serial_nos = None + has_serial_no = bool(item_dict.get("has_serial_no")) + has_batch_no = bool(item_dict.get("has_batch_no")) + + if not batch_no and has_batch_no: + # Not enough information to fetch data + return {"qty": 0, "rate": 0, "serial_nos": None} + + # TODO: fetch only selected batch's values data = get_stock_balance( item_code, warehouse, posting_date, posting_time, with_valuation_rate=with_valuation_rate, - with_serial_no=with_serial_no, + with_serial_no=has_serial_no, ) - if with_serial_no: + if has_serial_no: qty, rate, serial_nos = data else: qty, rate = data 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 6bbba051f9..79c2fcc252 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -16,15 +16,15 @@ "amount", "allow_zero_valuation_rate", "serial_no_and_batch_section", - "serial_no", - "column_break_11", "batch_no", + "column_break_11", + "serial_no", "section_break_3", "current_qty", - "current_serial_no", + "current_amount", "column_break_9", "current_valuation_rate", - "current_amount", + "current_serial_no", "section_break_14", "quantity_difference", "column_break_16", @@ -181,7 +181,7 @@ ], "istable": 1, "links": [], - "modified": "2021-05-21 12:13:33.041266", + "modified": "2022-04-02 04:19:40.380587", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", @@ -190,5 +190,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f83f692f64..d3a230e3d8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -50,7 +50,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru "transaction_date": None, "conversion_rate": 1.0, "buying_price_list": None, - "is_subcontracted": "Yes" / "No", + "is_subcontracted": 0/1, "ignore_pricing_rule": 0/1 "project": "" "set_warehouse": "" @@ -124,7 +124,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.transaction_date and item.lead_time_days: out.schedule_date = out.lead_time_date = add_days(args.transaction_date, item.lead_time_days) - if args.get("is_subcontracted") == "Yes": + if args.get("is_subcontracted"): out.bom = args.get("bom") or get_default_bom(args.item_code) get_gross_profit(out) @@ -240,7 +240,7 @@ def validate_item_details(args, item): throw(_("Item {0} is a template, please select one of its variants").format(item.name)) elif args.transaction_type == "buying" and args.doctype != "Material Request": - if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1: + if args.get("is_subcontracted") and item.is_sub_contracted_item != 1: throw(_("Item {0} must be a Sub-contracted Item").format(item.name)) @@ -261,7 +261,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "transaction_date": None, "conversion_rate": 1.0, "buying_price_list": None, - "is_subcontracted": "Yes" / "No", + "is_subcontracted": 0/1, "ignore_pricing_rule": 0/1 "project": "", barcode: "", diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js index ea27dd251d..61927f51a8 100644 --- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js +++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js @@ -68,7 +68,7 @@ frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) { options: [ {fieldname: 'stock_capacity', label: __('Capacity (Stock UOM)')}, {fieldname: 'percent_occupied', label: __('% Occupied')}, - {fieldname: 'actual_qty', label: __('Balance Qty (Stock ')} + {fieldname: 'actual_qty', label: __('Balance Qty (Stock)')} ] }, change: function(sort_by, sort_order) { diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index a96ffefd47..ee151b7517 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -246,8 +246,7 @@ def notify_errors(exceptions_list): _("Dear System Manager,") + "
" + _( - "An error occured for certain Items while creating Material Requests based on Re-order level. \ - Please rectify these issues :" + "An error occured for certain Items while creating Material Requests based on Re-order level. Please rectify these issues :" ) + "
" ) diff --git a/erpnext/stock/report/bom_search/bom_search.py b/erpnext/stock/report/bom_search/bom_search.py index 3be87abc39..56a65c3c30 100644 --- a/erpnext/stock/report/bom_search/bom_search.py +++ b/erpnext/stock/report/bom_search/bom_search.py @@ -3,6 +3,7 @@ import frappe +from frappe import _ def execute(filters=None): @@ -34,10 +35,10 @@ def execute(filters=None): return [ { "fieldname": "parent", - "label": "BOM", + "label": _("BOM"), "width": 200, "fieldtype": "Dynamic Link", "options": "doctype", }, - {"fieldname": "doctype", "label": "Type", "width": 200, "fieldtype": "Data"}, + {"fieldname": "doctype", "label": _("Type"), "width": 200, "fieldtype": "Data"}, ], data diff --git a/erpnext/stock/report/item_variant_details/item_variant_details.py b/erpnext/stock/report/item_variant_details/item_variant_details.py index d1bf2203f1..e3a2a65d8f 100644 --- a/erpnext/stock/report/item_variant_details/item_variant_details.py +++ b/erpnext/stock/report/item_variant_details/item_variant_details.py @@ -71,7 +71,7 @@ def get_columns(item): columns = [ { "fieldname": "variant_name", - "label": "Variant", + "label": _("Variant"), "fieldtype": "Link", "options": "Item", "width": 200, 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 6cc9061685..837c4a6d15 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 @@ -4,6 +4,7 @@ import json import frappe +from frappe import _ SLE_FIELDS = ( "name", @@ -105,155 +106,155 @@ def get_columns(): { "fieldname": "name", "fieldtype": "Link", - "label": "Stock Ledger Entry", + "label": _("Stock Ledger Entry"), "options": "Stock Ledger Entry", }, { "fieldname": "posting_date", "fieldtype": "Date", - "label": "Posting Date", + "label": _("Posting Date"), }, { "fieldname": "posting_time", "fieldtype": "Time", - "label": "Posting Time", + "label": _("Posting Time"), }, { "fieldname": "creation", "fieldtype": "Datetime", - "label": "Creation", + "label": _("Creation"), }, { "fieldname": "voucher_type", "fieldtype": "Link", - "label": "Voucher Type", + "label": _("Voucher Type"), "options": "DocType", }, { "fieldname": "voucher_no", "fieldtype": "Dynamic Link", - "label": "Voucher No", + "label": _("Voucher No"), "options": "voucher_type", }, { "fieldname": "batch_no", "fieldtype": "Link", - "label": "Batch", + "label": _("Batch"), "options": "Batch", }, { "fieldname": "use_batchwise_valuation", "fieldtype": "Check", - "label": "Batchwise Valuation", + "label": _("Batchwise Valuation"), }, { "fieldname": "actual_qty", "fieldtype": "Float", - "label": "Qty Change", + "label": _("Qty Change"), }, { "fieldname": "incoming_rate", "fieldtype": "Float", - "label": "Incoming Rate", + "label": _("Incoming Rate"), }, { "fieldname": "consumption_rate", "fieldtype": "Float", - "label": "Consumption Rate", + "label": _("Consumption Rate"), }, { "fieldname": "qty_after_transaction", "fieldtype": "Float", - "label": "(A) Qty After Transaction", + "label": _("(A) Qty After Transaction"), }, { "fieldname": "expected_qty_after_transaction", "fieldtype": "Float", - "label": "(B) Expected Qty After Transaction", + "label": _("(B) Expected Qty After Transaction"), }, { "fieldname": "difference_in_qty", "fieldtype": "Float", - "label": "A - B", + "label": _("A - B"), }, { "fieldname": "stock_queue", "fieldtype": "Data", - "label": "FIFO/LIFO Queue", + "label": _("FIFO/LIFO Queue"), }, { "fieldname": "fifo_queue_qty", "fieldtype": "Float", - "label": "(C) Total qty in queue", + "label": _("(C) Total qty in queue"), }, { "fieldname": "fifo_qty_diff", "fieldtype": "Float", - "label": "A - C", + "label": _("A - C"), }, { "fieldname": "stock_value", "fieldtype": "Float", - "label": "(D) Balance Stock Value", + "label": _("(D) Balance Stock Value"), }, { "fieldname": "fifo_stock_value", "fieldtype": "Float", - "label": "(E) Balance Stock Value in Queue", + "label": _("(E) Balance Stock Value in Queue"), }, { "fieldname": "fifo_value_diff", "fieldtype": "Float", - "label": "D - E", + "label": _("D - E"), }, { "fieldname": "stock_value_difference", "fieldtype": "Float", - "label": "(F) Stock Value Difference", + "label": _("(F) Stock Value Difference"), }, { "fieldname": "stock_value_from_diff", "fieldtype": "Float", - "label": "Balance Stock Value using (F)", + "label": _("Balance Stock Value using (F)"), }, { "fieldname": "diff_value_diff", "fieldtype": "Float", - "label": "K - D", + "label": _("K - D"), }, { "fieldname": "fifo_stock_diff", "fieldtype": "Float", - "label": "(G) Stock Value difference (FIFO queue)", + "label": _("(G) Stock Value difference (FIFO queue)"), }, { "fieldname": "fifo_difference_diff", "fieldtype": "Float", - "label": "F - G", + "label": _("F - G"), }, { "fieldname": "valuation_rate", "fieldtype": "Float", - "label": "(H) Valuation Rate", + "label": _("(H) Valuation Rate"), }, { "fieldname": "fifo_valuation_rate", "fieldtype": "Float", - "label": "(I) Valuation Rate as per FIFO", + "label": _("(I) Valuation Rate as per FIFO"), }, { "fieldname": "fifo_valuation_diff", "fieldtype": "Float", - "label": "H - I", + "label": _("H - I"), }, { "fieldname": "balance_value_by_qty", "fieldtype": "Float", - "label": "(J) Valuation = Value (D) ÷ Qty (A)", + "label": _("(J) Valuation = Value (D) ÷ Qty (A)"), }, { "fieldname": "valuation_diff", "fieldtype": "Float", - "label": "H - J", + "label": _("H - J"), }, ] diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 967b2b2294..3e0ddab6d3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -715,7 +715,7 @@ class update_entries_after(object): ) # Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice - if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == "Yes": + if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"): doc = frappe.get_doc(sle.voucher_type, sle.voucher_no) doc.update_valuation_rate(reset_outgoing_rate=False) for d in doc.items + doc.supplied_items: diff --git a/erpnext/stock/tests/test_valuation.py b/erpnext/stock/tests/test_valuation.py index 506a666c28..e60c1caac3 100644 --- a/erpnext/stock/tests/test_valuation.py +++ b/erpnext/stock/tests/test_valuation.py @@ -60,9 +60,9 @@ class TestFIFOValuation(unittest.TestCase): self.queue.remove_stock(1, 5) self.assertEqual(self.queue, [[-1, 5]]) - # XXX - self.queue.remove_stock(1, 10) + self.queue.remove_stock(1) self.assertTotalQty(-2) + self.assertEqual(self.queue, [[-2, 5]]) self.queue.add_stock(2, 10) self.assertTotalQty(0) @@ -93,7 +93,7 @@ class TestFIFOValuation(unittest.TestCase): self.queue.remove_stock(3, 20) self.assertEqual(self.queue, [[1, 10], [5, 20]]) - def test_collapsing_of_queue(self): + def test_queue_with_unknown_rate(self): self.queue.add_stock(1, 1) self.queue.add_stock(1, 2) self.queue.add_stock(1, 3) @@ -102,8 +102,7 @@ class TestFIFOValuation(unittest.TestCase): self.assertTotalValue(10) self.queue.remove_stock(3, 1) - # XXX - self.assertEqual(self.queue, [[1, 7]]) + self.assertEqual(self.queue, [[1, 4]]) def test_rounding_off(self): self.queue.add_stock(1.0, 1.0) @@ -172,6 +171,32 @@ class TestFIFOValuation(unittest.TestCase): self.assertTotalQty(total_qty) self.assertTotalValue(total_value) + @given(stock_queue_generator, st.floats(min_value=0.1, max_value=1e6)) + def test_fifo_qty_value_nonneg_hypothesis_with_outgoing_rate(self, stock_queue, outgoing_rate): + self.queue = FIFOValuation([]) + total_qty = 0.0 + total_value = 0.0 + + for qty, rate in stock_queue: + # don't allow negative stock + if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1: + continue + if qty > 0: + self.queue.add_stock(qty, rate) + total_qty += qty + total_value += qty * rate + else: + qty = abs(qty) + consumed = self.queue.remove_stock(qty, outgoing_rate) + self.assertAlmostEqual( + qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}" + ) + total_qty -= qty + total_value -= sum(q * r for q, r in consumed) + self.assertTotalQty(total_qty) + self.assertTotalValue(total_value) + self.assertGreaterEqual(total_value, 0) + class TestLIFOValuation(unittest.TestCase): def setUp(self): diff --git a/erpnext/stock/valuation.py b/erpnext/stock/valuation.py index 648b218287..35f4f12235 100644 --- a/erpnext/stock/valuation.py +++ b/erpnext/stock/valuation.py @@ -60,9 +60,7 @@ class FIFOValuation(BinWiseValuation): # specifying the attributes to save resources # ref: https://docs.python.org/3/reference/datamodel.html#slots - __slots__ = [ - "queue", - ] + __slots__ = ["queue"] def __init__(self, state: Optional[List[StockBin]]): self.queue: List[StockBin] = state if state is not None else [] @@ -123,15 +121,9 @@ class FIFOValuation(BinWiseValuation): index = idx break - # If no entry found with outgoing rate, collapse queue + # If no entry found with outgoing rate, consume as per FIFO if index is None: # nosemgrep - new_stock_value = sum(d[QTY] * d[RATE] for d in self.queue) - qty * outgoing_rate - new_stock_qty = sum(d[QTY] for d in self.queue) - qty - self.queue = [ - [new_stock_qty, new_stock_value / new_stock_qty if new_stock_qty > 0 else outgoing_rate] - ] - consumed_bins.append([qty, outgoing_rate]) - break + index = 0 else: index = 0 @@ -172,9 +164,7 @@ class LIFOValuation(BinWiseValuation): # specifying the attributes to save resources # ref: https://docs.python.org/3/reference/datamodel.html#slots - __slots__ = [ - "stack", - ] + __slots__ = ["stack"] def __init__(self, state: Optional[List[StockBin]]): self.stack: List[StockBin] = state if state is not None else [] diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py index 5b51ef81c7..57fa7bf2b7 100644 --- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py +++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py @@ -3,15 +3,16 @@ import frappe +from frappe import _ def execute(filters=None): columns = [ - {"fieldname": "creation_date", "label": "Date", "fieldtype": "Date", "width": 300}, + {"fieldname": "creation_date", "label": _("Date"), "fieldtype": "Date", "width": 300}, { "fieldname": "first_response_time", "fieldtype": "Duration", - "label": "First Response Time", + "label": _("First Response Time"), "width": 300, }, ] diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py index 77a749ef9e..3ed056f55e 100644 --- a/erpnext/templates/pages/product_search.py +++ b/erpnext/templates/pages/product_search.py @@ -1,6 +1,8 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +import json + import frappe from frappe.utils import cint, cstr from redisearch import AutoCompleter, Client, Query @@ -9,7 +11,7 @@ from erpnext.e_commerce.redisearch_utils import ( WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, WEBSITE_ITEM_INDEX, WEBSITE_ITEM_NAME_AUTOCOMPLETE, - is_search_module_loaded, + is_redisearch_enabled, make_key, ) from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website @@ -74,8 +76,8 @@ def search(query): def product_search(query, limit=10, fuzzy_search=True): search_results = {"from_redisearch": True, "results": []} - if not is_search_module_loaded(): - # Redisearch module not loaded + if not is_redisearch_enabled(): + # Redisearch module not enabled search_results["from_redisearch"] = False search_results["results"] = get_product_data(query, 0, limit) return search_results @@ -86,6 +88,8 @@ def product_search(query, limit=10, fuzzy_search=True): red = frappe.cache() query = clean_up_query(query) + # TODO: Check perf/correctness with Suggestions & Query vs only Query + # TODO: Use Levenshtein Distance in Query (max=3) ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=red) client = Client(make_key(WEBSITE_ITEM_INDEX), conn=red) suggestions = ac.get_suggestions( @@ -121,8 +125,8 @@ def convert_to_dict(redis_search_doc): def get_category_suggestions(query): search_results = {"results": []} - if not is_search_module_loaded(): - # Redisearch module not loaded, query db + if not is_redisearch_enabled(): + # Redisearch module not enabled, query db categories = frappe.db.get_all( "Item Group", filters={"name": ["like", "%{0}%".format(query)], "show_in_website": 1}, @@ -135,8 +139,10 @@ def get_category_suggestions(query): return search_results ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=frappe.cache()) - suggestions = ac.get_suggestions(query, num=10) + suggestions = ac.get_suggestions(query, num=10, with_payloads=True) - search_results["results"] = [s.string for s in suggestions] + results = [json.loads(s.payload) for s in suggestions] + + search_results["results"] = results return search_results diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py index 07291e851b..bf12181c52 100644 --- a/erpnext/tests/test_subcontracting.py +++ b/erpnext/tests/test_subcontracting.py @@ -50,7 +50,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -112,7 +112,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -175,7 +175,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -239,7 +239,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -298,7 +298,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -363,7 +363,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -421,7 +421,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -492,7 +492,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -529,7 +529,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -609,7 +609,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -675,7 +675,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -751,7 +751,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: @@ -834,7 +834,7 @@ class TestSubcontracting(unittest.TestCase): itemwise_details = make_stock_in_entry(rm_items=rm_items) po = create_purchase_order( - rm_items=items, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC" + rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC" ) for d in rm_items: diff --git a/erpnext/utilities/doctype/video/video.js b/erpnext/utilities/doctype/video/video.js index 9cb5a155ad..e6c6efbbb7 100644 --- a/erpnext/utilities/doctype/video/video.js +++ b/erpnext/utilities/doctype/video/video.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Video', { refresh: function (frm) { frm.events.toggle_youtube_statistics_section(frm); - frm.add_custom_button("Watch Video", () => frappe.help.show_video(frm.doc.url, frm.doc.title)); + frm.add_custom_button(__("Watch Video"), () => frappe.help.show_video(frm.doc.url, frm.doc.title)); }, toggle_youtube_statistics_section: (frm) => { diff --git a/erpnext/utilities/report/youtube_interactions/youtube_interactions.py b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py index a65a75f362..a2cb4e80cd 100644 --- a/erpnext/utilities/report/youtube_interactions/youtube_interactions.py +++ b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py @@ -67,7 +67,7 @@ def get_chart_summary_data(data): { "value": total_views, "indicator": "Blue", - "label": "Total Views", + "label": _("Total Views"), "datatype": "Float", } ] diff --git a/requirements.txt b/requirements.txt index 39591caf92..657054fb24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # frappe # https://github.com/frappe/frappe is installed during bench-init gocardless-pro~=1.22.0 googlemaps -pandas~=1.1.5 +pandas>=1.1.5,<2.0.0 plaid-python~=7.2.1 pycountry~=20.7.3 PyGithub~=1.55 @@ -10,4 +10,4 @@ python-youtube~=0.8.0 taxjar~=1.9.2 tweepy~=3.10.0 Unidecode~=1.2.0 -redisearch==2.0.0 \ No newline at end of file +redisearch~=2.1.0