diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 03c3eb0ac0..f96f59169e 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -293,6 +293,11 @@ def validate_accounts(file_name): accounts_dict = {} for account in accounts: accounts_dict.setdefault(account["account_name"], account) + if not hasattr(account, "parent_account"): + msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.") + msg += "

" + msg += _("Alternatively, you can download the template and fill your data in.") + frappe.throw(msg, title=_("Parent Account Missing")) if account["parent_account"] and accounts_dict.get(account["parent_account"]): accounts_dict[account["parent_account"]]["is_group"] = 1 diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c2798a36b6..2e26fd2a9b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -260,7 +260,10 @@ doc_events = { "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" ], - "on_trash": "erpnext.regional.check_deletion_permission" + "on_trash": "erpnext.regional.check_deletion_permission", + "validate": [ + "erpnext.regional.india.utils.validate_document_name" + ] }, "Purchase Invoice": { "validate": [ @@ -282,9 +285,6 @@ doc_events = { ('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { 'validate': ['erpnext.regional.india.utils.set_place_of_supply'] }, - ('Sales Invoice', 'Purchase Invoice'): { - 'validate': ['erpnext.regional.india.utils.validate_document_name'] - }, "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index f650b24d86..f4b56a0e17 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "hr", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "HR", "links": [ @@ -226,42 +227,12 @@ "onboard": 0, "type": "Card Break" }, - { - "dependencies": "Employee", - "hidden": 0, - "is_query_report": 0, - "label": "Leave Application", - "link_to": "Leave Application", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Employee", - "hidden": 0, - "is_query_report": 0, - "label": "Leave Allocation", - "link_to": "Leave Allocation", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Leave Type", - "hidden": 0, - "is_query_report": 0, - "label": "Leave Policy", - "link_to": "Leave Policy", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Leave Period", - "link_to": "Leave Period", + "label": "Holiday List", + "link_to": "Holiday List", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -280,8 +251,28 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Holiday List", - "link_to": "Holiday List", + "label": "Leave Period", + "link_to": "Leave Period", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Leave Type", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Policy", + "link_to": "Leave Policy", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Leave Policy", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Policy Assignment", + "link_to": "Leave Policy Assignment", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -290,8 +281,18 @@ "dependencies": "Employee", "hidden": 0, "is_query_report": 0, - "label": "Compensatory Leave Request", - "link_to": "Compensatory Leave Request", + "label": "Leave Application", + "link_to": "Leave Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Allocation", + "link_to": "Leave Allocation", "link_type": "DocType", "onboard": 0, "type": "Link" @@ -317,12 +318,12 @@ "type": "Link" }, { - "dependencies": "Leave Application", + "dependencies": "Employee", "hidden": 0, - "is_query_report": 1, - "label": "Employee Leave Balance", - "link_to": "Employee Leave Balance", - "link_type": "Report", + "is_query_report": 0, + "label": "Compensatory Leave Request", + "link_to": "Compensatory Leave Request", + "link_type": "DocType", "onboard": 0, "type": "Link" }, @@ -383,16 +384,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "Attendance", - "hidden": 0, - "is_query_report": 1, - "label": "Monthly Attendance Sheet", - "link_to": "Monthly Attendance Sheet", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -420,6 +411,15 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Travel Request", + "link_to": "Travel Request", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -464,6 +464,15 @@ "onboard": 0, "type": "Card Break" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Driver", + "link_to": "Driver", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -541,6 +550,24 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Appointment Letter", + "link_to": "Appointment Letter", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Appointment Letter Template", + "link_to": "Appointment Letter Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -625,33 +652,6 @@ "onboard": 0, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Reports", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "Employee", - "hidden": 0, - "is_query_report": 1, - "label": "Employee Birthday", - "link_to": "Employee Birthday", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Employee", - "hidden": 0, - "is_query_report": 1, - "label": "Employees working on a holiday", - "link_to": "Employees working on a holiday", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -702,7 +702,74 @@ { "hidden": 0, "is_query_report": 0, - "label": "Employee Tax and Benefits", + "label": "Key Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Monthly Attendance Sheet", + "link_to": "Monthly Attendance Sheet", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Staffing Plan", + "hidden": 0, + "is_query_report": 1, + "label": "Recruitment Analytics", + "link_to": "Recruitment Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Analytics", + "link_to": "Employee Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Leave Balance", + "link_to": "Employee Leave Balance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Leave Balance Summary", + "link_to": "Employee Leave Balance Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee Advance", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Advance Summary", + "link_to": "Employee Advance Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Other Reports", "onboard": 0, "type": "Card Break" }, @@ -710,74 +777,44 @@ "dependencies": "Employee", "hidden": 0, "is_query_report": 0, - "label": "Employee Tax Exemption Declaration", - "link_to": "Employee Tax Exemption Declaration", - "link_type": "DocType", + "label": "Employee Information", + "link_to": "Employee Information", + "link_type": "Report", "onboard": 0, "type": "Link" }, { "dependencies": "Employee", "hidden": 0, - "is_query_report": 0, - "label": "Employee Tax Exemption Proof Submission", - "link_to": "Employee Tax Exemption Proof Submission", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Employee, Payroll Period", - "hidden": 0, - "is_query_report": 0, - "label": "Employee Other Income", - "link_to": "Employee Other Income", - "link_type": "DocType", + "is_query_report": 1, + "label": "Employee Birthday", + "link_to": "Employee Birthday", + "link_type": "Report", "onboard": 0, "type": "Link" }, { "dependencies": "Employee", "hidden": 0, - "is_query_report": 0, - "label": "Employee Benefit Application", - "link_to": "Employee Benefit Application", - "link_type": "DocType", + "is_query_report": 1, + "label": "Employees Working on a Holiday", + "link_to": "Employees working on a holiday", + "link_type": "Report", "onboard": 0, "type": "Link" }, { - "dependencies": "Employee", + "dependencies": "Daily Work Summary", "hidden": 0, - "is_query_report": 0, - "label": "Employee Benefit Claim", - "link_to": "Employee Benefit Claim", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Employee", - "hidden": 0, - "is_query_report": 0, - "label": "Employee Tax Exemption Category", - "link_to": "Employee Tax Exemption Category", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "Employee", - "hidden": 0, - "is_query_report": 0, - "label": "Employee Tax Exemption Sub Category", - "link_to": "Employee Tax Exemption Sub Category", - "link_type": "DocType", + "is_query_report": 1, + "label": "Daily Work Summary Replies", + "link_to": "Daily Work Summary Replies", + "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2021-01-21 13:38:38.941001", + "modified": "2021-03-24 17:35:21.483297", "modified_by": "Administrator", "module": "HR", "name": "HR", diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 4050a7d3ed..cd61d2a31d 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.utils import cstr +from frappe.utils import cstr, flt from frappe.test_runner import make_test_records from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost @@ -81,15 +81,27 @@ class TestBOM(unittest.TestCase): bom = frappe.copy_doc(test_records[2]) bom.insert() - # test amounts in selected currency - self.assertEqual(bom.operating_cost, 100) - self.assertEqual(bom.raw_material_cost, 351.68) - self.assertEqual(bom.total_cost, 451.68) + raw_material_cost = 0.0 + op_cost = 0.0 + + for op_row in bom.operations: + op_cost += op_row.operating_cost + + for row in bom.items: + raw_material_cost += row.amount + + base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate")) + base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate")) # test amounts in selected currency - self.assertEqual(bom.base_operating_cost, 6000) - self.assertEqual(bom.base_raw_material_cost, 21100.80) - self.assertEqual(bom.base_total_cost, 27100.80) + self.assertEqual(bom.operating_cost, op_cost) + self.assertEqual(bom.raw_material_cost, raw_material_cost) + self.assertEqual(bom.total_cost, raw_material_cost + op_cost) + + # test amounts in selected currency + self.assertEqual(bom.base_operating_cost, base_op_cost) + self.assertEqual(bom.base_raw_material_cost, base_raw_material_cost) + self.assertEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 8aa0ffd774..92074c6288 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -47,6 +47,8 @@ class JobCard(Document): if d.completed_qty: self.total_completed_qty += d.completed_qty + self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) + def get_overlap_for(self, args, check_next_available_slot=False): production_capacity = 1 diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index a92bad1d5a..70139c6da8 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -33,12 +33,16 @@ class TestProject(unittest.TestCase): def test_project_template_having_parent_child_tasks(self): project_name = "Test Project with Template - Tasks with Parent-Child Relation" + + if frappe.db.get_value('Project', {'project_name': project_name}, 'name'): + project_name = frappe.db.get_value('Project', {'project_name': project_name}, 'name') + frappe.db.sql(""" delete from tabTask where project = %s """, project_name) frappe.delete_doc('Project', project_name) task1 = task_exists("Test Template Task Parent") if not task1: - task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4) + task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=10) task2 = task_exists("Test Template Task Child 1") if not task2: @@ -53,7 +57,7 @@ class TestProject(unittest.TestCase): tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc') self.assertEqual(tasks[0].subject, 'Test Template Task Parent') - self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4)) + self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 10)) self.assertEqual(tasks[1].subject, 'Test Template Task Child 1') self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3)) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 21a20a7bce..6c2144d6cb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -737,28 +737,34 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.trigger("item_code", cdt, cdn); } else { - var valid_serial_nos = []; - var serialnos = []; // Replacing all occurences of comma with carriage return item.serial_no = item.serial_no.replace(/,/g, '\n'); - serialnos = item.serial_no.split("\n"); - for (var i = 0; i < serialnos.length; i++) { - if (serialnos[i] != "") { - valid_serial_nos.push(serialnos[i]); - } - } item.conversion_factor = item.conversion_factor || 1; - refresh_field("serial_no", item.name, item.parentfield); - if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { - frappe.model.set_value(item.doctype, item.name, - "qty", valid_serial_nos.length / item.conversion_factor); - frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); + if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) { + setTimeout(() => { + me.update_qty(cdt, cdn); + }, 10000); } } } }, + update_qty: function(cdt, cdn) { + var valid_serial_nos = []; + var serialnos = []; + var item = frappe.get_doc(cdt, cdn); + serialnos = item.serial_no.split("\n"); + for (var i = 0; i < serialnos.length; i++) { + if (serialnos[i] != "") { + valid_serial_nos.push(serialnos[i]); + } + } + frappe.model.set_value(item.doctype, item.name, + "qty", valid_serial_nos.length / item.conversion_factor); + frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length); + }, + validate: function() { this.calculate_taxes_and_totals(false); }, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 8eccc3f565..3dd1b36fb6 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -787,6 +787,8 @@ class GSPConnector(): self.invoice.irn = res.get('Irn') self.invoice.ewaybill = res.get('EwbNo') + self.invoice.ack_no = res.get('AckNo') + self.invoice.ack_date = res.get('AckDt') self.invoice.signed_einvoice = dec_signed_invoice self.invoice.signed_qr_code = res.get('SignedQRCode') diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.js b/erpnext/setup/doctype/global_defaults/global_defaults.js index 552331aac8..942dd5989e 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.js +++ b/erpnext/setup/doctype/global_defaults/global_defaults.js @@ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', { method: "frappe.client.get_list", args: { doctype: "UOM Conversion Factor", - filters: { "category": "Length" }, + filters: { "category": __("Length") }, fields: ["to_uom"], limit_page_length: 500 }, diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 95cb92b1b3..933ca8ab3d 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -1,14 +1,14 @@ frappe.provide('erpnext.stock'); erpnext.stock.ItemDashboard = Class.extend({ - init: function(opts) { + init: function (opts) { $.extend(this, opts); this.make(); }, - make: function() { + make: function () { var me = this; this.start = 0; - if(!this.sort_by) { + if (!this.sort_by) { this.sort_by = 'projected_qty'; this.sort_order = 'asc'; } @@ -16,22 +16,25 @@ erpnext.stock.ItemDashboard = Class.extend({ this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent); this.result = this.content.find('.result'); - this.content.on('click', '.btn-move', function() { - handle_move_add($(this), "Move") + this.content.on('click', '.btn-move', function () { + handle_move_add($(this), "Move"); }); - this.content.on('click', '.btn-add', function() { - handle_move_add($(this), "Add") + this.content.on('click', '.btn-add', function () { + handle_move_add($(this), "Add"); }); - this.content.on('click', '.btn-edit', function() { + this.content.on('click', '.btn-edit', function () { let item = unescape($(this).attr('data-item')); let warehouse = unescape($(this).attr('data-warehouse')); let company = unescape($(this).attr('data-company')); - frappe.db.get_value('Putaway Rule', - {'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => { - frappe.set_route("Form", "Putaway Rule", r.name); - }); + frappe.db.get_value('Putaway Rule', { + 'item_code': item, + 'warehouse': warehouse, + 'company': company + }, 'name', (r) => { + frappe.set_route("Form", "Putaway Rule", r.name); + }); }); function handle_move_add(element, action) { @@ -39,23 +42,26 @@ erpnext.stock.ItemDashboard = Class.extend({ let warehouse = unescape(element.attr('data-warehouse')); let actual_qty = unescape(element.attr('data-actual_qty')); let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry'))); - let entry_type = action === "Move" ? "Material Transfer": null; + let entry_type = action === "Move" ? "Material Transfer" : null; if (disable_quick_entry) { open_stock_entry(item, warehouse, entry_type); } else { if (action === "Add") { let rate = unescape($(this).attr('data-rate')); - erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); }); - } - else { - erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); }); + erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () { + me.refresh(); + }); + } else { + erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () { + me.refresh(); + }); } } } function open_stock_entry(item, warehouse, entry_type) { - frappe.model.with_doctype('Stock Entry', function() { + frappe.model.with_doctype('Stock Entry', function () { var doc = frappe.model.get_new_doc('Stock Entry'); if (entry_type) doc.stock_entry_type = entry_type; @@ -64,18 +70,18 @@ erpnext.stock.ItemDashboard = Class.extend({ row.s_warehouse = warehouse; frappe.set_route('Form', doc.doctype, doc.name); - }) + }); } // more - this.content.find('.btn-more').on('click', function() { + this.content.find('.btn-more').on('click', function () { me.start += me.page_length; me.refresh(); }); }, - refresh: function() { - if(this.before_refresh) { + refresh: function () { + if (this.before_refresh) { this.before_refresh(); } @@ -94,13 +100,13 @@ erpnext.stock.ItemDashboard = Class.extend({ frappe.call({ method: this.method, args: args, - callback: function(r) { + callback: function (r) { me.render(r.message); } }); }, - render: function(data) { - if (this.start===0) { + render: function (data) { + if (this.start === 0) { this.max_count = 0; this.result.empty(); } @@ -115,7 +121,7 @@ erpnext.stock.ItemDashboard = Class.extend({ this.max_count = this.max_count; // show more button - if (data && data.length===(this.page_length + 1)) { + if (data && data.length === (this.page_length + 1)) { this.content.find('.more').removeClass('hidden'); // remove the last element @@ -137,15 +143,15 @@ erpnext.stock.ItemDashboard = Class.extend({ } }, - get_item_dashboard_data: function(data, max_count, show_item) { - if(!max_count) max_count = 0; - if(!data) data = []; + get_item_dashboard_data: function (data, max_count, show_item) { + if (!max_count) max_count = 0; + if (!data) data = []; - data.forEach(function(d) { + data.forEach(function (d) { d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract; d.pending_qty = 0; d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract; - if(d.actual_or_pending > d.actual_qty) { + if (d.actual_or_pending > d.actual_qty) { d.pending_qty = d.actual_or_pending - d.actual_qty; } @@ -161,16 +167,16 @@ erpnext.stock.ItemDashboard = Class.extend({ return { data: data, max_count: max_count, - can_write:can_write, + can_write: can_write, show_item: show_item || false }; }, - get_capacity_dashboard_data: function(data) { + get_capacity_dashboard_data: function (data) { if (!data) data = []; - data.forEach(function(d) { - d.color = d.percent_occupied >=80 ? "#f8814f" : "#2490ef"; + data.forEach(function (d) { + d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef"; }); let can_write = 0; @@ -185,53 +191,77 @@ erpnext.stock.ItemDashboard = Class.extend({ } }); -erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) { +erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) { var dialog = new frappe.ui.Dialog({ title: target ? __('Add Item') : __('Move Item'), - fields: [ - {fieldname: 'item_code', label: __('Item'), - fieldtype: 'Link', options: 'Item', read_only: 1}, - {fieldname: 'source', label: __('Source Warehouse'), - fieldtype: 'Link', options: 'Warehouse', read_only: 1}, - {fieldname: 'target', label: __('Target Warehouse'), - fieldtype: 'Link', options: 'Warehouse', reqd: 1}, - {fieldname: 'qty', label: __('Quantity'), reqd: 1, - fieldtype: 'Float', description: __('Available {0}', [actual_qty]) }, - {fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 }, + fields: [{ + fieldname: 'item_code', + label: __('Item'), + fieldtype: 'Link', + options: 'Item', + read_only: 1 + }, + { + fieldname: 'source', + label: __('Source Warehouse'), + fieldtype: 'Link', + options: 'Warehouse', + read_only: 1 + }, + { + fieldname: 'target', + label: __('Target Warehouse'), + fieldtype: 'Link', + options: 'Warehouse', + reqd: 1 + }, + { + fieldname: 'qty', + label: __('Quantity'), + reqd: 1, + fieldtype: 'Float', + description: __('Available {0}', [actual_qty]) + }, + { + fieldname: 'rate', + label: __('Rate'), + fieldtype: 'Currency', + hidden: 1 + }, ], - }) + }); dialog.show(); dialog.get_field('item_code').set_input(item); - if(source) { + if (source) { dialog.get_field('source').set_input(source); } else { dialog.get_field('source').df.hidden = 1; dialog.get_field('source').refresh(); } - if(rate) { + if (rate) { dialog.get_field('rate').set_value(rate); dialog.get_field('rate').df.hidden = 0; dialog.get_field('rate').refresh(); } - if(target) { + if (target) { dialog.get_field('target').df.read_only = 1; dialog.get_field('target').value = target; dialog.get_field('target').refresh(); } - dialog.set_primary_action(__('Submit'), function() { + dialog.set_primary_action(__('Submit'), function () { var values = dialog.get_values(); - if(!values) { + if (!values) { return; } - if(source && values.qty > actual_qty) { + if (source && values.qty > actual_qty) { frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty])); return; } - if(values.source === values.target) { + if (values.source === values.target) { frappe.msgprint(__('Source and target warehouse must be different')); } @@ -239,21 +269,21 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, freeze: true, - callback: function(r) { + callback: function (r) { frappe.show_alert(__('Stock Entry {0} created', - ['' + r.message.name+ ''])); + ['' + r.message.name + ''])); dialog.hide(); callback(r); }, }); }); - $('

' - + __("Add more items or open full form") + '

') + $('

' + + __("Add more items or open full form") + '

') .appendTo(dialog.body) .find('.link-open') - .on('click', function() { - frappe.model.with_doctype('Stock Entry', function() { + .on('click', function () { + frappe.model.with_doctype('Stock Entry', function () { var doc = frappe.model.get_new_doc('Stock Entry'); doc.from_warehouse = dialog.get_value('source'); doc.to_warehouse = dialog.get_value('target'); @@ -266,6 +296,6 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb row.transfer_qty = dialog.get_value('qty'); row.basic_rate = dialog.get_value('rate'); frappe.set_route('Form', doc.doctype, doc.name); - }) + }); }); -} +}; diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py index cafb5c3a0a..45e662807a 100644 --- a/erpnext/stock/dashboard/item_dashboard.py +++ b/erpnext/stock/dashboard/item_dashboard.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import frappe from frappe.model.db_query import DatabaseQuery +from frappe.utils import flt, cint @frappe.whitelist() def get_data(item_code=None, warehouse=None, item_group=None, @@ -42,11 +43,20 @@ def get_data(item_code=None, warehouse=None, item_group=None, limit_start=start, limit_page_length='21') + precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) + for item in items: item.update({ - 'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'), - 'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no') - or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'), + 'item_name': frappe.get_cached_value( + "Item", item.item_code, 'item_name'), + 'disable_quick_entry': frappe.get_cached_value( + "Item", item.item_code, 'has_batch_no') + or frappe.get_cached_value( + "Item", item.item_code, 'has_serial_no'), + 'projected_qty': flt(item.projected_qty, precision), + 'reserved_qty': flt(item.reserved_qty, precision), + 'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision), + 'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision), + 'actual_qty': flt(item.actual_qty, precision), }) - return items diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 36d0de1e5d..e0b89d8e45 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -494,7 +494,8 @@ def make_item_variant(): test_records = frappe.get_test_records('Item') -def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=None): +def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, + customer=None, is_purchase_item=None, opening_stock=None, company=None): if not frappe.db.exists("Item", item_code): item = frappe.new_doc("Item") item.item_code = item_code @@ -509,7 +510,7 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, item.customer = customer or '' item.append("item_defaults", { "default_warehouse": warehouse or '_Test Warehouse - _TC', - "company": "_Test Company" + "company": company or "_Test Company" }) item.save() else: diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 61b72092e7..755fa615bb 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -346,7 +346,7 @@ def create_delivery_note(source_name, target_doc=None): if dn_item: dn_item.warehouse = location.warehouse - dn_item.qty = location.picked_qty + dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1) dn_item.batch_no = location.batch_no dn_item.serial_no = location.serial_no diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 01f2b0b27c..c4da05a6d4 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -9,6 +9,7 @@ test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \ import EmptyStockReconciliationItemsError @@ -291,6 +292,61 @@ class TestPickList(unittest.TestCase): self.assertEqual(pick_list.locations[1].qty, 5) self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name) + def test_pick_list_for_items_with_multiple_UOM(self): + purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10) + purchase_receipt.submit() + + sales_order = frappe.get_doc({ + 'doctype': 'Sales Order', + 'customer': '_Test Customer', + 'company': '_Test Company', + 'items': [{ + 'item_code': '_Test Item', + 'qty': 1, + 'conversion_factor': 5, + 'delivery_date': frappe.utils.today() + }, { + 'item_code': '_Test Item', + 'qty': 1, + 'conversion_factor': 1, + 'delivery_date': frappe.utils.today() + }], + }).insert() + sales_order.submit() + + pick_list = frappe.get_doc({ + 'doctype': 'Pick List', + 'company': '_Test Company', + 'customer': '_Test Customer', + 'items_based_on': 'Sales Order', + 'locations': [{ + 'item_code': '_Test Item', + 'qty': 1, + 'stock_qty': 5, + 'conversion_factor': 5, + 'sales_order': sales_order.name, + 'sales_order_item': sales_order.items[0].name , + }, { + 'item_code': '_Test Item', + 'qty': 1, + 'stock_qty': 1, + 'conversion_factor': 1, + 'sales_order': sales_order.name, + 'sales_order_item': sales_order.items[1].name , + }] + }) + pick_list.set_item_locations() + pick_list.submit() + + delivery_note = create_delivery_note(pick_list.name) + + self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty) + self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty) + self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor) + + pick_list.cancel() + sales_order.cancel() + purchase_receipt.cancel() # def test_pick_list_skips_items_in_expired_batch(self): # pass diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 123f0c8647..a0e70516d4 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -179,11 +179,15 @@ class TestStockEntry(unittest.TestCase): def test_material_transfer_gl_entry(self): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1", + item_code = 'Hand Sanitizer - 001' + create_item(item_code =item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=1000, valuation_rate=10, company=company, warehouse="Stores - TCP1") + + mtn = make_stock_entry(item_code=item_code, source="Stores - TCP1", target="Finished Goods - TCP1", qty=45, company=company) self.check_stock_ledger_entries("Stock Entry", mtn.name, - [["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]]) + [[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]]) source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)