From a93b514b2f433eb5f4c0ad3dd28e5f219cbb5ae4 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Tue, 6 Apr 2021 17:10:52 +0530 Subject: [PATCH 001/159] feat: create Quality Inspections from account and stock documents --- erpnext/controllers/accounts_controller.py | 32 +++++ erpnext/public/js/controllers/transaction.js | 133 ++++++++++++++++++- 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 36d399cf19..0c966566fd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1412,6 +1412,38 @@ def validate_and_delete_children(parent, data): for d in deleted_children: update_bin_on_delete(d, parent.doctype) + +@frappe.whitelist() +def make_quality_inspections(doctype, docname, items): + items = json.loads(items).get('items') + inspections = [] + + for item in items: + if item.get("sample_size") > item.get("qty"): + frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( + item_name=item.get("item_name"), + sample_size=item.get("sample_size"), + accepted_quantity=item.get("qty") + )) + + quality_inspection = frappe.get_doc({ + "doctype": "Quality Inspection", + "inspection_type": "Incoming", + "inspected_by": frappe.session.user, + "reference_type": doctype, + "reference_name": docname, + "item_code": item.get("item_code"), + "description": item.get("description"), + "sample_size": item.get("sample_size"), + "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, + "batch_no": item.get("batch_no") + }).insert() + quality_inspection.save() + inspections.append(quality_inspection) + + return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + + @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def check_doc_permissions(doc, perm_type='create'): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 21a20a7bce..da0c87dd85 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -261,11 +261,19 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) { return; } - var me = this; - var inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype) + + const me = this; + if (!this.frm.is_new() && this.frm.doc.docstatus === 0) { + this.frm.add_custom_button(__("Quality Inspection(s)"), () => { + me.make_quality_inspection(); + }, __("Create")); + this.frm.page.set_inner_btn_group_as_primary(__('Create')); + } + + const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype) ? "Incoming" : "Outgoing"; - var quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); + let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); quality_inspection_field.get_route_options_for_new_doc = function(row) { if(me.frm.is_new()) return; return { @@ -280,7 +288,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = locals[cdt][cdn]; return { filters: { docstatus: 1, @@ -1909,6 +1917,123 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); }, + make_quality_inspection: function () { + let data = []; + const fields = [ + { + label: "Items", + fieldtype: "Table", + fieldname: "items", + cannot_add_rows: true, + in_place_edit: true, + data: data, + get_data: () => { return data }, + fields: [ + { + fieldtype: "Data", + fieldname: "docname", + hidden: true + }, + { + fieldtype: "Read Only", + fieldname: "item_code", + label: __("Item Code"), + in_list_view: true + }, + { + fieldtype: "Read Only", + fieldname: "item_name", + label: __("Item Name"), + in_list_view: true + }, + { + fieldtype: "Float", + fieldname: "qty", + label: __("Accepted Quantity"), + in_list_view: true, + read_only: true + }, + { + fieldtype: "Float", + fieldname: "sample_size", + label: __("Sample Size"), + reqd: true, + in_list_view: true + }, + { + fieldtype: "Data", + fieldname: "description", + label: __("Description"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "serial_no", + label: __("Serial No"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "batch_no", + label: __("Batch No"), + hidden: true + } + ] + } + ]; + + const me = this; + const dialog = new frappe.ui.Dialog({ + title: __("Select Items for Quality Inspection"), + fields: fields, + primary_action: function () { + const data = dialog.get_values(); + frappe.call({ + method: "erpnext.controllers.accounts_controller.make_quality_inspections", + args: { + doctype: me.frm.doc.doctype, + docname: me.frm.doc.name, + items: data + }, + freeze: true, + callback: function (r) { + if (r.message) { + frappe.msgprint({ + message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), + indicator: "green" + }) + } + dialog.hide(); + } + }); + }, + primary_action_label: __("Create") + }); + + this.frm.doc.items.forEach(item => { + if (!item.quality_inspection) { + let dialog_items = dialog.fields_dict.items; + dialog_items.df.data.push({ + "docname": item.name, + "item_code": item.item_code, + "item_name": item.item_name, + "qty": item.qty, + "description": item.description, + "serial_no": item.serial_no, + "batch_no": item.batch_no + }); + dialog_items.grid.refresh(); + } + }) + + data = dialog.fields_dict.items.df.data; + if (!data.length) { + frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); + } else { + dialog.show(); + } + }, + get_method_for_payment: function(){ var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){ From 7f8b95efe8c6c16cf50faf85480b8f97f8eda089 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 14 Apr 2021 14:12:03 +0530 Subject: [PATCH 002/159] fix: move QI logic to stock module --- erpnext/controllers/accounts_controller.py | 31 ------------ erpnext/controllers/stock_controller.py | 53 ++++++++++++++++---- erpnext/public/js/controllers/transaction.js | 2 +- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0c966566fd..ea0b495c10 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1413,37 +1413,6 @@ def validate_and_delete_children(parent, data): update_bin_on_delete(d, parent.doctype) -@frappe.whitelist() -def make_quality_inspections(doctype, docname, items): - items = json.loads(items).get('items') - inspections = [] - - for item in items: - if item.get("sample_size") > item.get("qty"): - frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( - item_name=item.get("item_name"), - sample_size=item.get("sample_size"), - accepted_quantity=item.get("qty") - )) - - quality_inspection = frappe.get_doc({ - "doctype": "Quality Inspection", - "inspection_type": "Incoming", - "inspected_by": frappe.session.user, - "reference_type": doctype, - "reference_name": docname, - "item_code": item.get("item_code"), - "description": item.get("description"), - "sample_size": item.get("sample_size"), - "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, - "batch_no": item.get("batch_no") - }).insert() - quality_inspection.save() - inspections.append(quality_inspection) - - return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] - - @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def check_doc_permissions(doc, perm_type='create'): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index f352bae30e..7f8da516aa 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1,17 +1,21 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate -from frappe import _ -import frappe.defaults +import json from collections import defaultdict -from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced + +import frappe +import frappe.defaults +from frappe import _ +from frappe.utils import cint, cstr, flt, get_link_to_form, getdate + +import erpnext from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map +from erpnext.accounts.utils import check_if_stock_and_account_balance_synced, get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController -from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock import get_warehouse_account_map +from erpnext.stock.stock_ledger import get_valuation_rate + class QualityInspectionRequiredError(frappe.ValidationError): pass class QualityInspectionRejectedError(frappe.ValidationError): pass @@ -190,7 +194,6 @@ class StockController(AccountsController): if hasattr(self, "items"): item_doclist = self.get("items") elif self.doctype == "Stock Reconciliation": - import json item_doclist = [] data = json.loads(self.reconciliation_json) for row in data[data.index(self.head_row)+1:]: @@ -320,7 +323,7 @@ class StockController(AccountsController): return serialized_items def validate_warehouse(self): - from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse + from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company warehouses = list(set([d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)])) @@ -501,6 +504,38 @@ class StockController(AccountsController): check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) + +@frappe.whitelist() +def make_quality_inspections(doctype, docname, items): + items = json.loads(items).get('items') + inspections = [] + + for item in items: + if item.get("sample_size") > item.get("qty"): + frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( + item_name=item.get("item_name"), + sample_size=item.get("sample_size"), + accepted_quantity=item.get("qty") + )) + + quality_inspection = frappe.get_doc({ + "doctype": "Quality Inspection", + "inspection_type": "Incoming", + "inspected_by": frappe.session.user, + "reference_type": doctype, + "reference_name": docname, + "item_code": item.get("item_code"), + "description": item.get("description"), + "sample_size": item.get("sample_size"), + "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, + "batch_no": item.get("batch_no") + }).insert() + quality_inspection.save() + inspections.append(quality_inspection) + + return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + + def is_reposting_pending(): return frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index da0c87dd85..0e0a87a8b5 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1989,7 +1989,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ primary_action: function () { const data = dialog.get_values(); frappe.call({ - method: "erpnext.controllers.accounts_controller.make_quality_inspections", + method: "erpnext.controllers.stock_controller.make_quality_inspections", args: { doctype: me.frm.doc.doctype, docname: me.frm.doc.name, From 03f711e3a2c6ce12fad80a4f375728a077a8a958 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 14 Apr 2021 14:41:55 +0530 Subject: [PATCH 003/159] style: sider issues --- erpnext/public/js/controllers/transaction.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0e0a87a8b5..478b21b441 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1927,7 +1927,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ cannot_add_rows: true, in_place_edit: true, data: data, - get_data: () => { return data }, + get_data: () => { + return data; + }, fields: [ { fieldtype: "Data", @@ -2001,7 +2003,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.msgprint({ message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), indicator: "green" - }) + }); } dialog.hide(); } @@ -2024,7 +2026,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); dialog_items.grid.refresh(); } - }) + }); data = dialog.fields_dict.items.df.data; if (!data.length) { From 1ac471e04ff995ad0591b68280ecf1c7939fe432 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 15 Apr 2021 16:48:01 +0530 Subject: [PATCH 004/159] feat: generate schedule is also triggered on save. --- .../doctype/maintenance_schedule/maintenance_schedule.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 0aefe19c8d..9acb6c29ae 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -33,7 +33,7 @@ class MaintenanceSchedule(TransactionBase): count = count + 1 child.sales_person = d.sales_person - self.save() + def on_submit(self): if not self.get('schedules'): @@ -169,9 +169,12 @@ class MaintenanceSchedule(TransactionBase): self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() + self.generate_schedule() def on_update(self): frappe.db.set(self, 'status', 'Draft') + + def update_amc_date(self, serial_nos, amc_expiry_date=None): for serial_no in serial_nos: From 2c802720c30c254c0b87e69af4af32d2509bc494 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 15 Apr 2021 16:52:23 +0530 Subject: [PATCH 005/159] feat: Automated setting end_date based on periodicity, no of visits and improved ux. --- .../maintenance_schedule.js | 71 ++- .../maintenance_schedule_item.json | 551 +++++------------- 2 files changed, 184 insertions(+), 438 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index ddbcdfde57..d954d905d9 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -2,9 +2,8 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.maintenance"); - frappe.ui.form.on('Maintenance Schedule', { - setup: function(frm) { + setup: function (frm) { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); @@ -12,30 +11,30 @@ frappe.ui.form.on('Maintenance Schedule', { frm.add_fetch('item_code', 'item_name', 'item_name'); frm.add_fetch('item_code', 'description', 'description'); }, - onload: function(frm) { + onload: function (frm) { if (!frm.doc.status) { - frm.set_value({status:'Draft'}); + frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { - frm.set_value({transaction_date: frappe.datetime.get_today()}); + frm.set_value({ transaction_date: frappe.datetime.get_today() }); } }, - refresh: function(frm) { + refresh: function (frm) { setTimeout(() => { frm.toggle_display('generate_schedule', !(frm.is_new())); frm.toggle_display('schedule', !(frm.is_new())); - },10); + }, 10); }, - customer: function(frm) { + customer: function (frm) { erpnext.utils.get_party_details(frm) }, - customer_address: function(frm) { + customer_address: function (frm) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display'); }, - contact_person: function(frm) { + contact_person: function (frm) { erpnext.utils.get_contact_details(frm); }, - generate_schedule: function(frm) { + generate_schedule: function (frm) { if (frm.is_new()) { frappe.msgprint(__('Please save first')); } else { @@ -46,14 +45,14 @@ frappe.ui.form.on('Maintenance Schedule', { // TODO commonify this code erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ - refresh: function() { - frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} + refresh: function () { + frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' } var me = this; if (this.frm.doc.docstatus === 0) { this.frm.add_custom_button(__('Sales Order'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule", source_doctype: "Sales Order", @@ -68,7 +67,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }); }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { - this.frm.add_custom_button(__('Create Maintenance Visit'), function() { + this.frm.add_custom_button(__('Create Maintenance Visit'), function () { frappe.model.open_mapped_doc({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", source_name: me.frm.doc.name, @@ -78,26 +77,26 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } }, - start_date: function(doc, cdt, cdn) { + start_date: function (doc, cdt, cdn) { this.set_no_of_visits(doc, cdt, cdn); }, - end_date: function(doc, cdt, cdn) { + end_date: function (doc, cdt, cdn) { this.set_no_of_visits(doc, cdt, cdn); }, - periodicity: function(doc, cdt, cdn) { + periodicity: function (doc, cdt, cdn) { this.set_no_of_visits(doc, cdt, cdn); - }, - set_no_of_visits: function(doc, cdt, cdn) { + }, + no_of_visits: function(doc,cdt,cdn){ + this.set_no_of_visits(doc,cdt,cdn); + }, + + set_no_of_visits: function (doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); - if (item.start_date && item.end_date && item.periodicity) { - if(item.start_date > item.end_date) { - frappe.msgprint(__("Row {0}:Start Date must be before End Date", [item.idx])); - return; - } + if (item.start_date && item.periodicity) { var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; @@ -110,10 +109,28 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); + if (no_of_visits == 0 || !no_of_visits) { + + let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]) + frappe.model.set_value(item.doctype, item.name, "end_date", end_date) + var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; + var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); + + } + else if(item.no_of_visits > no_of_visits){ + var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) + frappe.model.set_value(item.doctype, item.name, "end_date", end_date) + + } + else if(item.no_of_visits < no_of_visits){ + var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) + frappe.model.set_value(item.doctype, item.name, "end_date", end_date) + + } } }, }); -$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({frm: cur_frm})); +$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceSchedule({ frm: cur_frm })); diff --git a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json index b371dfc4f5..3dacdead62 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json @@ -1,431 +1,160 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-02-22 01:28:05", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:28:05", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "description", + "column_break_4", + "start_date", + "end_date", + "periodicity", + "schedule_details", + "no_of_visits", + "column_break_10", + "sales_person", + "reference", + "serial_no", + "sales_order" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "columns": 1, "fetch_from": "item_code.item_name", - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "item_code.description", - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Data", - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Data", + "print_width": "300px", + "read_only": 1, "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedule_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "schedule_details", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Start Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "start_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "oldfieldname": "start_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "End Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "end_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "oldfieldname": "end_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "periodicity", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Periodicity", - "length": 0, - "no_copy": 0, - "oldfieldname": "periodicity", - "oldfieldtype": "Select", - "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 1, + "fieldname": "periodicity", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Periodicity", + "oldfieldname": "periodicity", + "oldfieldtype": "Select", + "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "no_of_visits", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "No of Visits", - "length": 0, - "no_copy": 0, - "oldfieldname": "no_of_visits", - "oldfieldtype": "Int", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 1, + "fieldname": "no_of_visits", + "fieldtype": "Int", + "in_list_view": 1, + "label": "No of Visits", + "oldfieldname": "no_of_visits", + "oldfieldtype": "Int", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Person", - "length": 0, - "no_copy": 0, - "oldfieldname": "incharge_name", - "oldfieldtype": "Link", - "options": "Sales Person", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sales_person", + "fieldtype": "Link", + "label": "Sales Person", + "oldfieldname": "incharge_name", + "oldfieldtype": "Link", + "options": "Sales Person" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "reference", + "fieldtype": "Section Break", + "label": "Reference" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Serial No", - "length": 0, - "no_copy": 0, - "oldfieldname": "serial_no", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "serial_no", + "fieldtype": "Small Text", + "label": "Serial No", + "oldfieldname": "serial_no", + "oldfieldtype": "Small Text" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_order", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Order", - "length": 0, - "no_copy": 1, - "oldfieldname": "prevdoc_docname", - "oldfieldtype": "Data", - "options": "Sales Order", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "no_copy": 1, + "oldfieldname": "prevdoc_docname", + "oldfieldtype": "Data", + "options": "Sales Order", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "search_index": 1, "width": "150px" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-16 22:43:14.260729", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Schedule Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 0, - "track_seen": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-04-15 16:09:47.311994", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Schedule Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From e6fd3b86bdca28e45568484e6ee0e9256a2b5f6c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 16 Apr 2021 15:48:36 +0530 Subject: [PATCH 006/159] fix: fixed sider issues and translation syntax. --- .../maintenance_schedule.js | 37 ++++++++----------- .../maintenance_schedule.py | 3 +- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index d954d905d9..124524684e 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -7,9 +7,6 @@ frappe.ui.form.on('Maintenance Schedule', { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); - - frm.add_fetch('item_code', 'item_name', 'item_name'); - frm.add_fetch('item_code', 'description', 'description'); }, onload: function (frm) { if (!frm.doc.status) { @@ -46,7 +43,7 @@ frappe.ui.form.on('Maintenance Schedule', { // TODO commonify this code erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ refresh: function () { - frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' } + frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' }; var me = this; @@ -89,10 +86,10 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ this.set_no_of_visits(doc, cdt, cdn); }, - no_of_visits: function(doc,cdt,cdn){ - this.set_no_of_visits(doc,cdt,cdn); + no_of_visits: function (doc, cdt, cdn) { + this.set_no_of_visits(doc, cdt, cdn); }, - + set_no_of_visits: function (doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); @@ -111,23 +108,21 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); if (no_of_visits == 0 || !no_of_visits) { - let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]) - frappe.model.set_value(item.doctype, item.name, "end_date", end_date) - var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; - var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); + let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "end_date", end_date); + date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; + no_of_visits = cint(date_diff / days_in_period[item.periodicity]); frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); + } else if (item.no_of_visits > no_of_visits) { + let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "end_date", end_date); + + } else if (item.no_of_visits < no_of_visits) { + let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); + frappe.model.set_value(item.doctype, item.name, "end_date", end_date); + } - else if(item.no_of_visits > no_of_visits){ - var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) - frappe.model.set_value(item.doctype, item.name, "end_date", end_date) - - } - else if(item.no_of_visits < no_of_visits){ - var end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits*days_in_period[item.periodicity]) - frappe.model.set_value(item.doctype, item.name, "end_date", end_date) - - } } }, }); diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 9acb6c29ae..60dd2983b9 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -135,8 +135,7 @@ class MaintenanceSchedule(TransactionBase): } if date_diff < days_in_period[d.periodicity]: - throw(_("Row {0}: To set {1} periodicity, difference between from and to date \ - must be greater than or equal to {2}") + throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}") .format(d.idx, d.periodicity, days_in_period[d.periodicity])) def validate_maintenance_detail(self): From 5ebc6abfadff17f7a0320317b660d67a324405b0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 16 Apr 2021 16:59:10 +0530 Subject: [PATCH 007/159] feat: Sales Person field is now editable after submitting. --- .../maintenance_schedule.json | 1046 ++++------------- .../maintenance_schedule_detail.json | 298 ++--- 2 files changed, 313 insertions(+), 1031 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json index 606d22f52b..1871228711 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json @@ -1,852 +1,258 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-01-10 16:34:30", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "actions": [], + "autoname": "naming_series:", + "creation": "2013-01-10 16:34:30", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "customer_details", + "naming_series", + "customer", + "column_break0", + "status", + "transaction_date", + "items_section", + "items", + "schedule", + "generate_schedule", + "schedules", + "contact_info", + "customer_name", + "contact_person", + "contact_mobile", + "contact_email", + "contact_display", + "column_break_17", + "customer_address", + "address_display", + "territory", + "customer_group", + "company", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-user", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-user" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "options": "MAT-MSH-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-MSH-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nCancelled", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "\nDraft\nSubmitted\nCancelled", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transaction Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "transaction_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "transaction_date", + "fieldtype": "Date", + "label": "Transaction Date", + "oldfieldname": "transaction_date", + "oldfieldtype": "Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "items_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_maintenance_detail", - "oldfieldtype": "Table", - "options": "Maintenance Schedule Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "item_maintenance_detail", + "oldfieldtype": "Table", + "options": "Maintenance Schedule Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedule", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Schedule", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-time", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "schedule", + "fieldtype": "Section Break", + "label": "Schedule", + "oldfieldtype": "Section Break", + "options": "fa fa-time" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "generate_schedule", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Generate Schedule", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Button", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "generate_schedule", + "fieldtype": "Button", + "label": "Generate Schedule", + "oldfieldtype": "Button" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedules", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Schedules", - "length": 0, - "no_copy": 0, - "oldfieldname": "schedules", - "oldfieldtype": "Table", - "options": "Maintenance Schedule Detail", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "schedules", + "fieldtype": "Table", + "label": "Schedules", + "oldfieldname": "schedules", + "oldfieldtype": "Table", + "options": "Maintenance Schedule Detail" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Info", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact_info", + "fieldtype": "Section Break", + "label": "Contact Info" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "customer_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Customer Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "bold": 1, + "depends_on": "customer", + "fieldname": "customer_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Customer Name", + "oldfieldname": "customer_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Person", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_mobile", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "contact_mobile", + "fieldtype": "Data", + "hidden": 1, + "label": "Mobile No", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_email", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact_display", + "fieldtype": "Small Text", + "hidden": 1, + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_17", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "customer_address", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "address_display", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "description": "", - "fieldname": "territory", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Territory", - "length": 0, - "no_copy": 0, - "oldfieldname": "territory", - "oldfieldtype": "Link", - "options": "Territory", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "oldfieldname": "territory", + "oldfieldtype": "Link", + "options": "Territory" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "description": "", - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Maintenance Schedule", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Maintenance Schedule", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-calendar", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Schedule", - "owner": "Administrator", + ], + "icon": "fa fa-calendar", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-04-16 15:53:36.670816", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Schedule", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Maintenance Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "status,customer,customer_name", - "show_name_in_global_search": 0, - "sort_order": "DESC", - "timeline_field": "customer", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "status,customer,customer_name", + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "customer" } \ No newline at end of file diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 7cd3086155..73536f6549 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -1,222 +1,98 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-02-22 01:28:05", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:28:05", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "scheduled_date", + "actual_date", + "sales_person", + "serial_no" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "read_only": 1, + "search_index": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "scheduled_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Scheduled Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "scheduled_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "scheduled_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Scheduled Date", + "oldfieldname": "scheduled_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_date", - "fieldtype": "Date", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Actual Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "actual_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "actual_date", + "fieldtype": "Date", + "label": "Actual Date", + "no_copy": 1, + "oldfieldname": "actual_date", + "oldfieldtype": "Date", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Sales Person", - "length": 0, - "no_copy": 0, - "oldfieldname": "incharge_name", - "oldfieldtype": "Link", - "options": "Sales Person", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "columns": 2, + "fieldname": "sales_person", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Person", + "oldfieldname": "incharge_name", + "oldfieldtype": "Link", + "options": "Sales Person" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Serial No", - "length": 0, - "no_copy": 0, - "oldfieldname": "serial_no", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "160px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "serial_no", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Serial No", + "oldfieldname": "serial_no", + "oldfieldtype": "Small Text", + "print_width": "160px", + "read_only": 1, "width": "160px" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-02-17 17:05:44.644663", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Schedule Detail", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-04-16 16:01:53.271287", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Schedule Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From 9a0a561ec65f5a1d9cf5ced858aa99c0ef49301c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 19 Apr 2021 16:42:50 +0530 Subject: [PATCH 008/159] feat: Created Dialog Box on trying to create a maintenance visit. --- .../maintenance_schedule.js | 92 +++++++++++++++++- .../maintenance_schedule.py | 93 ++++++++++--------- .../maintenance_schedule_detail.json | 12 ++- .../maintenance_visit_purpose.json | 10 +- 4 files changed, 153 insertions(+), 54 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 124524684e..d07710878d 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -65,11 +65,93 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { this.frm.add_custom_button(__('Create Maintenance Visit'), function () { - frappe.model.open_mapped_doc({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", - source_name: me.frm.doc.name, - frm: me.frm - }); + let items = me.frm.doc.items; + let s = me.frm.doc.schedules; + let options = ""; + let dates = ""; + for (let i in items) { + for(let d in s){ + if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { + options = options + '\n' + items[i].item_name + break + } + } + } + function formatDate(date) { + var d = new Date(date), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); + + if (month.length < 2) + month = '0' + month; + if (day.length < 2) + day = '0' + day; + + return [day, month, year].join('-'); + } + var schedule_id = "" + var d = new frappe.ui.Dialog({ + title: __("Enter Visit Details"), + fields: [{ + fieldtype: "Select", + fieldname: "item_name", + label: __("Item Name"), + options: options, + reqd: 1, + onchange: function () { + let field = d.get_field("scheduled_date"); + dates = "" + for (let i in s) { + if (s[i].item_name == this.value) { + dates = dates + '\n' + formatDate(s[i].scheduled_date); + } + + } + field.df.options = dates; + field.refresh(); + } + }, + { + label: __('Scheduled Date'), + fieldname: 'scheduled_date', + fieldtype: 'Select', + options: dates, + reqd: 1, + onchange: function(){ + let field = d.get_field('item_name'); + for(let i in s ){ + if(s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value){ + schedule_id = s[i].name; + } + } + } + }, + ], + primary_action_label: 'Create Visit', + primary_action(values) { + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", + args: { + item_name: values.item_name, + s_id: schedule_id, + source_name: me.frm.doc.name, + + }, + callback: function (r) { + if (!r.exc) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + + + }); + d.hide(); + } + }) + d.show() + }, __('Create')); } }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 60dd2983b9..fd06a4ef9b 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -32,8 +32,7 @@ class MaintenanceSchedule(TransactionBase): child.idx = count count = count + 1 child.sales_person = d.sales_person - - + child.completion_status = "Pending" def on_submit(self): if not self.get('schedules'): @@ -58,9 +57,9 @@ class MaintenanceSchedule(TransactionBase): if no_email_sp: frappe.msgprint( - frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( - self.owner, "
" + "
".join(no_email_sp) - )) + frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( + self.owner, "
" + "
".join(no_email_sp) + )) scheduled_date = frappe.db.sql("""select scheduled_date from `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and @@ -69,12 +68,12 @@ class MaintenanceSchedule(TransactionBase): for key in scheduled_date: description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer) event = frappe.get_doc({ - "doctype": "Event", - "owner": email_map.get(d.sales_person, self.owner), - "subject": description, - "description": description, - "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", - "event_type": "Private", + "doctype": "Event", + "owner": email_map.get(d.sales_person, self.owner), + "subject": description, + "description": description, + "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", + "event_type": "Private", }) event.add_participant(self.doctype, self.name) event.insert(ignore_permissions=1) @@ -92,7 +91,7 @@ class MaintenanceSchedule(TransactionBase): start_date_copy = add_days(start_date_copy, add_by) if len(schedule_list) < no_of_visit: schedule_date = self.validate_schedule_date_for_holiday_list(getdate(start_date_copy), - sales_person) + sales_person) if schedule_date > getdate(end_date): schedule_date = getdate(end_date) schedule_list.append(schedule_date) @@ -127,16 +126,16 @@ class MaintenanceSchedule(TransactionBase): if d.start_date and d.end_date and d.periodicity and d.periodicity!="Random": date_diff = (getdate(d.end_date) - getdate(d.start_date)).days + 1 days_in_period = { - "Weekly": 7, - "Monthly": 30, - "Quarterly": 90, - "Half Yearly": 180, - "Yearly": 365 + "Weekly": 7, + "Monthly": 30, + "Quarterly": 90, + "Half Yearly": 180, + "Yearly": 365 } if date_diff < days_in_period[d.periodicity]: throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}") - .format(d.idx, d.periodicity, days_in_period[d.periodicity])) + .format(d.idx, d.periodicity, days_in_period[d.periodicity])) def validate_maintenance_detail(self): if not self.get('items'): @@ -172,8 +171,8 @@ class MaintenanceSchedule(TransactionBase): def on_update(self): frappe.db.set(self, 'status', 'Draft') - - + + def update_amc_date(self, serial_nos, amc_expiry_date=None): for serial_no in serial_nos: @@ -184,27 +183,27 @@ class MaintenanceSchedule(TransactionBase): def validate_serial_no(self, item_code, serial_nos, amc_start_date): for serial_no in serial_nos: sr_details = frappe.db.get_value("Serial No", serial_no, - ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"], as_dict=1) + ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"], as_dict=1) if not sr_details: frappe.throw(_("Serial No {0} not found").format(serial_no)) if sr_details.get("item_code") != item_code: frappe.throw(_("Serial No {0} does not belong to Item {1}") - .format(frappe.bold(serial_no), frappe.bold(item_code)), title="Invalid") + .format(frappe.bold(serial_no), frappe.bold(item_code)), title="Invalid") if sr_details.warranty_expiry_date \ - and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date): + and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under warranty upto {1}") - .format(serial_no, sr_details.warranty_expiry_date)) + .format(serial_no, sr_details.warranty_expiry_date)) if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under maintenance contract upto {1}") - .format(serial_no, sr_details.amc_expiry_date)) + .format(serial_no, sr_details.amc_expiry_date)) if not sr_details.warehouse and sr_details.delivery_date and \ - getdate(sr_details.delivery_date) >= getdate(amc_start_date): - throw(_("Maintenance start date can not be before delivery date for Serial No {0}") + getdate(sr_details.delivery_date) >= getdate(amc_start_date): + throw(_("Maintenance start date can not be before delivery date for Serial No {0}") .format(serial_no)) def validate_schedule(self): @@ -248,31 +247,37 @@ class MaintenanceSchedule(TransactionBase): delete_events(self.doctype, self.name) @frappe.whitelist() -def make_maintenance_visit(source_name, target_doc=None): +def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None): from frappe.model.mapper import get_mapped_doc def update_status(source, target, parent): target.maintenance_type = "Scheduled" + def update_sid(source, target, parent): + target.prevdoc_detail_docname = s_id + doclist = get_mapped_doc("Maintenance Schedule", source_name, { - "Maintenance Schedule": { - "doctype": "Maintenance Visit", - "field_map": { - "name": "maintenance_schedule" + "Maintenance Schedule": { + "doctype": "Maintenance Visit", + "field_map": { + "name": "maintenance_schedule" + }, + "validation": { + "docstatus": ["=", 1] + }, + "postprocess": update_status }, - "validation": { - "docstatus": ["=", 1] - }, - "postprocess": update_status - }, - "Maintenance Schedule Item": { - "doctype": "Maintenance Visit Purpose", - "field_map": { - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", - "sales_person": "service_person" + "Maintenance Schedule Item": { + "doctype": "Maintenance Visit Purpose", + "field_map": { + "parent": "prevdoc_docname", + "parenttype": "prevdoc_doctype", + }, + "condition": lambda doc: doc.item_name == item_name, + + "postprocess": update_sid + } - } }, target_doc) return doclist diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 73536f6549..7fda687ca4 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -12,7 +12,8 @@ "scheduled_date", "actual_date", "sales_person", - "serial_no" + "serial_no", + "completion_status" ], "fields": [ { @@ -52,6 +53,7 @@ { "fieldname": "actual_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Actual Date", "no_copy": 1, "oldfieldname": "actual_date", @@ -81,12 +83,18 @@ "print_width": "160px", "read_only": 1, "width": "160px" + }, + { + "fieldname": "completion_status", + "fieldtype": "Select", + "label": "Completion Status", + "options": "Pending\nPartially Completed\nFully Completed" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-16 16:01:53.271287", + "modified": "2021-04-19 16:18:36.723319", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 467441d841..60e5afe806 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-02-22 01:28:06", "doctype": "DocType", @@ -62,6 +63,8 @@ "fieldtype": "Section Break" }, { + "fetch_from": "prevdoc_detail_docname.sales_person", + "fetch_if_empty": 1, "fieldname": "service_person", "fieldtype": "Link", "in_list_view": 1, @@ -110,12 +113,12 @@ }, { "fieldname": "prevdoc_detail_docname", - "fieldtype": "Data", - "hidden": 1, + "fieldtype": "Link", "label": "Against Document Detail No", "no_copy": 1, "oldfieldname": "prevdoc_detail_docname", "oldfieldtype": "Data", + "options": "Maintenance Schedule Detail", "print_hide": 1, "print_width": "160px", "read_only": 1, @@ -125,7 +128,8 @@ ], "idx": 1, "istable": 1, - "modified": "2020-09-18 17:26:09.703215", + "links": [], + "modified": "2021-04-19 16:08:10.671163", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", From 5d7d338d2ae407ad2b48511405465311355e4a09 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 19 Apr 2021 18:39:34 +0530 Subject: [PATCH 009/159] feat: Schedule status is now updated on submitting a visit. --- .../maintenance_schedule.js | 11 +++++++- .../maintenance_schedule.py | 9 +++--- .../maintenance_schedule_detail.json | 23 ++++++++++----- .../maintenance_visit/maintenance_visit.py | 28 +++++++++++++++++++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index d07710878d..45e632cd35 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -64,6 +64,14 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }); }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { + var s = me.frm.doc.schedules; + let flag = 0 + for(let i in s){ + if (s[i].completion_status == pending){ + flag = 1 + } + } + if(count){ this.frm.add_custom_button(__('Create Maintenance Visit'), function () { let items = me.frm.doc.items; let s = me.frm.doc.schedules; @@ -103,7 +111,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let field = d.get_field("scheduled_date"); dates = "" for (let i in s) { - if (s[i].item_name == this.value) { + if (s[i].item_name == this.value && s[i].completion_status == "Pending") { dates = dates + '\n' + formatDate(s[i].scheduled_date); } @@ -154,6 +162,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }, __('Create')); } + } }, start_date: function (doc, cdt, cdn) { diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index fd06a4ef9b..aa582ee219 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -33,6 +33,7 @@ class MaintenanceSchedule(TransactionBase): count = count + 1 child.sales_person = d.sales_person child.completion_status = "Pending" + child.item_ref = d.name def on_submit(self): if not self.get('schedules'): @@ -171,9 +172,7 @@ class MaintenanceSchedule(TransactionBase): def on_update(self): frappe.db.set(self, 'status', 'Draft') - - - + def update_amc_date(self, serial_nos, amc_expiry_date=None): for serial_no in serial_nos: serial_no_doc = frappe.get_doc("Serial No", serial_no) @@ -255,6 +254,8 @@ def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None def update_sid(source, target, parent): target.prevdoc_detail_docname = s_id + sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') + target.service_person = sales_person doclist = get_mapped_doc("Maintenance Schedule", source_name, { "Maintenance Schedule": { @@ -275,7 +276,7 @@ def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None }, "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sid + "postprocess": update_sid } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 7fda687ca4..f1e2e21a12 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -10,10 +10,11 @@ "item_code", "item_name", "scheduled_date", - "actual_date", "sales_person", + "actual_date", + "completion_status", "serial_no", - "completion_status" + "item_ref" ], "fields": [ { @@ -29,11 +30,9 @@ "search_index": 1 }, { - "columns": 2, "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, - "in_list_view": 1, "label": "Item Name", "oldfieldname": "item_name", "oldfieldtype": "Data", @@ -71,7 +70,8 @@ "label": "Sales Person", "oldfieldname": "incharge_name", "oldfieldtype": "Link", - "options": "Sales Person" + "options": "Sales Person", + "read_only_depends_on": "eval:doc.completion_status != \"Pending\"" }, { "fieldname": "serial_no", @@ -85,16 +85,25 @@ "width": "160px" }, { + "columns": 2, "fieldname": "completion_status", "fieldtype": "Select", + "in_list_view": 1, "label": "Completion Status", - "options": "Pending\nPartially Completed\nFully Completed" + "options": "Pending\nPartially Completed\nFully Completed", + "read_only": 1 + }, + { + "fieldname": "item_ref", + "fieldtype": "Data", + "label": "Item Reference", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-19 16:18:36.723319", + "modified": "2021-04-19 17:42:31.685710", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 2f2ad00e02..9505a36545 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import get_datetime from erpnext.utilities.transaction_base import TransactionBase @@ -16,8 +17,33 @@ class MaintenanceVisit(TransactionBase): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): frappe.throw(_("Serial No {0} does not exist").format(d.serial_no)) + def validate_mntc_date(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + item_ref = frappe.db.get_value('Maintenance Schedule Detail', detail_ref , 'item_ref') + start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) + if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): + frappe.throw(_("Date must be between {0} and {1}").format(start_date,end_date)) + def validate(self): self.validate_serial_no() + self.validate_mntc_date() + + def update_completion_status(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) + + def update_actual_date(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) def update_customer_issue(self, flag): for d in self.get('purposes'): @@ -77,6 +103,8 @@ class MaintenanceVisit(TransactionBase): def on_submit(self): self.update_customer_issue(1) frappe.db.set(self, 'status', 'Submitted') + self.update_completion_status() + self.update_actual_date() def on_cancel(self): self.check_if_last_visit() From 57f487a16b03e337b51f7889e51c39bed8e54091 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Tue, 20 Apr 2021 13:12:14 +0530 Subject: [PATCH 010/159] fix: Updated serial_no filters in maintenance visit. --- .../maintenance_schedule.js | 178 +++++++++--------- .../maintenance_schedule.py | 15 +- .../maintenance_visit/maintenance_visit.js | 67 ++++--- .../maintenance_visit/maintenance_visit.py | 21 ++- 4 files changed, 158 insertions(+), 123 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 45e632cd35..3c05526496 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -65,104 +65,104 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { var s = me.frm.doc.schedules; - let flag = 0 - for(let i in s){ - if (s[i].completion_status == pending){ - flag = 1 + let flag = 0; + for (let i in s) { + if (s[i].completion_status == "Pending") { + flag = 1; } } - if(count){ - this.frm.add_custom_button(__('Create Maintenance Visit'), function () { - let items = me.frm.doc.items; - let s = me.frm.doc.schedules; - let options = ""; - let dates = ""; - for (let i in items) { - for(let d in s){ - if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { - options = options + '\n' + items[i].item_name - break + if (flag) { + this.frm.add_custom_button(__('Create Maintenance Visit'), function () { + let items = me.frm.doc.items; + let s = me.frm.doc.schedules; + let options = ""; + let dates = ""; + for (let i in items) { + for (let d in s) { + if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { + options = options + '\n' + items[i].item_name; + break; + } } } - } - function formatDate(date) { - var d = new Date(date), - month = '' + (d.getMonth() + 1), - day = '' + d.getDate(), - year = d.getFullYear(); + function formatDate(date) { + var d = new Date(date), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); - if (month.length < 2) - month = '0' + month; - if (day.length < 2) - day = '0' + day; + if (month.length < 2) + month = '0' + month; + if (day.length < 2) + day = '0' + day; - return [day, month, year].join('-'); - } - var schedule_id = "" - var d = new frappe.ui.Dialog({ - title: __("Enter Visit Details"), - fields: [{ - fieldtype: "Select", - fieldname: "item_name", - label: __("Item Name"), - options: options, - reqd: 1, - onchange: function () { - let field = d.get_field("scheduled_date"); - dates = "" - for (let i in s) { - if (s[i].item_name == this.value && s[i].completion_status == "Pending") { - dates = dates + '\n' + formatDate(s[i].scheduled_date); - } - - } - field.df.options = dates; - field.refresh(); - } - }, - { - label: __('Scheduled Date'), - fieldname: 'scheduled_date', - fieldtype: 'Select', - options: dates, - reqd: 1, - onchange: function(){ - let field = d.get_field('item_name'); - for(let i in s ){ - if(s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value){ - schedule_id = s[i].name; - } - } - } - }, - ], - primary_action_label: 'Create Visit', - primary_action(values) { - frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", - args: { - item_name: values.item_name, - s_id: schedule_id, - source_name: me.frm.doc.name, - - }, - callback: function (r) { - if (!r.exc) { - frappe.model.sync(r.message); - frappe.set_route("Form", r.message.doctype, r.message.name); - } - } - - - }); - d.hide(); + return [day, month, year].join('-'); } - }) - d.show() + var schedule_id = ""; + var d = new frappe.ui.Dialog({ + title: __("Enter Visit Details"), + fields: [{ + fieldtype: "Select", + fieldname: "item_name", + label: __("Item Name"), + options: options, + reqd: 1, + onchange: function () { + let field = d.get_field("scheduled_date"); + dates = ""; + for (let i in s) { + if (s[i].item_name == this.value && s[i].completion_status == "Pending") { + dates = dates + '\n' + formatDate(s[i].scheduled_date); + } - }, __('Create')); + } + field.df.options = dates; + field.refresh(); + } + }, + { + label: __('Scheduled Date'), + fieldname: 'scheduled_date', + fieldtype: 'Select', + options: dates, + reqd: 1, + onchange: function () { + let field = d.get_field('item_name'); + for (let i in s) { + if (s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value) { + schedule_id = s[i].name; + } + } + } + }, + ], + primary_action_label: 'Create Visit', + primary_action(values) { + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", + args: { + item_name: values.item_name, + s_id: schedule_id, + source_name: me.frm.doc.name, + + }, + callback: function (r) { + if (!r.exc) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + + + }); + d.hide(); + } + }); + d.show(); + + }, __('Create')); + } } - } }, start_date: function (doc, cdt, cdn) { diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index aa582ee219..b89d540ebb 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -10,6 +10,7 @@ from frappe import throw, _ from erpnext.utilities.transaction_base import TransactionBase, delete_events from erpnext.stock.utils import get_valid_serial_nos from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class MaintenanceSchedule(TransactionBase): @frappe.whitelist() @@ -245,18 +246,28 @@ class MaintenanceSchedule(TransactionBase): def on_trash(self): delete_events(self.doctype, self.name) +@frappe.whitelist() +def update_serial_nos(s_id): + serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') + if serial_nos: + serial_nos = get_serial_nos(serial_nos) + return serial_nos + else: + return False + @frappe.whitelist() def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None): from frappe.model.mapper import get_mapped_doc def update_status(source, target, parent): target.maintenance_type = "Scheduled" - + def update_sid(source, target, parent): target.prevdoc_detail_docname = s_id sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') target.service_person = sales_person - + target.serial_no = '' + doclist = get_mapped_doc("Maintenance Schedule", source_name, { "Maintenance Schedule": { "doctype": "Maintenance Visit", diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 4cbb02a5b3..d5e8e51dfd 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -2,39 +2,62 @@ // License: GNU General Public License v3. See license.txt frappe.provide("erpnext.maintenance"); - +var serial_nos = []; frappe.ui.form.on('Maintenance Visit', { - refresh: function(frm) { + refresh: function (frm) { //filters for serial_no based on item_code - frm.set_query('serial_no', 'purposes', function(frm, cdt, cdn) { + frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) { let item = locals[cdt][cdn]; - return { - filters: { - 'item_code': item.item_code - } - }; + if (serial_nos) { + return { + filters: { + 'item_code': item.item_code, + 'name': ["in", serial_nos] + } + }; + + } else { + + return { + filters: { + 'item_code': item.item_code + } + }; + } + }); }, - setup: function(frm) { + setup: function (frm) { frm.set_query('contact_person', erpnext.queries.contact_query); frm.set_query('customer_address', erpnext.queries.address_query); frm.set_query('customer', erpnext.queries.customer); }, - onload: function(frm) { + onload: function (frm, cdt, cdn) { + let item = locals[cdt][cdn]; + let s_id = item.purposes[0].prevdoc_detail_docname; + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", + args: { + s_id: s_id + }, + callback: function (r) { + serial_nos = r.message; + } + }); if (!frm.doc.status) { - frm.set_value({status:'Draft'}); + frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { - frm.set_value({mntc_date: frappe.datetime.get_today()}); + frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, - customer: function(frm) { + customer: function (frm) { erpnext.utils.get_party_details(frm); }, - customer_address: function(frm) { + customer_address: function (frm) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display'); }, - contact_person: function(frm) { + contact_person: function (frm) { erpnext.utils.get_contact_details(frm); } @@ -42,14 +65,14 @@ frappe.ui.form.on('Maintenance Visit', { // TODO commonify this code erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ - refresh: function() { - frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} + refresh: function () { + frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer' }; var me = this; - if (this.frm.doc.docstatus===0) { + if (this.frm.doc.docstatus === 0) { this.frm.add_custom_button(__('Maintenance Schedule'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", source_doctype: "Maintenance Schedule", @@ -64,7 +87,7 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ }) }, __("Get Items From")); this.frm.add_custom_button(__('Warranty Claim'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit", source_doctype: "Warranty Claim", @@ -80,7 +103,7 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ }) }, __("Get Items From")); this.frm.add_custom_button(__('Sales Order'), - function() { + function () { erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit", source_doctype: "Sales Order", @@ -99,4 +122,4 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({ }, }); -$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({frm: cur_frm})); \ No newline at end of file +$.extend(cur_frm.cscript, new erpnext.maintenance.MaintenanceVisit({ frm: cur_frm })); \ No newline at end of file diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 9505a36545..8a3094cb36 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -30,20 +30,21 @@ class MaintenanceVisit(TransactionBase): def validate(self): self.validate_serial_no() self.validate_mntc_date() + + def get_schedule_datail_ref(self): + if self.maintenance_type == "Scheduled": + p = self.purposes + for i in p: + detail_ref = i.prevdoc_detail_docname + return detail_ref def update_completion_status(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) + detail_ref = self.get_schedule_datail_ref() + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) def update_actual_date(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) + detail_ref = self.get_schedule_datail_ref() + frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) def update_customer_issue(self, flag): for d in self.get('purposes'): From fa9629c1e1cf110b7c55230da3ea65b0ecd7d0d6 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 21 Apr 2021 11:29:40 +0530 Subject: [PATCH 011/159] fix: Updated forms and fixed an error. --- .../maintenance_schedule.json | 9 ++++-- .../maintenance_schedule_detail.json | 28 +++++++++++++++++-- .../maintenance_visit/maintenance_visit.js | 25 ++++++++++------- .../maintenance_visit_purpose.json | 16 +++++++++-- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json index 1871228711..4df0c6c0f7 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json @@ -229,8 +229,13 @@ "icon": "fa fa-calendar", "idx": 1, "is_submittable": 1, - "links": [], - "modified": "2021-04-16 15:53:36.670816", + "links": [ + { + "link_doctype": "Maintenance Visit Purpose", + "link_fieldname": "prevdoc_docname" + } + ], + "modified": "2021-04-21 11:27:05.744109", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule", diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index f1e2e21a12..76acefbf61 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -9,10 +9,14 @@ "field_order": [ "item_code", "item_name", + "column_break_3", "scheduled_date", - "sales_person", "actual_date", + "section_break_6", + "sales_person", + "column_break_8", "completion_status", + "section_break_10", "serial_no", "item_ref" ], @@ -95,15 +99,33 @@ }, { "fieldname": "item_ref", - "fieldtype": "Data", + "fieldtype": "Link", + "hidden": 1, "label": "Item Reference", + "options": "Maintenance Schedule Item", "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-19 17:42:31.685710", + "modified": "2021-04-21 11:07:29.524071", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index d5e8e51dfd..403d1ab4cc 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -34,16 +34,21 @@ frappe.ui.form.on('Maintenance Visit', { }, onload: function (frm, cdt, cdn) { let item = locals[cdt][cdn]; - let s_id = item.purposes[0].prevdoc_detail_docname; - frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", - args: { - s_id: s_id - }, - callback: function (r) { - serial_nos = r.message; - } - }); + if (frm.maintenance_type == 'Scheduled') { + + let s_id = item.purposes[0].prevdoc_detail_docname; + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", + args: { + s_id: s_id + }, + callback: function (r) { + serial_nos = r.message; + } + }); + + } + if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); } diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 60e5afe806..0d19d708d9 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -9,10 +9,12 @@ "field_order": [ "item_code", "item_name", + "column_break_3", + "service_person", "serial_no", + "section_break_6", "description", "work_details", - "service_person", "work_done", "prevdoc_doctype", "prevdoc_docname", @@ -86,6 +88,7 @@ { "fieldname": "prevdoc_doctype", "fieldtype": "Link", + "hidden": 1, "label": "Document Type", "no_copy": 1, "oldfieldname": "prevdoc_doctype", @@ -114,6 +117,7 @@ { "fieldname": "prevdoc_detail_docname", "fieldtype": "Link", + "hidden": 1, "label": "Against Document Detail No", "no_copy": 1, "oldfieldname": "prevdoc_detail_docname", @@ -124,12 +128,20 @@ "read_only": 1, "report_hide": 1, "width": "160px" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-19 16:08:10.671163", + "modified": "2021-04-21 11:16:52.025914", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", From 1e912db3bb43afcff98947e46149c1c848287c0e Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 26 Apr 2021 15:46:18 +0530 Subject: [PATCH 012/159] feat: Added check box to combine items --- .../production_plan/production_plan.js | 18 +- .../production_plan/production_plan.json | 22 +- .../production_plan/production_plan.py | 43 +- .../production_plan_item.json | 950 ++++-------------- .../__init__.py | 0 .../production_plan_item_reference.json | 52 + .../production_plan_item_reference.py | 10 + .../doctype/work_order/work_order.py | 27 +- 8 files changed, 355 insertions(+), 767 deletions(-) create mode 100644 erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py create mode 100644 erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json create mode 100644 erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 288c1d0cd6..39b8c94bc1 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -211,16 +211,30 @@ frappe.ui.form.on('Production Plan', { }); }, - get_items: function(frm) { + get_items: function (frm) { + frm.clear_table('prod_plan_ref'); + frappe.call({ method: "get_items", freeze: true, doc: frm.doc, - callback: function() { + callback: function () { refresh_field('po_items'); } }); }, + combine_items: function (frm) { + frm.clear_table('po_items'); + frm.clear_table('prod_plan_ref'); + + frappe.call({ + method: "get_items", + freeze: true, + doc: frm.doc, + }); + + + }, get_items_for_mr: function(frm) { if (!frm.doc.for_warehouse) { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index f11470086a..5c73992d1b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -28,7 +28,10 @@ "material_requests", "select_items_to_manufacture_section", "get_items", + "combine_items", "po_items", + "section_break_25", + "prod_plan_ref", "material_request_planning", "include_non_stock_items", "include_subcontracted_items", @@ -316,13 +319,30 @@ "fieldname": "include_safety_stock", "fieldtype": "Check", "label": "Include Safety Stock in Required Qty Calculation" + }, + { + "fieldname": "prod_plan_ref", + "fieldtype": "Table", + "hidden": 1, + "label": "Production Plan Item Reference", + "options": "Production Plan Item Reference" + }, + { + "default": "0", + "fieldname": "combine_items", + "fieldtype": "Check", + "label": "Consolidate Items" + }, + { + "fieldname": "section_break_25", + "fieldtype": "Section Break" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-03-08 11:17:25.470147", + "modified": "2021-04-26 14:11:43.564957", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a3e23a6897..b2bc21fb6d 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -96,8 +96,10 @@ class ProductionPlan(Document): @frappe.whitelist() def get_items(self): + self.set('po_items', []) if self.get_items_from == "Sales Order": - self.get_so_items() + self.get_so_items() + elif self.get_items_from == "Material Request": self.get_mr_items() @@ -165,10 +167,24 @@ class ProductionPlan(Document): self.calculate_total_planned_qty() def add_items(self, items): - self.set('po_items', []) + refs = {} for data in items: item_details = get_item_details(data.item_code) + if self.combine_items: + if item_details.bom_no in refs.keys(): + refs[item_details.bom_no]['qty'] = refs[item_details.bom_no]['qty'] + data.pending_qty + refs[item_details.bom_no]['so'].append(data.parent) + refs[item_details.bom_no]['so_items'].append(data.name) + refs[item_details.bom_no]['planned_qty'].append(data.pending_qty) + continue + else: + refs[item_details.bom_no] = {'qty': data.pending_qty, 'ref': data.name} + refs[item_details.bom_no]['so'] = [data.parent] + refs[item_details.bom_no]['so_items'] = [data.name] + refs[item_details.bom_no]['planned_qty'] = [data.pending_qty] + pi = self.append('po_items', { + 'name': data.name, 'include_exploded_items': 1, 'warehouse': data.warehouse, 'item_code': data.item_code, @@ -185,11 +201,32 @@ class ProductionPlan(Document): pi.sales_order = data.parent pi.sales_order_item = data.name pi.description = data.description - + + elif self.get_items_from == "Material Request": pi.material_request = data.parent pi.material_request_item = data.name pi.description = data.description + + if refs: + for d in self.po_items: + d.planned_qty = refs[d.bom_no]['qty'] + d.pending_qty = refs[d.bom_no]['qty'] + d.sales_order = '' + self.add_pp_ref(refs) + + def add_pp_ref(self, refs): + for r in refs: + idx = 0 + for so in refs[r]['so']: + self.append('prod_plan_ref', { + 'item_ref': refs[r]['ref'], + 'sales_order': so, + 'sales_order_item':refs[r]['so_items'][idx], + 'qty':refs[r]['planned_qty'][idx] + }) + idx+=1 + def calculate_total_planned_qty(self): self.total_planned_qty = 0 diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index d0dce53437..9ff1717e70 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -1,792 +1,222 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2013-02-22 01:27:49", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:27:49", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "include_exploded_items", + "item_code", + "bom_no", + "planned_qty", + "column_break_6", + "make_work_order_for_sub_assembly_items", + "warehouse", + "planned_start_date", + "section_break_9", + "pending_qty", + "ordered_qty", + "produced_qty", + "column_break_17", + "description", + "stock_uom", + "reference_section", + "sales_order", + "sales_order_item", + "column_break_19", + "material_request", + "material_request_item", + "product_bundle_item" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fetch_if_empty": 0, - "fieldname": "include_exploded_items", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Include Exploded Items", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "default": "0", + "fieldname": "include_exploded_items", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Include Exploded Items" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fetch_if_empty": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "print_width": "150px", + "reqd": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fetch_if_empty": 0, - "fieldname": "bom_no", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "BOM No", - "length": 0, - "no_copy": 0, - "oldfieldname": "bom_no", - "oldfieldtype": "Link", - "options": "BOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "columns": 2, + "fieldname": "bom_no", + "fieldtype": "Link", + "in_list_view": 1, + "label": "BOM No", + "oldfieldname": "bom_no", + "oldfieldtype": "Link", + "options": "BOM", + "print_width": "100px", + "reqd": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "planned_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Planned Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "planned_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "planned_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Planned Qty", + "oldfieldname": "planned_qty", + "oldfieldtype": "Currency", + "print_width": "100px", + "reqd": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", - "fetch_if_empty": 0, - "fieldname": "make_work_order_for_sub_assembly_items", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Make Work Order for Sub Assembly Items", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", + "fieldname": "make_work_order_for_sub_assembly_items", + "fieldtype": "Check", + "label": "Make Work Order for Sub Assembly Items" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "For Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "For Warehouse", + "options": "Warehouse" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fetch_if_empty": 0, - "fieldname": "planned_start_date", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Planned Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "planned_start_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Planned Start Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quantity and Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Quantity and Description" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "pending_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Pending Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "prevdoc_reqd_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "default": "0", + "fieldname": "pending_qty", + "fieldtype": "Float", + "label": "Pending Qty", + "oldfieldname": "prevdoc_reqd_qty", + "oldfieldtype": "Currency", + "print_width": "100px", + "read_only": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "ordered_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ordered Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "ordered_qty", + "fieldtype": "Float", + "label": "Ordered Qty", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "produced_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Produced Qty", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "produced_qty", + "fieldtype": "Float", + "label": "Produced Qty", + "no_copy": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_17", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "200px", + "read_only": 1, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "UOM", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "80px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "UOM", + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "print_width": "80px", + "read_only": 1, + "reqd": 1, "width": "80px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "reference_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "reference_section", + "fieldtype": "Section Break", + "label": "Reference" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sales_order", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Order", - "length": 0, - "no_copy": 0, - "oldfieldname": "source_docname", - "oldfieldtype": "Data", - "options": "Sales Order", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "oldfieldname": "source_docname", + "oldfieldtype": "Data", + "options": "Sales Order", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sales_order_item", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Order Item", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sales_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Sales Order Item", + "no_copy": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_19", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "material_request", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Material Request", - "length": 0, - "no_copy": 0, - "options": "Material Request", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "options": "Material Request", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "material_request_item", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "material_request_item", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "material_request_item", + "fieldtype": "Data", + "hidden": 1, + "label": "material_request_item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "product_bundle_item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Product Bundle Item", - "length": 0, - "no_copy": 1, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "product_bundle_item", + "fieldtype": "Link", + "label": "Product Bundle Item", + "no_copy": 1, + "options": "Item", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 1, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-04-08 23:09:57.199423", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Production Plan Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-04-22 12:10:01.102440", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py b/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json new file mode 100644 index 0000000000..19e813c5b3 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "creation": "2021-04-22 10:32:58.896330", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_ref", + "sales_order", + "sales_order_item", + "qty" + ], + "fields": [ + { + "fieldname": "item_ref", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Reference" + }, + { + "fieldname": "sales_order", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sales Order Reference", + "options": "Sales Order" + }, + { + "fieldname": "sales_order_item", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Sales Order Item" + }, + { + "fieldname": "qty", + "fieldtype": "Data", + "in_list_view": 1, + "label": "qty" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-23 16:55:22.161418", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Item Reference", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py new file mode 100644 index 0000000000..51fbc3633b --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ProductionPlanItemReference(Document): + pass diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 8507f5eb34..bd286507ec 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -241,7 +241,13 @@ class WorkOrder(Document): if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) - self.update_work_order_qty_in_so() + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_ref = prod_plan.prod_plan_ref + if pp_ref: + self.update_work_order_qty_in_combined_so() + else: + self.update_work_order_qty_in_so() + self.update_reserved_qty_for_production() self.update_completed_qty_in_material_request() self.update_planned_qty() @@ -357,6 +363,25 @@ class WorkOrder(Document): work_order_qty = qty[0][0] if qty and qty[0][0] else 0 frappe.db.set_value('Sales Order Item', self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + + def update_work_order_qty_in_combined_so(self): + total_bundle_qty = 1 + if self.product_bundle_item: + total_bundle_qty = frappe.db.sql(""" select sum(qty) from + `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0] + + if not total_bundle_qty: + # product bundle is 0 (product bundle allows 0 qty for items) + total_bundle_qty = 1 + + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_ref = prod_plan.prod_plan_ref + for p in pp_ref: + if p.item_ref == self.production_plan_item: + work_order_qty = int(p.qty) + frappe.db.set_value('Sales Order Item', + p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + def update_completed_qty_in_material_request(self): if self.material_request: From 82905166d988f162b6fa84a49ee63a0704bda661 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 28 Apr 2021 10:12:17 +0530 Subject: [PATCH 013/159] fix: Fixed updating sales order work qty after cancelling work order --- .../doctype/production_plan/production_plan.js | 1 - .../manufacturing/doctype/work_order/work_order.py | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 39b8c94bc1..29c3d5b18e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -224,7 +224,6 @@ frappe.ui.form.on('Production Plan', { }); }, combine_items: function (frm) { - frm.clear_table('po_items'); frm.clear_table('prod_plan_ref'); frappe.call({ diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index bd286507ec..87d57ad42c 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -258,7 +258,13 @@ class WorkOrder(Document): self.validate_cancel() frappe.db.set(self,'status', 'Cancelled') - self.update_work_order_qty_in_so() + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_ref = prod_plan.prod_plan_ref + if pp_ref: + self.update_work_order_qty_in_combined_so(cancel = True) + else: + self.update_work_order_qty_in_so() + self.delete_job_card() self.update_completed_qty_in_material_request() self.update_planned_qty() @@ -364,7 +370,7 @@ class WorkOrder(Document): frappe.db.set_value('Sales Order Item', self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) - def update_work_order_qty_in_combined_so(self): + def update_work_order_qty_in_combined_so(self, cancel = None): total_bundle_qty = 1 if self.product_bundle_item: total_bundle_qty = frappe.db.sql(""" select sum(qty) from @@ -378,7 +384,7 @@ class WorkOrder(Document): pp_ref = prod_plan.prod_plan_ref for p in pp_ref: if p.item_ref == self.production_plan_item: - work_order_qty = int(p.qty) + work_order_qty = int(p.qty) if not cancel else 0 frappe.db.set_value('Sales Order Item', p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) From b7ca9139042784530157d104126f3569bcd1d1d8 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 28 Apr 2021 19:21:36 +0530 Subject: [PATCH 014/159] fix: Added Item Reference field to link tables and update work_order_qty --- .../doctype/production_plan/production_plan.py | 1 + .../production_plan_item/production_plan_item.json | 11 +++++++++-- .../manufacturing/doctype/work_order/work_order.py | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index b2bc21fb6d..088089f87f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -201,6 +201,7 @@ class ProductionPlan(Document): pi.sales_order = data.parent pi.sales_order_item = data.name pi.description = data.description + pi.item_reference = data.name elif self.get_items_from == "Material Request": diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 9ff1717e70..89ab7aa0a0 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -27,7 +27,8 @@ "column_break_19", "material_request", "material_request_item", - "product_bundle_item" + "product_bundle_item", + "item_reference" ], "fields": [ { @@ -206,12 +207,18 @@ "options": "Item", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "item_reference", + "fieldtype": "Data", + "hidden": 1, + "label": "Item Reference" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-22 12:10:01.102440", + "modified": "2021-04-28 19:14:57.772123", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 87d57ad42c..d77c46fb03 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -382,8 +382,10 @@ class WorkOrder(Document): prod_plan = frappe.get_doc('Production Plan', self.production_plan) pp_ref = prod_plan.prod_plan_ref + pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) + item_ref = pp_item.item_reference for p in pp_ref: - if p.item_ref == self.production_plan_item: + if p.item_ref == item_ref: work_order_qty = int(p.qty) if not cancel else 0 frappe.db.set_value('Sales Order Item', p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) From 56f697052cc01f3beab8a12d5b9978e2bf86ebbe Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 28 Apr 2021 19:25:22 +0530 Subject: [PATCH 015/159] test: added test case for combining items --- .../production_plan/test_production_plan.py | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 27335aa204..2f52ad9021 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -100,7 +100,7 @@ class TestProductionPlan(unittest.TestCase): def test_production_plan_sales_orders(self): item = 'Test Production Item 1' - so = make_sales_order(item_code=item, qty=5) + so = make_sales_order(item_code=item, qty=1) sales_order = so.name sales_order_item = so.items[0].name @@ -124,8 +124,8 @@ class TestProductionPlan(unittest.TestCase): wo_doc = frappe.get_doc('Work Order', work_order) wo_doc.update({ - 'wip_warehouse': '_Test Warehouse 1 - _TC', - 'fg_warehouse': '_Test Warehouse - _TC' + 'wip_warehouse': 'Work In Progress - _TC', + 'fg_warehouse': 'Finished Goods - _TC' }) wo_doc.submit() @@ -145,6 +145,57 @@ class TestProductionPlan(unittest.TestCase): self.assertEqual(sales_orders, []) + def test_production_plan_combine_items(self): + item = 'Test Production Item 1' + so = make_sales_order(item_code=item, qty=1) + sales_order = so.name + sales_order_item = so.items[0].name + + pln = frappe.new_doc('Production Plan') + pln.company = so.company + pln.get_items_from = 'Sales Order' + pln.append('sales_orders', { + 'sales_order': so.name, + 'sales_order_date': so.transaction_date, + 'customer': so.customer, + 'grand_total': so.grand_total + }) + so = make_sales_order(item_code=item, qty=2) + pln.append('sales_orders', { + 'sales_order': so.name, + 'sales_order_date': so.transaction_date, + 'customer': so.customer, + 'grand_total': so.grand_total + }) + pln.combine_items = 1 + pln.get_so_items() + pln.save() + pp = frappe.get_doc('Production Plan',pln.name) + for d in pp.prod_plan_ref: + d.item_ref = pp.po_items[0].name + pln.submit() + + self.assertTrue(pln.po_items[0].planned_qty,3) + + + pln.make_work_order() + + work_order = frappe.db.get_value('Work Order', {'production_plan_item': pln.po_items[0].name, + 'production_plan': pln.name,}, 'name') + + wo_doc = frappe.get_doc('Work Order', work_order) + wo_doc.update({ + 'wip_warehouse': 'Work In Progress - _TC', + }) + + wo_doc.submit() + for d in pln.prod_plan_ref: + so_wo_qty = frappe.db.get_value('Sales Order Item', d.sales_order_item, 'work_order_qty') + self.assertTrue(so_wo_qty,d.qty) + + + + def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) From 93c22ebbb98324153b6644475478c62ee15f8ff8 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 29 Apr 2021 12:57:41 +0530 Subject: [PATCH 016/159] refactor: created separate function to update work_order on cancel --- .../doctype/work_order/work_order.py | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index d77c46fb03..d9956e5bca 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -242,8 +242,8 @@ class WorkOrder(Document): frappe.throw(_("For Warehouse is required before Submit")) prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_ref = prod_plan.prod_plan_ref - if pp_ref: + + if prod_plan.prod_plan_ref: self.update_work_order_qty_in_combined_so() else: self.update_work_order_qty_in_so() @@ -259,9 +259,9 @@ class WorkOrder(Document): frappe.db.set(self,'status', 'Cancelled') prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_ref = prod_plan.prod_plan_ref - if pp_ref: - self.update_work_order_qty_in_combined_so(cancel = True) + + if prod_plan.prod_plan_ref: + self.update_work_order_combined_on_cancel() else: self.update_work_order_qty_in_so() @@ -370,7 +370,7 @@ class WorkOrder(Document): frappe.db.set_value('Sales Order Item', self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) - def update_work_order_qty_in_combined_so(self, cancel = None): + def update_work_order_qty_in_combined_so(self): total_bundle_qty = 1 if self.product_bundle_item: total_bundle_qty = frappe.db.sql(""" select sum(qty) from @@ -381,14 +381,22 @@ class WorkOrder(Document): total_bundle_qty = 1 prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_ref = prod_plan.prod_plan_ref pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) - item_ref = pp_item.item_reference - for p in pp_ref: - if p.item_ref == item_ref: - work_order_qty = int(p.qty) if not cancel else 0 + + for p in prod_plan.prod_plan_ref: + if p.item_ref == pp_item.item_reference: + work_order_qty = int(p.qty) frappe.db.set_value('Sales Order Item', p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + + def update_work_order_combined_on_cancel(self): + prod_plan = frappe.get_doc('Production Plan', self.production_plan) + pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) + + for p in prod_plan.prod_plan_ref: + if p.item_ref == pp_item.item_reference: + frappe.db.set_value('Sales Order Item', + p.sales_order_item, 'work_order_qty', 0.0) def update_completed_qty_in_material_request(self): From 90c667205adce829d9e0f9c4d74c4dd9effaa065 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 29 Apr 2021 12:58:35 +0530 Subject: [PATCH 017/159] test: added on_cancel test --- .../production_plan/test_production_plan.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2f52ad9021..19b06bc8dd 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -148,8 +148,6 @@ class TestProductionPlan(unittest.TestCase): def test_production_plan_combine_items(self): item = 'Test Production Item 1' so = make_sales_order(item_code=item, qty=1) - sales_order = so.name - sales_order_item = so.items[0].name pln = frappe.new_doc('Production Plan') pln.company = so.company @@ -169,17 +167,13 @@ class TestProductionPlan(unittest.TestCase): }) pln.combine_items = 1 pln.get_so_items() - pln.save() - pp = frappe.get_doc('Production Plan',pln.name) - for d in pp.prod_plan_ref: - d.item_ref = pp.po_items[0].name + for d in pln.prod_plan_ref: + d.item_ref = pln.po_items[0].name pln.submit() - self.assertTrue(pln.po_items[0].planned_qty,3) - - - pln.make_work_order() + self.assertTrue(pln.po_items[0].planned_qty,3) + pln.make_work_order() work_order = frappe.db.get_value('Work Order', {'production_plan_item': pln.po_items[0].name, 'production_plan': pln.name,}, 'name') @@ -189,13 +183,19 @@ class TestProductionPlan(unittest.TestCase): }) wo_doc.submit() + so_items = [] for d in pln.prod_plan_ref: + so_items.append(d.sales_order_item) so_wo_qty = frappe.db.get_value('Sales Order Item', d.sales_order_item, 'work_order_qty') - self.assertTrue(so_wo_qty,d.qty) - - - - + self.assertEqual(so_wo_qty, d.qty) + wo_doc.cancel() + for s in so_items: + so_wo_qty = frappe.db.get_value('Sales Order Item', s, 'work_order_qty') + self.assertEqual(so_wo_qty, 0.0) + + lat_plan = frappe.get_doc('Production Plan',pln.name) + lat_plan.cancel() + def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) From 7ad66caf7f656347d2e9b3fe253ba64b6fd35ae0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 30 Apr 2021 11:42:16 +0530 Subject: [PATCH 018/159] refactor: migrated calculation and validation logic in js to py --- .../maintenance_schedule.js | 84 ++++--------------- .../maintenance_schedule.py | 60 ++++++++++++- 2 files changed, 74 insertions(+), 70 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 3c05526496..79167ae45f 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -73,31 +73,11 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } if (flag) { this.frm.add_custom_button(__('Create Maintenance Visit'), function () { - let items = me.frm.doc.items; - let s = me.frm.doc.schedules; let options = ""; - let dates = ""; - for (let i in items) { - for (let d in s) { - if (s[d].item_name == items[i].item_name && s[d].completion_status == "Pending") { - options = options + '\n' + items[i].item_name; - break; - } - } - } - function formatDate(date) { - var d = new Date(date), - month = '' + (d.getMonth() + 1), - day = '' + d.getDate(), - year = d.getFullYear(); - - if (month.length < 2) - month = '0' + month; - if (day.length < 2) - day = '0' + day; - - return [day, month, year].join('-'); - } + + me.frm.call('get_pending_data',{data_type:"items"}).then(r =>{ + options = r.message + var schedule_id = ""; var d = new frappe.ui.Dialog({ title: __("Enter Visit Details"), @@ -109,30 +89,23 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ reqd: 1, onchange: function () { let field = d.get_field("scheduled_date"); - dates = ""; - for (let i in s) { - if (s[i].item_name == this.value && s[i].completion_status == "Pending") { - dates = dates + '\n' + formatDate(s[i].scheduled_date); - } - - } - field.df.options = dates; - field.refresh(); + me.frm.call('get_pending_data',{item_name:this.value,data_type:"date"}).then(r =>{ + field.df.options = r.message; + field.refresh(); + }) } }, { label: __('Scheduled Date'), fieldname: 'scheduled_date', fieldtype: 'Select', - options: dates, + options: "", reqd: 1, onchange: function () { let field = d.get_field('item_name'); - for (let i in s) { - if (s[i].item_name == field.value && formatDate(s[i].scheduled_date) == this.value) { - schedule_id = s[i].name; - } - } + me.frm.call('get_pending_data',{item_name:field.value,s_date:this.value,data_type:"id"}).then(r =>{ + schedule_id = r.message; + }) } }, ], @@ -159,7 +132,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ } }); d.show(); - + }); }, __('Create')); } } @@ -185,35 +158,8 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ var item = frappe.get_doc(cdt, cdn); if (item.start_date && item.periodicity) { - - var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; - - var days_in_period = { - "Weekly": 7, - "Monthly": 30, - "Quarterly": 91, - "Half Yearly": 182, - "Yearly": 365 - } - - var no_of_visits = cint(date_diff / days_in_period[item.periodicity]); - if (no_of_visits == 0 || !no_of_visits) { - - let end_date = frappe.datetime.add_days(item.start_date, days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "end_date", end_date); - date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1; - no_of_visits = cint(date_diff / days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits); - - } else if (item.no_of_visits > no_of_visits) { - let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "end_date", end_date); - - } else if (item.no_of_visits < no_of_visits) { - let end_date = frappe.datetime.add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]); - frappe.model.set_value(item.doctype, item.name, "end_date", end_date); - - } + me.frm.call('validate_end_date_visits') + } }, }); diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index b89d540ebb..d11bf7e735 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import add_days, getdate, cint, cstr +from frappe.utils import add_days, getdate, cint, cstr, date_diff, formatdate from frappe import throw, _ from erpnext.utilities.transaction_base import TransactionBase, delete_events @@ -36,6 +36,39 @@ class MaintenanceSchedule(TransactionBase): child.completion_status = "Pending" child.item_ref = d.name + @frappe.whitelist() + def validate_end_date_visits(self): + days_in_period = { + "Weekly": 7, + "Monthly": 30, + "Quarterly": 91, + "Half Yearly": 182, + "Yearly": 365 + } + for i in self.items: + + if i.periodicity and i.start_date: + if not i.end_date: + if i.no_of_visits: + i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + else: + i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) + + diff = date_diff(i.end_date, i.start_date) + 1 + no_of_visits = cint(diff / days_in_period[i.periodicity]) + + if not i.no_of_visits or i.no_of_visits == 0: + i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) + diff = date_diff(i.end_date, i.start_date ) + 1 + i.no_of_visits = cint(diff / days_in_period[i.periodicity]) + + elif i.no_of_visits > no_of_visits: + i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + + elif i.no_of_visits < no_of_visits: + i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + + def on_submit(self): if not self.get('schedules'): throw(_("Please click on 'Generate Schedule' to get schedule")) @@ -166,6 +199,7 @@ class MaintenanceSchedule(TransactionBase): throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order)) def validate(self): + self.validate_end_date_visits() self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() @@ -246,6 +280,30 @@ class MaintenanceSchedule(TransactionBase): def on_trash(self): delete_events(self.doctype, self.name) + + @frappe.whitelist() + def get_pending_data(self,data_type,s_date = None, item_name = None): + if data_type == "date": + dates = "" + for i in self.schedules: + if i.item_name == item_name and i.completion_status == "Pending": + dates = dates + "\n" + formatdate(i.scheduled_date, "dd-MM-yyyy") + return dates + elif data_type == "items": + items = "" + for i in self.items: + for s in self.schedules: + if i.item_name == s.item_name and s.completion_status == "Pending": + items = items + "\n" + i.item_name + break + return items + elif data_type == "id": + for s in self.schedules: + if s.item_name == item_name and s_date == formatdate(s.scheduled_date,"dd-mm-yyyy"): + return s.name + + + @frappe.whitelist() def update_serial_nos(s_id): serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') From c0352010d360870d7ae2a057a1edefb72e72399b Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 30 Apr 2021 14:09:30 +0530 Subject: [PATCH 019/159] test: creating schedule and visit --- .../test_maintenance_schedule.py | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 3c307e920f..834c05476e 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -2,7 +2,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals -from frappe.utils.data import get_datetime, add_days +from frappe.utils.data import add_days, today +from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import make_maintenance_visit import frappe import unittest @@ -21,6 +22,52 @@ class TestMaintenanceSchedule(unittest.TestCase): ms.cancel() events_after_cancel = get_events(ms) self.assertTrue(len(events_after_cancel) == 0) + + def test_make_schedule(self): + ms = make_maintenance_schedule() + ms.save() + i = ms.items[0] + expected_end_date = add_days(i.start_date, i.no_of_visits * 7) + self.assertEqual(i.end_date, expected_end_date) + + i.no_of_visits = 2 + ms.save() + expected_end_date = add_days(i.start_date, i.no_of_visits * 7) + self.assertEqual(i.end_date, expected_end_date) + + items = ms.get_pending_data(data_type = "items") + items = items.split('\n') + items.pop(0) + expected_items = ['_Test Item'] + self.assertTrue(items,expected_items) + + dates = ms.get_pending_data(data_type = "date",item_name = i.item_name) + dates = dates.split('\n') + dates.pop(0) + expected_dates = ['07-05-2021','14-05-2021'] + self.assertEqual(dates,expected_dates) + + + ms.submit() + s_id = ms.get_pending_data(data_type = "id",item_name = i.item_name, s_date = "14-05-2021") + test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) + visit = frappe.new_doc('Maintenance Visit') + visit = test + visit.completion_status = "Partially Completed" + + visit.set('purposes',[{ + 'item_code':i.item_code, + 'description':"test", + 'work_done':"test", + 'prevdoc_docname':ms.name, + 'prevdoc_doctype':ms.doctype, + 'prevdoc_detail_docname':s_id + }]) + visit.submit() + ms = frappe.get_doc('Maintenance Schedule',ms.name) + self.assertTrue(ms.schedules[1].completion_status,"Partially Completed") + + def get_events(ms): return frappe.get_all("Event Participants", filters={ @@ -29,16 +76,16 @@ def get_events(ms): "parenttype": "Event" }) + def make_maintenance_schedule(): ms = frappe.new_doc("Maintenance Schedule") ms.company = "_Test Company" ms.customer = "_Test Customer" - ms.transaction_date = get_datetime() + ms.transaction_date = today() ms.append("items", { "item_code": "_Test Item", - "start_date": get_datetime(), - "end_date": add_days(get_datetime(), 32), + "start_date": today(), "periodicity": "Weekly", "no_of_visits": 4, "sales_person": "Sales Team", From 50f52dfbcbf3124c1f9468807f1f22e6df2cb10c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 7 May 2021 17:18:48 +0530 Subject: [PATCH 020/159] refactor: variable names and refactored cancel function into submit function --- .../production_plan/production_plan.json | 18 +++++------ .../production_plan/production_plan.py | 16 +++++----- .../production_plan/test_production_plan.py | 17 +++++----- .../production_plan_item_reference.json | 16 +++++----- .../doctype/work_order/work_order.py | 32 ++++++------------- 5 files changed, 44 insertions(+), 55 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 5c73992d1b..3041507caf 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -31,7 +31,7 @@ "combine_items", "po_items", "section_break_25", - "prod_plan_ref", + "prod_plan_references", "material_request_planning", "include_non_stock_items", "include_subcontracted_items", @@ -320,13 +320,6 @@ "fieldtype": "Check", "label": "Include Safety Stock in Required Qty Calculation" }, - { - "fieldname": "prod_plan_ref", - "fieldtype": "Table", - "hidden": 1, - "label": "Production Plan Item Reference", - "options": "Production Plan Item Reference" - }, { "default": "0", "fieldname": "combine_items", @@ -336,13 +329,20 @@ { "fieldname": "section_break_25", "fieldtype": "Section Break" + }, + { + "fieldname": "prod_plan_references", + "fieldtype": "Table", + "hidden": 1, + "label": "Production Plan Item Reference", + "options": "Production Plan Item Reference" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-26 14:11:43.564957", + "modified": "2021-05-07 16:56:00.255001", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 088089f87f..8d578fd935 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -172,7 +172,7 @@ class ProductionPlan(Document): item_details = get_item_details(data.item_code) if self.combine_items: if item_details.bom_no in refs.keys(): - refs[item_details.bom_no]['qty'] = refs[item_details.bom_no]['qty'] + data.pending_qty + refs[item_details.bom_no]['qty'] += data.pending_qty refs[item_details.bom_no]['so'].append(data.parent) refs[item_details.bom_no]['so_items'].append(data.name) refs[item_details.bom_no]['planned_qty'].append(data.pending_qty) @@ -217,15 +217,15 @@ class ProductionPlan(Document): self.add_pp_ref(refs) def add_pp_ref(self, refs): - for r in refs: + for bom_no in refs: idx = 0 - for so in refs[r]['so']: - self.append('prod_plan_ref', { - 'item_ref': refs[r]['ref'], + for so in refs[bom_no]['so']: + self.append('prod_plan_references', { + 'item_reference': refs[bom_no]['ref'], 'sales_order': so, - 'sales_order_item':refs[r]['so_items'][idx], - 'qty':refs[r]['planned_qty'][idx] - }) + 'sales_order_item':refs[bom_no]['so_items'][idx], + 'qty':refs[bom_no]['planned_qty'][idx] + }) idx+=1 diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 19b06bc8dd..ec5c5e0e13 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -167,8 +167,8 @@ class TestProductionPlan(unittest.TestCase): }) pln.combine_items = 1 pln.get_so_items() - for d in pln.prod_plan_ref: - d.item_ref = pln.po_items[0].name + for plan_reference in pln.prod_plan_references: + plan_reference.item_reference = pln.po_items[0].name pln.submit() self.assertTrue(pln.po_items[0].planned_qty,3) @@ -184,13 +184,14 @@ class TestProductionPlan(unittest.TestCase): wo_doc.submit() so_items = [] - for d in pln.prod_plan_ref: - so_items.append(d.sales_order_item) - so_wo_qty = frappe.db.get_value('Sales Order Item', d.sales_order_item, 'work_order_qty') - self.assertEqual(so_wo_qty, d.qty) + for plan_reference in pln.prod_plan_references: + so_items.append(plan_reference.sales_order_item) + so_wo_qty = frappe.db.get_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty') + self.assertEqual(so_wo_qty, plan_reference.qty) + wo_doc.cancel() - for s in so_items: - so_wo_qty = frappe.db.get_value('Sales Order Item', s, 'work_order_qty') + for so_item in so_items: + so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty') self.assertEqual(so_wo_qty, 0.0) lat_plan = frappe.get_doc('Production Plan',pln.name) diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json index 19e813c5b3..84dee4ad28 100644 --- a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json +++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json @@ -5,18 +5,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "item_ref", + "item_reference", "sales_order", "sales_order_item", "qty" ], "fields": [ - { - "fieldname": "item_ref", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Reference" - }, { "fieldname": "sales_order", "fieldtype": "Link", @@ -35,12 +29,18 @@ "fieldtype": "Data", "in_list_view": 1, "label": "qty" + }, + { + "fieldname": "item_reference", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Reference" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-23 16:55:22.161418", + "modified": "2021-05-07 17:03:49.707487", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item Reference", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index d9956e5bca..bb6450b775 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -240,10 +240,8 @@ class WorkOrder(Document): frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) - - prod_plan = frappe.get_doc('Production Plan', self.production_plan) - if prod_plan.prod_plan_ref: + if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}): self.update_work_order_qty_in_combined_so() else: self.update_work_order_qty_in_so() @@ -256,12 +254,10 @@ class WorkOrder(Document): def on_cancel(self): self.validate_cancel() - frappe.db.set(self,'status', 'Cancelled') - prod_plan = frappe.get_doc('Production Plan', self.production_plan) - if prod_plan.prod_plan_ref: - self.update_work_order_combined_on_cancel() + if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}): + self.update_work_order_qty_in_combined_so() else: self.update_work_order_qty_in_so() @@ -381,24 +377,16 @@ class WorkOrder(Document): total_bundle_qty = 1 prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) + item_reference = frappe.get_value('Production Plan Item', self.production_plan_item,'item_reference') - for p in prod_plan.prod_plan_ref: - if p.item_ref == pp_item.item_reference: - work_order_qty = int(p.qty) + for plan_reference in prod_plan.prod_plan_references: + work_order_qty = 0.0 + if plan_reference.item_reference == item_reference: + if self.docstatus == 1: + work_order_qty = cint(plan_reference.qty) / total_bundle_qty frappe.db.set_value('Sales Order Item', - p.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2)) + plan_reference.sales_order_item, 'work_order_qty', work_order_qty) - def update_work_order_combined_on_cancel(self): - prod_plan = frappe.get_doc('Production Plan', self.production_plan) - pp_item = frappe.get_doc('Production Plan Item', self.production_plan_item) - - for p in prod_plan.prod_plan_ref: - if p.item_ref == pp_item.item_reference: - frappe.db.set_value('Sales Order Item', - p.sales_order_item, 'work_order_qty', 0.0) - - def update_completed_qty_in_material_request(self): if self.material_request: frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item]) From 516c789127d8514368fc35392368b197534d5e0b Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:29:30 +0530 Subject: [PATCH 021/159] refactor: variable names and suggested changes --- .../production_plan/production_plan.js | 6 +-- .../production_plan/production_plan.py | 51 ++++++++++--------- .../production_plan/test_production_plan.py | 16 +++--- .../doctype/work_order/work_order.py | 2 +- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 29c3d5b18e..64d584118f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -212,7 +212,7 @@ frappe.ui.form.on('Production Plan', { }, get_items: function (frm) { - frm.clear_table('prod_plan_ref'); + frm.clear_table('prod_plan_references'); frappe.call({ method: "get_items", @@ -224,15 +224,13 @@ frappe.ui.form.on('Production Plan', { }); }, combine_items: function (frm) { - frm.clear_table('prod_plan_ref'); + frm.clear_table('prod_plan_references'); frappe.call({ method: "get_items", freeze: true, doc: frm.doc, }); - - }, get_items_for_mr: function(frm) { diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 8d578fd935..46e047654b 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -171,20 +171,28 @@ class ProductionPlan(Document): for data in items: item_details = get_item_details(data.item_code) if self.combine_items: - if item_details.bom_no in refs.keys(): + if item_details.bom_no in refs: + refs[item_details.bom_no]['so_details'].append({ + 'sales_order': data.parent, + 'sales_order_item': data.name, + 'qty': data.pending_qty + }) refs[item_details.bom_no]['qty'] += data.pending_qty - refs[item_details.bom_no]['so'].append(data.parent) - refs[item_details.bom_no]['so_items'].append(data.name) - refs[item_details.bom_no]['planned_qty'].append(data.pending_qty) continue + else: - refs[item_details.bom_no] = {'qty': data.pending_qty, 'ref': data.name} - refs[item_details.bom_no]['so'] = [data.parent] - refs[item_details.bom_no]['so_items'] = [data.name] - refs[item_details.bom_no]['planned_qty'] = [data.pending_qty] - + refs[item_details.bom_no] = { + 'qty': data.pending_qty, + 'po_item_ref': data.name, + 'so_details': [] + } + refs[item_details.bom_no]['so_details'].append({ + 'sales_order': data.parent, + 'sales_order_item': data.name, + 'qty': data.pending_qty + }) + pi = self.append('po_items', { - 'name': data.name, 'include_exploded_items': 1, 'warehouse': data.warehouse, 'item_code': data.item_code, @@ -201,8 +209,6 @@ class ProductionPlan(Document): pi.sales_order = data.parent pi.sales_order_item = data.name pi.description = data.description - pi.item_reference = data.name - elif self.get_items_from == "Material Request": pi.material_request = data.parent @@ -210,24 +216,21 @@ class ProductionPlan(Document): pi.description = data.description if refs: - for d in self.po_items: - d.planned_qty = refs[d.bom_no]['qty'] - d.pending_qty = refs[d.bom_no]['qty'] - d.sales_order = '' + for po_item in self.po_items: + po_item.planned_qty = refs[po_item.bom_no]['qty'] + po_item.pending_qty = refs[po_item.bom_no]['qty'] + po_item.sales_order = '' self.add_pp_ref(refs) def add_pp_ref(self, refs): for bom_no in refs: - idx = 0 - for so in refs[bom_no]['so']: + for so_detail in refs[bom_no]['so_details']: self.append('prod_plan_references', { - 'item_reference': refs[bom_no]['ref'], - 'sales_order': so, - 'sales_order_item':refs[bom_no]['so_items'][idx], - 'qty':refs[bom_no]['planned_qty'][idx] + 'item_reference': refs[bom_no]['po_item_ref'], + 'sales_order': so_detail['sales_order'], + 'sales_order_item': so_detail['sales_order_item'], + 'qty': so_detail['qty'] }) - idx+=1 - def calculate_total_planned_qty(self): self.total_planned_qty = 0 diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index ec5c5e0e13..768f99eb43 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -166,16 +166,16 @@ class TestProductionPlan(unittest.TestCase): 'grand_total': so.grand_total }) pln.combine_items = 1 - pln.get_so_items() - for plan_reference in pln.prod_plan_references: - plan_reference.item_reference = pln.po_items[0].name + pln.get_items() pln.submit() - self.assertTrue(pln.po_items[0].planned_qty,3) + self.assertTrue(pln.po_items[0].planned_qty, 3) pln.make_work_order() - work_order = frappe.db.get_value('Work Order', {'production_plan_item': pln.po_items[0].name, - 'production_plan': pln.name,}, 'name') + work_order = frappe.db.get_value('Work Order', { + 'production_plan_item': pln.po_items[0].name, + 'production_plan': pln.name + }, 'name') wo_doc = frappe.get_doc('Work Order', work_order) wo_doc.update({ @@ -194,8 +194,8 @@ class TestProductionPlan(unittest.TestCase): so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty') self.assertEqual(so_wo_qty, 0.0) - lat_plan = frappe.get_doc('Production Plan',pln.name) - lat_plan.cancel() + latest_plan = frappe.get_doc('Production Plan', pln.name) + latest_plan.cancel() def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index bb6450b775..a154464a8b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -377,7 +377,7 @@ class WorkOrder(Document): total_bundle_qty = 1 prod_plan = frappe.get_doc('Production Plan', self.production_plan) - item_reference = frappe.get_value('Production Plan Item', self.production_plan_item,'item_reference') + item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item') for plan_reference in prod_plan.prod_plan_references: work_order_qty = 0.0 From 08598238d73f4f033048baeedf9c96b4e7fc5102 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 20 May 2021 13:23:10 +0530 Subject: [PATCH 022/159] refactor: suggested changes --- .../maintenance_schedule.js | 26 +++--- .../maintenance_schedule.py | 79 +++++++++---------- .../maintenance_visit/maintenance_visit.js | 9 +-- 3 files changed, 52 insertions(+), 62 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 79167ae45f..075bd40660 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -64,22 +64,17 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ }); }, __("Get Items From")); } else if (this.frm.doc.docstatus === 1) { - var s = me.frm.doc.schedules; - let flag = 0; - for (let i in s) { - if (s[i].completion_status == "Pending") { - flag = 1; - } - } + let schedules = me.frm.doc.schedules; + let flag = schedules.some(schedule => schedule.completion_status === "Pending"); if (flag) { this.frm.add_custom_button(__('Create Maintenance Visit'), function () { let options = ""; - me.frm.call('get_pending_data',{data_type:"items"}).then(r =>{ + me.frm.call('get_pending_data', {data_type: "items"}).then(r =>{ options = r.message - var schedule_id = ""; - var d = new frappe.ui.Dialog({ + let schedule_id = ""; + let d = new frappe.ui.Dialog({ title: __("Enter Visit Details"), fields: [{ fieldtype: "Select", @@ -103,7 +98,13 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ reqd: 1, onchange: function () { let field = d.get_field('item_name'); - me.frm.call('get_pending_data',{item_name:field.value,s_date:this.value,data_type:"id"}).then(r =>{ + me.frm.call( + 'get_pending_data', + { + item_name: field.value, + s_date: this.value, + data_type: "id" + }).then(r =>{ schedule_id = r.message; }) } @@ -117,7 +118,6 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ item_name: values.item_name, s_id: schedule_id, source_name: me.frm.doc.name, - }, callback: function (r) { if (!r.exc) { @@ -125,8 +125,6 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ frappe.set_route("Form", r.message.doctype, r.message.name); } } - - }); d.hide(); } diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index d11bf7e735..0bc2e87a6c 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -45,28 +45,27 @@ class MaintenanceSchedule(TransactionBase): "Half Yearly": 182, "Yearly": 365 } - for i in self.items: - - if i.periodicity and i.start_date: - if not i.end_date: - if i.no_of_visits: - i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + for item in self.items: + if item.periodicity and item.start_date: + if not item.end_date: + if item.no_of_visits: + item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) else: - i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) + item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) - diff = date_diff(i.end_date, i.start_date) + 1 - no_of_visits = cint(diff / days_in_period[i.periodicity]) + diff = date_diff(item.end_date, item.start_date) + 1 + no_of_visits = cint(diff / days_in_period[item.periodicity]) - if not i.no_of_visits or i.no_of_visits == 0: - i.end_date = add_days(i.start_date, days_in_period[i.periodicity]) - diff = date_diff(i.end_date, i.start_date ) + 1 - i.no_of_visits = cint(diff / days_in_period[i.periodicity]) + if not item.no_of_visits or item.no_of_visits == 0: + item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) + diff = date_diff(item.end_date, item.start_date ) + 1 + item.no_of_visits = cint(diff / days_in_period[item.periodicity]) - elif i.no_of_visits > no_of_visits: - i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + elif item.no_of_visits > no_of_visits: + item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) - elif i.no_of_visits < no_of_visits: - i.end_date = add_days(i.start_date, i.no_of_visits * days_in_period[i.periodicity]) + elif item.no_of_visits < no_of_visits: + item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) def on_submit(self): @@ -92,9 +91,10 @@ class MaintenanceSchedule(TransactionBase): if no_email_sp: frappe.msgprint( - frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( - self.owner, "
" + "
".join(no_email_sp) - )) + _("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( + self.owner, "
" + "
".join(no_email_sp) + ) + ) scheduled_date = frappe.db.sql("""select scheduled_date from `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and @@ -103,12 +103,12 @@ class MaintenanceSchedule(TransactionBase): for key in scheduled_date: description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer) event = frappe.get_doc({ - "doctype": "Event", - "owner": email_map.get(d.sales_person, self.owner), - "subject": description, - "description": description, - "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", - "event_type": "Private", + "doctype": "Event", + "owner": email_map.get(d.sales_person, self.owner), + "subject": description, + "description": description, + "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", + "event_type": "Private", }) event.add_participant(self.doctype, self.name) event.insert(ignore_permissions=1) @@ -126,7 +126,7 @@ class MaintenanceSchedule(TransactionBase): start_date_copy = add_days(start_date_copy, add_by) if len(schedule_list) < no_of_visit: schedule_date = self.validate_schedule_date_for_holiday_list(getdate(start_date_copy), - sales_person) + sales_person) if schedule_date > getdate(end_date): schedule_date = getdate(end_date) schedule_list.append(schedule_date) @@ -280,30 +280,27 @@ class MaintenanceSchedule(TransactionBase): def on_trash(self): delete_events(self.doctype, self.name) - @frappe.whitelist() def get_pending_data(self,data_type,s_date = None, item_name = None): if data_type == "date": dates = "" - for i in self.schedules: - if i.item_name == item_name and i.completion_status == "Pending": - dates = dates + "\n" + formatdate(i.scheduled_date, "dd-MM-yyyy") + for schedule in self.schedules: + if schedule.item_name == item_name and schedule.completion_status == "Pending": + dates = dates + "\n" + formatdate(schedule.scheduled_date, "dd-MM-yyyy") return dates elif data_type == "items": items = "" - for i in self.items: - for s in self.schedules: - if i.item_name == s.item_name and s.completion_status == "Pending": - items = items + "\n" + i.item_name + for item in self.items: + for schedule in self.schedules: + if item.item_name == schedule.item_name and schedule.completion_status == "Pending": + items = items + "\n" + item.item_name break return items elif data_type == "id": - for s in self.schedules: - if s.item_name == item_name and s_date == formatdate(s.scheduled_date,"dd-mm-yyyy"): - return s.name + for schedule in self.schedules: + if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date,"dd-mm-yyyy"): + return schedule.name - - @frappe.whitelist() def update_serial_nos(s_id): serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') @@ -314,7 +311,7 @@ def update_serial_nos(s_id): return False @frappe.whitelist() -def make_maintenance_visit(source_name, target_doc=None,item_name=None,s_id=None): +def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): from frappe.model.mapper import get_mapped_doc def update_status(source, target, parent): diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 403d1ab4cc..d6105c657e 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -15,16 +15,13 @@ frappe.ui.form.on('Maintenance Visit', { 'name': ["in", serial_nos] } }; - } else { - return { filters: { 'item_code': item.item_code } }; } - }); }, setup: function (frm) { @@ -35,18 +32,16 @@ frappe.ui.form.on('Maintenance Visit', { onload: function (frm, cdt, cdn) { let item = locals[cdt][cdn]; if (frm.maintenance_type == 'Scheduled') { - - let s_id = item.purposes[0].prevdoc_detail_docname; + let schedule_id = item.purposes[0].prevdoc_detail_docname; frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", args: { - s_id: s_id + s_id: schedule_id }, callback: function (r) { serial_nos = r.message; } }); - } if (!frm.doc.status) { From 0169cd845ac9d6d00eba92b61195650a88fa05d0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 20 May 2021 14:20:50 +0530 Subject: [PATCH 023/159] fix: sider --- .../maintenance_schedule.js | 122 +++++++++--------- .../maintenance_schedule.py | 4 +- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 075bd40660..1e5773c8bc 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -71,65 +71,69 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let options = ""; me.frm.call('get_pending_data', {data_type: "items"}).then(r =>{ - options = r.message + options = r.message; - let schedule_id = ""; - let d = new frappe.ui.Dialog({ - title: __("Enter Visit Details"), - fields: [{ - fieldtype: "Select", - fieldname: "item_name", - label: __("Item Name"), - options: options, - reqd: 1, - onchange: function () { - let field = d.get_field("scheduled_date"); - me.frm.call('get_pending_data',{item_name:this.value,data_type:"date"}).then(r =>{ - field.df.options = r.message; - field.refresh(); - }) - } - }, - { - label: __('Scheduled Date'), - fieldname: 'scheduled_date', - fieldtype: 'Select', - options: "", - reqd: 1, - onchange: function () { - let field = d.get_field('item_name'); - me.frm.call( - 'get_pending_data', - { - item_name: field.value, - s_date: this.value, - data_type: "id" - }).then(r =>{ - schedule_id = r.message; - }) - } - }, - ], - primary_action_label: 'Create Visit', - primary_action(values) { - frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", - args: { - item_name: values.item_name, - s_id: schedule_id, - source_name: me.frm.doc.name, - }, - callback: function (r) { - if (!r.exc) { - frappe.model.sync(r.message); - frappe.set_route("Form", r.message.doctype, r.message.name); - } + let schedule_id = ""; + let d = new frappe.ui.Dialog({ + title: __("Enter Visit Details"), + fields: [{ + fieldtype: "Select", + fieldname: "item_name", + label: __("Item Name"), + options: options, + reqd: 1, + onchange: function () { + let field = d.get_field("scheduled_date"); + me.frm.call('get_pending_data', + { + item_name: this.value, + data_type: "date" + }).then(r => { + field.df.options = r.message; + field.refresh(); + }); } - }); - d.hide(); - } - }); - d.show(); + }, + { + label: __('Scheduled Date'), + fieldname: 'scheduled_date', + fieldtype: 'Select', + options: "", + reqd: 1, + onchange: function () { + let field = d.get_field('item_name'); + me.frm.call( + 'get_pending_data', + { + item_name: field.value, + s_date: this.value, + data_type: "id" + }).then(r =>{ + schedule_id = r.message; + }); + } + }, + ], + primary_action_label: 'Create Visit', + primary_action(values) { + frappe.call({ + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", + args: { + item_name: values.item_name, + s_id: schedule_id, + source_name: me.frm.doc.name, + }, + callback: function (r) { + if (!r.exc) { + frappe.model.sync(r.message); + frappe.set_route("Form", r.message.doctype, r.message.name); + } + } + }); + d.hide(); + } + }); + d.show(); }); }, __('Create')); } @@ -154,9 +158,9 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ set_no_of_visits: function (doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); - + let me = this; if (item.start_date && item.periodicity) { - me.frm.call('validate_end_date_visits') + me.frm.call('validate_end_date_visits'); } }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 0bc2e87a6c..5d573c5524 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -58,7 +58,7 @@ class MaintenanceSchedule(TransactionBase): if not item.no_of_visits or item.no_of_visits == 0: item.end_date = add_days(item.start_date, days_in_period[item.periodicity]) - diff = date_diff(item.end_date, item.start_date ) + 1 + diff = date_diff(item.end_date, item.start_date) + 1 item.no_of_visits = cint(diff / days_in_period[item.periodicity]) elif item.no_of_visits > no_of_visits: @@ -93,8 +93,8 @@ class MaintenanceSchedule(TransactionBase): frappe.msgprint( _("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format( self.owner, "
" + "
".join(no_email_sp) - ) ) + ) scheduled_date = frappe.db.sql("""select scheduled_date from `tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and From f0e6a169101fe92994d93e1e6f91bee40f8baf9c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 20 May 2021 19:29:12 +0530 Subject: [PATCH 024/159] test: updated test for generated schedule dates --- .../test_maintenance_schedule.py | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 834c05476e..58ee964fb5 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -2,7 +2,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals -from frappe.utils.data import add_days, today +from frappe.utils.data import add_days, today, formatdate from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import make_maintenance_visit import frappe @@ -27,6 +27,7 @@ class TestMaintenanceSchedule(unittest.TestCase): ms = make_maintenance_schedule() ms.save() i = ms.items[0] + expected_dates = [] expected_end_date = add_days(i.start_date, i.no_of_visits * 7) self.assertEqual(i.end_date, expected_end_date) @@ -39,36 +40,39 @@ class TestMaintenanceSchedule(unittest.TestCase): items = items.split('\n') items.pop(0) expected_items = ['_Test Item'] - self.assertTrue(items,expected_items) + self.assertTrue(items, expected_items) - dates = ms.get_pending_data(data_type = "date",item_name = i.item_name) + # "dates" contains all generated schedule dates + dates = ms.get_pending_data(data_type = "date", item_name = i.item_name) dates = dates.split('\n') dates.pop(0) - expected_dates = ['07-05-2021','14-05-2021'] - self.assertEqual(dates,expected_dates) + expected_dates.append(formatdate(add_days(i.start_date, 7), "dd-MM-yyyy")) + expected_dates.append(formatdate(add_days(i.start_date, 14), "dd-MM-yyyy")) + + # test for generated schedule dates + self.assertEqual(dates, expected_dates) - ms.submit() - s_id = ms.get_pending_data(data_type = "id",item_name = i.item_name, s_date = "14-05-2021") + s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1]) test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) visit = frappe.new_doc('Maintenance Visit') visit = test visit.completion_status = "Partially Completed" - visit.set('purposes',[{ - 'item_code':i.item_code, - 'description':"test", - 'work_done':"test", - 'prevdoc_docname':ms.name, - 'prevdoc_doctype':ms.doctype, - 'prevdoc_detail_docname':s_id + visit.set('purposes', [{ + 'item_code': i.item_code, + 'description': "test", + 'work_done': "test", + 'prevdoc_docname' :ms.name, + 'prevdoc_doctype': ms.doctype, + 'prevdoc_detail_docname': s_id }]) visit.submit() - ms = frappe.get_doc('Maintenance Schedule',ms.name) - self.assertTrue(ms.schedules[1].completion_status,"Partially Completed") + ms = frappe.get_doc('Maintenance Schedule', ms.name) + + #checks if visit status is back updated in schedule + self.assertTrue(ms.schedules[1].completion_status, "Partially Completed") - - def get_events(ms): return frappe.get_all("Event Participants", filters={ "reference_doctype": ms.doctype, @@ -76,7 +80,6 @@ def get_events(ms): "parenttype": "Event" }) - def make_maintenance_schedule(): ms = frappe.new_doc("Maintenance Schedule") ms.company = "_Test Company" From 330353a5ced3d40dc2e699a4481234de205b1fd7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:07:45 +0530 Subject: [PATCH 025/159] refactor: use frappe.throw instread of recreating _msgprint was basically duplicating behvaiour of frappe.throw --- erpnext/stock/doctype/item/item.py | 25 ++++++------------- .../stock_reconciliation.py | 6 ++--- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index dbac79465e..174c87b48d 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1068,42 +1068,31 @@ def get_timeline_data(doctype, name): return out -def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1): +def validate_end_of_life(item_code, end_of_life=None, disabled=None): if (not end_of_life) or (disabled is None): end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"]) if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date(): - msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life)) - _msgprint(msg, verbose) + frappe.throw(_("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))) if disabled: - _msgprint(_("Item {0} is disabled").format(item_code), verbose) + frappe.throw(_("Item {0} is disabled").format(item_code)) -def validate_is_stock_item(item_code, is_stock_item=None, verbose=1): +def validate_is_stock_item(item_code, is_stock_item=None): if not is_stock_item: is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item") if is_stock_item != 1: - msg = _("Item {0} is not a stock Item").format(item_code) - - _msgprint(msg, verbose) + frappe.throw(_("Item {0} is not a stock Item").format(item_code)) -def validate_cancelled_item(item_code, docstatus=None, verbose=1): +def validate_cancelled_item(item_code, docstatus=None): if docstatus is None: docstatus = frappe.db.get_value("Item", item_code, "docstatus") if docstatus == 2: - msg = _("Item {0} is cancelled").format(item_code) - _msgprint(msg, verbose) - -def _msgprint(msg, verbose): - if verbose: - msgprint(msg, raise_exception=True) - else: - raise frappe.ValidationError(msg) - + frappe.throw(_("Item {0} is cancelled").format(item_code)) def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): """returns last purchase details in stock uom""" diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 7e216d6181..96b1cadaaf 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -167,8 +167,8 @@ class StockReconciliation(StockController): item = frappe.get_doc("Item", item_code) # end of life and stock item - validate_end_of_life(item_code, item.end_of_life, item.disabled, verbose=0) - validate_is_stock_item(item_code, item.is_stock_item, verbose=0) + validate_end_of_life(item_code, item.end_of_life, item.disabled) + validate_is_stock_item(item_code, item.is_stock_item) # item should not be serialized if item.has_serial_no and not row.serial_no and not item.serial_no_series: @@ -179,7 +179,7 @@ class StockReconciliation(StockController): raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code)) # docstatus should be < 2 - validate_cancelled_item(item_code, item.docstatus, verbose=0) + validate_cancelled_item(item_code, item.docstatus) except Exception as e: self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e)) From 4a2dbd4885777e435282df7afe8631f157f7a0a8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:11:30 +0530 Subject: [PATCH 026/159] refactor: cleanup get_timeline_data, remove py2 --- erpnext/stock/doctype/item/item.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 174c87b48d..61d7e56d13 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -12,7 +12,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError, copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes) from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for) from frappe import _, msgprint -from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate, +from frappe.utils import (cint, cstr, flt, formatdate, getdate, now_datetime, random_string, strip, get_link_to_form, nowtime) from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ @@ -21,8 +21,6 @@ from frappe.website.doctype.website_slideshow.website_slideshow import \ from frappe.website.render import clear_cache from frappe.website.website_generator import WebsiteGenerator -from six import iteritems - class DuplicateReorderRows(frappe.ValidationError): pass @@ -1054,18 +1052,15 @@ def make_item_price(item, price_list_name, item_price): }).insert() def get_timeline_data(doctype, name): - '''returns timeline data based on stock ledger entry''' - out = {} - items = dict(frappe.db.sql('''select posting_date, count(*) - from `tabStock Ledger Entry` where item_code=%s - and posting_date > date_sub(curdate(), interval 1 year) - group by posting_date''', name)) + """get timeline data based on Stock Ledger Entry. This is displayed as heatmap on the item page.""" - for date, count in iteritems(items): - timestamp = get_timestamp(date) - out.update({timestamp: count}) + items = frappe.db.sql("""select unix_timestamp(posting_date), count(*) + from `tabStock Ledger Entry` + where item_code=%s and posting_date > date_sub(curdate(), interval 1 year) + group by posting_date""", name) + + return dict(items) - return out def validate_end_of_life(item_code, end_of_life=None, disabled=None): From ad58a8164aeabfe0c87c54052ec5ba3db4c1ca56 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:58:22 +0530 Subject: [PATCH 027/159] refactor: code cleanup minor fixes for improving code quality --- erpnext/stock/doctype/item/item.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 61d7e56d13..0b92e27152 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -201,7 +201,7 @@ class Item(WebsiteGenerator): def make_route(self): if not self.route: return cstr(frappe.db.get_value('Item Group', self.item_group, - 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5)) + 'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5)) def validate_website_image(self): if frappe.flags.in_import: @@ -256,7 +256,6 @@ class Item(WebsiteGenerator): "attached_to_name": self.name }) except frappe.DoesNotExistError: - pass # cleanup frappe.local.message_log.pop() From 0b4858d8e5d84723d82544721784d60e8541e3c2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 22:59:52 +0530 Subject: [PATCH 028/159] refactor: eliminate unnecessary loop, container casts --- erpnext/stock/doctype/item/item.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 0b92e27152..c41dd67727 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -539,10 +539,7 @@ class Item(WebsiteGenerator): def fill_customer_code(self): """ Append all the customer codes and insert into "customer_code" field of item table """ - cust_code = [] - for d in self.get('customer_items'): - cust_code.append(d.ref_code) - self.customer_code = ','.join(cust_code) + self.customer_code = ','.join(d.ref_code for d in self.get("customer_items", [])) def check_item_tax(self): """Check whether Tax Rate is not entered twice for same Tax Type""" @@ -755,7 +752,7 @@ class Item(WebsiteGenerator): template_item.save() def validate_item_defaults(self): - companies = list(set([row.company for row in self.item_defaults])) + companies = {row.company for row in self.item_defaults} if len(companies) != len(self.item_defaults): frappe.throw(_("Cannot set multiple Item Defaults for a company.")) From 83e6e2e68aec4ef9b6095652a83d19e44bf90e31 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:01:50 +0530 Subject: [PATCH 029/159] refactor: add guard clause for readability --- erpnext/stock/doctype/item/item.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index c41dd67727..b665eb8e46 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -953,20 +953,22 @@ class Item(WebsiteGenerator): d.variant_of = self.variant_of def cant_change(self): - if not self.get("__islocal"): - fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no") + if self.get("__islocal"): + return - values = frappe.db.get_value("Item", self.name, fields, as_dict=True) - if not values.get('valuation_method') and self.get('valuation_method'): - values['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO" + fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no") - if values: - for field in fields: - if cstr(self.get(field)) != cstr(values.get(field)): - if not self.check_if_linked_document_exists(field): - break # no linked document, allowed - else: - frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) + values = frappe.db.get_value("Item", self.name, fields, as_dict=True) + if not values.get('valuation_method') and self.get('valuation_method'): + values['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO" + + if values: + for field in fields: + if cstr(self.get(field)) != cstr(values.get(field)): + if not self.check_if_linked_document_exists(field): + break # no linked document, allowed + else: + frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) def check_if_linked_document_exists(self, field): linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item", From 931c886f92c34453f87b54e315b8f3614a10df48 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:06:16 +0530 Subject: [PATCH 030/159] fix: not checking all fields `break` will break out of the loop without checking remaining fields. --- erpnext/stock/doctype/item/item.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index b665eb8e46..d09a4aa0dc 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -965,9 +965,7 @@ class Item(WebsiteGenerator): if values: for field in fields: if cstr(self.get(field)) != cstr(values.get(field)): - if not self.check_if_linked_document_exists(field): - break # no linked document, allowed - else: + if self.check_if_linked_document_exists(field): frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field)))) def check_if_linked_document_exists(self, field): From 4b484d741d81834ad9749e9395b2510397b7ae09 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:10:54 +0530 Subject: [PATCH 031/159] refactor: use is_new() instead of __islocal Interface over implementation. --- erpnext/stock/doctype/item/item.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index d09a4aa0dc..7906923e6f 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -127,7 +127,7 @@ class Item(WebsiteGenerator): self.cant_change() self.update_show_in_website() - if not self.get("__islocal"): + if not self.is_new(): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") self.old_website_item_groups = frappe.db.sql_list("""select item_group from `tabWebsite Item Group` @@ -807,7 +807,7 @@ class Item(WebsiteGenerator): frappe.throw(_("Item has variants.")) def validate_attributes_in_variants(self): - if not self.has_variants or self.get("__islocal"): + if not self.has_variants or self.is_new(): return old_doc = self.get_doc_before_save() @@ -895,7 +895,7 @@ class Item(WebsiteGenerator): frappe.throw(_("Variant Based On cannot be changed")) def validate_uom(self): - if not self.get("__islocal"): + if not self.is_new(): check_stock_uom_with_bin(self.name, self.stock_uom) if self.has_variants: for d in frappe.db.get_all("Item", filters={"variant_of": self.name}): @@ -953,7 +953,7 @@ class Item(WebsiteGenerator): d.variant_of = self.variant_of def cant_change(self): - if self.get("__islocal"): + if self.is_new(): return fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no") From c229ac932288189366ec6dd57f74db6f34248b1f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:15:32 +0530 Subject: [PATCH 032/159] refactor: add guard clause for readability Both functions only execute based on a condition. In such cases condition should immediately exit the function, this is called "guard clause" and helps in readability (less indent, and easy to "exit" when reading the code. --- erpnext/stock/doctype/item/item.py | 94 ++++++++++++++++-------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 7906923e6f..f7856be4ae 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -359,47 +359,49 @@ class Item(WebsiteGenerator): context.update(get_slideshow(self)) def set_attribute_context(self, context): - if self.has_variants: - attribute_values_available = {} - context.attribute_values = {} - context.selected_attributes = {} + if not self.has_variants: + return - # load attributes - for v in context.variants: - v.attributes = frappe.get_all("Item Variant Attribute", - fields=["attribute", "attribute_value"], - filters={"parent": v.name}) - # make a map for easier access in templates - v.attribute_map = frappe._dict({}) - for attr in v.attributes: - v.attribute_map[attr.attribute] = attr.attribute_value + attribute_values_available = {} + context.attribute_values = {} + context.selected_attributes = {} - for attr in v.attributes: - values = attribute_values_available.setdefault(attr.attribute, []) - if attr.attribute_value not in values: - values.append(attr.attribute_value) + # load attributes + for v in context.variants: + v.attributes = frappe.get_all("Item Variant Attribute", + fields=["attribute", "attribute_value"], + filters={"parent": v.name}) + # make a map for easier access in templates + v.attribute_map = frappe._dict({}) + for attr in v.attributes: + v.attribute_map[attr.attribute] = attr.attribute_value - if v.name == context.variant.name: - context.selected_attributes[attr.attribute] = attr.attribute_value + for attr in v.attributes: + values = attribute_values_available.setdefault(attr.attribute, []) + if attr.attribute_value not in values: + values.append(attr.attribute_value) - # filter attributes, order based on attribute table - for attr in self.attributes: - values = context.attribute_values.setdefault(attr.attribute, []) + if v.name == context.variant.name: + context.selected_attributes[attr.attribute] = attr.attribute_value - if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")): - for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt): - values.append(val) + # filter attributes, order based on attribute table + for attr in self.attributes: + values = context.attribute_values.setdefault(attr.attribute, []) - else: - # get list of values defined (for sequence) - for attr_value in frappe.db.get_all("Item Attribute Value", - fields=["attribute_value"], - filters={"parent": attr.attribute}, order_by="idx asc"): + if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")): + for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt): + values.append(val) - if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): - values.append(attr_value.attribute_value) + else: + # get list of values defined (for sequence) + for attr_value in frappe.db.get_all("Item Attribute Value", + fields=["attribute_value"], + filters={"parent": attr.attribute}, order_by="idx asc"): - context.variant_info = json.dumps(context.variants) + if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): + values.append(attr_value.attribute_value) + + context.variant_info = json.dumps(context.variants) def set_disabled_attributes(self, context): """Disable selection options of attribute combinations that do not result in a variant""" @@ -736,20 +738,22 @@ class Item(WebsiteGenerator): def update_template_item(self): """Set Show in Website for Template Item if True for its Variant""" - if self.variant_of: - if self.show_in_website: - self.show_variant_in_website = 1 - self.show_in_website = 0 + if not self.variant_of: + return - if self.show_variant_in_website: - # show template - template_item = frappe.get_doc("Item", self.variant_of) + if self.show_in_website: + self.show_variant_in_website = 1 + self.show_in_website = 0 - if not template_item.show_in_website: - template_item.show_in_website = 1 - template_item.flags.dont_update_variants = True - template_item.flags.ignore_permissions = True - template_item.save() + if self.show_variant_in_website: + # show template + template_item = frappe.get_doc("Item", self.variant_of) + + if not template_item.show_in_website: + template_item.show_in_website = 1 + template_item.flags.dont_update_variants = True + template_item.flags.ignore_permissions = True + template_item.save() def validate_item_defaults(self): companies = {row.company for row in self.item_defaults} From bf1b3b89d1cb07481006a94d78112c110be74f70 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 24 May 2021 17:03:15 +0530 Subject: [PATCH 033/159] refactor: updated conditional visibility of check box --- .../manufacturing/doctype/production_plan/production_plan.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 3041507caf..1c0dde227c 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -322,6 +322,7 @@ }, { "default": "0", + "depends_on": "eval:doc.get_items_from == 'Sales Order'", "fieldname": "combine_items", "fieldtype": "Check", "label": "Consolidate Items" @@ -342,7 +343,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-07 16:56:00.255001", + "modified": "2021-05-24 16:59:03.643211", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", From 44c489223b855f837a279c65c989f30500ea70e8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:43:28 +0530 Subject: [PATCH 034/159] chore: remove py2 compat code --- erpnext/stock/doctype/item/item.py | 4 +--- erpnext/stock/doctype/item/test_item.py | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f7856be4ae..a6f5160b5c 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1,8 +1,6 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import itertools import json import erpnext diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e0b89d8e45..c300132ad0 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -14,7 +14,6 @@ from erpnext.stock.doctype.item.item import get_uom_conv_factor from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details -from six import iteritems test_ignore = ["BOM"] test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand"] @@ -98,7 +97,7 @@ class TestItem(unittest.TestCase): "ignore_pricing_rule": 1 }) - for key, value in iteritems(to_check): + for key, value in to_check.items(): self.assertEqual(value, details.get(key)) def test_item_tax_template(self): @@ -194,7 +193,7 @@ class TestItem(unittest.TestCase): "plc_conversion_rate": 1, "customer": "_Test Customer", }) - for key, value in iteritems(sales_item_check): + for key, value in sales_item_check.items(): self.assertEqual(value, sales_item_details.get(key)) purchase_item_check = { @@ -215,7 +214,7 @@ class TestItem(unittest.TestCase): "plc_conversion_rate": 1, "supplier": "_Test Supplier", }) - for key, value in iteritems(purchase_item_check): + for key, value in purchase_item_check.items(): self.assertEqual(value, purchase_item_details.get(key)) def test_item_attribute_change_after_variant(self): @@ -464,7 +463,7 @@ class TestItem(unittest.TestCase): self.assertEqual(len(matching_barcodes), 1) details = matching_barcodes[0] - for key, value in iteritems(barcode_properties): + for key, value in barcode_properties.items(): self.assertEqual(value, details.get(key)) # Add barcode again - should cause DuplicateEntryError From ccbde0efa07306710676d144fb7faf29635639db Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:52:00 +0530 Subject: [PATCH 035/159] refactor: use enumerate instead of trackign index also removed dead code --- erpnext/stock/doctype/item/item.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a6f5160b5c..9a52fb4fc1 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -72,8 +72,6 @@ class Item(WebsiteGenerator): if not self.description: self.description = self.item_name - # if self.is_sales_item and not self.get('is_item_from_hub'): - # self.publish_in_hub = 1 def after_insert(self): '''set opening stock and item price''' @@ -1277,14 +1275,13 @@ def get_item_attribute(parent, attribute_value=''): filters = {'parent': parent, 'attribute_value': ("like", "%%%s%%" % attribute_value)}) def update_variants(variants, template, publish_progress=True): - count=0 - for d in variants: + total = len(variants) + for count, d in enumerate(variants, start=1): variant = frappe.get_doc("Item", d) copy_attributes_to_variant(template, variant) variant.save() - count+=1 if publish_progress: - frappe.publish_progress(count*100/len(variants), title = _("Updating Variants...")) + frappe.publish_progress(count / total * 100, title=_("Updating Variants...")) def on_doctype_update(): # since route is a Text column, it needs a length for indexing From 958d485ba49d47af46324395519d9683cbde4674 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 22 May 2021 23:58:38 +0530 Subject: [PATCH 036/159] refactor: msgprint(raise_exception)->frappe.throw --- erpnext/stock/doctype/item/item.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 9a52fb4fc1..e865bda5c1 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -516,7 +516,7 @@ class Item(WebsiteGenerator): def validate_item_type(self): if self.has_serial_no == 1 and self.is_stock_item == 0 and not self.is_fixed_asset: - msgprint(_("'Has Serial No' can not be 'Yes' for non-stock item"), raise_exception=1) + frappe.throw(_("'Has Serial No' can not be 'Yes' for non-stock item")) if self.has_serial_no == 0 and self.serial_no_series: self.serial_no_series = None @@ -1269,7 +1269,7 @@ def get_uom_conv_factor(uom, stock_uom): @frappe.whitelist() def get_item_attribute(parent, attribute_value=''): if not frappe.has_permission("Item"): - frappe.msgprint(_("No Permission"), raise_exception=1) + frappe.throw(_("No Permission")) return frappe.get_all("Item Attribute Value", fields = ["attribute_value"], filters = {'parent': parent, 'attribute_value': ("like", "%%%s%%" % attribute_value)}) From 297dc5e345b494c6f9cdba12653cdf45721a2af3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 00:17:08 +0530 Subject: [PATCH 037/159] perf: add basic optimisation for uom conversion --- erpnext/stock/doctype/item/item.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index e865bda5c1..2c862dcfb7 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1244,6 +1244,9 @@ def get_item_details(item_code, company=None): @frappe.whitelist() def get_uom_conv_factor(uom, stock_uom): + if uom == stock_uom: + return 1.0 + uoms = [uom, stock_uom] value = "" uom_details = frappe.db.sql("""select to_uom, from_uom, value from `tabUOM Conversion Factor`\ From 0d7f54c6c22578797f1e55eb4c29fbc452c591ce Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 00:30:27 +0530 Subject: [PATCH 038/159] refactor: simplify UOM conversion logic - Remove unnecessary sql query - Remove convoluted matching logic and be explcitiy while querying. - better variable names for understanding matching cases --- erpnext/stock/doctype/item/item.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 2c862dcfb7..ef855c7db5 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1247,27 +1247,22 @@ def get_uom_conv_factor(uom, stock_uom): if uom == stock_uom: return 1.0 - uoms = [uom, stock_uom] - value = "" - uom_details = frappe.db.sql("""select to_uom, from_uom, value from `tabUOM Conversion Factor`\ - where to_uom in ({0}) - """.format(', '.join([frappe.db.escape(i, percent=False) for i in uoms])), as_dict=True) + exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom, "from_uom": uom}, ["value"], as_dict=1) + if exact_match: + return exact_match.value - for d in uom_details: - if d.from_uom == stock_uom and d.to_uom == uom: - value = 1/flt(d.value) - elif d.from_uom == uom and d.to_uom == stock_uom: - value = d.value + inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom, "from_uom": stock_uom}, ["value"], as_dict=1) + if inverse_match: + return 1 / inverse_match.value - if not value: - uom_stock = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom}, ["from_uom", "value"], as_dict=1) - uom_row = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom}, ["from_uom", "value"], as_dict=1) + # This attempts to try and get conversion from intermediate UOM. E.g. mg <=> g <=> kg + uom_stock = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom}, ["from_uom", "value"], as_dict=1) + uom_row = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom}, ["from_uom", "value"], as_dict=1) - if uom_stock and uom_row: - if uom_stock.from_uom == uom_row.from_uom: - value = flt(uom_stock.value) * 1/flt(uom_row.value) + if uom_stock and uom_row: + if uom_stock.from_uom == uom_row.from_uom: + return flt(uom_stock.value) * 1/flt(uom_row.value) - return value @frappe.whitelist() def get_item_attribute(parent, attribute_value=''): From 019be66b7b1d137e686ca9b8189f638abdd5f47d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 01:12:47 +0530 Subject: [PATCH 039/159] fix: consider all UOMs for intermediate conversion - Using `get_value` will restrict intermediate UOM to first UOM that is found. - A self join is required to truly capture the required behaviour. - Add explanation and examples. --- erpnext/stock/doctype/item/item.py | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index ef855c7db5..a5bc492422 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1244,24 +1244,40 @@ def get_item_details(item_code, company=None): @frappe.whitelist() def get_uom_conv_factor(uom, stock_uom): + """ Get UOM conversion factor from uom to stock_uom + e.g. uom = "Kg", stock_uom = "Gram" then returns 1000.0 + """ if uom == stock_uom: return 1.0 - exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom, "from_uom": uom}, ["value"], as_dict=1) + from_uom, to_uom = uom, stock_uom # renaming for readability + + exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1) if exact_match: return exact_match.value - inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom, "from_uom": stock_uom}, ["value"], as_dict=1) + inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": from_uom, "from_uom": to_uom}, ["value"], as_dict=1) if inverse_match: return 1 / inverse_match.value - # This attempts to try and get conversion from intermediate UOM. E.g. mg <=> g <=> kg - uom_stock = frappe.db.get_value("UOM Conversion Factor", {"to_uom": stock_uom}, ["from_uom", "value"], as_dict=1) - uom_row = frappe.db.get_value("UOM Conversion Factor", {"to_uom": uom}, ["from_uom", "value"], as_dict=1) + # This attempts to try and get conversion from intermediate UOM. + # case: + # g -> mg = 1000 + # g -> kg = 0.001 + # therefore kg -> mg = 1000 / 0.001 = 1,000,000 + intermediate_match = frappe.db.sql(""" + select (first.value / second.value) as value + from `tabUOM Conversion Factor` first + join `tabUOM Conversion Factor` second + on first.from_uom = second.from_uom + where + first.to_uom = %(to_uom)s + and second.to_uom = %(from_uom)s + limit 1 + """, {"to_uom": to_uom, "from_uom": from_uom}, as_dict=1) - if uom_stock and uom_row: - if uom_stock.from_uom == uom_row.from_uom: - return flt(uom_stock.value) * 1/flt(uom_row.value) + if intermediate_match: + return intermediate_match[0].value @frappe.whitelist() From b9fa12d5721dddd00291fca1e9ef157527fb5905 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 12:26:01 +0530 Subject: [PATCH 040/159] test: add tests for uom conversion function --- erpnext/stock/doctype/item/test_item.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index c300132ad0..2366f06f6d 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -374,6 +374,14 @@ class TestItem(unittest.TestCase): self.assertEqual(item_doc.uoms[1].uom, "Kg") self.assertEqual(item_doc.uoms[1].conversion_factor, 1000) + def test_uom_conv_intermediate(self): + factor = get_uom_conv_factor("Pound", "Gram") + self.assertAlmostEqual(factor, 453.592, 3) + + def test_uom_conv_base_case(self): + factor = get_uom_conv_factor("m", "m") + self.assertEqual(factor, 1.0) + def test_item_variant_by_manufacturer(self): fields = [{'field_name': 'description'}, {'field_name': 'variant_based_on'}] set_item_variant_settings(fields) From f5a937bc45e2fe8dd4a18a3b804a86e3caa84cad Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 12:38:15 +0530 Subject: [PATCH 041/159] test: check index creation on item table --- erpnext/stock/doctype/item/test_item.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 2366f06f6d..d9d1e5a44d 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -487,6 +487,20 @@ class TestItem(unittest.TestCase): new_barcode.barcode_type = 'EAN' self.assertRaises(InvalidBarcode, item_doc.save) + def test_index_creation(self): + "check if index is getting created in db" + from erpnext.stock.doctype.item.item import on_doctype_update + on_doctype_update() + + indices = frappe.db.sql("show index from tabItem", as_dict=1) + expected_columns = {"item_code", "item_name", "item_group", "route"} + for index in indices: + expected_columns.discard(index.get("Column_name")) + + if expected_columns: + self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") + + def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') doc.set('fields', fields) From eb177328767c940857f46ca2345e972e9915eda2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 13:12:16 +0530 Subject: [PATCH 042/159] test: add test for item attribute completion --- erpnext/stock/doctype/item/item.py | 5 +++-- erpnext/stock/doctype/item/test_item.py | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a5bc492422..ec46f60f2b 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1281,12 +1281,13 @@ def get_uom_conv_factor(uom, stock_uom): @frappe.whitelist() -def get_item_attribute(parent, attribute_value=''): +def get_item_attribute(parent, attribute_value=""): + """Used for providing auto-completions in child table.""" if not frappe.has_permission("Item"): frappe.throw(_("No Permission")) return frappe.get_all("Item Attribute Value", fields = ["attribute_value"], - filters = {'parent': parent, 'attribute_value': ("like", "%%%s%%" % attribute_value)}) + filters = {'parent': parent, 'attribute_value': ("like", f"%{attribute_value}%")}) def update_variants(variants, template, publish_progress=True): total = len(variants) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index d9d1e5a44d..7cd6050c02 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -10,13 +10,13 @@ from frappe.test_runner import make_test_objects from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError, InvalidItemAttributeValueError, get_variant) from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode -from erpnext.stock.doctype.item.item import get_uom_conv_factor +from erpnext.stock.doctype.item.item import get_uom_conv_factor, get_item_attribute from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details test_ignore = ["BOM"] -test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand"] +test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] def make_item(item_code, properties=None): if frappe.db.exists("Item", item_code): @@ -500,6 +500,21 @@ class TestItem(unittest.TestCase): if expected_columns: self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") + def test_attribute_completions(self): + expected_attrs = [{'attribute_value': 'Small'}, + {'attribute_value': 'Extra Small'}, + {'attribute_value': 'Extra Large'}, + {'attribute_value': 'Large'}, + {'attribute_value': '2XL'}, + {'attribute_value': 'Medium'}] + + attrs = get_item_attribute("Test Size") + self.assertEqual(attrs, expected_attrs) + + attrs = get_item_attribute("Test Size", attribute_value="extra") + self.assertEqual(attrs, [{'attribute_value': 'Extra Small'}, {'attribute_value': 'Extra Large'}]) + + def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') From a11a8e8ab2e30eaa9f3b1cd80c27dc9ad8f13aeb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 13:19:22 +0530 Subject: [PATCH 043/159] chore: add blame ignore file --- .git-blame-ignore-revs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..be425ec2d9 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,12 @@ +# Since version 2.23 (released in August 2019), git-blame has a feature +# to ignore or bypass certain commits. +# +# This file contains a list of commits that are not likely what you +# are looking for in a blame, such as mass reformatting or renaming. +# You can set this file as a default ignore file for blame by running +# the following command. +# +# $ git config blame.ignoreRevsFile .git-blame-ignore-revs + +# This commit just changes spaces to tabs for indentation in some files +5f473611bd6ed57703716244a054d3fb5ba9cd23 From 4e360f805f5cb4f7ed500316aa97ca7e52b2f9bf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 14:48:03 +0530 Subject: [PATCH 044/159] test: hoist defaults to function signature --- erpnext/stock/doctype/item/test_item.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 7cd6050c02..9694927914 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -530,23 +530,24 @@ 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, company=None): +def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC", + is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0, + company="_Test Company"): if not frappe.db.exists("Item", item_code): item = frappe.new_doc("Item") item.item_code = item_code item.item_name = item_code item.description = item_code item.item_group = "All Item Groups" - item.is_stock_item = is_stock_item or 1 - item.opening_stock = opening_stock or 0 - item.valuation_rate = valuation_rate or 0.0 + item.is_stock_item = is_stock_item + item.opening_stock = opening_stock + item.valuation_rate = valuation_rate item.is_purchase_item = is_purchase_item item.is_customer_provided_item = is_customer_provided_item item.customer = customer or '' item.append("item_defaults", { - "default_warehouse": warehouse or '_Test Warehouse - _TC', - "company": company or "_Test Company" + "default_warehouse": warehouse, + "company": company }) item.save() else: From fc54cf68ac33b213dd44821c10e01f84a6c4727a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 15:04:41 +0530 Subject: [PATCH 045/159] test: add tests for checking stock_uom with bin --- erpnext/stock/doctype/item/test_item.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 9694927914..8df12a3f16 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -514,6 +514,35 @@ class TestItem(unittest.TestCase): attrs = get_item_attribute("Test Size", attribute_value="extra") self.assertEqual(attrs, [{'attribute_value': 'Extra Small'}, {'attribute_value': 'Extra Large'}]) + def test_check_stock_uom_with_bin(self): + # this item has opening stock and stock_uom set in test_records. + item = frappe.get_doc("Item", "_Test Item") + item.stock_uom = "Gram" + self.assertRaises(frappe.ValidationError, item.save) + + def test_check_stock_uom_with_bin_no_sle(self): + from erpnext.stock.stock_balance import update_bin_qty + item = create_item("_Item with bin qty") + item.stock_uom = "Gram" + item.save() + + update_bin_qty(item.item_code, "_Test Warehouse - _TC", { + "reserved_qty": 10 + }) + + item.stock_uom = "Kilometer" + self.assertRaises(frappe.ValidationError, item.save) + + update_bin_qty(item.item_code, "_Test Warehouse - _TC", { + "reserved_qty": 0 + }) + + item.load_from_db() + item.stock_uom = "Kilometer" + try: + item.save() + except frappe.ValidationError as e: + self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}") def set_item_variant_settings(fields): From 57266a7343edd1fb963d20db28593bed3f80ae50 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 14:21:24 +0530 Subject: [PATCH 046/159] refactor: check_stock_uom_with_bin --- erpnext/stock/doctype/item/item.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index ec46f60f2b..a0bd49543e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -1183,27 +1183,25 @@ def check_stock_uom_with_bin(item, stock_uom): if stock_uom == frappe.db.get_value("Item", item, "stock_uom"): return - matched = True ref_uom = frappe.db.get_value("Stock Ledger Entry", {"item_code": item}, "stock_uom") if ref_uom: if cstr(ref_uom) != cstr(stock_uom): - matched = False - else: - bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1) - for bin in bin_list: - if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 - or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom): - matched = False - break + frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item)) - if matched and bin_list: - frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item)) + bin_list = frappe.db.sql(""" + select * from tabBin where item_code = %s + and (reserved_qty > 0 or ordered_qty > 0 or indented_qty > 0 or planned_qty > 0) + and stock_uom != %s + """, (item, stock_uom), as_dict=1) + + if bin_list: + frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You need to either cancel the linked documents or create a new Item.").format(item)) + + # No SLE or documents against item. Bin UOM can be changed safely. + frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item)) - if not matched: - frappe.throw( - _("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item)) def get_item_defaults(item_code, company): item = frappe.get_cached_doc('Item', item_code) From e971b4592e3bb1894294f561a522f2b06336908b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 15:33:06 +0530 Subject: [PATCH 047/159] test: add test for is_stock_item --- erpnext/stock/doctype/item/test_item.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 8df12a3f16..d9c77efc24 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -10,7 +10,8 @@ from frappe.test_runner import make_test_objects from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError, InvalidItemAttributeValueError, get_variant) from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode -from erpnext.stock.doctype.item.item import get_uom_conv_factor, get_item_attribute +from erpnext.stock.doctype.item.item import (get_uom_conv_factor, get_item_attribute, + validate_is_stock_item) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details @@ -544,6 +545,13 @@ class TestItem(unittest.TestCase): except frappe.ValidationError as e: self.fail(f"UoM change not allowed even though no SLE / BIN with positive qty exists: {e}") + def test_validate_stock_item(self): + self.assertRaises(frappe.ValidationError, validate_is_stock_item, "_Test Non Stock Item") + + try: + validate_is_stock_item("_Test Item") + except frappe.ValidationError as e: + self.fail(f"stock item considered non-stock item: {e}") def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') From 42e057d079c1807393d376d347762e97100b6883 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 15:45:25 +0530 Subject: [PATCH 048/159] test: add test for get_timeline_data in item --- erpnext/stock/doctype/item/test_item.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index d9c77efc24..234a9132c2 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -11,7 +11,7 @@ from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsE InvalidItemAttributeValueError, get_variant) from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode from erpnext.stock.doctype.item.item import (get_uom_conv_factor, get_item_attribute, - validate_is_stock_item) + validate_is_stock_item, get_timeline_data) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details @@ -488,6 +488,20 @@ class TestItem(unittest.TestCase): new_barcode.barcode_type = 'EAN' self.assertRaises(InvalidBarcode, item_doc.save) + def test_heatmap_data(self): + import time + data = get_timeline_data("Item", "_Test Item") + self.assertTrue(isinstance(data, dict)) + + now = time.time() + one_year_ago = now - 366 * 24 * 60 * 60 + + for timestamp, count in data.items(): + self.assertIsInstance(timestamp, int) + self.assertTrue(one_year_ago <= timestamp <= now) + self.assertIsInstance(count, int) + self.assertTrue(count >= 0) + def test_index_creation(self): "check if index is getting created in db" from erpnext.stock.doctype.item.item import on_doctype_update From 76dd6e904682a1bec1ff21d66c45e164fd26a47b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 16:19:48 +0530 Subject: [PATCH 049/159] test: contextmanager to change settings --- erpnext/tests/utils.py | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 16ecd5180b..11eb6afc1f 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -1,7 +1,8 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +import copy +from contextlib import contextmanager import frappe @@ -41,3 +42,38 @@ def create_test_contact_and_address(): contact.add_email("test_contact_customer@example.com", is_primary=True) contact.add_phone("+91 0000000000", is_primary_phone=True) contact.insert() + + +@contextmanager +def change_settings(doctype, settings_dict): + """ A context manager to ensure that settings are changed before running + function and restored after running it regardless of exceptions occured. + This is useful in tests where you want to make changes in a function but + don't retain those changes. + import and use as decorator to cover full function or using `with` statement. + + example: + @change_settings("Stock Settings", {"item_naming_by": "Naming Series"}) + def test_case(self): + ... + """ + + try: + settings = frappe.get_doc(doctype) + # remember setting + previous_settings = copy.deepcopy(settings_dict) + for key in previous_settings: + previous_settings[key] = getattr(settings, key) + + # change setting + for key, value in settings_dict.items(): + setattr(settings, key, value) + settings.save() + yield # yield control to calling function + + finally: + # restore settings + settings = frappe.get_doc(doctype) + for key, value in previous_settings.items(): + setattr(settings, key, value) + settings.save() From c15fef571fda7fa6bf2ae7f43380b29098775d87 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 16:26:37 +0530 Subject: [PATCH 050/159] test: item naming series behaviour --- erpnext/stock/doctype/item/test_item.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 234a9132c2..9adacdfb78 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -14,6 +14,7 @@ from erpnext.stock.doctype.item.item import (get_uom_conv_factor, get_item_attri validate_is_stock_item, get_timeline_data) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details +from erpnext.tests.utils import change_settings test_ignore = ["BOM"] @@ -567,6 +568,13 @@ class TestItem(unittest.TestCase): except frappe.ValidationError as e: self.fail(f"stock item considered non-stock item: {e}") + @change_settings("Stock Settings", {"item_naming_by": "Naming Series"}) + def test_autoname_series(self): + item = frappe.new_doc("Item") + item.item_group = "All Item Groups" + item.save() # if item code saved without item_code then series worked + + def set_item_variant_settings(fields): doc = frappe.get_doc('Item Variant Settings') doc.set('fields', fields) From 3aed662f4690ad6fb5fda680aad4246103eded81 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 16:41:03 +0530 Subject: [PATCH 051/159] chore: translation / semgrep / sider fixes --- erpnext/stock/doctype/item/item.py | 6 +++--- erpnext/stock/doctype/item/test_item.py | 10 +++++----- .../stock_reconciliation/stock_reconciliation.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a0bd49543e..dd815404fa 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -365,8 +365,8 @@ class Item(WebsiteGenerator): # load attributes for v in context.variants: v.attributes = frappe.get_all("Item Variant Attribute", - fields=["attribute", "attribute_value"], - filters={"parent": v.name}) + fields=["attribute", "attribute_value"], + filters={"parent": v.name}) # make a map for easier access in templates v.attribute_map = frappe._dict({}) for attr in v.attributes: @@ -1256,7 +1256,7 @@ def get_uom_conv_factor(uom, stock_uom): inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": from_uom, "from_uom": to_uom}, ["value"], as_dict=1) if inverse_match: - return 1 / inverse_match.value + return 1 / inverse_match.value # This attempts to try and get conversion from intermediate UOM. # case: diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 9adacdfb78..406039dc58 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -518,11 +518,11 @@ class TestItem(unittest.TestCase): def test_attribute_completions(self): expected_attrs = [{'attribute_value': 'Small'}, - {'attribute_value': 'Extra Small'}, - {'attribute_value': 'Extra Large'}, - {'attribute_value': 'Large'}, - {'attribute_value': '2XL'}, - {'attribute_value': 'Medium'}] + {'attribute_value': 'Extra Small'}, + {'attribute_value': 'Extra Large'}, + {'attribute_value': 'Large'}, + {'attribute_value': '2XL'}, + {'attribute_value': 'Medium'}] attrs = get_item_attribute("Test Size") self.assertEqual(attrs, expected_attrs) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 96b1cadaaf..b9f91906c6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -96,7 +96,7 @@ class StockReconciliation(StockController): def validate_data(self): def _get_msg(row_num, msg): - return _("Row # {0}: ").format(row_num+1) + msg + return _("Row # {0}:").format(row_num+1) + " " + msg self.validation_messages = [] item_warehouse_combinations = [] @@ -182,7 +182,7 @@ class StockReconciliation(StockController): validate_cancelled_item(item_code, item.docstatus) except Exception as e: - self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e)) + self.validation_messages.append(_("Row #") + " " + ("%d: " % (row.idx)) + cstr(e)) def update_stock_ledger(self): """ find difference between current and expected entries From 15f8a0fb22addd730b1ebfb43635cfb29f1ddb90 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 18:10:21 +0530 Subject: [PATCH 052/159] test: fix flaky test --- erpnext/stock/doctype/item/test_item.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 406039dc58..c7467a5a0f 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -517,18 +517,15 @@ class TestItem(unittest.TestCase): self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") def test_attribute_completions(self): - expected_attrs = [{'attribute_value': 'Small'}, - {'attribute_value': 'Extra Small'}, - {'attribute_value': 'Extra Large'}, - {'attribute_value': 'Large'}, - {'attribute_value': '2XL'}, - {'attribute_value': 'Medium'}] + expected_attrs = {"Small", "Extra Small", "Extra Large", "Large", "2XL", "Medium"} attrs = get_item_attribute("Test Size") - self.assertEqual(attrs, expected_attrs) + received_attrs = {attr.attribute_value for attr in attrs} + self.assertEqual(received_attrs, expected_attrs) attrs = get_item_attribute("Test Size", attribute_value="extra") - self.assertEqual(attrs, [{'attribute_value': 'Extra Small'}, {'attribute_value': 'Extra Large'}]) + received_attrs = {attr.attribute_value for attr in attrs} + self.assertEqual(received_attrs, {"Extra Small", "Extra Large"}) def test_check_stock_uom_with_bin(self): # this item has opening stock and stock_uom set in test_records. From 3efd411ddb6ddb9b4f655e48d50cd7d8efa62fd6 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Tue, 25 May 2021 16:24:01 +0530 Subject: [PATCH 053/159] fix: Hold status is added in the report --- .../report/issue_summary/issue_summary.js | 1 + .../report/issue_summary/issue_summary.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js index eb0e06cd08..a5e1de627e 100644 --- a/erpnext/support/report/issue_summary/issue_summary.js +++ b/erpnext/support/report/issue_summary/issue_summary.js @@ -42,6 +42,7 @@ frappe.query_reports["Issue Summary"] = { "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, + {label: __('Hold'), value: 'Hold'}, {label: __('Resolved'), value: 'Resolved'}, {label: __('Closed'), value: 'Closed'} ] diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py index 7861e30d25..d93790d593 100644 --- a/erpnext/support/report/issue_summary/issue_summary.py +++ b/erpnext/support/report/issue_summary/issue_summary.py @@ -62,7 +62,7 @@ class IssueSummary(object): 'width': 200 }) - self.statuses = ['Open', 'Replied', 'Resolved', 'Closed'] + self.statuses = ['Open', 'Replied', 'Hold', 'Resolved', 'Closed'] for status in self.statuses: self.columns.append({ 'label': _(status), @@ -265,6 +265,7 @@ class IssueSummary(object): labels = [] open_issues = [] replied_issues = [] + hold_issues = [] resolved_issues = [] closed_issues = [] @@ -277,6 +278,7 @@ class IssueSummary(object): labels.append(entry.get(entity_field)) open_issues.append(entry.get('open')) replied_issues.append(entry.get('replied')) + hold_issues.append(entry.get('hold')) resolved_issues.append(entry.get('resolved')) closed_issues.append(entry.get('closed')) @@ -292,6 +294,10 @@ class IssueSummary(object): 'name': 'Replied', 'values': replied_issues[:30] }, + { + 'name': 'Hold', + 'values': hold_issues[:30] + }, { 'name': 'Resolved', 'values': resolved_issues[:30] @@ -313,12 +319,14 @@ class IssueSummary(object): open_issues = 0 replied = 0 + hold = 0 resolved = 0 closed = 0 for entry in self.data: open_issues += entry.get('open') replied += entry.get('replied') + hold += entry.get('hold') resolved += entry.get('resolved') closed += entry.get('closed') @@ -335,6 +343,12 @@ class IssueSummary(object): 'label': _('Replied'), 'datatype': 'Int', }, + { + 'value': hold, + 'indicator': 'Grey', + 'label': _('Hold'), + 'datatype': 'Int', + }, { 'value': resolved, 'indicator': 'Green', From 73e41c0bd6e7984c468ca5c3feaac0d5920110d2 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Tue, 25 May 2021 18:01:47 +0530 Subject: [PATCH 054/159] refactor: suggested changes --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index a154464a8b..2600790a59 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -383,7 +383,7 @@ class WorkOrder(Document): work_order_qty = 0.0 if plan_reference.item_reference == item_reference: if self.docstatus == 1: - work_order_qty = cint(plan_reference.qty) / total_bundle_qty + work_order_qty = flt(plan_reference.qty) / total_bundle_qty frappe.db.set_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty', work_order_qty) From db5217e48e23a5bfbfa6c8127a99b1fd4fb8feab Mon Sep 17 00:00:00 2001 From: Anuja P Date: Wed, 26 May 2021 11:09:48 +0530 Subject: [PATCH 055/159] fix: renaming hold to on hold --- erpnext/support/doctype/issue/issue.json | 4 ++-- .../report/issue_summary/issue_summary.js | 2 +- .../report/issue_summary/issue_summary.py | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index a43381c5c6..bc29821ee2 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -119,7 +119,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "Open\nReplied\nHold\nResolved\nClosed", + "options": "Open\nReplied\nOn Hold\nResolved\nClosed", "search_index": 1 }, { @@ -410,7 +410,7 @@ "icon": "fa fa-ticket", "idx": 7, "links": [], - "modified": "2020-08-11 18:49:07.574769", + "modified": "2021-05-26 10:49:07.574769", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js index a5e1de627e..a5122d03ad 100644 --- a/erpnext/support/report/issue_summary/issue_summary.js +++ b/erpnext/support/report/issue_summary/issue_summary.js @@ -42,7 +42,7 @@ frappe.query_reports["Issue Summary"] = { "", {label: __('Open'), value: 'Open'}, {label: __('Replied'), value: 'Replied'}, - {label: __('Hold'), value: 'Hold'}, + {label: __('On Hold'), value: 'On Hold'}, {label: __('Resolved'), value: 'Resolved'}, {label: __('Closed'), value: 'Closed'} ] diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py index d93790d593..bba25b8bed 100644 --- a/erpnext/support/report/issue_summary/issue_summary.py +++ b/erpnext/support/report/issue_summary/issue_summary.py @@ -62,7 +62,7 @@ class IssueSummary(object): 'width': 200 }) - self.statuses = ['Open', 'Replied', 'Hold', 'Resolved', 'Closed'] + self.statuses = ['Open', 'Replied', 'On Hold', 'Resolved', 'Closed'] for status in self.statuses: self.columns.append({ 'label': _(status), @@ -265,7 +265,7 @@ class IssueSummary(object): labels = [] open_issues = [] replied_issues = [] - hold_issues = [] + on_hold_issues = [] resolved_issues = [] closed_issues = [] @@ -278,7 +278,7 @@ class IssueSummary(object): labels.append(entry.get(entity_field)) open_issues.append(entry.get('open')) replied_issues.append(entry.get('replied')) - hold_issues.append(entry.get('hold')) + on_hold_issues.append(entry.get('on_hold')) resolved_issues.append(entry.get('resolved')) closed_issues.append(entry.get('closed')) @@ -295,8 +295,8 @@ class IssueSummary(object): 'values': replied_issues[:30] }, { - 'name': 'Hold', - 'values': hold_issues[:30] + 'name': 'On Hold', + 'values': on_hold_issues[:30] }, { 'name': 'Resolved', @@ -319,14 +319,14 @@ class IssueSummary(object): open_issues = 0 replied = 0 - hold = 0 + on_hold = 0 resolved = 0 closed = 0 for entry in self.data: open_issues += entry.get('open') replied += entry.get('replied') - hold += entry.get('hold') + on_hold += entry.get('on_hold') resolved += entry.get('resolved') closed += entry.get('closed') @@ -344,9 +344,9 @@ class IssueSummary(object): 'datatype': 'Int', }, { - 'value': hold, + 'value': on_hold, 'indicator': 'Grey', - 'label': _('Hold'), + 'label': _('On Hold'), 'datatype': 'Int', }, { From 1cdf5a0dbad7eea6111839bfc14e4b59f8970f59 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 14:42:15 +0530 Subject: [PATCH 056/159] fix: add requested changes --- erpnext/controllers/stock_controller.py | 8 +- erpnext/public/js/controllers/transaction.js | 15 +- .../stock/doctype/stock_entry/stock_entry.js | 130 ++++++++++++++++++ 3 files changed, 144 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 6f97743b60..abc966a477 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -508,7 +508,7 @@ def make_quality_inspections(doctype, docname, items): inspections = [] for item in items: - if item.get("sample_size") > item.get("qty"): + if flt(item.get("sample_size")) > flt(item.get("qty")): frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( item_name=item.get("item_name"), sample_size=item.get("sample_size"), @@ -523,14 +523,14 @@ def make_quality_inspections(doctype, docname, items): "reference_name": docname, "item_code": item.get("item_code"), "description": item.get("description"), - "sample_size": item.get("sample_size"), + "sample_size": flt(item.get("sample_size")), "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, "batch_no": item.get("batch_no") }).insert() quality_inspection.save() - inspections.append(quality_inspection) + inspections.append(quality_inspection.name) - return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + return inspections def is_reposting_pending(): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8738957166..95562baa8e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2039,11 +2039,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, freeze: true, callback: function (r) { - if (r.message) { - frappe.msgprint({ - message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), - indicator: "green" - }); + if (r.message.length > 0) { + if (r.message.length === 1) { + frappe.set_route("Form", "Quality Inspection", r.message[0]); + } else { + frappe.route_options = { + "reference_type": me.frm.doc.doctype, + "reference_name": me.frm.doc.name + }; + frappe.set_route("List", "Quality Inspection"); + } } dialog.hide(); } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index de23e769f8..3524c41720 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -115,6 +115,13 @@ frappe.ui.form.on('Stock Entry', { return; } + if (!frm.is_new() && frm.doc.docstatus === 0) { + frm.add_custom_button(__("Quality Inspection(s)"), () => { + frm.trigger("make_quality_inspection"); + }, __("Create")); + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + let quality_inspection_field = frm.get_docfield("items", "quality_inspection"); quality_inspection_field.get_route_options_for_new_doc = function(row) { if (frm.is_new()) return; @@ -142,6 +149,129 @@ frappe.ui.form.on('Stock Entry', { }); }, + make_quality_inspection: function (frm) { + let data = []; + const fields = [ + { + label: "Items", + fieldtype: "Table", + fieldname: "items", + cannot_add_rows: true, + in_place_edit: true, + data: data, + get_data: () => { + return data; + }, + fields: [ + { + fieldtype: "Data", + fieldname: "docname", + hidden: true + }, + { + fieldtype: "Read Only", + fieldname: "item_code", + label: __("Item Code"), + in_list_view: true + }, + { + fieldtype: "Read Only", + fieldname: "item_name", + label: __("Item Name"), + in_list_view: true + }, + { + fieldtype: "Float", + fieldname: "qty", + label: __("Accepted Quantity"), + in_list_view: true, + read_only: true + }, + { + fieldtype: "Float", + fieldname: "sample_size", + label: __("Sample Size"), + reqd: true, + in_list_view: true + }, + { + fieldtype: "Data", + fieldname: "description", + label: __("Description"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "serial_no", + label: __("Serial No"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "batch_no", + label: __("Batch No"), + hidden: true + } + ] + } + ]; + + const dialog = new frappe.ui.Dialog({ + title: __("Select Items for Quality Inspection"), + fields: fields, + primary_action: function () { + const data = dialog.get_values(); + frappe.call({ + method: "erpnext.controllers.stock_controller.make_quality_inspections", + args: { + doctype: frm.doc.doctype, + docname: frm.doc.name, + items: data + }, + freeze: true, + callback: function (r) { + if (r.message.length > 0) { + if (r.message.length === 1) { + frappe.set_route("Form", "Quality Inspection", r.message[0]); + } else { + frappe.route_options = { + "reference_type": frm.doc.doctype, + "reference_name": frm.doc.name + }; + frappe.set_route("List", "Quality Inspection"); + } + } + dialog.hide(); + } + }); + }, + primary_action_label: __("Create") + }); + + frm.doc.items.forEach(item => { + if (!item.quality_inspection) { + let dialog_items = dialog.fields_dict.items; + dialog_items.df.data.push({ + "docname": item.name, + "item_code": item.item_code, + "item_name": item.item_name, + "qty": item.qty, + "description": item.description, + "serial_no": item.serial_no, + "batch_no": item.batch_no + }); + dialog_items.grid.refresh(); + } + }); + + data = dialog.fields_dict.items.df.data; + if (!data.length) { + frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); + } else { + dialog.show(); + } + }, + outgoing_stock_entry: function(frm) { frappe.call({ doc: frm.doc, From 1e3a3b27c6e4f66aa38c9adaf86b5bde39293a00 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 15:18:10 +0530 Subject: [PATCH 057/159] style: semgrep issues --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2c3519b04a..c77ea55775 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -225,7 +225,7 @@ class AccountsController(TransactionBase): def validate_date_with_fiscal_year(self): if self.meta.get_field("fiscal_year"): - date_field = "" + date_field = None if self.meta.get_field("posting_date"): date_field = "posting_date" elif self.meta.get_field("transaction_date"): From 9857d63523d3bffc1377622cbca2ff1c4c0815d0 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 15:41:36 +0530 Subject: [PATCH 058/159] fix: add requested changes --- .../stock/doctype/stock_entry/stock_entry.js | 130 +----------------- 1 file changed, 4 insertions(+), 126 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 3524c41720..93a6fc0e0a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -117,7 +117,8 @@ frappe.ui.form.on('Stock Entry', { if (!frm.is_new() && frm.doc.docstatus === 0) { frm.add_custom_button(__("Quality Inspection(s)"), () => { - frm.trigger("make_quality_inspection"); + let transaction_controller = new erpnext.TransactionController({ frm: frm }); + transaction_controller.make_quality_inspection(); }, __("Create")); frm.page.set_inner_btn_group_as_primary(__('Create')); } @@ -149,129 +150,6 @@ frappe.ui.form.on('Stock Entry', { }); }, - make_quality_inspection: function (frm) { - let data = []; - const fields = [ - { - label: "Items", - fieldtype: "Table", - fieldname: "items", - cannot_add_rows: true, - in_place_edit: true, - data: data, - get_data: () => { - return data; - }, - fields: [ - { - fieldtype: "Data", - fieldname: "docname", - hidden: true - }, - { - fieldtype: "Read Only", - fieldname: "item_code", - label: __("Item Code"), - in_list_view: true - }, - { - fieldtype: "Read Only", - fieldname: "item_name", - label: __("Item Name"), - in_list_view: true - }, - { - fieldtype: "Float", - fieldname: "qty", - label: __("Accepted Quantity"), - in_list_view: true, - read_only: true - }, - { - fieldtype: "Float", - fieldname: "sample_size", - label: __("Sample Size"), - reqd: true, - in_list_view: true - }, - { - fieldtype: "Data", - fieldname: "description", - label: __("Description"), - hidden: true - }, - { - fieldtype: "Data", - fieldname: "serial_no", - label: __("Serial No"), - hidden: true - }, - { - fieldtype: "Data", - fieldname: "batch_no", - label: __("Batch No"), - hidden: true - } - ] - } - ]; - - const dialog = new frappe.ui.Dialog({ - title: __("Select Items for Quality Inspection"), - fields: fields, - primary_action: function () { - const data = dialog.get_values(); - frappe.call({ - method: "erpnext.controllers.stock_controller.make_quality_inspections", - args: { - doctype: frm.doc.doctype, - docname: frm.doc.name, - items: data - }, - freeze: true, - callback: function (r) { - if (r.message.length > 0) { - if (r.message.length === 1) { - frappe.set_route("Form", "Quality Inspection", r.message[0]); - } else { - frappe.route_options = { - "reference_type": frm.doc.doctype, - "reference_name": frm.doc.name - }; - frappe.set_route("List", "Quality Inspection"); - } - } - dialog.hide(); - } - }); - }, - primary_action_label: __("Create") - }); - - frm.doc.items.forEach(item => { - if (!item.quality_inspection) { - let dialog_items = dialog.fields_dict.items; - dialog_items.df.data.push({ - "docname": item.name, - "item_code": item.item_code, - "item_name": item.item_name, - "qty": item.qty, - "description": item.description, - "serial_no": item.serial_no, - "batch_no": item.batch_no - }); - dialog_items.grid.refresh(); - } - }); - - data = dialog.fields_dict.items.df.data; - if (!data.length) { - frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); - } else { - dialog.show(); - } - }, - outgoing_stock_entry: function(frm) { frappe.call({ doc: frm.doc, @@ -285,7 +163,7 @@ frappe.ui.form.on('Stock Entry', { refresh: function(frm) { if(!frm.doc.docstatus) { frm.trigger('validate_purpose_consumption'); - frm.add_custom_button(__('Create Material Request'), function() { + frm.add_custom_button(__('Material Request'), function() { frappe.model.with_doctype('Material Request', function() { var mr = frappe.model.get_new_doc('Material Request'); var items = frm.get_field('items').grid.get_selected_children(); @@ -308,7 +186,7 @@ frappe.ui.form.on('Stock Entry', { }); frappe.set_route('Form', 'Material Request', mr.name); }); - }); + }, __("Create")); } if(frm.doc.items) { From dc513886668aa595bf73ebecd7312935c82284b5 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 26 May 2021 18:28:24 +0530 Subject: [PATCH 059/159] style: fixed indentations and formatting --- .../maintenance_schedule.js | 4 +- .../maintenance_schedule.py | 70 +++++++++---------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index 1e5773c8bc..e2de941963 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -70,7 +70,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ this.frm.add_custom_button(__('Create Maintenance Visit'), function () { let options = ""; - me.frm.call('get_pending_data', {data_type: "items"}).then(r =>{ + me.frm.call('get_pending_data', {data_type: "items"}).then(r => { options = r.message; let schedule_id = ""; @@ -108,7 +108,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ item_name: field.value, s_date: this.value, data_type: "id" - }).then(r =>{ + }).then(r => { schedule_id = r.message; }); } diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 5d573c5524..ea76e91b3f 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -140,7 +140,7 @@ class MaintenanceSchedule(TransactionBase): if employee: holiday_list = get_holiday_list_for_employee(employee) else: - holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list") + holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list") holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list) @@ -161,16 +161,16 @@ class MaintenanceSchedule(TransactionBase): if d.start_date and d.end_date and d.periodicity and d.periodicity!="Random": date_diff = (getdate(d.end_date) - getdate(d.start_date)).days + 1 days_in_period = { - "Weekly": 7, - "Monthly": 30, - "Quarterly": 90, - "Half Yearly": 180, - "Yearly": 365 + "Weekly": 7, + "Monthly": 30, + "Quarterly": 90, + "Half Yearly": 180, + "Yearly": 365 } if date_diff < days_in_period[d.periodicity]: throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}") - .format(d.idx, d.periodicity, days_in_period[d.periodicity])) + .format(d.idx, d.periodicity, days_in_period[d.periodicity])) def validate_maintenance_detail(self): if not self.get('items'): @@ -217,28 +217,28 @@ class MaintenanceSchedule(TransactionBase): def validate_serial_no(self, item_code, serial_nos, amc_start_date): for serial_no in serial_nos: sr_details = frappe.db.get_value("Serial No", serial_no, - ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"], as_dict=1) + ["warranty_expiry_date", "amc_expiry_date", "warehouse", "delivery_date", "item_code"], as_dict=1) if not sr_details: frappe.throw(_("Serial No {0} not found").format(serial_no)) if sr_details.get("item_code") != item_code: frappe.throw(_("Serial No {0} does not belong to Item {1}") - .format(frappe.bold(serial_no), frappe.bold(item_code)), title="Invalid") + .format(frappe.bold(serial_no), frappe.bold(item_code)), title="Invalid") if sr_details.warranty_expiry_date \ - and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date): + and getdate(sr_details.warranty_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under warranty upto {1}") - .format(serial_no, sr_details.warranty_expiry_date)) + .format(serial_no, sr_details.warranty_expiry_date)) if sr_details.amc_expiry_date and getdate(sr_details.amc_expiry_date) >= getdate(amc_start_date): throw(_("Serial No {0} is under maintenance contract upto {1}") - .format(serial_no, sr_details.amc_expiry_date)) + .format(serial_no, sr_details.amc_expiry_date)) if not sr_details.warehouse and sr_details.delivery_date and \ - getdate(sr_details.delivery_date) >= getdate(amc_start_date): + getdate(sr_details.delivery_date) >= getdate(amc_start_date): throw(_("Maintenance start date can not be before delivery date for Serial No {0}") - .format(serial_no)) + .format(serial_no)) def validate_schedule(self): item_lst1 =[] @@ -281,7 +281,7 @@ class MaintenanceSchedule(TransactionBase): delete_events(self.doctype, self.name) @frappe.whitelist() - def get_pending_data(self,data_type,s_date = None, item_name = None): + def get_pending_data(self, data_type, s_date=None, item_name=None): if data_type == "date": dates = "" for schedule in self.schedules: @@ -298,7 +298,7 @@ class MaintenanceSchedule(TransactionBase): return items elif data_type == "id": for schedule in self.schedules: - if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date,"dd-mm-yyyy"): + if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date, "dd-mm-yyyy"): return schedule.name @frappe.whitelist() @@ -324,27 +324,25 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No target.serial_no = '' doclist = get_mapped_doc("Maintenance Schedule", source_name, { - "Maintenance Schedule": { - "doctype": "Maintenance Visit", - "field_map": { - "name": "maintenance_schedule" - }, - "validation": { - "docstatus": ["=", 1] - }, - "postprocess": update_status + "Maintenance Schedule": { + "doctype": "Maintenance Visit", + "field_map": { + "name": "maintenance_schedule" }, - "Maintenance Schedule Item": { - "doctype": "Maintenance Visit Purpose", - "field_map": { - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", - }, - "condition": lambda doc: doc.item_name == item_name, - - "postprocess": update_sid - - } + "validation": { + "docstatus": ["=", 1] + }, + "postprocess": update_status + }, + "Maintenance Schedule Item": { + "doctype": "Maintenance Visit Purpose", + "field_map": { + "parent": "prevdoc_docname", + "parenttype": "prevdoc_doctype", + }, + "condition": lambda doc: doc.item_name == item_name, + "postprocess": update_sid + } }, target_doc) return doclist From 5e4128e70ca2d851b199a1fb1419c01c9d16a5df Mon Sep 17 00:00:00 2001 From: Anuja P Date: Wed, 26 May 2021 20:03:14 +0530 Subject: [PATCH 060/159] fix: patch for existing issues --- erpnext/patches.txt | 1 + .../rename_issue_status_hold_to_on_hold.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1e8ce3c658..3a7aa1bce3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -781,3 +781,4 @@ erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed erpnext.patches.v13_0.update_timesheet_changes erpnext.patches.v13_0.set_training_event_attendance +erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py new file mode 100644 index 0000000000..b466678095 --- /dev/null +++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py @@ -0,0 +1,20 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + if frappe.db.exists('DocType', 'Issue'): + frappe.reload_doc("support", "doctype", "issue") + rename_status() + +def rename_status(): + frappe.db.sql(""" + UPDATE + `tabIssue` + SET + status = 'On Hold' + WHERE + status = 'Hold' + """) \ No newline at end of file From e2059fb9f65a854d2b8ed9f52d51eb40a4c0ad32 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Wed, 26 May 2021 20:07:53 +0530 Subject: [PATCH 061/159] fix: spaces to tabs --- .../v13_0/rename_issue_status_hold_to_on_hold.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py index b466678095..48325fc2d4 100644 --- a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py +++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py @@ -6,15 +6,15 @@ import frappe def execute(): if frappe.db.exists('DocType', 'Issue'): - frappe.reload_doc("support", "doctype", "issue") - rename_status() + frappe.reload_doc("support", "doctype", "issue") + rename_status() def rename_status(): frappe.db.sql(""" UPDATE - `tabIssue` + `tabIssue` SET - status = 'On Hold' - WHERE - status = 'Hold' + status = 'On Hold' + WHERE + status = 'Hold' """) \ No newline at end of file From 45a89a7c6747b7708fa9b7cc77c2691365d81c20 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 27 May 2021 13:49:45 +0530 Subject: [PATCH 062/159] fix(pos): cannot add same item with different rates --- .../page/point_of_sale/pos_controller.js | 34 ++++++++++++------- .../page/point_of_sale/pos_item_cart.js | 25 +++++++++----- .../page/point_of_sale/pos_item_details.js | 28 ++++++++++----- .../page/point_of_sale/pos_item_selector.js | 9 +++-- 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 4f4f1b2240..0921f010e7 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -241,10 +241,8 @@ erpnext.PointOfSale.Controller = class { events: { get_frm: () => this.frm, - cart_item_clicked: (item_code, batch_no, uom) => { - const search_field = batch_no ? 'batch_no' : 'item_code'; - const search_value = batch_no || item_code; - const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom); + cart_item_clicked: (item_code, batch_no, uom, rate) => { + const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate) this.item_details.toggle_item_details_section(item_row); }, @@ -275,18 +273,25 @@ erpnext.PointOfSale.Controller = class { this.cart.toggle_numpad(minimize); }, - form_updated: async (cdt, cdn, fieldname, value) => { + form_updated: (cdt, cdn, fieldname, value) => { const item_row = frappe.model.get_doc(cdt, cdn); if (item_row && item_row[fieldname] != value) { - const { item_code, batch_no, uom } = this.item_details.current_item; + const { item_code, batch_no, uom, rate } = this.item_details.current_item; const event = { field: fieldname, value, - item: { item_code, batch_no, uom } + item: { item_code, batch_no, uom, rate } } return this.on_cart_update(event) } + + return Promise.resolve(); + }, + + highlight_cart_item: (item) => { + const cart_item = this.cart.get_cart_item(item); + this.cart.toggle_item_highlight(cart_item); }, item_field_focused: (fieldname) => { @@ -501,8 +506,8 @@ erpnext.PointOfSale.Controller = class { let item_row = undefined; try { let { field, value, item } = args; - const { item_code, batch_no, serial_no, uom } = item; - item_row = this.get_item_from_frm(item_code, batch_no, uom); + const { item_code, batch_no, serial_no, uom, rate } = item; + item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); const item_selected_from_selector = field === 'qty' && value === "+1" @@ -535,7 +540,7 @@ erpnext.PointOfSale.Controller = class { item_selected_from_selector && (value = flt(value)) - const args = { item_code, batch_no, [field]: value }; + const args = { item_code, batch_no, rate, [field]: value }; if (serial_no) { await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no); @@ -550,9 +555,11 @@ erpnext.PointOfSale.Controller = class { await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); await this.trigger_new_item_events(item_row); - - this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row); + this.update_cart_html(item_row); + + this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row); + this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row); } } catch (error) { @@ -563,12 +570,13 @@ erpnext.PointOfSale.Controller = class { } } - get_item_from_frm(item_code, batch_no, uom) { + get_item_from_frm(item_code, batch_no, uom, rate) { const has_batch_no = batch_no; return this.frm.doc.items.find( i => i.item_code === item_code && (!has_batch_no || (has_batch_no && i.batch_no === batch_no)) && (i.uom === uom) + && (i.rate == rate) ); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 11a63b3d4a..f7db9d4349 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -184,7 +184,8 @@ erpnext.PointOfSale.ItemCart = class { const item_code = unescape($cart_item.attr('data-item-code')); const batch_no = unescape($cart_item.attr('data-batch-no')); const uom = unescape($cart_item.attr('data-uom')); - me.events.cart_item_clicked(item_code, batch_no, uom); + const rate = unescape($cart_item.attr('data-rate')); + me.events.cart_item_clicked(item_code, batch_no, uom, rate); this.numpad_value = ''; }); @@ -520,28 +521,34 @@ erpnext.PointOfSale.ItemCart = class { } } - get_cart_item({ item_code, batch_no, uom }) { + get_cart_item({ item_code, batch_no, uom, rate }) { const batch_attr = `[data-batch-no="${escape(batch_no)}"]`; const item_code_attr = `[data-item-code="${escape(item_code)}"]`; const uom_attr = `[data-uom="${escape(uom)}"]`; + const rate_attr = `[data-rate="${escape(rate)}"]`; const item_selector = batch_no ? - `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`; + `.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`; return this.$cart_items_wrapper.find(item_selector); } + get_item_from_frm(item) { + const doc = this.events.get_frm().doc; + const { item_code, batch_no, uom, rate } = item; + const search_field = batch_no ? 'batch_no' : 'item_code'; + const search_value = batch_no || item_code; + + return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate); + } + update_item_html(item, remove_item) { const $item = this.get_cart_item(item); if (remove_item) { $item && $item.next().remove() && $item.remove(); } else { - const { item_code, batch_no, uom } = item; - const search_field = batch_no ? 'batch_no' : 'item_code'; - const search_value = batch_no || item_code; - const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom); - + const item_row = this.get_item_from_frm(item); this.render_cart_item(item_row, $item); } @@ -559,7 +566,7 @@ erpnext.PointOfSale.ItemCart = class { this.$cart_items_wrapper.append( `
+ data-batch-no="${escape(item_data.batch_no || '')}" data-rate="${escape(item_data.rate)}">
` ) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 32a4556766..1afce32018 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -54,13 +54,24 @@ erpnext.PointOfSale.ItemDetails = class { this.$dicount_section = this.$component.find('.discount-section'); } - toggle_item_details_section(item) { - const { item_code, batch_no, uom } = this.current_item; + has_item_has_changed(item) { + const { item_code, batch_no, uom, rate } = this.current_item; const item_code_is_same = item && item_code === item.item_code; const batch_is_same = item && batch_no == item.batch_no; const uom_is_same = item && uom === item.uom; + const rate_is_same = item && rate === item.rate; + + if (!item) + return false - this.item_has_changed = !item ? false : item_code_is_same && batch_is_same && uom_is_same ? false : true; + if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same) + return false + + return true; + } + + toggle_item_details_section(item) { + this.item_has_changed = this.has_item_has_changed(item) this.events.toggle_item_selector(this.item_has_changed); this.toggle_component(this.item_has_changed); @@ -72,11 +83,12 @@ erpnext.PointOfSale.ItemDetails = class { this.item_row = item; this.currency = this.events.get_frm().doc.currency; - this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom }; + this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom, rate: item.rate }; this.render_dom(item); this.render_discount_dom(item); this.render_form(item); + this.events.highlight_cart_item(item); } else { this.validate_serial_batch_item(); this.current_item = {}; @@ -198,12 +210,14 @@ erpnext.PointOfSale.ItemDetails = class { if (this.allow_rate_change) { this.rate_control.df.onchange = function() { if (this.value || flt(this.value) === 0) { + me.events.set_value_in_current_cart_item('rate', this.value); me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => { const item_row = frappe.get_doc(me.doctype, me.name); const doc = me.events.get_frm().doc; me.$item_price.html(format_currency(item_row.rate, doc.currency)); me.render_discount_dom(item_row); }); + me.current_item.rate = this.value; } }; } else { @@ -292,11 +306,7 @@ erpnext.PointOfSale.ItemDetails = class { frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { const field_control = this[`${fieldname}_control`]; - const { item_code, batch_no, uom } = this.current_item; - const item_code_is_same = item_code === item_row.item_code; - const batch_is_same = batch_no == item_row.batch_no; - const uom_is_same = uom === item_row.uom; - const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false; + const item_is_same = !this.has_item_has_changed(item_row); if (item_is_same && field_control && field_control.get_value() !== value) { field_control.set_value(value); 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 b8a82a9eda..55b07e71cb 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -78,7 +78,7 @@ erpnext.PointOfSale.ItemSelector = class { get_item_html(item) { const me = this; // eslint-disable-next-line no-unused-vars - const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; + const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; let qty_to_display = actual_qty; @@ -108,6 +108,7 @@ erpnext.PointOfSale.ItemSelector = class { `
${get_item_image_html()} @@ -116,7 +117,7 @@ erpnext.PointOfSale.ItemSelector = class {
${frappe.ellipsis(item.item_name, 18)}
-
${format_currency(item.price_list_rate, item.currency, 0) || 0}
+
${format_currency(price_list_rate, item.currency, 0) || 0}
` ); @@ -213,13 +214,15 @@ erpnext.PointOfSale.ItemSelector = class { let batch_no = unescape($item.attr('data-batch-no')); let serial_no = unescape($item.attr('data-serial-no')); let uom = unescape($item.attr('data-uom')); + let rate = unescape($item.attr('data-rate')); // escape(undefined) returns "undefined" then unescape returns "undefined" batch_no = batch_no === "undefined" ? undefined : batch_no; serial_no = serial_no === "undefined" ? undefined : serial_no; uom = uom === "undefined" ? undefined : uom; + rate = rate === "undefined" ? undefined : rate; - me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }}); + me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }}); me.set_search_value(''); }); From 3382655b5eb7f0e6477c63ab91ef2792735ef601 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 27 May 2021 14:27:38 +0530 Subject: [PATCH 063/159] fix: sider issues --- erpnext/selling/page/point_of_sale/pos_controller.js | 2 +- erpnext/selling/page/point_of_sale/pos_item_details.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 0921f010e7..ae3f9e3c9d 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -242,7 +242,7 @@ erpnext.PointOfSale.Controller = class { get_frm: () => this.frm, cart_item_clicked: (item_code, batch_no, uom, rate) => { - const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate) + const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); this.item_details.toggle_item_details_section(item_row); }, diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index 1afce32018..df62696c4b 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -62,16 +62,16 @@ erpnext.PointOfSale.ItemDetails = class { const rate_is_same = item && rate === item.rate; if (!item) - return false + return false; if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same) - return false + return false; return true; } toggle_item_details_section(item) { - this.item_has_changed = this.has_item_has_changed(item) + this.item_has_changed = this.has_item_has_changed(item); this.events.toggle_item_selector(this.item_has_changed); this.toggle_component(this.item_has_changed); From 9e4c28852e10848feda7bad57235a35fad807792 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:25:50 +0530 Subject: [PATCH 064/159] refactor: added maintenance visit to maintenance schedule dashboard --- .../doctype/maintenance_schedule/maintenance_schedule.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json index 4df0c6c0f7..4f89a679c8 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json @@ -231,11 +231,12 @@ "is_submittable": 1, "links": [ { - "link_doctype": "Maintenance Visit Purpose", - "link_fieldname": "prevdoc_docname" + "group": "Visits", + "link_doctype": "Maintenance Visit", + "link_fieldname": "maintenance_schedule" } ], - "modified": "2021-04-21 11:27:05.744109", + "modified": "2021-05-27 16:05:10.746465", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule", From bd783e8072c8d4dcae4b9249b5a88363bb0a2d1b Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:34:16 +0530 Subject: [PATCH 065/159] refactor: renamed item_ref to item_reference --- .../maintenance_schedule_detail.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 76acefbf61..8ccef6a817 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -18,7 +18,7 @@ "completion_status", "section_break_10", "serial_no", - "item_ref" + "item_reference" ], "fields": [ { @@ -97,14 +97,6 @@ "options": "Pending\nPartially Completed\nFully Completed", "read_only": 1 }, - { - "fieldname": "item_ref", - "fieldtype": "Link", - "hidden": 1, - "label": "Item Reference", - "options": "Maintenance Schedule Item", - "read_only": 1 - }, { "fieldname": "column_break_3", "fieldtype": "Column Break" @@ -120,12 +112,20 @@ { "fieldname": "section_break_10", "fieldtype": "Section Break" + }, + { + "fieldname": "item_reference", + "fieldtype": "Link", + "hidden": 1, + "label": "Item Reference", + "options": "Maintenance Schedule Item", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-21 11:07:29.524071", + "modified": "2021-05-27 16:07:25.905015", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", From fbc86942911035f29a85fc9302e81e091b990657 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:37:57 +0530 Subject: [PATCH 066/159] refactor: added maintenance schedule links to parent form --- .../maintenance_visit/maintenance_visit.json | 1286 ++++------------- 1 file changed, 284 insertions(+), 1002 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index 32bfa0e324..ec32239518 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -1,1042 +1,324 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-01-10 16:34:31", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "actions": [], + "autoname": "naming_series:", + "creation": "2013-01-10 16:34:31", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "customer_details", + "column_break0", + "naming_series", + "customer", + "customer_name", + "address_display", + "contact_display", + "contact_mobile", + "contact_email", + "maintenance_schedule", + "maintenance_schedule_detail", + "column_break1", + "mntc_date", + "mntc_time", + "maintenance_details", + "completion_status", + "column_break_14", + "maintenance_type", + "section_break0", + "purposes", + "more_info", + "customer_feedback", + "col_break3", + "status", + "amended_from", + "company", + "contact_info_section", + "customer_address", + "contact_person", + "col_break4", + "territory", + "customer_group" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-user", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-user" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "options": "MAT-MVS-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MAT-MVS-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_name", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "bold": 1, + "fieldname": "customer_name", + "fieldtype": "Data", + "hidden": 1, + "in_global_search": 1, + "label": "Customer Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "address_display", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact_display", + "fieldtype": "Small Text", + "hidden": 1, + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_mobile", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact_mobile", + "fieldtype": "Data", + "hidden": 1, + "label": "Mobile No", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_email", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "mntc_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Maintenance Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "mntc_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "mntc_date", + "fieldtype": "Date", + "label": "Maintenance Date", + "no_copy": 1, + "oldfieldname": "mntc_date", + "oldfieldtype": "Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mntc_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Maintenance Time", - "length": 0, - "no_copy": 1, - "oldfieldname": "mntc_time", - "oldfieldtype": "Time", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "mntc_time", + "fieldtype": "Time", + "label": "Maintenance Time", + "no_copy": 1, + "oldfieldname": "mntc_time", + "oldfieldtype": "Time" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintenance_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-wrench", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "maintenance_details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-wrench" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "completion_status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Completion Status", - "length": 0, - "no_copy": 0, - "oldfieldname": "completion_status", - "oldfieldtype": "Select", - "options": "\nPartially Completed\nFully Completed", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "completion_status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Completion Status", + "oldfieldname": "completion_status", + "oldfieldtype": "Select", + "options": "\nPartially Completed\nFully Completed", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Unscheduled", - "fieldname": "maintenance_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Maintenance Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "maintenance_type", - "oldfieldtype": "Select", - "options": "\nScheduled\nUnscheduled\nBreakdown", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Unscheduled", + "fieldname": "maintenance_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Maintenance Type", + "oldfieldname": "maintenance_type", + "oldfieldtype": "Select", + "options": "\nScheduled\nUnscheduled\nBreakdown", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break0", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-wrench", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break0", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-wrench" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purposes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purposes", - "length": 0, - "no_copy": 0, - "oldfieldname": "maintenance_visit_details", - "oldfieldtype": "Table", - "options": "Maintenance Visit Purpose", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "purposes", + "fieldtype": "Table", + "label": "Purposes", + "oldfieldname": "maintenance_visit_details", + "oldfieldtype": "Table", + "options": "Maintenance Visit Purpose", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "more_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "More Information", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_feedback", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Feedback", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer_feedback", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer_feedback", + "fieldtype": "Small Text", + "label": "Customer Feedback", + "oldfieldname": "customer_feedback", + "oldfieldtype": "Small Text" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "col_break3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Data", - "options": "\nDraft\nCancelled\nSubmitted", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Data", + "options": "\nDraft\nCancelled\nSubmitted", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Maintenance Visit", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Maintenance Visit", + "print_hide": 1, + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Select", - "options": "Company", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Select", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_info_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Info", - "length": 0, - "no_copy": 0, - "options": "fa fa-bullhorn", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "customer", + "fieldname": "contact_info_section", + "fieldtype": "Section Break", + "label": "Contact Info", + "options": "fa fa-bullhorn" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer_address", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_person", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Person", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "col_break4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "territory", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Territory", - "length": 0, - "no_copy": 0, - "options": "Territory", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group", + "print_hide": 1 + }, + { + "fieldname": "maintenance_schedule", + "fieldtype": "Link", + "label": "Maintenance Schedule", + "options": "Maintenance Schedule", + "read_only": 1 + }, + { + "fieldname": "maintenance_schedule_detail", + "fieldtype": "Link", + "hidden": 1, + "label": "Maintenance Schedule Detail", + "options": "Maintenance Schedule Detail" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-file-text", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Maintenance", - "name": "Maintenance Visit", - "owner": "Administrator", + ], + "icon": "fa fa-file-text", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-05-27 16:06:17.352572", + "modified_by": "Administrator", + "module": "Maintenance", + "name": "Maintenance Visit", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Maintenance User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance User", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "customer", - "title_field": "customer_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "customer", + "title_field": "customer_name" } \ No newline at end of file From 46d39d27aaea10eda484cf41dda0b3bb6392f67f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 May 2021 14:16:01 +0200 Subject: [PATCH 067/159] fix: validate company in taxes setup --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 429a558c58..dd0ebd1517 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -11,6 +11,9 @@ from frappe import _ def setup_taxes_and_charges(company_name: str, country: str): + if not frappe.db.exists('Company', company_name): + frappe.throw(_('Company {} does not exist yet. Taxes setup aborted.').format(company_name)) + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json') with open(file_path, 'r') as json_file: tax_data = json.load(json_file) From 86ee3ebb09ea11e971a48e1718822a2e998fba10 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 May 2021 14:16:26 +0200 Subject: [PATCH 068/159] feat: create tax category during taxes setup --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index dd0ebd1517..672caf2606 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -102,6 +102,9 @@ def make_taxes_and_charges_template(company_name, doctype, template): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return + if template.get('tax_category'): + ensure_tax_category_exists(template.get('tax_category')) + for tax_row in template.get('taxes'): account_data = tax_row.get('account_head') tax_row_defaults = { @@ -233,3 +236,10 @@ def get_or_create_tax_group(company_name, root_type): tax_group_name = tax_group_account.name return tax_group_name + + +def ensure_tax_category_exists(name): + if not frappe.db.exists('Tax Category', name): + doc = frappe.new_doc('Tax Category') + doc.title = name + doc.save() From 50794407b49469682a53b6f91dd3257ca7b25f9d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 May 2021 14:17:53 +0200 Subject: [PATCH 069/159] feat: Item Tax Templates for Germany --- .../setup_wizard/data/country_wise_tax.json | 307 +++++++++++++----- 1 file changed, 232 insertions(+), 75 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 5876488033..2e2a0ca726 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -485,33 +485,32 @@ "SKR04 mit Kontonummern": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "account_number": "3806", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Umsatzsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Umsatzsteuer 7%", "account_number": "3801", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } ], "purchase_tax_templates": [ { - "title": "Abziehbare Vorsteuer 19%", + "title": "Vorsteuer", + "tax_category": "Vorsteuer", "taxes": [ { "account_head": { @@ -519,25 +518,23 @@ "account_number": "1406", "root_type": "Asset", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Abziehbare Vorsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Abziehbare Vorsteuer 7%", "account_number": "1401", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] }, { "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", + "tax_category": "Innergemeinschaftlicher Erwerb 19%", "taxes": [ { "account_head": { @@ -564,33 +561,32 @@ "SKR03 mit Kontonummern": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "account_number": "1776", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Umsatzsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Umsatzsteuer 7%", "account_number": "1771", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } ], "purchase_tax_templates": [ { - "title": "Abziehbare Vorsteuer 19%", + "title": "Vorsteuer", + "tax_category": "Vorsteuer", "taxes": [ { "account_head": { @@ -598,20 +594,17 @@ "account_number": "1576", "root_type": "Asset", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Abziehbare Vorsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Abziehbare Vorsteuer 7%", "account_number": "1571", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } @@ -620,33 +613,32 @@ "Standard with Numbers": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "account_number": "2301", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Umsatzsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Umsatzsteuer 7%", "account_number": "2302", "tax_rate": 7.00 - } + }, + "rate": 0.00 } ] } ], "purchase_tax_templates": [ { - "title": "Abziehbare Vorsteuer 19%", + "title": "Vorsteuer", + "tax_category": "Vorsteuer", "taxes": [ { "account_head": { @@ -654,20 +646,107 @@ "account_number": "1501", "root_type": "Asset", "tax_rate": 19.00 - } - } - ] - }, - { - "title": "Abziehbare Vorsteuer 7%", - "taxes": [ + }, + "rate": 0.00 + }, { "account_head": { "account_name": "Abziehbare Vorsteuer 7%", "account_number": "1502", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "rate": 0.00 + } + ] + } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + }, + { + "title": "Vorsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 } ] } @@ -676,13 +755,67 @@ "*": { "sales_tax_templates": [ { - "title": "Umsatzsteuer 19%", + "title": "Umsatzsteuer", + "tax_category": "Umsatzsteuer", "taxes": [ { "account_head": { "account_name": "Umsatzsteuer 19%", "tax_rate": 19.00 - } + }, + "rate": 0.00 + }, + { + "account_head": { + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + }, + "rate": 0.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Vorsteuer 19%", + "tax_category": "Vorsteuer", + "taxes": [ + { + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "tax_rate": 19.00, + "root_type": "Asset" + }, + "rate": 0.00 + }, + { + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "rate": 0.00 + } + ] + } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 } ] }, @@ -690,36 +823,60 @@ "title": "Umsatzsteuer 7%", "taxes": [ { - "account_head": { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { "account_name": "Umsatzsteuer 7%", "tax_rate": 7.00 - } - } - ] - } - ], - "purchase_tax_templates": [ - { - "title": "Abziehbare Vorsteuer 19%", - "taxes": [ - { - "account_head": { - "account_name": "Abziehbare Vorsteuer 19%", - "tax_rate": 19.00, - "root_type": "Asset" - } + }, + "tax_rate": 7.00 } ] }, { - "title": "Abziehbare Vorsteuer 7%", + "title": "Vorsteuer 19%", "taxes": [ { - "account_head": { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { "account_name": "Abziehbare Vorsteuer 7%", "root_type": "Asset", "tax_rate": 7.00 - } + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 } ] } From 591f3f0bb9ec944757a482db40e34162b3eb2fbe Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 4 May 2021 18:36:45 +0530 Subject: [PATCH 070/159] ci: Try Parallel tests --- .github/helper/install.sh | 2 +- .github/workflows/ci-tests.yml | 55 ++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 7b0f944c66..fd32624c2d 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,7 +12,7 @@ sudo apt install npm pip install frappe-bench -git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 +git clone https://github.com/surajshetty3416/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 84ecfb1457..de8d7ac503 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -13,7 +13,10 @@ jobs: include: - TYPE: "server" JOB_NAME: "Server" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage + RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext + - TYPE: "server" + JOB_NAME: "Server" + RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "patch" JOB_NAME: "Patch" RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate @@ -80,29 +83,29 @@ jobs: env: TYPE: ${{ matrix.TYPE }} - - name: Coverage - Pull Request - if: matrix.TYPE == 'server' && github.event_name == 'pull_request' - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip install coveralls==2.2.0 - pip install coverage==4.5.4 - coveralls --service=github - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - COVERALLS_SERVICE_NAME: github - - - name: Coverage - Push - if: matrix.TYPE == 'server' && github.event_name == 'push' - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip install coveralls==2.2.0 - pip install coverage==4.5.4 - coveralls --service=github-actions - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - COVERALLS_SERVICE_NAME: github-actions + # - name: Coverage - Pull Request + # if: matrix.TYPE == 'server' && github.event_name == 'pull_request' + # run: | + # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + # cd ${GITHUB_WORKSPACE} + # pip install coveralls==2.2.0 + # pip install coverage==4.5.4 + # coveralls --service=github + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + # COVERALLS_SERVICE_NAME: github + + # - name: Coverage - Push + # if: matrix.TYPE == 'server' && github.event_name == 'push' + # run: | + # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + # cd ${GITHUB_WORKSPACE} + # pip install coveralls==2.2.0 + # pip install coverage==4.5.4 + # coveralls --service=github-actions + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + # COVERALLS_SERVICE_NAME: github-actions From dd1530492104759f28df37e5b0235ab19551cff5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 4 May 2021 18:58:20 +0530 Subject: [PATCH 071/159] fix: Frappe branch --- .github/helper/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index fd32624c2d..44659f22fa 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,7 +12,7 @@ sudo apt install npm pip install frappe-bench -git clone https://github.com/surajshetty3416/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 +git clone https://github.com/surajshetty3416/frappe --branch "python-distributed-testing" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site From 34e620fb5b50b6198544ae16b9480b345933ab70 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 13:04:12 +0530 Subject: [PATCH 072/159] test: Fix dependency --- erpnext/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py index e69de29bb2..dcc4f5336d 100644 --- a/erpnext/tests/__init__.py +++ b/erpnext/tests/__init__.py @@ -0,0 +1 @@ +global_test_dependencies = ['User', 'Company'] From 85dd5d2252d5d2539bec0cabb364c87df22d8660 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 13:05:03 +0530 Subject: [PATCH 073/159] chore: Debug --- .github/workflows/ci-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index de8d7ac503..7373c1efa6 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -1,6 +1,6 @@ name: CI -on: [pull_request, workflow_dispatch, push] +on: [pull_request, workflow_dispatch] jobs: test: From 0f3d862ba993e56b1312e67334ae138ee52b663c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 15:13:09 +0530 Subject: [PATCH 074/159] chore: Debug --- .github/workflows/ci-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 7373c1efa6..b03aad794b 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -17,6 +17,9 @@ jobs: - TYPE: "server" JOB_NAME: "Server" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext + - TYPE: "server" + JOB_NAME: "Server" + RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "patch" JOB_NAME: "Patch" RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate From 7b74985a548aaf80778555b84fe10104ee2b90e6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 17:44:26 +0530 Subject: [PATCH 075/159] test: Fix test_dependencies --- erpnext/accounts/doctype/budget/test_budget.py | 2 ++ erpnext/education/doctype/fees/test_fees.py | 3 +-- erpnext/tests/__init__.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index c5ec23c829..603e21ea24 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -11,6 +11,8 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +test_dependencies = ['Monthly Distribution'] + class TestBudget(unittest.TestCase): def test_monthly_budget_crossed_ignore(self): set_total_expense_zero(nowdate(), "cost_center") diff --git a/erpnext/education/doctype/fees/test_fees.py b/erpnext/education/doctype/fees/test_fees.py index eedc2ae730..c6bb704b41 100644 --- a/erpnext/education/doctype/fees/test_fees.py +++ b/erpnext/education/doctype/fees/test_fees.py @@ -9,8 +9,7 @@ from frappe.utils import nowdate from frappe.utils.make_random import get_random from erpnext.education.doctype.program.test_program import make_program_and_linked_courses -# test_records = frappe.get_test_records('Fees') - +test_dependencies = ['Company'] class TestFees(unittest.TestCase): def test_fees(self): diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py index dcc4f5336d..593bc7c71b 100644 --- a/erpnext/tests/__init__.py +++ b/erpnext/tests/__init__.py @@ -1 +1 @@ -global_test_dependencies = ['User', 'Company'] +global_test_dependencies = ['User', 'Company', 'Cost Center', 'Account', 'Warehouse', 'Item'] From 232cd28d67921096ce10fb206894790264ef9e54 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 18:37:14 +0530 Subject: [PATCH 076/159] test: Fix dependencies --- .../doctype/accounting_dimension/test_accounting_dimension.py | 3 ++- .../test_accounting_dimension_filter.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index fc1d7e344a..6fb661eb54 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -7,7 +7,8 @@ import frappe import unittest from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension + +test_dependencies = ['Location'] class TestAccountingDimension(unittest.TestCase): def setUp(self): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index 7877abd026..78a88eb48c 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -9,6 +9,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError +test_dependencies = ['Location'] + class TestAccountingDimensionFilter(unittest.TestCase): def setUp(self): create_dimension() From c26d41acf98216fbd31a3b97344e9865ec25d8f5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 6 May 2021 18:37:45 +0530 Subject: [PATCH 077/159] chore: Remove unnecessary print statements --- erpnext/controllers/accounts_controller.py | 1 - erpnext/setup/doctype/email_digest/email_digest.py | 3 +-- erpnext/stock/stock_balance.py | 5 +---- erpnext/utilities/__init__.py | 1 - 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 544e624725..f88e8df728 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1011,7 +1011,6 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 8c97322a71..5db54eeee1 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -808,7 +808,6 @@ def get_incomes_expenses_for_period(account, from_date, to_date): val = balance_on_to_date - balance_before_from_date else: last_year_closing_balance = get_balance_on(account, date=fy_start_date - timedelta(days=1)) - print(fy_start_date - timedelta(days=1), last_year_closing_balance) val = balance_on_to_date + (last_year_closing_balance - balance_before_from_date) return val @@ -837,4 +836,4 @@ def get_future_date_for_calendaer_event(frequency): elif frequency == "Monthly": to_date = add_to_date(from_date, months=1) - return from_date, to_date \ No newline at end of file + return from_date, to_date diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 8ba1f1ca5c..8917bfeae4 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -194,9 +194,6 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin serial_nos = frappe.db.sql("""select count(name) from `tabSerial No` where item_code=%s and warehouse=%s and docstatus < 2""", (d[0], d[1])) - if serial_nos and flt(serial_nos[0][0]) != flt(d[2]): - print(d[0], d[1], d[2], serial_nos[0][0]) - sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry` where item_code = %s and warehouse = %s and is_cancelled = 0 order by posting_date desc limit 1""", (d[0], d[1])) @@ -230,7 +227,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin }) update_bin(args) - + create_repost_item_valuation_entry({ "item_code": d[0], "warehouse": d[1], diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py index 618cc985ae..0a5aa3c49b 100644 --- a/erpnext/utilities/__init__.py +++ b/erpnext/utilities/__init__.py @@ -12,7 +12,6 @@ def update_doctypes(): for f in dt.fields: if f.fieldname == d.fieldname and f.fieldtype in ("Text", "Small Text"): - print(f.parent, f.fieldname) f.fieldtype = "Text Editor" dt.save() break From 3b69aa80fcd0d4abf270076762bc451141af29ac Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 7 May 2021 00:32:00 +0530 Subject: [PATCH 078/159] ci: Enable coveralls --- .github/workflows/ci-tests.yml | 62 +++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index b03aad794b..4d955190be 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -12,13 +12,13 @@ jobs: matrix: include: - TYPE: "server" - JOB_NAME: "Server" + JOB_NAME: "Server.1" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "server" - JOB_NAME: "Server" + JOB_NAME: "Server.2" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "server" - JOB_NAME: "Server" + JOB_NAME: "Server.3" RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - TYPE: "patch" JOB_NAME: "Patch" @@ -86,29 +86,37 @@ jobs: env: TYPE: ${{ matrix.TYPE }} - # - name: Coverage - Pull Request - # if: matrix.TYPE == 'server' && github.event_name == 'pull_request' - # run: | - # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - # cd ${GITHUB_WORKSPACE} - # pip install coveralls==2.2.0 - # pip install coverage==4.5.4 - # coveralls --service=github - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - # COVERALLS_SERVICE_NAME: github + - name: Upload Coverage Data + if: matrix.TYPE == 'server' + run: | + cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + cd ${GITHUB_WORKSPACE} + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls --service=github-actions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_FLAG_NAME: run-${{ matrix.container }} + COVERALLS_SERVICE_NAME: github-actions + COVERALLS_PARALLEL: true - # - name: Coverage - Push - # if: matrix.TYPE == 'server' && github.event_name == 'push' - # run: | - # cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - # cd ${GITHUB_WORKSPACE} - # pip install coveralls==2.2.0 - # pip install coverage==4.5.4 - # coveralls --service=github-actions - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - # COVERALLS_SERVICE_NAME: github-actions + coveralls: + name: Coverage Wrap Up + needs: test + container: python:3-slim + runs-on: ubuntu-18.04 + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Coveralls Finished + run: | + cd ${GITHUB_WORKSPACE} + ls -al + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b4c958caf05c2605c615e07fb82ab1d9df0419c0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 00:04:34 +0530 Subject: [PATCH 079/159] chore: Debug --- .github/workflows/patch.yml | 69 +++++++++++++++++++ .../{ci-tests.yml => server-tests.yml} | 31 +++------ 2 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/patch.yml rename .github/workflows/{ci-tests.yml => server-tests.yml} (70%) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml new file mode 100644 index 0000000000..7c9e0272c9 --- /dev/null +++ b/.github/workflows/patch.yml @@ -0,0 +1,69 @@ +name: Patch + +on: [pull_request, workflow_dispatch] + +jobs: + test: + runs-on: ubuntu-18.04 + + name: Patch Test + + services: + mysql: + image: mariadb:10.3 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: YES + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.6 + + - name: Add to Hosts + run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install + run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + + - name: Run Patch Tests + run: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/server-tests.yml similarity index 70% rename from .github/workflows/ci-tests.yml rename to .github/workflows/server-tests.yml index 4d955190be..4042a407e7 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/server-tests.yml @@ -1,4 +1,4 @@ -name: CI +name: Server on: [pull_request, workflow_dispatch] @@ -7,24 +7,12 @@ jobs: runs-on: ubuntu-18.04 strategy: - fail-fast: false + fail-fast: true matrix: - include: - - TYPE: "server" - JOB_NAME: "Server.1" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - - TYPE: "server" - JOB_NAME: "Server.2" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - - TYPE: "server" - JOB_NAME: "Server.3" - RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --with-coverage --ci-build-id $GITHUB_RUN_ID --app erpnext - - TYPE: "patch" - JOB_NAME: "Patch" - RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate + container: [1, 2] - name: ${{ matrix.JOB_NAME }} + name: Server Tests services: mysql: @@ -42,7 +30,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts @@ -55,6 +43,7 @@ jobs: restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- + - name: Cache node modules uses: actions/cache@v2 env: @@ -66,6 +55,7 @@ jobs: ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- + - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" @@ -82,12 +72,11 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Tests - run: ${{ matrix.RUN_COMMAND }} + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 2 --with-coverage env: - TYPE: ${{ matrix.TYPE }} + TYPE: server - name: Upload Coverage Data - if: matrix.TYPE == 'server' run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} @@ -113,10 +102,8 @@ jobs: - name: Coveralls Finished run: | cd ${GITHUB_WORKSPACE} - ls -al pip3 install coverage==5.5 pip3 install coveralls==3.0.1 coveralls --finish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - From 5e88b14a01e790fd324c537f9f7a71928b4bffea Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 00:05:48 +0530 Subject: [PATCH 080/159] chore: Debug --- .github/workflows/server-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 4042a407e7..1e9196119e 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: true matrix: - container: [1, 2] + container: [1, 2, 3] name: Server Tests @@ -72,7 +72,7 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 2 --with-coverage + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 3 --with-coverage env: TYPE: server From 6ca8989013656aa749b89c8afadd173ce1406ec8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 13:51:34 +0530 Subject: [PATCH 081/159] ci: Disble failfast temporarily --- .github/workflows/server-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 1e9196119e..0d5a3ba61a 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-18.04 strategy: - fail-fast: true + fail-fast: false matrix: container: [1, 2, 3] From 8696580254f35c6145063a34de54b0a811068c04 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 16:02:40 +0530 Subject: [PATCH 082/159] ci: Fix coveralls --- .github/workflows/server-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 0d5a3ba61a..e5fb72538d 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -82,12 +82,12 @@ jobs: cd ${GITHUB_WORKSPACE} pip3 install coverage==5.5 pip3 install coveralls==3.0.1 - coveralls --service=github-actions + coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: github-actions + COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} COVERALLS_PARALLEL: true coveralls: From 175cb27bcbdd23ef41e256bbf09748229135feeb Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 8 May 2021 19:03:53 +0530 Subject: [PATCH 083/159] test: Pass ConflictingTaxRule during tax rule test --- erpnext/shopping_cart/test_shopping_cart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py index d857bf5f5c..ac61aebc56 100644 --- a/erpnext/shopping_cart/test_shopping_cart.py +++ b/erpnext/shopping_cart/test_shopping_cart.py @@ -7,7 +7,7 @@ import frappe from frappe.utils import nowdate, add_months from erpnext.shopping_cart.cart import _get_cart_quotation, update_cart, get_party from erpnext.tests.utils import create_test_contact_and_address - +from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule # test_dependencies = ['Payment Terms Template'] @@ -125,7 +125,7 @@ class TestShoppingCart(unittest.TestCase): tax_rule = frappe.get_test_records("Tax Rule")[0] try: frappe.get_doc(tax_rule).insert() - except frappe.DuplicateEntryError: + except (frappe.DuplicateEntryError, ConflictingTaxRule): pass def create_quotation(self): From c79f5d7514e73fb64f4e868dc672d6fd33a27ad3 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 9 May 2021 11:29:00 +0530 Subject: [PATCH 084/159] ci: Try parallel testing with orchestrator --- .github/workflows/server-tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index e5fb72538d..0e638104c8 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -72,9 +72,10 @@ jobs: run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --build-number ${{ matrix.container }} --total-builds 3 --with-coverage + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage env: TYPE: server + CI_BUILD_ID: ${{ github.run_id }} - name: Upload Coverage Data run: | From ab8816e11da22887e413b53b1a11bb916f21127e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 9 May 2021 14:38:28 +0530 Subject: [PATCH 085/159] test: Fix test dependency --- .../doctype/accounting_dimension/test_accounting_dimension.py | 2 +- .../test_accounting_dimension_filter.py | 2 +- .../doctype/accounting_period/test_accounting_period.py | 4 +++- erpnext/tests/__init__.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index 6fb661eb54..e657a9ae34 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -8,7 +8,7 @@ import unittest from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry -test_dependencies = ['Location'] +test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department'] class TestAccountingDimension(unittest.TestCase): def setUp(self): diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py index 78a88eb48c..7f6254f99f 100644 --- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py +++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py @@ -9,7 +9,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError -test_dependencies = ['Location'] +test_dependencies = ['Location', 'Cost Center', 'Department'] class TestAccountingDimensionFilter(unittest.TestCase): def setUp(self): diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 10cd939894..3b4f138495 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -10,6 +10,8 @@ from erpnext.accounts.general_ledger import ClosedAccountingPeriod from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +test_dependencies = ['Item'] + class TestAccountingPeriod(unittest.TestCase): def test_overlap(self): ap1 = create_accounting_period(start_date = "2018-04-01", @@ -38,7 +40,7 @@ def create_accounting_period(**args): accounting_period.start_date = args.start_date or nowdate() accounting_period.end_date = args.end_date or add_months(nowdate(), 1) accounting_period.company = args.company or "_Test Company" - accounting_period.period_name =args.period_name or "_Test_Period_Name_1" + accounting_period.period_name = args.period_name or "_Test_Period_Name_1" accounting_period.append("closed_documents", { "document_type": 'Sales Invoice', "closed": 1 }) diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py index 593bc7c71b..a504340d40 100644 --- a/erpnext/tests/__init__.py +++ b/erpnext/tests/__init__.py @@ -1 +1 @@ -global_test_dependencies = ['User', 'Company', 'Cost Center', 'Account', 'Warehouse', 'Item'] +global_test_dependencies = ['User', 'Company', 'Item'] From a70e11450e9e17cc81ff47ffba967a6b76c1fb61 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sun, 9 May 2021 14:38:54 +0530 Subject: [PATCH 086/159] ci: Check limits --- .github/workflows/server-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 0e638104c8..e7830c07f6 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: - container: [1, 2, 3] + container: [1, 2, 3, 4] name: Server Tests @@ -30,7 +30,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.6 - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts From 19c5fd72d65e92da553c58c4712ab200bd3174aa Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 09:18:25 +0530 Subject: [PATCH 087/159] refactor: Rename assertEquals to assertEqual to avoid deprecation warnings --- erpnext/accounts/doctype/dunning/test_dunning.py | 2 +- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 2 +- .../doctype/mpesa_settings/test_mpesa_settings.py | 4 ++-- erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py | 2 +- erpnext/regional/india/utils.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index c5ce514cdd..e2d4d82e41 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -29,7 +29,7 @@ class TestDunning(unittest.TestCase): self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44) self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44) self.assertEqual(round(amounts.get('grand_total'), 2), 120.44) - + def test_gl_entries(self): dunning = create_dunning() dunning.submit() diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index aedf1c6f1a..556f49d34c 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -152,7 +152,7 @@ class PricingRule(Document): frappe.throw(_("Valid from date must be less than valid upto date")) def validate_condition(self): - if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition): + if self.condition and ("=" in self.condition) and re.match(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+', self.condition): frappe.throw(_("Invalid condition expression")) #-------------------------------------------------------------------------------- diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 30a270c204..3cd4b802c1 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -566,7 +566,7 @@ class TestAsset(unittest.TestCase): doc = make_invoice(pr.name) self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) - + def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"]) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index d370fbcda7..3c2e59ab82 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -81,7 +81,7 @@ class TestMpesaSettings(unittest.TestCase): integration_request.reload() self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") self.assertEqual(integration_request.status, "Completed") - + frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") integration_request.delete() pr.reload() @@ -139,7 +139,7 @@ class TestMpesaSettings(unittest.TestCase): pr.cancel() pr.delete() pos_invoice.delete() - + def test_processing_of_only_one_succes_callback_payload(self): create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index d079bedb42..113fa513f9 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -29,7 +29,7 @@ class TestTherapyPlan(unittest.TestCase): self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') patient, medical_department, practitioner = create_healthcare_docs() - appointment = create_appointment(patient, practitioner, nowdate()) + appointment = create_appointment(patient, practitioner, nowdate()) session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) session = frappe.get_doc(session) session.submit() diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index fc227defbf..075c698fea 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -500,7 +500,7 @@ def download_ewb_json(): if not isinstance(docname, list): # removes characters not allowed in a filename (https://stackoverflow.com/a/38766141/4767738) - filename_prefix = re.sub('[^\w_.)( -]', '', docname) + filename_prefix = re.sub(r'[^\w_.)( -]', '', docname) frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(filename_prefix, frappe.utils.random_string(5)) From b3e647fecace96eb3c817c5dcf9241a5ebc3a3a4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 16:13:43 +0530 Subject: [PATCH 088/159] fix: Pass ORCHESTRATOR_URL via secrets --- .github/workflows/server-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index e7830c07f6..0acafcab43 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -76,6 +76,7 @@ jobs: env: TYPE: server CI_BUILD_ID: ${{ github.run_id }} + ORCHESTRATOR_URL: ${{ secrets.ORCHESTRATOR_URL }} - name: Upload Coverage Data run: | From a891d6eed2894c776d91a9a3995d8a3d8de11fe5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 17:25:21 +0530 Subject: [PATCH 089/159] fix: allow_zero_valuation_rate for rejected qty --- .../doctype/purchase_invoice/test_purchase_invoice.py | 7 ++++--- .../doctype/stock_ledger_entry/test_stock_ledger_entry.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 66be11ff23..53db689c84 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -636,8 +636,8 @@ class TestPurchaseInvoice(unittest.TestCase): def test_rejected_serial_no(self): pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1, - rejected_qty=1, rate=500, update_stock=1, - rejected_warehouse = "_Test Rejected Warehouse - _TC") + rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC", + allow_zero_valuation_rate=1) self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"), pi.get("items")[0].warehouse) @@ -994,7 +994,8 @@ def make_purchase_invoice(**args): "project": args.project, "rejected_warehouse": args.rejected_warehouse or "", "rejected_serial_no": args.rejected_serial_no or "", - "asset_location": args.location or "" + "asset_location": args.location or "", + "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0 }) if args.get_taxes_and_charges: 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 3296f5ba4a..a1f1897041 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 @@ -15,10 +15,12 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import create_landed_cost_voucher from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import BackDatedStockTransaction +from frappe.core.page.permission_manager.permission_manager import reset class TestStockLedgerEntry(unittest.TestCase): def setUp(self): items = create_items() + reset('Stock Entry') # delete SLE and BINs for all items frappe.db.sql("delete from `tabStock Ledger Entry` where item_code in (%s)" % (', '.join(['%s']*len(items))), items) From d1a13ec0504e39edfb7de193539ba2c94f5b52b9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 17:37:44 +0530 Subject: [PATCH 090/159] test: Fix valuation rate for raw materials --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index e5ef978ca3..5095a80214 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -297,6 +297,8 @@ class TestPurchaseReceipt(unittest.TestCase): item_code = "Test Extra Item 1", qty=10, basic_rate=100) se2 = make_stock_entry(target="_Test Warehouse - _TC", item_code = "_Test FG Item", qty=1, basic_rate=100) + se3 = make_stock_entry(target="_Test Warehouse - _TC", + item_code = "Test Extra Item 2", qty=1, basic_rate=100) rm_items = [ { "item_code": item_code, @@ -331,6 +333,7 @@ class TestPurchaseReceipt(unittest.TestCase): se.cancel() se1.cancel() se2.cancel() + se3.cancel() po.reload() po.cancel() From d48ea663d9c458a20b30f7a8f3dc6f639406c4b7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 18:01:53 +0530 Subject: [PATCH 091/159] test: Fix test name - Rename TestSubcontractedItemToBeReceived > TestSubcontractedItemToBeTransferred --- ...t_subcontracted_raw_materials_to_be_transferred.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 6900938236..c1fc6fb82f 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 @@ -9,12 +9,12 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import execute import json, frappe, unittest -class TestSubcontractedItemToBeReceived(unittest.TestCase): +class TestSubcontractedItemToBeTransferred(unittest.TestCase): - def test_pending_and_received_qty(self): + def test_pending_and_transferred_qty(self): po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes') - make_stock_entry(item_code='_Test Item', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100) - make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100) + make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100) + make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100) transfer_subcontracted_raw_materials(po.name) col, data = execute(filters=frappe._dict({'supplier': po.supplier, 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)), @@ -38,7 +38,8 @@ def transfer_subcontracted_raw_materials(po): 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}] rm_item_string = json.dumps(rm_item) se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string)) + se.from_warehouse = '_Test Warehouse 1 - _TC' se.to_warehouse = '_Test Warehouse 1 - _TC' se.stock_entry_type = 'Send to Subcontractor' se.save() - se.submit() \ No newline at end of file + se.submit() From 54354a84e1c15c8fadf836865d8bc0b5405321c9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 18:39:09 +0530 Subject: [PATCH 092/159] test: Fix permission error --- .../doctype/stock_ledger_entry/test_stock_ledger_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 a1f1897041..ba31ad7b06 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 @@ -316,10 +316,11 @@ class TestStockLedgerEntry(unittest.TestCase): # Set User with Stock User role but not Stock Manager try: user = frappe.get_doc("User", "test@example.com") - frappe.set_user(user.name) user.add_roles("Stock User") user.remove_roles("Stock Manager") + frappe.set_user(user.name) + stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, posting_date=add_days(today(), -1), do_not_submit=True) From 273589e835342ea364c3d096a1fd327769df98c8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 20:33:47 +0530 Subject: [PATCH 093/159] test: Fix a case where test used to fail due to holiday list - fixes: "Please set a default Holiday List for Employee EMP-00009 or Company Wind Power LLC" error --- .../hr/doctype/upload_attendance/test_upload_attendance.py | 7 +++++++ .../payroll/doctype/payroll_entry/test_payroll_entry.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py index 6e151d0e3c..03b0cf3da2 100644 --- a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py @@ -5,11 +5,18 @@ from __future__ import unicode_literals import frappe import unittest +import erpnext from frappe.utils import getdate from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data from erpnext.hr.doctype.employee.test_employee import make_employee +test_dependencies = ['Holiday List'] + class TestUploadAttendance(unittest.TestCase): + @classmethod + def setUpClass(cls): + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List') + def test_date_range(self): employee = make_employee("test_employee@company.com") employee_doc = frappe.get_doc("Employee", employee) diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py index 7528bf7a7f..b80b32061f 100644 --- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py @@ -15,7 +15,13 @@ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_ from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans +test_dependencies = ['Holiday List'] + class TestPayrollEntry(unittest.TestCase): + @classmethod + def setUpClass(cls): + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List') + def setUp(self): for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]: From 96542c3d043a2865af4f77ab6da754eb62bebdc4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 20:49:07 +0530 Subject: [PATCH 094/159] style: Fix sider issues --- .../doctype/accounting_period/test_accounting_period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 3b4f138495..dc472c7695 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -40,7 +40,7 @@ def create_accounting_period(**args): accounting_period.start_date = args.start_date or nowdate() accounting_period.end_date = args.end_date or add_months(nowdate(), 1) accounting_period.company = args.company or "_Test Company" - accounting_period.period_name = args.period_name or "_Test_Period_Name_1" + accounting_period.period_name = args.period_name or "_Test_Period_Name_1" accounting_period.append("closed_documents", { "document_type": 'Sales Invoice', "closed": 1 }) From b9a8afc234b2a1647bc66e647d136b87d684f22a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 23:48:37 +0530 Subject: [PATCH 095/159] ci: Add test orchestrator URL --- .github/workflows/server-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 0acafcab43..bd60081064 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -76,7 +76,7 @@ jobs: env: TYPE: server CI_BUILD_ID: ${{ github.run_id }} - ORCHESTRATOR_URL: ${{ secrets.ORCHESTRATOR_URL }} + ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - name: Upload Coverage Data run: | @@ -87,7 +87,6 @@ jobs: coveralls env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} COVERALLS_FLAG_NAME: run-${{ matrix.container }} COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} COVERALLS_PARALLEL: true From 77f2686142568b12dcbea50db03928f6b8ec5673 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 10:09:06 +0530 Subject: [PATCH 096/159] ci: Use only 3 containers for now --- .github/workflows/server-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index bd60081064..6597048bc7 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: - container: [1, 2, 3, 4] + container: [1, 2, 3] name: Server Tests From 113a6f3d80b95fd249dbe95224f0be197d533c86 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 10:59:16 +0530 Subject: [PATCH 097/159] fix: Use frappe.safe_eval instead of eval --- erpnext/setup/doctype/email_digest/email_digest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 5db54eeee1..340d89bdf8 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -249,7 +249,7 @@ class EmailDigest(Document): card = cache.get(cache_key) if card: - card = eval(card) + card = frappe.safe_eval(card) else: card = frappe._dict(getattr(self, "get_" + key)()) From 6da2212e2d4f1eba80a4a5d845d515f62a1eaf62 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 12 May 2021 18:14:22 +0530 Subject: [PATCH 098/159] ci: Update frappe branch --- .github/helper/install.sh | 2 +- .github/workflows/server-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 44659f22fa..7b0f944c66 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -12,7 +12,7 @@ sudo apt install npm pip install frappe-bench -git clone https://github.com/surajshetty3416/frappe --branch "python-distributed-testing" --depth 1 +git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench mkdir ~/frappe-bench/sites/test_site diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 6597048bc7..6f831ac3d1 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -12,7 +12,7 @@ jobs: matrix: container: [1, 2, 3] - name: Server Tests + name: Python Unit Tests services: mysql: From 85aeca14432ecbec86c88549358eac338298eaa4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 12 May 2021 23:13:11 +0530 Subject: [PATCH 099/159] ci: Update Python version to 3.7 --- .github/workflows/server-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 6f831ac3d1..92685e2177 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts From bcf116422a2d7ed1a4ea33eda1a894b055bbf5b0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 28 May 2021 09:12:24 +0530 Subject: [PATCH 100/159] ci: Do not generate coverage report for hotfix branch --- .github/workflows/server-tests.yml | 31 ------------------------------ 1 file changed, 31 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 92685e2177..de5cc61d1e 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -77,34 +77,3 @@ jobs: TYPE: server CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - - - name: Upload Coverage Data - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} - COVERALLS_PARALLEL: true - - coveralls: - name: Coverage Wrap Up - needs: test - container: python:3-slim - runs-on: ubuntu-18.04 - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Coveralls Finished - run: | - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls --finish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c4d4be3265d216b48aaabeffa777984f47626dbc Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 18 May 2021 15:42:13 +0530 Subject: [PATCH 101/159] fix: ensure website theme is applied correctly --- erpnext/public/scss/shopping_cart.scss | 18 +++++++++--------- erpnext/public/scss/website.scss | 9 ++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 159a8a47cd..92e5d32219 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -1,4 +1,3 @@ -@import "frappe/public/scss/desk/variables"; @import "frappe/public/scss/common/mixins"; body.product-page { @@ -217,12 +216,12 @@ body.product-page { border-color: var(--table-border-color) !important; padding: 15px; - @include media-breakpoint-between(xs, md) { + @media (max-width: 840px) { height: 300px; width: 300px; } - @include media-breakpoint-up(lg) { + @media (min-width: 1090px) { height: 350px; width: 350px; } @@ -233,11 +232,12 @@ body.product-page { } .item-slideshow { - @include media-breakpoint-between(xs, md) { + + @media (max-width: 840px) { max-height: 320px; } - @include media-breakpoint-up(lg) { + @media (min-width: 1090px) { max-height: 430px; } @@ -254,7 +254,7 @@ body.product-page { cursor: pointer; &:hover, &.active { - border-color: $primary; + border-color: var(--primary); } } @@ -316,12 +316,12 @@ body.product-page { } .item-group-slideshow { - .item-group-description { + // .item-group-description { // max-width: 900px; - } + // } .carousel-inner.rounded-carousel { - border-radius: $card-border-radius; + border-radius: var(--card-border-radius); } } diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss index 56b717c424..f4325c03f5 100644 --- a/erpnext/public/scss/website.scss +++ b/erpnext/public/scss/website.scss @@ -1,4 +1,3 @@ -@import "frappe/public/scss/website/variables"; .filter-options { max-height: 300px; @@ -14,7 +13,7 @@ } &.active { - border-color: $primary; + border-color: var(--primary); .check { display: inline-flex; @@ -25,7 +24,7 @@ .check { display: inline-flex; padding: 0.25rem; - background: $primary; + background: var(--primary); color: white; border-radius: 50%; font-size: 12px; @@ -38,12 +37,12 @@ } .result { - border-bottom: 1px solid $border-color; + border-bottom: 1px solid var(--border-color); } .transaction-list-item { padding: 1rem 0; - border-top: 1px solid $border-color; + border-top: 1px solid var(--border-color); position: relative; a.transaction-item-link { From f4db3139b7fb0b1f00b3b55fe3ed5005f3ee2d14 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 28 May 2021 08:55:38 +0530 Subject: [PATCH 102/159] refactor: Use css variables for breakpoint value --- erpnext/public/scss/shopping_cart.scss | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 92e5d32219..9402cf9ea4 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -73,15 +73,6 @@ body.product-page { } } - // .card-body { - // text-align: center; - // } - - // .featured-item { - // .card-body { - // text-align: left; - // } - // } .card-img-container { height: 210px; width: 100%; @@ -216,12 +207,12 @@ body.product-page { border-color: var(--table-border-color) !important; padding: 15px; - @media (max-width: 840px) { + @media (max-width: var(--md-width)) { height: 300px; width: 300px; } - @media (min-width: 1090px) { + @media (min-width: var(--lg-width)) { height: 350px; width: 350px; } @@ -233,11 +224,11 @@ body.product-page { .item-slideshow { - @media (max-width: 840px) { + @media (max-width: var(--md-width)) { max-height: 320px; } - @media (min-width: 1090px) { + @media (min-width: var(--lg-width)) { max-height: 430px; } @@ -316,9 +307,6 @@ body.product-page { } .item-group-slideshow { - // .item-group-description { - // max-width: 900px; - // } .carousel-inner.rounded-carousel { border-radius: var(--card-border-radius); From 8a776a63cd8884bce7ea4f2dcb4173c630055157 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 28 May 2021 09:56:30 +0530 Subject: [PATCH 103/159] fix(POS): Add Product Bundles to POS item search (#25860) --- erpnext/selling/page/point_of_sale/point_of_sale.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 750a1a6071..09a3efc491 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -62,7 +62,6 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va `tabItem` item {bin_join_selection} WHERE item.disabled = 0 - AND item.is_stock_item = 1 AND item.has_variants = 0 AND item.is_sales_item = 1 AND item.is_fixed_asset = 0 @@ -84,6 +83,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va ), {'warehouse': warehouse}, as_dict=1) if items_data: + items_data = filter_service_items(items_data) items = [d.item_code for d in items_data] item_prices_data = frappe.get_all("Item Price", fields = ["item_code", "price_list_rate", "currency"], @@ -135,6 +135,14 @@ def search_serial_or_batch_or_barcode_number(search_value): return {} +def filter_service_items(items): + for item in items: + if not item['is_stock_item']: + if not frappe.db.exists('Product Bundle', item['item_code']): + items.remove(item) + + return items + def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: return "item.name = {0}".format(frappe.db.escape(item_code)) From 17736afab555307bb95d743667f22be2c4592535 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 28 May 2021 10:03:41 +0530 Subject: [PATCH 104/159] fix(POS): Fix stock availability calculation if negative_stock_allowed is checked (#25859) --- erpnext/selling/page/point_of_sale/point_of_sale.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 09a3efc491..cb811df8e8 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -15,7 +15,6 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va data = dict() result = [] - allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items']) if not frappe.db.exists('Item Group', item_group): @@ -96,10 +95,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va for item in items_data: item_code = item.item_code item_price = item_prices.get(item_code) or {} - if allow_negative_stock: - item_stock_qty = frappe.db.sql("""select ifnull(sum(actual_qty), 0) from `tabBin` where item_code = %s""", item_code)[0][0] - else: - item_stock_qty = get_stock_availability(item_code, warehouse) + item_stock_qty = get_stock_availability(item_code, warehouse) row = {} row.update(item) From 3bca90dbe6b6b9335765e41345b2902b512171f7 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:42:31 +0530 Subject: [PATCH 105/159] refactor: updated mapping for maintenance schedule links in maintenance visit --- .../maintenance_schedule.py | 17 +++++++---------- .../test_maintenance_schedule.py | 6 ++++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index ea76e91b3f..d6e42f3ee1 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -34,7 +34,7 @@ class MaintenanceSchedule(TransactionBase): count = count + 1 child.sales_person = d.sales_person child.completion_status = "Pending" - child.item_ref = d.name + child.item_reference = d.name @frappe.whitelist() def validate_end_date_visits(self): @@ -314,11 +314,12 @@ def update_serial_nos(s_id): def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): from frappe.model.mapper import get_mapped_doc - def update_status(source, target, parent): + def update_status_and_detail(source, target, parent): target.maintenance_type = "Scheduled" + target.maintenance_schedule = source.name + target.maintenance_schedule_detail = s_id - def update_sid(source, target, parent): - target.prevdoc_detail_docname = s_id + def update_sales(source, target, parent): sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') target.service_person = sales_person target.serial_no = '' @@ -332,16 +333,12 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No "validation": { "docstatus": ["=", 1] }, - "postprocess": update_status + "postprocess": update_status_and_detail }, "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", - "field_map": { - "parent": "prevdoc_docname", - "parenttype": "prevdoc_doctype", - }, "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sid + "postprocess": update_sales } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 58ee964fb5..08282b4c3d 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -57,16 +57,18 @@ class TestMaintenanceSchedule(unittest.TestCase): test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id) visit = frappe.new_doc('Maintenance Visit') visit = test + visit.maintenance_schedule = ms.name + visit.maintenance_schedule_detail = s_id visit.completion_status = "Partially Completed" - visit.set('purposes', [{ 'item_code': i.item_code, 'description': "test", 'work_done': "test", + 'service_person': "Sales Team", 'prevdoc_docname' :ms.name, 'prevdoc_doctype': ms.doctype, - 'prevdoc_detail_docname': s_id }]) + visit.save() visit.submit() ms = frappe.get_doc('Maintenance Schedule', ms.name) From 7fa045c1c9142fa4a3d26790fe999e2f455dcef6 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:44:28 +0530 Subject: [PATCH 106/159] refactor: using parent form links of maintenance schedule --- .../maintenance_visit/maintenance_visit.py | 91 +++++++++---------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 8a3094cb36..79d65c921a 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -17,70 +17,61 @@ class MaintenanceVisit(TransactionBase): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): frappe.throw(_("Serial No {0} does not exist").format(d.serial_no)) - def validate_mntc_date(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - item_ref = frappe.db.get_value('Maintenance Schedule Detail', detail_ref , 'item_ref') + def validate_maintenance_date(self): + if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: + item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference') start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): - frappe.throw(_("Date must be between {0} and {1}").format(start_date,end_date)) + frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date)) def validate(self): self.validate_serial_no() - self.validate_mntc_date() + self.validate_maintenance_date() - def get_schedule_datail_ref(self): - if self.maintenance_type == "Scheduled": - p = self.purposes - for i in p: - detail_ref = i.prevdoc_detail_docname - return detail_ref - def update_completion_status(self): - detail_ref = self.get_schedule_datail_ref() - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'completion_status', self.completion_status) + if self.maintenance_schedule_detail: + frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', self.completion_status) def update_actual_date(self): - detail_ref = self.get_schedule_datail_ref() - frappe.db.set_value('Maintenance Schedule Detail', detail_ref, 'actual_date', self.mntc_date) + if self.maintenance_schedule_detail: + frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', self.mntc_date) def update_customer_issue(self, flag): - for d in self.get('purposes'): - if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' : - if flag==1: - mntc_date = self.mntc_date - service_person = d.service_person - work_done = d.work_done - status = "Open" - if self.completion_status == 'Fully Completed': - status = 'Closed' - elif self.completion_status == 'Partially Completed': - status = 'Work In Progress' - else: - nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name)) - - if nm: - status = 'Work In Progress' - mntc_date = nm and nm[0][1] or '' - service_person = nm and nm[0][2] or '' - work_done = nm and nm[0][3] or '' + if not self.maintenance_schedule: + for d in self.get('purposes'): + if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' : + if flag==1: + mntc_date = self.mntc_date + service_person = d.service_person + work_done = d.work_done + status = "Open" + if self.completion_status == 'Fully Completed': + status = 'Closed' + elif self.completion_status == 'Partially Completed': + status = 'Work In Progress' else: - status = 'Open' - mntc_date = None - service_person = None - work_done = None + nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name)) - wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname) - wc_doc.update({ - 'resolution_date': mntc_date, - 'resolved_by': service_person, - 'resolution_details': work_done, - 'status': status - }) + if nm: + status = 'Work In Progress' + mntc_date = nm and nm[0][1] or '' + service_person = nm and nm[0][2] or '' + work_done = nm and nm[0][3] or '' + else: + status = 'Open' + mntc_date = None + service_person = None + work_done = None - wc_doc.db_update() + wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname) + wc_doc.update({ + 'resolution_date': mntc_date, + 'resolved_by': service_person, + 'resolution_details': work_done, + 'status': status + }) + + wc_doc.db_update() def check_if_last_visit(self): """check if last maintenance visit against same sales order/ Warranty Claim""" From d071586589bb24162e268ec0fb4e295334ac4067 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Thu, 27 May 2021 17:48:43 +0530 Subject: [PATCH 107/159] refactor: removed maintenance schedule detail link --- .../maintenance_visit_purpose.json | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 0d19d708d9..158f143ae8 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -17,8 +17,7 @@ "work_details", "work_done", "prevdoc_doctype", - "prevdoc_docname", - "prevdoc_detail_docname" + "prevdoc_docname" ], "fields": [ { @@ -90,44 +89,14 @@ "fieldtype": "Link", "hidden": 1, "label": "Document Type", - "no_copy": 1, - "oldfieldname": "prevdoc_doctype", - "oldfieldtype": "Data", - "options": "DocType", - "print_hide": 1, - "print_width": "150px", - "read_only": 1, - "report_hide": 1, - "width": "150px" + "options": "DocType" }, { "fieldname": "prevdoc_docname", "fieldtype": "Dynamic Link", - "label": "Against Document No", - "no_copy": 1, - "oldfieldname": "prevdoc_docname", - "oldfieldtype": "Data", - "options": "prevdoc_doctype", - "print_hide": 1, - "print_width": "160px", - "read_only": 1, - "report_hide": 1, - "width": "160px" - }, - { - "fieldname": "prevdoc_detail_docname", - "fieldtype": "Link", "hidden": 1, - "label": "Against Document Detail No", - "no_copy": 1, - "oldfieldname": "prevdoc_detail_docname", - "oldfieldtype": "Data", - "options": "Maintenance Schedule Detail", - "print_hide": 1, - "print_width": "160px", - "read_only": 1, - "report_hide": 1, - "width": "160px" + "label": "Against Document No", + "options": "prevdoc_doctype" }, { "fieldname": "column_break_3", @@ -141,7 +110,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-21 11:16:52.025914", + "modified": "2021-05-27 17:47:21.474282", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", From 0b02f1335f136a00066dd62b4d06f29cfe157d25 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 28 May 2021 11:04:34 +0530 Subject: [PATCH 108/159] refactor: removed redundant links in test case --- .../doctype/maintenance_schedule/test_maintenance_schedule.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 08282b4c3d..09981bad05 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -65,8 +65,6 @@ class TestMaintenanceSchedule(unittest.TestCase): 'description': "test", 'work_done': "test", 'service_person': "Sales Team", - 'prevdoc_docname' :ms.name, - 'prevdoc_doctype': ms.doctype, }]) visit.save() visit.submit() From faa25166a91022dbb969f0753fef8f07a0d01eef Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 28 May 2021 11:23:18 +0530 Subject: [PATCH 109/159] revert: "ci: Do not generate coverage report for hotfix branch" Reverts: https://github.com/frappe/erpnext/commit/bcf116422a2d7ed1a4ea33eda1a894b055bbf5b0 --- .github/workflows/server-tests.yml | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index de5cc61d1e..92685e2177 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -77,3 +77,34 @@ jobs: TYPE: server CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io + + - name: Upload Coverage Data + run: | + cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + cd ${GITHUB_WORKSPACE} + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: run-${{ matrix.container }} + COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} + COVERALLS_PARALLEL: true + + coveralls: + name: Coverage Wrap Up + needs: test + container: python:3-slim + runs-on: ubuntu-18.04 + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Coveralls Finished + run: | + cd ${GITHUB_WORKSPACE} + pip3 install coverage==5.5 + pip3 install coveralls==3.0.1 + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d24eccd623a60d99bc6981f484fc5b9439cd8dd0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 11:36:24 +0530 Subject: [PATCH 110/159] fix(plaid): cannot reset plaid link for a bank account --- erpnext/accounts/doctype/bank/bank.js | 102 ++++++++++++++++++ .../doctype/plaid_settings/plaid_connector.py | 2 + .../doctype/plaid_settings/plaid_settings.py | 42 ++++++-- 3 files changed, 140 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 059e1d3158..337c93ef61 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -25,6 +25,10 @@ frappe.ui.form.on('Bank', { frm.add_custom_button(__('Refresh Plaid Link'), () => { new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token); }); + + frm.add_custom_button(__('Reset Plaid Link'), () => { + new erpnext.integrations.plaidLink(frm); + }); } } }); @@ -121,3 +125,101 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' }); } }; + +erpnext.integrations.plaidLink = class plaidLink { + constructor(parent) { + this.frm = parent; + this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; + this.init_config(); + } + + async init_config() { + this.product = ["auth", "transactions"]; + this.plaid_env = this.frm.doc.plaid_env; + this.client_name = frappe.boot.sitename; + this.token = await this.get_link_token(); + this.init_plaid(); + } + + async get_link_token() { + const token = await this.frm.call("get_link_token").then(resp => resp.message); + if (!token) { + frappe.throw(__('Cannot retrieve link token. Check Error Log for more information')); + } + return token; + } + + init_plaid() { + const me = this; + me.loadScript(me.plaidUrl) + .then(() => { + me.onScriptLoaded(me); + }) + .then(() => { + if (me.linkHandler) { + me.linkHandler.open(); + } + }) + .catch((error) => { + me.onScriptError(error); + }); + } + + loadScript(src) { + return new Promise(function (resolve, reject) { + if (document.querySelector('script[src="' + src + '"]')) { + resolve(); + return; + } + const el = document.createElement('script'); + el.type = 'text/javascript'; + el.async = true; + el.src = src; + el.addEventListener('load', resolve); + el.addEventListener('error', reject); + el.addEventListener('abort', reject); + document.head.appendChild(el); + }); + } + + onScriptLoaded(me) { + me.linkHandler = Plaid.create({ + clientName: me.client_name, + product: me.product, + env: me.plaid_env, + token: me.token, + onSuccess: me.plaid_success + }); + } + + onScriptError(error) { + frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information")); + console.log(error); + } + + plaid_success(token, response) { + const me = this; + + frappe.prompt({ + fieldtype: "Link", + options: "Company", + label: __("Company"), + fieldname: "company", + reqd: 1 + }, (data) => { + me.company = data.company; + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', { + token: token, + response: response + }).then((result) => { + frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', { + response: response, + bank: result, + company: me.company + }); + }).then(() => { + frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' }); + }); + }, __("Select a company"), __("Continue")); + } +}; diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index 5f990cdd03..42d4b9b2b4 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -99,5 +99,7 @@ class PlaidConnector(): response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions)) transactions.extend(response["transactions"]) return transactions + except ItemError as e: + raise e except Exception: frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index ce15e47c5e..3ef069b5e2 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -12,6 +12,7 @@ from frappe.desk.doctype.tag.tag import add_tag from frappe.model.document import Document from frappe.utils import add_months, formatdate, getdate, today +from plaid.errors import ItemError class PlaidSettings(Document): @staticmethod @@ -51,7 +52,7 @@ def add_institution(token, response): }) bank.insert() except Exception: - frappe.throw(frappe.get_traceback()) + frappe.log_error(frappe.get_traceback(), title=_('Plaid Link Error')) else: bank = frappe.get_doc("Bank", response["institution"]["name"]) bank.plaid_access_token = access_token @@ -83,7 +84,12 @@ def add_bank_accounts(response, bank, company): if not acc_subtype: add_account_subtype(account["subtype"]) - if not frappe.db.exists("Bank Account", dict(integration_id=account["id"])): + existing_bank_account = frappe.db.exists("Bank Account", { + 'account_name': account["name"], + 'bank': bank["bank_name"] + }) + + if not existing_bank_account: try: new_account = frappe.get_doc({ "doctype": "Bank Account", @@ -103,10 +109,27 @@ def add_bank_accounts(response, bank, company): except frappe.UniqueValidationError: frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"])) except Exception: - frappe.throw(frappe.get_traceback()) + frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error")) + frappe.throw(_("There was an error creating Bank Account while linking with Plaid."), + title=_("Plaid Link Failed")) else: - result.append(frappe.db.get_value("Bank Account", dict(integration_id=account["id"]), "name")) + try: + existing_account = frappe.get_doc('Bank Account', existing_bank_account) + existing_account.update({ + "bank": bank["bank_name"], + "account_name": account["name"], + "account_type": account.get("type", ""), + "account_subtype": account.get("subtype", ""), + "mask": account.get("mask", ""), + "integration_id": account["id"] + }) + existing_account.save() + result.append(existing_bank_account) + except Exception: + frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error")) + frappe.throw(_("There was an error updating Bank Account {} while linking with Plaid.").format( + existing_bank_account), title=_("Plaid Link Failed")) return result @@ -172,9 +195,16 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): account_id = None plaid = PlaidConnector(access_token) - transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) - return transactions + try: + transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) + except ItemError as e: + if e.code == "ITEM_LOGIN_REQUIRED": + msg = _("There was an error syncing transactions.") + " " + msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " " + frappe.log_error(msg, title=_("Plaid Link Refresh Required")) + + return transactions or [] def new_bank_transaction(transaction): From bbce2e91a375feef3b1dd7e26c2239769e11a730 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 11:56:47 +0530 Subject: [PATCH 111/159] fix: reset plaid link button --- erpnext/accounts/doctype/bank/bank.js | 4 ---- .../doctype/plaid_settings/plaid_settings.js | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 337c93ef61..f2c599c5c1 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -25,10 +25,6 @@ frappe.ui.form.on('Bank', { frm.add_custom_button(__('Refresh Plaid Link'), () => { new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token); }); - - frm.add_custom_button(__('Reset Plaid Link'), () => { - new erpnext.integrations.plaidLink(frm); - }); } } }); diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js index bbc2ca8846..37bf282450 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -16,6 +16,10 @@ frappe.ui.form.on('Plaid Settings', { new erpnext.integrations.plaidLink(frm); }); + frm.add_custom_button(__('Reset Plaid Link'), () => { + new erpnext.integrations.plaidLink(frm); + }); + frm.add_custom_button(__("Sync Now"), () => { frappe.call({ method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization", From 20be7fb93dbe7db28920c44fd1815607fe9da249 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 28 May 2021 11:57:38 +0530 Subject: [PATCH 112/159] fix(India): Show only company addresses for ITC reversal entry --- .../doctype/journal_entry/regional/india.js | 17 +++++++++++++++++ .../doctype/gstr_3b_report/gstr_3b_report.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 erpnext/accounts/doctype/journal_entry/regional/india.js diff --git a/erpnext/accounts/doctype/journal_entry/regional/india.js b/erpnext/accounts/doctype/journal_entry/regional/india.js new file mode 100644 index 0000000000..75a69ac0cf --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry/regional/india.js @@ -0,0 +1,17 @@ +frappe.ui.form.on("Journal Entry", { + refresh: function(frm) { + frm.set_query('company_address', function(doc) { + if(!doc.company) { + frappe.throw(__('Please set Company')); + } + + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { + link_doctype: 'Company', + link_name: doc.company + } + }; + }); + } +}); \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 3ddcc58867..641520437f 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -310,7 +310,7 @@ class GSTR3BReport(Document): self.report_dict['sup_details']['osup_det']['txval'] += taxable_value gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category') - place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply', '00-Other Territory') + place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply') or '00-Other Territory' if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \ self.gst_details.get("gst_state") != place_of_supply.split("-")[1]: From 05386ff12f22ac672bb0b87c10257a185dc1db78 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 12:58:18 +0530 Subject: [PATCH 113/159] chore: remove unwanted method --- erpnext/accounts/doctype/bank/bank.js | 100 +------------------------- 1 file changed, 1 insertion(+), 99 deletions(-) diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index f2c599c5c1..19041a3f73 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -120,102 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { plaid_success(token, response) { frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' }); } -}; - -erpnext.integrations.plaidLink = class plaidLink { - constructor(parent) { - this.frm = parent; - this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; - this.init_config(); - } - - async init_config() { - this.product = ["auth", "transactions"]; - this.plaid_env = this.frm.doc.plaid_env; - this.client_name = frappe.boot.sitename; - this.token = await this.get_link_token(); - this.init_plaid(); - } - - async get_link_token() { - const token = await this.frm.call("get_link_token").then(resp => resp.message); - if (!token) { - frappe.throw(__('Cannot retrieve link token. Check Error Log for more information')); - } - return token; - } - - init_plaid() { - const me = this; - me.loadScript(me.plaidUrl) - .then(() => { - me.onScriptLoaded(me); - }) - .then(() => { - if (me.linkHandler) { - me.linkHandler.open(); - } - }) - .catch((error) => { - me.onScriptError(error); - }); - } - - loadScript(src) { - return new Promise(function (resolve, reject) { - if (document.querySelector('script[src="' + src + '"]')) { - resolve(); - return; - } - const el = document.createElement('script'); - el.type = 'text/javascript'; - el.async = true; - el.src = src; - el.addEventListener('load', resolve); - el.addEventListener('error', reject); - el.addEventListener('abort', reject); - document.head.appendChild(el); - }); - } - - onScriptLoaded(me) { - me.linkHandler = Plaid.create({ - clientName: me.client_name, - product: me.product, - env: me.plaid_env, - token: me.token, - onSuccess: me.plaid_success - }); - } - - onScriptError(error) { - frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information")); - console.log(error); - } - - plaid_success(token, response) { - const me = this; - - frappe.prompt({ - fieldtype: "Link", - options: "Company", - label: __("Company"), - fieldname: "company", - reqd: 1 - }, (data) => { - me.company = data.company; - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', { - token: token, - response: response - }).then((result) => { - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', { - response: response, - bank: result, - company: me.company - }); - }).then(() => { - frappe.show_alert({ message: __("Bank accounts added"), indicator: 'green' }); - }); - }, __("Select a company"), __("Continue")); - } -}; +}; \ No newline at end of file From 6fb218e03344ec00a8af0a8d8df57beacf89cf4b Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 28 May 2021 16:15:50 +0530 Subject: [PATCH 114/159] refactor: suggested changes --- .../doctype/maintenance_schedule/maintenance_schedule.js | 2 +- .../doctype/maintenance_visit/maintenance_visit.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index e2de941963..44712d543b 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -67,7 +67,7 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({ let schedules = me.frm.doc.schedules; let flag = schedules.some(schedule => schedule.completion_status === "Pending"); if (flag) { - this.frm.add_custom_button(__('Create Maintenance Visit'), function () { + this.frm.add_custom_button(__('Maintenance Visit'), function () { let options = ""; me.frm.call('get_pending_data', {data_type: "items"}).then(r => { diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 79d65c921a..7fffc942a0 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -20,9 +20,10 @@ class MaintenanceVisit(TransactionBase): def validate_maintenance_date(self): if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference') - start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) - if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): - frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date)) + if item_ref: + start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date']) + if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date): + frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date)) def validate(self): self.validate_serial_no() From b72b4c0bf9f2f19d80456407cddc4062a7174198 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 28 May 2021 17:38:01 +0530 Subject: [PATCH 115/159] fix(pos): rendering of broken image on pos --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 12 +++++++++++- .../selling/page/point_of_sale/pos_item_selector.js | 11 ++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 11a63b3d4a..8a989731cc 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -636,13 +636,23 @@ erpnext.PointOfSale.ItemCart = class { function get_item_image_html() { const { image, item_name } = item_data; if (image) { - return `
${image}
`; + return ` +
+ ${frappe.get_abbr(item_name)} +
`; } else { return `
${frappe.get_abbr(item_name)}
`; } } } + handle_broken_image($img) { + const item_abbr = $($img).attr('alt'); + $($img).parent().replaceWith(`
${item_abbr}
`); + } + scroll_to_item($item) { if ($item.length === 0) return; const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop(); 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 b8a82a9eda..b6109acc57 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -94,7 +94,11 @@ erpnext.PointOfSale.ItemSelector = class { ${qty_to_display}
- ${frappe.get_abbr(item.item_name)} + ${frappe.get_abbr(item.item_name)}
`; } else { return `
@@ -122,6 +126,11 @@ erpnext.PointOfSale.ItemSelector = class { ); } + handle_broken_image($img) { + const item_abbr = $($img).attr('alt'); + $($img).parent().replaceWith(`
${item_abbr}
`); + } + make_search_bar() { const me = this; const doc = me.events.get_frm().doc; From 431d3295b444d46b4bc44b5e882de507be006b42 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 28 May 2021 21:12:25 +0530 Subject: [PATCH 116/159] fix: use dictionary filter instead of list (#25874) Item query doesn't support list filter anymore. --- erpnext/manufacturing/doctype/work_order/work_order.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index a6086fb88d..3e5a72db9a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -76,9 +76,9 @@ frappe.ui.form.on("Work Order", { frm.set_query("production_item", function() { return { query: "erpnext.controllers.queries.item_query", - filters:[ - ['is_stock_item', '=',1] - ] + filters: { + "is_stock_item": 1, + } }; }); From 3be28054de5e6d917af9608d53c48604e3285450 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 31 May 2021 09:11:42 +0530 Subject: [PATCH 117/159] refactor: Vehicle Expenses Report (#25727) * refactor: Vehicle Expense Report * test: Vehicle Expenses Report * feat: Added Employee filter to report - fix Vehicle Log form view * fix: set currency fieldtype for chart data - added filters for employee and vehicle * fix: service expenses not getting set --- .../doctype/vehicle_log/test_vehicle_log.py | 81 +++--- .../hr/doctype/vehicle_log/vehicle_log.json | 12 +- .../vehicle_expenses/test_vehicle_expenses.py | 73 ++++++ .../vehicle_expenses/vehicle_expenses.js | 77 +++--- .../vehicle_expenses/vehicle_expenses.json | 35 +-- .../vehicle_expenses/vehicle_expenses.py | 231 ++++++++++++++---- 6 files changed, 374 insertions(+), 135 deletions(-) create mode 100644 erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py index cf0048c1a7..ed52c4e122 100644 --- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py +++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate,flt, cstr,random_string +from frappe.utils import nowdate, flt, cstr, random_string from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim @@ -18,23 +18,13 @@ class TestVehicleLog(unittest.TestCase): self.employee_id = make_employee("testdriver@example.com", company="_Test Company") self.license_plate = get_vehicle(self.employee_id) - + def tearDown(self): frappe.delete_doc("Vehicle", self.license_plate, force=1) frappe.delete_doc("Employee", self.employee_id, force=1) def test_make_vehicle_log_and_syncing_of_odometer_value(self): - vehicle_log = frappe.get_doc({ - "doctype": "Vehicle Log", - "license_plate": cstr(self.license_plate), - "employee": self.employee_id, - "date":frappe.utils.nowdate(), - "odometer":5010, - "fuel_qty":frappe.utils.flt(50), - "price": frappe.utils.flt(500) - }) - vehicle_log.save() - vehicle_log.submit() + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id) #checking value of vehicle odometer value on submit. vehicle = frappe.get_doc("Vehicle", self.license_plate) @@ -51,19 +41,9 @@ class TestVehicleLog(unittest.TestCase): self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled) vehicle_log.delete() - + def test_vehicle_log_fuel_expense(self): - vehicle_log = frappe.get_doc({ - "doctype": "Vehicle Log", - "license_plate": cstr(self.license_plate), - "employee": self.employee_id, - "date": frappe.utils.nowdate(), - "odometer":5010, - "fuel_qty":frappe.utils.flt(50), - "price": frappe.utils.flt(500) - }) - vehicle_log.save() - vehicle_log.submit() + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id) expense_claim = make_expense_claim(vehicle_log.name) fuel_expense = expense_claim.expenses[0].amount @@ -73,6 +53,18 @@ class TestVehicleLog(unittest.TestCase): frappe.delete_doc("Expense Claim", expense_claim.name) frappe.delete_doc("Vehicle Log", vehicle_log.name) + def test_vehicle_log_with_service_expenses(self): + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True) + + expense_claim = make_expense_claim(vehicle_log.name) + expenses = expense_claim.expenses[0].amount + self.assertEqual(expenses, 27000) + + vehicle_log.cancel() + frappe.delete_doc("Expense Claim", expense_claim.name) + frappe.delete_doc("Vehicle Log", vehicle_log.name) + + def get_vehicle(employee_id): license_plate=random_string(10).upper() vehicle = frappe.get_doc({ @@ -81,15 +73,46 @@ def get_vehicle(employee_id): "make": "Maruti", "model": "PCM", "employee": employee_id, - "last_odometer":5000, - "acquisition_date":frappe.utils.nowdate(), + "last_odometer": 5000, + "acquisition_date": nowdate(), "location": "Mumbai", "chassis_no": "1234ABCD", "uom": "Litre", - "vehicle_value":frappe.utils.flt(500000) + "vehicle_value": flt(500000) }) try: vehicle.insert() except frappe.DuplicateEntryError: pass - return license_plate \ No newline at end of file + return license_plate + + +def make_vehicle_log(license_plate, employee_id, with_services=False): + vehicle_log = frappe.get_doc({ + "doctype": "Vehicle Log", + "license_plate": cstr(license_plate), + "employee": employee_id, + "date": nowdate(), + "odometer": 5010, + "fuel_qty": flt(50), + "price": flt(500) + }) + + if with_services: + vehicle_log.append("service_detail", { + "service_item": "Oil Change", + "type": "Inspection", + "frequency": "Mileage", + "expense_amount": flt(500) + }) + vehicle_log.append("service_detail", { + "service_item": "Wheels", + "type": "Change", + "frequency": "Half Yearly", + "expense_amount": flt(1500) + }) + + vehicle_log.save() + vehicle_log.submit() + + return vehicle_log \ No newline at end of file diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.json b/erpnext/hr/doctype/vehicle_log/vehicle_log.json index 619e295ebe..4ea904542d 100644 --- a/erpnext/hr/doctype/vehicle_log/vehicle_log.json +++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2016-09-03 14:14:51.788550", "doctype": "DocType", @@ -10,7 +11,6 @@ "naming_series", "license_plate", "employee", - "column_break_4", "column_break_7", "model", "make", @@ -65,10 +65,6 @@ "options": "Employee", "reqd": 1 }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, { "fieldname": "column_break_7", "fieldtype": "Column Break" @@ -142,7 +138,6 @@ { "fieldname": "service_detail", "fieldtype": "Table", - "label": "Service Detail", "options": "Vehicle Service" }, { @@ -158,7 +153,7 @@ "fetch_from": "license_plate.last_odometer", "fieldname": "last_odometer", "fieldtype": "Int", - "label": "last Odometer Value ", + "label": "Last Odometer Value ", "read_only": 1, "reqd": 1 }, @@ -168,7 +163,8 @@ } ], "is_submittable": 1, - "modified": "2020-03-18 16:45:45.060761", + "links": [], + "modified": "2021-05-17 00:10:21.188352", "modified_by": "Administrator", "module": "HR", "name": "Vehicle Log", diff --git a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py new file mode 100644 index 0000000000..26e0f26392 --- /dev/null +++ b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py @@ -0,0 +1,73 @@ +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import unittest +import frappe +from frappe.utils import getdate +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim +from erpnext.hr.doctype.vehicle_log.test_vehicle_log import get_vehicle, make_vehicle_log +from erpnext.hr.report.vehicle_expenses.vehicle_expenses import execute +from erpnext.accounts.utils import get_fiscal_year + +class TestVehicleExpenses(unittest.TestCase): + @classmethod + def setUpClass(self): + frappe.db.sql('delete from `tabVehicle Log`') + + employee_id = frappe.db.sql('''select name from `tabEmployee` where name="testdriver@example.com"''') + self.employee_id = employee_id[0][0] if employee_id else None + if not self.employee_id: + self.employee_id = make_employee('testdriver@example.com', company='_Test Company') + + self.license_plate = get_vehicle(self.employee_id) + + def test_vehicle_expenses_based_on_fiscal_year(self): + vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True) + expense_claim = make_expense_claim(vehicle_log.name) + + # Based on Fiscal Year + filters = { + 'filter_based_on': 'Fiscal Year', + 'fiscal_year': get_fiscal_year(getdate())[0] + } + + report = execute(filters) + + expected_data = [{ + 'vehicle': self.license_plate, + 'make': 'Maruti', + 'model': 'PCM', + 'location': 'Mumbai', + 'log_name': vehicle_log.name, + 'odometer': 5010, + 'date': getdate(), + 'fuel_qty': 50.0, + 'fuel_price': 500.0, + 'fuel_expense': 25000.0, + 'service_expense': 2000.0, + 'employee': self.employee_id + }] + + self.assertEqual(report[1], expected_data) + + # Based on Date Range + fiscal_year = get_fiscal_year(getdate(), as_dict=True) + filters = { + 'filter_based_on': 'Date Range', + 'from_date': fiscal_year.year_start_date, + 'to_date': fiscal_year.year_end_date + } + + report = execute(filters) + self.assertEqual(report[1], expected_data) + + # clean up + vehicle_log.cancel() + frappe.delete_doc('Expense Claim', expense_claim.name) + frappe.delete_doc('Vehicle Log', vehicle_log.name) + + def tearDown(self): + frappe.delete_doc('Vehicle', self.license_plate, force=1) + frappe.delete_doc('Employee', self.employee_id, force=1) diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js index b66bebbec1..879acd18ef 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js @@ -1,31 +1,52 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Vehicle Expenses"] = { - "filters": [ - { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1, - "on_change": function(query_report) { - var fiscal_year = query_report.get_values().fiscal_year; - if (!fiscal_year) { - return; - } - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - - frappe.query_report.set_filter({ - from_date: fy.year_start_date, - to_date: fy.year_end_date - }); - }); - } - } - ] - } -}); +frappe.query_reports["Vehicle Expenses"] = { + "filters": [ + { + "fieldname": "filter_based_on", + "label": __("Filter Based On"), + "fieldtype": "Select", + "options": ["Fiscal Year", "Date Range"], + "default": ["Fiscal Year"], + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", + "reqd": 1 + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "depends_on": "eval: doc.filter_based_on == 'Date Range'", + "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12) + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "depends_on": "eval: doc.filter_based_on == 'Date Range'", + "default": frappe.datetime.nowdate() + }, + { + "fieldname": "vehicle", + "label": __("Vehicle"), + "fieldtype": "Link", + "options": "Vehicle" + }, + { + "fieldname": "employee", + "label": __("Employee"), + "fieldtype": "Link", + "options": "Employee" + } + ] +}; diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json index 2ab0c143b8..1a3e5a93bb 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json @@ -1,20 +1,23 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2016-09-09 03:33:40.605734", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 2, - "is_standard": "Yes", - "modified": "2017-02-24 19:59:18.641284", - "modified_by": "Administrator", - "module": "HR", - "name": "Vehicle Expenses", - "owner": "Administrator", - "ref_doctype": "Vehicle", - "report_name": "Vehicle Expenses", - "report_type": "Script Report", + "add_total_row": 1, + "columns": [], + "creation": "2016-09-09 03:33:40.605734", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 2, + "is_standard": "Yes", + "modified": "2021-05-16 22:48:22.767535", + "modified_by": "Administrator", + "module": "HR", + "name": "Vehicle Expenses", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Vehicle", + "report_name": "Vehicle Expenses", + "report_type": "Script Report", "roles": [ { "role": "Fleet Manager" diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py index eab58ffbbc..d847cbb5c9 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py @@ -5,86 +5,209 @@ from __future__ import unicode_literals import frappe import erpnext from frappe import _ -from frappe.utils import flt,cstr +from frappe.utils import flt from erpnext.accounts.report.financial_statements import get_period_list def execute(filters=None): - columns, data, chart = [], [], [] - if filters.get('fiscal_year'): - company = erpnext.get_default_company() - period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'), - '', '', 'Fiscal Year', 'Monthly', company=company) - columns=get_columns() - data=get_log_data(filters) - chart=get_chart_data(data,period_list) + filters = frappe._dict(filters or {}) + + columns = get_columns() + data = get_vehicle_log_data(filters) + chart = get_chart_data(data, filters) + return columns, data, None, chart def get_columns(): - columns = [_("License") + ":Link/Vehicle:100", _('Create') + ":data:50", - _("Model") + ":data:50", _("Location") + ":data:100", - _("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80", - _("Date") + ":Date:100", _("Fuel Qty") + ":Float:80", - _("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100", - _("Service Expense") + ":Float:100" + return [ + { + 'fieldname': 'vehicle', + 'fieldtype': 'Link', + 'label': _('Vehicle'), + 'options': 'Vehicle', + 'width': 150 + }, + { + 'fieldname': 'make', + 'fieldtype': 'Data', + 'label': _('Make'), + 'width': 100 + }, + { + 'fieldname': 'model', + 'fieldtype': 'Data', + 'label': _('Model'), + 'width': 80 + }, + { + 'fieldname': 'location', + 'fieldtype': 'Data', + 'label': _('Location'), + 'width': 100 + }, + { + 'fieldname': 'log_name', + 'fieldtype': 'Link', + 'label': _('Vehicle Log'), + 'options': 'Vehicle Log', + 'width': 100 + }, + { + 'fieldname': 'odometer', + 'fieldtype': 'Int', + 'label': _('Odometer Value'), + 'width': 120 + }, + { + 'fieldname': 'date', + 'fieldtype': 'Date', + 'label': _('Date'), + 'width': 100 + }, + { + 'fieldname': 'fuel_qty', + 'fieldtype': 'Float', + 'label': _('Fuel Qty'), + 'width': 80 + }, + { + 'fieldname': 'fuel_price', + 'fieldtype': 'Float', + 'label': _('Fuel Price'), + 'width': 100 + }, + { + 'fieldname': 'fuel_expense', + 'fieldtype': 'Currency', + 'label': _('Fuel Expense'), + 'width': 150 + }, + { + 'fieldname': 'service_expense', + 'fieldtype': 'Currency', + 'label': _('Service Expense'), + 'width': 150 + }, + { + 'fieldname': 'employee', + 'fieldtype': 'Link', + 'label': _('Employee'), + 'options': 'Employee', + 'width': 150 + } ] + return columns -def get_log_data(filters): - fy = frappe.db.get_value('Fiscal Year', filters.get('fiscal_year'), ['year_start_date', 'year_end_date'], as_dict=True) - data = frappe.db.sql("""select - vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model", - vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer", - log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price", - log.fuel_qty * log.price as "Fuel Expense" - from + +def get_vehicle_log_data(filters): + start_date, end_date = get_period_dates(filters) + conditions, values = get_conditions(filters) + + data = frappe.db.sql(""" + SELECT + vhcl.license_plate as vehicle, vhcl.make, vhcl.model, + vhcl.location, log.name as log_name, log.odometer, + log.date, log.employee, log.fuel_qty, + log.price as fuel_price, + log.fuel_qty * log.price as fuel_expense + FROM `tabVehicle` vhcl,`tabVehicle Log` log - where - vhcl.license_plate = log.license_plate and log.docstatus = 1 and date between %s and %s - order by date""" ,(fy.year_start_date, fy.year_end_date), as_dict=1) - dl=list(data) - for row in dl: - row["Service Expense"]= get_service_expense(row["Log"]) - return dl + WHERE + vhcl.license_plate = log.license_plate + and log.docstatus = 1 + and date between %(start_date)s and %(end_date)s + {0} + ORDER BY date""".format(conditions), values, as_dict=1) + + for row in data: + row['service_expense'] = get_service_expense(row.log_name) + + return data + + +def get_conditions(filters): + conditions = '' + + start_date, end_date = get_period_dates(filters) + values = { + 'start_date': start_date, + 'end_date': end_date + } + + if filters.employee: + conditions += ' and log.employee = %(employee)s' + values['employee'] = filters.employee + + if filters.vehicle: + conditions += ' and vhcl.license_plate = %(vehicle)s' + values['vehicle'] = filters.vehicle + + return conditions, values + + +def get_period_dates(filters): + if filters.filter_based_on == 'Fiscal Year' and filters.fiscal_year: + fy = frappe.db.get_value('Fiscal Year', filters.fiscal_year, + ['year_start_date', 'year_end_date'], as_dict=True) + return fy.year_start_date, fy.year_end_date + else: + return filters.from_date, filters.to_date + def get_service_expense(logname): - expense_amount = frappe.db.sql("""select sum(expense_amount) - from `tabVehicle Log` log,`tabVehicle Service` ser - where ser.parent=log.name and log.name=%s""",logname) - return flt(expense_amount[0][0]) if expense_amount else 0 + expense_amount = frappe.db.sql(""" + SELECT sum(expense_amount) + FROM + `tabVehicle Log` log, `tabVehicle Service` service + WHERE + service.parent=log.name and log.name=%s + """, logname) + + return flt(expense_amount[0][0]) if expense_amount else 0.0 + + +def get_chart_data(data, filters): + period_list = get_period_list(filters.fiscal_year, filters.fiscal_year, + filters.from_date, filters.to_date, filters.filter_based_on, 'Monthly') + + fuel_data, service_data = [], [] -def get_chart_data(data,period_list): - fuel_exp_data,service_exp_data,fueldata,servicedata = [],[],[],[] - service_exp_data = [] - fueldata = [] for period in period_list: - total_fuel_exp=0 - total_ser_exp=0 - for row in data: - if row["Date"] <= period.to_date and row["Date"] >= period.from_date: - total_fuel_exp+=flt(row["Fuel Expense"]) - total_ser_exp+=flt(row["Service Expense"]) - fueldata.append([period.key,total_fuel_exp]) - servicedata.append([period.key,total_ser_exp]) + total_fuel_exp = 0 + total_service_exp = 0 + + for row in data: + if row.date <= period.to_date and row.date >= period.from_date: + total_fuel_exp += flt(row.fuel_expense) + total_service_exp += flt(row.service_expense) + + fuel_data.append([period.key, total_fuel_exp]) + service_data.append([period.key, total_service_exp]) + + labels = [period.label for period in period_list] + fuel_exp_data= [row[1] for row in fuel_data] + service_exp_data= [row[1] for row in service_data] - labels = [period.key for period in period_list] - fuel_exp_data= [row[1] for row in fueldata] - service_exp_data= [row[1] for row in servicedata] datasets = [] if fuel_exp_data: datasets.append({ - 'name': 'Fuel Expenses', + 'name': _('Fuel Expenses'), 'values': fuel_exp_data }) + if service_exp_data: datasets.append({ - 'name': 'Service Expenses', + 'name': _('Service Expenses'), 'values': service_exp_data }) + chart = { - "data": { + 'data': { 'labels': labels, 'datasets': datasets - } + }, + 'type': 'line', + 'fieldtype': 'Currency' } - chart["type"] = "line" + return chart From 89f621baa465942efb98487684c7d4feff1db2ca Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 31 May 2021 09:56:49 +0530 Subject: [PATCH 118/159] fix(pos): closing entry shows incorrect expected amount (#25868) --- .../accounts/doctype/pos_closing_entry/pos_closing_entry.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 8c5a34a0d8..6418d73090 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -107,7 +107,7 @@ frappe.ui.form.on('POS Closing Entry', { frm.set_value("taxes", []); for (let row of frm.doc.payment_reconciliation) { - row.expected_amount = 0; + row.expected_amount = row.opening_amount; } for (let row of frm.doc.pos_transactions) { @@ -154,6 +154,9 @@ function add_to_pos_transaction(d, frm) { function refresh_payments(d, frm) { d.payments.forEach(p => { const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment); + if (p.account == d.account_for_change_amount) { + p.amount -= flt(d.change_amount); + } if (payment) { payment.expected_amount += flt(p.amount); payment.difference = payment.closing_amount - payment.expected_amount; From 80540d38ba5c17ec12eab15531bc63be09eeb609 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 31 May 2021 12:06:20 +0200 Subject: [PATCH 119/159] fix: add_deduct_tax in Purchase Taxes setup (#25871) --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 429a558c58..5019837441 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -106,6 +106,9 @@ def make_taxes_and_charges_template(company_name, doctype, template): 'charge_type': 'On Net Total' } + if doctype == 'Purchase Taxes and Charges Template': + tax_row_defaults['add_deduct_tax'] = 'Add' + # if account_head is a dict, search or create the account and get it's name if isinstance(account_data, dict): tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate')) From 0358b6474368462690211a0191138b644c3bd3be Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 31 May 2021 15:59:32 +0530 Subject: [PATCH 120/159] fix: featching serialized items --- .../stock_reconciliation/stock_reconciliation.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 7e216d6181..32e0ccb93f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -477,19 +477,19 @@ class StockReconciliation(StockController): def get_items(warehouse, posting_date, posting_time, company): lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) items = frappe.db.sql(""" - select i.name, i.item_name, bin.warehouse + select i.name, i.item_name, bin.warehouse, i.has_serial_no from tabBin bin, tabItem i where i.name=bin.item_code and i.disabled=0 and i.is_stock_item = 1 - and i.has_variants = 0 and i.has_serial_no = 0 and i.has_batch_no = 0 + and i.has_variants = 0 and i.has_batch_no = 0 and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse) """, (lft, rgt)) items += frappe.db.sql(""" - select i.name, i.item_name, id.default_warehouse + select i.name, i.item_name, id.default_warehouse, i.has_serial_no from tabItem i, `tabItem Default` id where i.name = id.parent and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse) - and i.is_stock_item = 1 and i.has_serial_no = 0 and i.has_batch_no = 0 + and i.is_stock_item = 1 and i.has_batch_no = 0 and i.has_variants = 0 and i.disabled = 0 and id.company=%s group by i.name """, (lft, rgt, company)) @@ -497,7 +497,7 @@ def get_items(warehouse, posting_date, posting_time, company): res = [] for d in set(items): stock_bal = get_stock_balance(d[0], d[2], posting_date, posting_time, - with_valuation_rate=True) + with_valuation_rate=True , with_serial_no=cint(d[3])) if frappe.db.get_value("Item", d[0], "disabled") == 0: res.append({ @@ -507,7 +507,9 @@ def get_items(warehouse, posting_date, posting_time, company): "item_name": d[1], "valuation_rate": stock_bal[1], "current_qty": stock_bal[0], - "current_valuation_rate": stock_bal[1] + "current_valuation_rate": stock_bal[1], + "current_serial_no": stock_bal[2] if cint(d[3]) else '', + "serial_no": stock_bal[2] if cint(d[3]) else '' }) return res From 0ea6725baaf61637aa1625098ccc5e3365c40730 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 31 May 2021 19:48:31 +0530 Subject: [PATCH 121/159] fix: Rename Loan Management workspace to Loans (#25856) --- .../workspace/loan_management/loan_management.json | 6 +++--- erpnext/patches.txt | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json index 18559dceef..d0b67f7c64 100644 --- a/erpnext/loan_management/workspace/loan_management/loan_management.json +++ b/erpnext/loan_management/workspace/loan_management/loan_management.json @@ -12,7 +12,7 @@ "idx": 0, "is_default": 0, "is_standard": 1, - "label": "Loan Management", + "label": "Loans", "links": [ { "hidden": 0, @@ -220,10 +220,10 @@ "type": "Link" } ], - "modified": "2021-02-18 17:31:53.586508", + "modified": "2021-05-25 17:31:53.586508", "modified_by": "Administrator", "module": "Loan Management", - "name": "Loan Management", + "name": "Loans", "owner": "Administrator", "pin_to_bottom": 0, "pin_to_top": 0, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3a7aa1bce3..93689a0ef3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -779,6 +779,7 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed +execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True) erpnext.patches.v13_0.update_timesheet_changes erpnext.patches.v13_0.set_training_event_attendance erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold From 908b0090e967cd2503d69e4df99878c048ab35ba Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 31 May 2021 19:49:03 +0530 Subject: [PATCH 122/159] fix: Create POS Invoice for Product Bundles (#25847) Co-authored-by: Saqib --- .../doctype/pos_invoice/pos_invoice.py | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index f55fdab21c..8ec4ef224c 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -140,6 +140,7 @@ class POSInvoice(SalesInvoice): return available_stock = get_stock_availability(d.item_code, d.warehouse) + item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty) if flt(available_stock) <= 0: frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.') @@ -213,8 +214,9 @@ class POSInvoice(SalesInvoice): for d in self.get("items"): is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item") if not is_stock_item: - frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ") - .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) + if not frappe.db.exists('Product Bundle', d.item_code): + frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.") + .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) def validate_mode_of_payment(self): if len(self.payments) == 0: @@ -455,15 +457,36 @@ class POSInvoice(SalesInvoice): @frappe.whitelist() def get_stock_availability(item_code, warehouse): + if frappe.db.get_value('Item', item_code, 'is_stock_item'): + bin_qty = get_bin_qty(item_code, warehouse) + pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) + return bin_qty - pos_sales_qty + else: + if frappe.db.exists('Product Bundle', item_code): + return get_bundle_availability(item_code, warehouse) + +def get_bundle_availability(bundle_item_code, warehouse): + product_bundle = frappe.get_doc('Product Bundle', bundle_item_code) + + bundle_bin_qty = 1000000 + for item in product_bundle.items: + item_bin_qty = get_bin_qty(item.item_code, warehouse) + item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse) + available_qty = item_bin_qty - item_pos_reserved_qty + + max_available_bundles = available_qty / item.qty + if bundle_bin_qty > max_available_bundles: + bundle_bin_qty = max_available_bundles + + pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse) + return bundle_bin_qty - pos_sales_qty + +def get_bin_qty(item_code, warehouse): bin_qty = frappe.db.sql("""select actual_qty from `tabBin` where item_code = %s and warehouse = %s limit 1""", (item_code, warehouse), as_dict=1) - pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) - - bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0 - - return bin_qty - pos_sales_qty + return bin_qty[0].actual_qty or 0 if bin_qty else 0 def get_pos_reserved_qty(item_code, warehouse): reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty @@ -522,4 +545,4 @@ def add_return_modes(doc, pos_profile): mode_of_payment = pos_payment_method.mode_of_payment if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]: payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) - append_payment(payment_mode[0]) \ No newline at end of file + append_payment(payment_mode[0]) From 022b5c973a6fced4271bb4b77360dbd4c1425d03 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 31 May 2021 19:49:53 +0530 Subject: [PATCH 123/159] fix: wrap dates in getdate for leave application (#25899) * fix: wrap dates in getdate for leave application * fix: translation issue * fix: replaced today with getdate --- .../leave_application/leave_application.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 0bf551e178..cee6f374fd 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -4,8 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ - comma_or, get_fullname, add_days, nowdate, get_datetime_str +from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee @@ -85,7 +84,7 @@ class LeaveApplication(Document): def validate_dates(self): if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"): - if self.from_date and self.from_date < frappe.utils.today(): + if self.from_date and getdate(self.from_date) < getdate(): allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application") if allowed_role not in frappe.get_roles(): frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role)) @@ -248,9 +247,9 @@ class LeaveApplication(Document): self.throw_overlap_error(d) def throw_overlap_error(self, d): - msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee, - d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \ - + """ {0}""".format(d["name"]) + form_link = get_link_to_form("Leave Application", d.name) + msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(self.employee, + d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date']), form_link) frappe.throw(msg, OverlapError) def get_total_leaves_on_half_day(self): @@ -356,7 +355,7 @@ class LeaveApplication(Document): sender = dict() sender['email'] = frappe.get_doc('User', frappe.session.user).email - sender['full_name'] = frappe.utils.get_fullname(sender['email']) + sender['full_name'] = get_fullname(sender['email']) try: frappe.sendmail( @@ -823,4 +822,4 @@ def get_leave_approver(employee): leave_approver = frappe.db.get_value('Department Approver', {'parent': department, 'parentfield': 'leave_approvers', 'idx': 1}, 'approver') - return leave_approver \ No newline at end of file + return leave_approver From c8b34798feadff95d38f7d5961d7b8aa83730a8e Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 1 Jun 2021 10:44:26 +0530 Subject: [PATCH 124/159] fix(Delivery Note): Assign Product Bundle's conversion_factor to Packed Items (#25840) --- erpnext/stock/doctype/packed_item/packed_item.json | 8 +++++++- erpnext/stock/doctype/packed_item/packed_item.py | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index f1d7f8c8c9..bb396e806f 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -13,6 +13,7 @@ "section_break_6", "warehouse", "target_warehouse", + "conversion_factor", "column_break_9", "qty", "uom", @@ -209,13 +210,18 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-24 09:25:13.050151", + "modified": "2021-05-26 07:08:05.111385", "modified_by": "Administrator", "module": "Stock", "name": "Packed Item", diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 5341f29853..4ab71bdf62 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -53,6 +53,7 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip pi.parent_detail_docname = main_item_row.name pi.uom = item.stock_uom pi.qty = flt(qty) + pi.conversion_factor = main_item_row.conversion_factor if description and not pi.description: pi.description = description if not pi.warehouse and not doc.amended_from: From 1175e0686bd920356264522200c40769f76bf658 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 1 Jun 2021 10:53:00 +0530 Subject: [PATCH 125/159] refactor: leave balance report (#25771) * refactor: leave balance report * refactor: sql to orm --- .../employee_leave_balance.js | 19 +++- .../employee_leave_balance.py | 88 ++++++++++++++----- erpnext/hr/utils.py | 1 + 3 files changed, 85 insertions(+), 23 deletions(-) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js index 05728a297b..8bb3457190 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js @@ -37,5 +37,22 @@ frappe.query_reports["Employee Leave Balance"] = { "fieldtype": "Link", "options": "Employee", } - ] + ], + + onload: () => { + frappe.call({ + type: "GET", + method: "erpnext.hr.utils.get_leave_period", + args: { + "from_date": frappe.defaults.get_default("year_start_date"), + "to_date": frappe.defaults.get_default("year_end_date"), + "company": frappe.defaults.get_user_default("Company") + }, + freeze: true, + callback: (data) => { + frappe.query_report.set_filter_value("from_date", data.message[0].from_date); + frappe.query_report.set_filter_value("to_date", data.message[0].to_date); + } + }); + } } diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 06f9160363..4dd4570e8c 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -6,15 +6,16 @@ import frappe from frappe.utils import flt, add_days from frappe import _ from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on +from itertools import groupby def execute(filters=None): if filters.to_date <= filters.from_date: - frappe.throw(_('"From date" can not be greater than or equal to "To date"')) + frappe.throw(_('"From Date" can not be greater than or equal to "To Date"')) columns = get_columns() data = get_data(filters) - - return columns, data + charts = get_chart_data(data) + return columns, data, None, charts def get_columns(): columns = [{ @@ -31,9 +32,10 @@ def get_columns(): 'options': 'Employee' }, { 'label': _('Employee Name'), - 'fieldtype': 'Data', + 'fieldtype': 'Dynamic Link', 'fieldname': 'employee_name', 'width': 100, + 'options': 'employee' }, { 'label': _('Opening Balance'), 'fieldtype': 'float', @@ -64,8 +66,7 @@ def get_columns(): return columns def get_data(filters): - leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC") - + leave_types = frappe.db.get_list('Leave Type', pluck='name', order_by='name') conditions = get_conditions(filters) user = frappe.session.user @@ -113,12 +114,8 @@ def get_data(filters): # not be shown on the basis of days left it create in user mind for carry_forward leave row.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken)) - - row.indent = 1 data.append(row) - new_leaves_allocated = 0 - return data @@ -129,27 +126,37 @@ def get_conditions(filters): if filters.get('employee'): conditions['name'] = filters.get('employee') - if filters.get('employee'): - conditions['name'] = filters.get('employee') - if filters.get('company'): conditions['company'] = filters.get('company') + if filters.get('department'): + conditions['department'] = filters.get('department') + return conditions def get_department_leave_approver_map(department=None): - conditions='' - if department: - conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department} # get current department and all its child - department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec - + department_list = frappe.get_list('Department', + filters={ + 'disabled': 0 + }, + or_filters={ + 'name': department, + 'parent_department': department + }, + fields=['name'], + pluck='name' + ) # retrieve approvers list from current department and from its subsequent child departments - approver_list = frappe.get_all('Department Approver', filters={ - 'parentfield': 'leave_approvers', - 'parent': ('in', department_list) - }, fields=['parent', 'approver'], as_list=1) + approver_list = frappe.get_all('Department Approver', + filters={ + 'parentfield': 'leave_approvers', + 'parent': ('in', department_list) + }, + fields=['parent', 'approver'], + as_list=1 + ) approvers = {} @@ -190,3 +197,40 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type): new_allocation += record.leaves return new_allocation, expired_leaves + +def get_chart_data(data): + labels = [] + datasets = [] + employee_data = data + + if data and data[0].get('employee_name'): + get_dataset_for_chart(employee_data, datasets, labels) + + chart = { + 'data': { + 'labels': labels, + 'datasets': datasets + }, + 'type': 'bar', + 'colors': ['#456789', '#EE8888', '#7E77BF'] + } + + return chart + +def get_dataset_for_chart(employee_data, datasets, labels): + leaves = [] + employee_data = sorted(employee_data, key=lambda k: k['employee_name']) + + for key, group in groupby(employee_data, lambda x: x['employee_name']): + for grp in group: + if grp.closing_balance: + leaves.append(frappe._dict({ + 'leave_type': grp.leave_type, + 'closing_balance': grp.closing_balance + })) + + if leaves: + labels.append(key) + + for leave in leaves: + datasets.append({'name': leave.leave_type, 'values': [leave.closing_balance]}) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 80189e87b7..ebb1734347 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -269,6 +269,7 @@ def get_total_exemption_amount(declarations): total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()]) return total_exemption_amount +@frappe.whitelist() def get_leave_period(from_date, to_date, company): leave_period = frappe.db.sql(""" select name, from_date, to_date From 2c7fac0e7689b1eba05cc18abeee535fd52726b0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 1 Jun 2021 11:56:00 +0530 Subject: [PATCH 126/159] fix: reload doc for possible future schema changes (#25904) --- erpnext/patches/v12_0/purchase_receipt_status.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/patches/v12_0/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py index 1a99b3163b..459221e769 100644 --- a/erpnext/patches/v12_0/purchase_receipt_status.py +++ b/erpnext/patches/v12_0/purchase_receipt_status.py @@ -19,6 +19,9 @@ def execute(): logger.info("purchase_receipt_status: begin patch, PR count: {}" .format(len(affected_purchase_receipts))) + frappe.reload_doc("stock", "doctype", "Purchase Receipt") + frappe.reload_doc("stock", "doctype", "Purchase Receipt Item") + for pr in affected_purchase_receipts: pr_name = pr[0] From 865663857c30de89f5b0a7808421ecf05c53224e Mon Sep 17 00:00:00 2001 From: Fisher Yu <12823863+szufisher@users.noreply.github.com> Date: Tue, 1 Jun 2021 14:45:58 +0800 Subject: [PATCH 127/159] fix: chart of accounts importer always error (#25882) * fix: chart of accounts importer always error chart of accounts importer always error Parent Account Missing * Update chart_of_accounts_importer.py --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ef44626b37..3b764aab10 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,7 +293,7 @@ def validate_accounts(file_name): accounts_dict = {} for account in accounts: accounts_dict.setdefault(account["account_name"], account) - if not hasattr(account, "parent_account"): + if "parent_account" not in 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.") From 721b4130db37e0219843b0fbca9acd1a36ee816b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 2 Jun 2021 14:13:09 +0530 Subject: [PATCH 128/159] fix: not able to select the item code in work order --- erpnext/controllers/queries.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 46301b7587..638503edfa 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -204,7 +204,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if "description" in searchfields: searchfields.remove("description") - + columns = '' extra_searchfields = [field for field in searchfields if not field in ["name", "item_group", "description"]] @@ -216,9 +216,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if not field in searchfields] searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) - if filters.get('supplier'): - item_group_list = frappe.get_all('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) - + if filters and isinstance(filters, dict) and filters.get('supplier'): + item_group_list = frappe.get_all('Supplier Item Group', + filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + item_groups = [] for i in item_group_list: item_groups.append(i.item_group) @@ -227,7 +228,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if item_groups: filters['item_group'] = ['in', item_groups] - + description_cond = '' if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 From a06ec03efcaedeeffaf9b8f9818bfb866f44e2d1 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 2 Jun 2021 14:55:31 +0530 Subject: [PATCH 129/159] test: add test for new QI function --- erpnext/controllers/stock_controller.py | 5 +- erpnext/public/js/controllers/transaction.js | 2 +- .../test_quality_inspection.py | 152 ++++++++++++------ 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index abc966a477..0da723d56e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -504,9 +504,10 @@ class StockController(AccountsController): @frappe.whitelist() def make_quality_inspections(doctype, docname, items): - items = json.loads(items).get('items') - inspections = [] + if isinstance(items, str): + items = json.loads(items) + inspections = [] for item in items: if flt(item.get("sample_size")) > flt(item.get("qty")): frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 95562baa8e..982b1fe1eb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2035,7 +2035,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ args: { doctype: me.frm.doc.doctype, docname: me.frm.doc.name, - items: data + items: data.items }, freeze: true, callback: function (r) { diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 56b046a92e..7f3d701034 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -1,29 +1,45 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + +import frappe from frappe.utils import nowdate -from erpnext.stock.doctype.item.test_item import create_item + +from erpnext.controllers.stock_controller import ( + QualityInspectionNotSubmittedError, + QualityInspectionRejectedError, + QualityInspectionRequiredError, + make_quality_inspections, +) from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry -from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError # test_records = frappe.get_test_records('Quality Inspection') + class TestQualityInspection(unittest.TestCase): def setUp(self): create_item("_Test Item with QA") - frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1) + frappe.db.set_value( + "Item", "_Test Item with QA", "inspection_required_before_delivery", 1 + ) def test_qa_for_delivery(self): - make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100) + make_stock_entry( + item_code="_Test Item with QA", + target="_Test Warehouse - _TC", + qty=1, + basic_rate=100 + ) dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) self.assertRaises(QualityInspectionRequiredError, dn.submit) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected") + qa = create_quality_inspection( + reference_type="Delivery Note", reference_name=dn.name, status="Rejected" + ) dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) @@ -38,7 +54,9 @@ class TestQualityInspection(unittest.TestCase): def test_qa_not_submit(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True) + qa = create_quality_inspection( + reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True + ) dn.items[0].quality_inspection = qa.name self.assertRaises(QualityInspectionNotSubmittedError, dn.submit) @@ -48,21 +66,28 @@ class TestQualityInspection(unittest.TestCase): def test_value_based_qi_readings(self): # Test QI based on acceptance values (Non formula) dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - readings = [{ - "specification": "Iron Content", # numeric reading - "min_value": 0.1, - "max_value": 0.9, - "reading_1": "0.4" - }, - { - "specification": "Particle Inspection Needed", # non-numeric reading - "numeric": 0, - "value": "Yes", - "reading_value": "Yes" - }] + readings = [ + { + "specification": "Iron Content", # numeric reading + "min_value": 0.1, + "max_value": 0.9, + "reading_1": "0.4" + }, + { + "specification": "Particle Inspection Needed", # non-numeric reading + "numeric": 0, + "value": "Yes", + "reading_value": "Yes" + } + ] + + qa = create_quality_inspection( + reference_type="Delivery Note", + reference_name=dn.name, + readings=readings, + do_not_save=True + ) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, - readings=readings, do_not_save=True) qa.save() # status must be auto set as per formula @@ -74,36 +99,43 @@ class TestQualityInspection(unittest.TestCase): def test_formula_based_qi_readings(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - readings = [{ - "specification": "Iron Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", - "reading_1": "0.4" - }, - { - "specification": "Calcium Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", - "reading_1": "0.7" - }, - { - "specification": "Mg Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "mean < 0.9", - "reading_1": "0.5", - "reading_2": "0.7", - "reading_3": "random text" # check if random string input causes issues - }, - { - "specification": "Calcium Content", # non-numeric reading - "formula_based_criteria": 1, - "numeric": 0, - "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", - "reading_value": "Grade B" - }] + readings = [ + { + "specification": "Iron Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", + "reading_1": "0.4" + }, + { + "specification": "Calcium Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", + "reading_1": "0.7" + }, + { + "specification": "Mg Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "mean < 0.9", + "reading_1": "0.5", + "reading_2": "0.7", + "reading_3": "random text" # check if random string input causes issues + }, + { + "specification": "Calcium Content", # non-numeric reading + "formula_based_criteria": 1, + "numeric": 0, + "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", + "reading_value": "Grade B" + } + ] + + qa = create_quality_inspection( + reference_type="Delivery Note", + reference_name=dn.name, + readings=readings, + do_not_save=True + ) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, - readings=readings, do_not_save=True) qa.save() # status must be auto set as per formula @@ -115,6 +147,19 @@ class TestQualityInspection(unittest.TestCase): qa.delete() dn.delete() + def test_make_quality_inspections_from_linked_document(self): + dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + for item in dn.items: + item.sample_size = item.qty + quality_inspections = make_quality_inspections(dn.doctype, dn.name, dn.items) + self.assertEqual(len(dn.items), len(quality_inspections)) + + # cleanup + for qi in quality_inspections: + frappe.delete_doc("Quality Inspection", qi) + dn.delete() + + def create_quality_inspection(**args): args = frappe._dict(args) qa = frappe.new_doc("Quality Inspection") @@ -134,7 +179,7 @@ def create_quality_inspection(**args): readings = args.readings if args.status == "Rejected": - readings["reading_1"] = "12" # status is auto set in child on save + readings["reading_1"] = "12" # status is auto set in child on save if isinstance(readings, list): for entry in readings: @@ -150,10 +195,11 @@ def create_quality_inspection(**args): return qa + def create_quality_inspection_parameter(parameter): if not frappe.db.exists("Quality Inspection Parameter", parameter): frappe.get_doc({ "doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter - }).insert() \ No newline at end of file + }).insert() From f259bf48aaa40630b0a7b08b6d323a14ffc8aa03 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:20:03 +0200 Subject: [PATCH 130/159] refactor: make tax category --- .../setup_wizard/data/country_wise_tax.json | 4 +++ .../setup_wizard/operations/taxes_setup.py | 33 +++++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 2e2a0ca726..5ed8ab92bf 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -481,6 +481,10 @@ }, "Germany": { + "tax_categories": [ + "Umsatzsteuer", + "Vorsteuer" + ], "chart_of_accounts": { "SKR04 mit Kontonummern": { "sales_tax_templates": [ diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 4a3de957b9..f4fe18e116 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -26,7 +26,7 @@ def setup_taxes_and_charges(company_name: str, country: str): if 'chart_of_accounts' not in country_wise_tax: country_wise_tax = simple_to_detailed(country_wise_tax) - from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) + from_detailed_data(company_name, country_wise_tax) def simple_to_detailed(templates): @@ -77,10 +77,16 @@ def simple_to_detailed(templates): def from_detailed_data(company_name, data): """Create Taxes and Charges Templates from detailed data.""" coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') - tax_templates = data.get(coa_name) or data.get('*') - sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') - purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*') - item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') + coa_data = data.get('chart_of_accounts', {}) + tax_templates = coa_data.get(coa_name) or coa_data.get('*', {}) + tax_categories = data.get('tax_categories') + sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*', {}) + purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*', {}) + item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*', {}) + + if tax_categories: + for tax_category in tax_categories: + make_tax_catgory(tax_category) if sales_tax_templates: for template in sales_tax_templates: @@ -102,9 +108,6 @@ def make_taxes_and_charges_template(company_name, doctype, template): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - if template.get('tax_category'): - ensure_tax_category_exists(template.get('tax_category')) - for tax_row in template.get('taxes'): account_data = tax_row.get('account_head') tax_row_defaults = { @@ -241,8 +244,12 @@ def get_or_create_tax_group(company_name, root_type): return tax_group_name -def ensure_tax_category_exists(name): - if not frappe.db.exists('Tax Category', name): - doc = frappe.new_doc('Tax Category') - doc.title = name - doc.save() +def make_tax_catgory(tax_category): + doctype = 'Tax Category' + if isinstance(tax_category, str): + tax_category = {'title': tax_category} + + tax_category['doctype'] = doctype + if not frappe.db.exists(doctype, tax_category['title']): + doc = frappe.get_doc(tax_category) + doc.insert(ignore_permissions=True) From b6f27a4caef1fd479762579fd6f612fdc78fa69d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:20:33 +0200 Subject: [PATCH 131/159] feat: set is_default for german tax templates --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 5ed8ab92bf..f4728ef398 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -491,6 +491,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -515,6 +516,7 @@ { "title": "Vorsteuer", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -567,6 +569,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -591,6 +594,7 @@ { "title": "Vorsteuer", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -619,6 +623,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -643,6 +648,7 @@ { "title": "Vorsteuer", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -761,6 +767,7 @@ { "title": "Umsatzsteuer", "tax_category": "Umsatzsteuer", + "is_default": 1, "taxes": [ { "account_head": { @@ -783,6 +790,7 @@ { "title": "Vorsteuer 19%", "tax_category": "Vorsteuer", + "is_default": 1, "taxes": [ { "account_head": { From 3c748efae3cfc78a88447ac9edeccfea860f1358 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:27:15 +0200 Subject: [PATCH 132/159] feat: add Item Tax Templates for german COAs "SKR03" and "SKR04" --- .../setup_wizard/data/country_wise_tax.json | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index f4728ef398..8305cae73e 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -562,6 +562,96 @@ } ] } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + }, + { + "title": "Vorsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + } ] }, "SKR03 mit Kontonummern": { @@ -616,6 +706,96 @@ } ] } + ], + "item_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + }, + { + "title": "Vorsteuer 19%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 19.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 0.00 + } + ] + }, + { + "title": "Vorsteuer 7%", + "taxes": [ + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + }, + "tax_rate": 0.00 + }, + { + "tax_type": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + }, + "tax_rate": 7.00 + } + ] + } ] }, "Standard with Numbers": { From f6627550d11ea95170ce5f208a257bb31218585f Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 2 Jun 2021 15:17:00 +0200 Subject: [PATCH 133/159] fix: remove wrong tax_category --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 8305cae73e..ec9a6d6b70 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -540,7 +540,6 @@ }, { "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", - "tax_category": "Innergemeinschaftlicher Erwerb 19%", "taxes": [ { "account_head": { From 311b378328ec3d8ea774749f69188210eef237cd Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 3 Jun 2021 12:58:53 +0530 Subject: [PATCH 134/159] fix(pos): searching items with barcode or serial no --- .../page/point_of_sale/point_of_sale.py | 88 ++++++++++--------- .../page/point_of_sale/pos_item_selector.js | 6 +- 2 files changed, 49 insertions(+), 45 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index cb811df8e8..7742f24385 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -8,38 +8,52 @@ from frappe.utils import cint from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability -from six import string_types +def search_by_term(search_term, warehouse, price_list): + result = search_for_serial_or_batch_or_barcode_number(search_term) + + item_code = result.get("item_code") or search_term + serial_no = result.get("serial_no") or "" + batch_no = result.get("batch_no") or "" + barcode = result.get("barcode") or "" + + if result: + item_info = frappe.db.get_value("Item", item_code, + ["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"], + as_dict=1) + + item_stock_qty = get_stock_availability(item_code, warehouse) + price_list_rate, currency = frappe.db.get_value('Item Price', { + 'price_list': price_list, + 'item_code': item_code + }, ["price_list_rate", "currency"]) + + item_info.update({ + 'serial_no': serial_no, + 'batch_no': batch_no, + 'barcode': barcode, + 'price_list_rate': price_list_rate, + 'currency': currency, + 'actual_qty': item_stock_qty + }) + + return {'items': [item_info]} @frappe.whitelist() -def get_items(start, page_length, price_list, item_group, pos_profile, search_value=""): - data = dict() +def get_items(start, page_length, price_list, item_group, pos_profile, search_term=""): + warehouse, hide_unavailable_items = frappe.db.get_value( + 'POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items']) + result = [] - warehouse, hide_unavailable_items = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'hide_unavailable_items']) + if search_term: + result = search_by_term(search_term, warehouse, price_list) + if result: + return result if not frappe.db.exists('Item Group', item_group): item_group = get_root_of('Item Group') - if search_value: - data = search_serial_or_batch_or_barcode_number(search_value) - - item_code = data.get("item_code") if data.get("item_code") else search_value - serial_no = data.get("serial_no") if data.get("serial_no") else "" - batch_no = data.get("batch_no") if data.get("batch_no") else "" - barcode = data.get("barcode") if data.get("barcode") else "" - - if data: - item_info = frappe.db.get_value( - "Item", data.get("item_code"), - ["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"] - , as_dict=1) - item_info.setdefault('serial_no', serial_no) - item_info.setdefault('batch_no', batch_no) - item_info.setdefault('barcode', barcode) - - return { 'items': [item_info] } - - condition = get_conditions(item_code, serial_no, batch_no, barcode) + condition = get_conditions(search_term) condition += get_item_group_condition(pos_profile) lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt']) @@ -106,14 +120,10 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va }) result.append(row) - res = { - 'items': result - } - - return res + return {'items': result} @frappe.whitelist() -def search_serial_or_batch_or_barcode_number(search_value): +def search_for_serial_or_batch_or_barcode_number(search_value): # search barcode no barcode_data = frappe.db.get_value('Item Barcode', {'barcode': search_value}, ['barcode', 'parent as item_code'], as_dict=True) if barcode_data: @@ -139,27 +149,21 @@ def filter_service_items(items): return items -def get_conditions(item_code, serial_no, batch_no, barcode): - if serial_no or batch_no or barcode: - return "item.name = {0}".format(frappe.db.escape(item_code)) - - return make_condition(item_code) - -def make_condition(item_code): +def get_conditions(search_term): condition = "(" - condition += """item.name like {item_code} - or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%')) - condition += add_search_fields_condition(item_code) + condition += """item.name like {search_term} + or item.item_name like {search_term}""".format(search_term=frappe.db.escape('%' + search_term + '%')) + condition += add_search_fields_condition(search_term) condition += ")" return condition -def add_search_fields_condition(item_code): +def add_search_fields_condition(search_term): condition = '' search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname']) if search_fields: for field in search_fields: - condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%')) + condition += " or item.`{0}` like {1}".format(field['fieldname'], frappe.db.escape('%' + search_term + '%')) return condition def get_item_group_condition(pos_profile): 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 5b48725d9c..3e3377e5dc 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -51,7 +51,7 @@ erpnext.PointOfSale.ItemSelector = class { }); } - get_items({start = 0, page_length = 40, search_value=''}) { + get_items({start = 0, page_length = 40, search_term=''}) { const doc = this.events.get_frm().doc; const price_list = (doc && doc.selling_price_list) || this.price_list; let { item_group, pos_profile } = this; @@ -61,7 +61,7 @@ erpnext.PointOfSale.ItemSelector = class { return frappe.call({ method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items", freeze: true, - args: { start, page_length, price_list, item_group, search_value, pos_profile }, + args: { start, page_length, price_list, item_group, search_term, pos_profile }, }); } @@ -302,7 +302,7 @@ erpnext.PointOfSale.ItemSelector = class { } } - this.get_items({ search_value: search_term }) + this.get_items({ search_term }) .then(({ message }) => { // eslint-disable-next-line no-unused-vars const { items, serial_no, batch_no, barcode } = message; From 81b9a5ee4776613ad8720e7004267a82037715f4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 3 Jun 2021 12:59:09 +0530 Subject: [PATCH 135/159] fix(pos): cannot cancel consolidated sales invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 1808005f62..f813425e6b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte var me = this; this._super(); - this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet']; + this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry']; if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { // show debit_to in print format this.frm.set_df_property("debit_to", "print_hide", 0); From 10558344b0fa25b12f71fda9ca21bb8c5170cb83 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 27 May 2021 17:05:36 +0530 Subject: [PATCH 136/159] fix: timeout error in the repost item valuation --- erpnext/hooks.py | 4 +++- .../repost_item_valuation/repost_item_valuation.py | 13 ++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 55169dffba..8ad77a1524 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -332,7 +332,9 @@ scheduler_events = { "erpnext.projects.doctype.project.project.collect_project_status", "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", - "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", + "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders" + ], + "hourly_long": [ "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries" ], "daily": [ diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 5b626ea345..55f2ebb224 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -37,6 +37,9 @@ class RepostItemValuation(Document): self.db_set('status', status) def on_submit(self): + if not frappe.flags.in_test: + return + frappe.enqueue(repost, timeout=1800, queue='long', job_name='repost_sle', now=frappe.flags.in_test, doc=self) @@ -115,12 +118,6 @@ def notify_error_to_stock_managers(doc, traceback): frappe.sendmail(recipients=recipients, subject=subject, message=message) def repost_entries(): - job_log = frappe.get_all('Scheduled Job Log', fields = ['status', 'creation'], - filters = {'scheduled_job_type': 'repost_item_valuation.repost_entries'}, order_by='creation desc', limit=1) - - if job_log and job_log[0]['status'] == 'Start' and time_diff_in_hours(now(), job_log[0]['creation']) < 2: - return - riv_entries = get_repost_item_valuation_entries() for row in riv_entries: @@ -135,9 +132,7 @@ def repost_entries(): check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): - date = add_to_date(now(), hours=-3) - return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, date, as_dict=1) + """, now(), as_dict=1) From c59371ab0c500d9d863db334931d527e8face6eb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 3 Jun 2021 20:02:58 +0530 Subject: [PATCH 137/159] fix: filter type for item query (#25942) --- erpnext/controllers/queries.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 638503edfa..81ac234e70 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe import erpnext +import json from frappe.desk.reportview import get_match_cond, get_filters_cond from frappe.utils import nowdate, getdate from collections import defaultdict @@ -198,6 +199,9 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters): def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] + if isinstance(filters, str): + filters = json.loads(filters) + #Get searchfields from meta and use in Item Link field query meta = frappe.get_meta("Item", cached=True) searchfields = meta.get_search_fields() From c6e016e5452950f393348710f6872febc9f9fb04 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 4 Jun 2021 10:08:22 +0530 Subject: [PATCH 138/159] fix: wrong round off gl entry posted in case of purchase invoice (#25775) --- erpnext/accounts/general_ledger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index f1717c50d8..d4b249429b 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -185,10 +185,10 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): for d in gl_map: if d.account == round_off_account: round_off_gle = d - if d.debit_in_account_currency: - debit_credit_diff -= flt(d.debit_in_account_currency) + if d.debit: + debit_credit_diff -= flt(d.debit) else: - debit_credit_diff += flt(d.credit_in_account_currency) + debit_credit_diff += flt(d.credit) round_off_account_exists = True if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)): From af4794b2d13b9499bff8333e1cb0b479e8405032 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 4 Jun 2021 10:53:44 +0530 Subject: [PATCH 139/159] fix: custom conversion factor field not mapped from job card to stock entry --- erpnext/manufacturing/doctype/job_card/job_card.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index fb26062566..d764db33f8 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -433,7 +433,8 @@ def make_material_request(source_name, target_doc=None): def make_stock_entry(source_name, target_doc=None): def update_item(obj, target, source_parent): target.t_warehouse = source_parent.wip_warehouse - target.conversion_factor = 1 + if not target.conversion_factor: + target.conversion_factor = 1 def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" From 3f45901f257f69d9e3155507da020c18c5155cdc Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 4 Jun 2021 11:16:38 +0530 Subject: [PATCH 140/159] fix: invalid 'depends_on' expression in opportunity (#25955) --- erpnext/crm/doctype/opportunity/opportunity.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 2e09a76c0f..4ba4140244 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -280,7 +280,6 @@ "read_only": 1 }, { - "depends_on": "eval:", "fieldname": "territory", "fieldtype": "Link", "label": "Territory", @@ -431,7 +430,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2021-01-06 19:42:46.190051", + "modified": "2021-06-04 10:11:22.831139", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", From 215516f81940c5e743380d4f97a13eda7d9969be Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 4 Jun 2021 13:09:14 +0530 Subject: [PATCH 141/159] fix: AttributeError: 'PurchaseReceiptItem' object has no attribute 'purchase_invoice' (#25902) (#25957) * fix: AttributeError: 'PurchaseReceiptItem' object has no attribute 'purchase_invoice' This error occurs when upgrading from erpnext 13.0.1 to 13.4.0 after typing bench update --patch --reset * fix(minor): use .get instead of getattr Co-authored-by: D Tim Cummings --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index f1292d8cbd..83ba324495 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -497,7 +497,7 @@ class PurchaseReceipt(BuyingController): def update_billing_status(self, update_modified=True): updated_pr = [self.name] for d in self.get("items"): - if d.purchase_invoice and d.purchase_invoice_item: + if d.get("purchase_invoice") and d.get("purchase_invoice_item"): d.db_set('billed_amt', d.amount, update_modified=update_modified) elif d.purchase_order_item: updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified) @@ -748,4 +748,3 @@ def get_item_account_wise_additional_cost(purchase_document): account.base_amount * item.get(based_on_field) / total_item_cost return item_account_wise_cost - From a85c2c16b42d0ba5bc5f0157eaf87a062052ab6b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 6 Jun 2021 11:07:20 +0530 Subject: [PATCH 142/159] fix(pos): item rate precision in item selector --- erpnext/selling/page/point_of_sale/pos_item_selector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3e3377e5dc..64c529ee4a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -80,6 +80,7 @@ erpnext.PointOfSale.ItemSelector = class { // eslint-disable-next-line no-unused-vars const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; + const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0; let qty_to_display = actual_qty; @@ -121,7 +122,7 @@ erpnext.PointOfSale.ItemSelector = class {
${frappe.ellipsis(item.item_name, 18)}
-
${format_currency(price_list_rate, item.currency, 0) || 0}
+
${format_currency(price_list_rate, item.currency, precision) || 0}
` ); From bc46a9772d7daaa50d0ecdac301e604cb43b5878 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 6 Jun 2021 11:11:07 +0530 Subject: [PATCH 143/159] fix(pos): cash shortcuts not working --- erpnext/selling/page/point_of_sale/pos_payment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 600f160490..156fb777fe 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -171,7 +171,7 @@ erpnext.PointOfSale.Payment = class { this.setup_listener_for_payments(); - this.$payment_modes.on('click', '.shortcut', () => { + this.$payment_modes.on('click', '.shortcut', function() { const value = $(this).attr('data-value'); me.selected_mode.set_value(value); }); From 3f32969bee847e9a3abfc89ae2c7c84c07a7e53e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 6 Jun 2021 11:18:20 +0530 Subject: [PATCH 144/159] fix(pos): broken image in item details section --- .../selling/page/point_of_sale/pos_item_details.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index df62696c4b..5e09df8efe 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -133,13 +133,24 @@ erpnext.PointOfSale.ItemDetails = class { this.$item_description.html(get_description_html()); this.$item_price.html(format_currency(price_list_rate, this.currency)); if (image) { - this.$item_image.html(`${image}`); + this.$item_image.html( + `${frappe.get_abbr(item_name)}` + ); } else { this.$item_image.html(`
${frappe.get_abbr(item_name)}
`); } } + handle_broken_image($img) { + const item_abbr = $($img).attr('alt'); + $($img).replaceWith(`
${item_abbr}
`); + } + render_discount_dom(item) { if (item.discount_percentage) { this.$dicount_section.html( From 7a20c3b92ae05395bf75703a1e6aaad31f537a79 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 6 Jun 2021 19:23:21 +0530 Subject: [PATCH 145/159] fix: Ignore internal transfer inoices from GST Reports --- erpnext/regional/report/gstr_1/gstr_1.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index b7c096248f..80e2d725a2 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -147,6 +147,13 @@ class Gstr1Report(object): def get_invoice_data(self): self.invoices = frappe._dict() conditions = self.get_conditions() + + company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True) + + self.filters.update({ + 'company_gstins': company_gstins + }) + invoice_data = frappe.db.sql(""" select {select_columns} @@ -193,6 +200,9 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ + + conditions += " AND billing_address_gstin NOT IN %(company_gstins)s" + return conditions def get_invoice_items(self): @@ -810,7 +820,8 @@ def get_rate_and_tax_details(row, gstin): return {"num": int(num), "itm_det": itm_det} -def get_company_gstin_number(company, address=None): +def get_company_gstin_number(company, address=None, all_gstins=False): + gstin = '' if address: gstin = frappe.db.get_value("Address", address, "gstin") @@ -822,9 +833,9 @@ def get_company_gstin_number(company, address=None): ["Dynamic Link", "parenttype", "=", "Address"], ] gstin = frappe.get_all("Address", filters=filters, pluck="gstin") - if gstin: - gstin[0] - + if gstin and not all_gstins: + gstin = gstin[0] + if not gstin: address = frappe.bold(address) if address else "" frappe.throw(_("Please set valid GSTIN No. in Company Address {} for company {}").format( From d4398fd84aee28ef82c98ab44d42082550195c39 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 7 Jun 2021 16:20:21 +0530 Subject: [PATCH 146/159] fix: update cost center from pos (#25971) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 023f4b049c..f8b5179d2c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -531,7 +531,7 @@ class SalesInvoice(SellingController): # set pos values in items for item in self.get("items"): if item.get('item_code'): - profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos) + profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos, update_data=True) for fname, val in iteritems(profile_details): if (not for_validate) or (for_validate and not item.get(fname)): item.set(fname, val) From 447c978757ffeef8ebaeedc2dd34124ed9b45fff Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 28 May 2021 21:28:42 +0530 Subject: [PATCH 147/159] fix: choose correct Salary Structure Assignment when getting data for formula eval --- .../doctype/salary_slip/salary_slip.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index afdf081ac8..cc9e8d1043 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -493,8 +493,28 @@ class SalarySlip(TransactionBase): '''Returns data for evaluating formula''' data = frappe._dict() + salary_structure_assignment = frappe.get_value( + "Salary Structure Assignment", + { + "employee": self.employee, + "salary_structure": self.salary_structure, + "from_date": ("<=", self.start_date), + "docstatus": 1, + }, + order_by="from_date desc", + ) + + if not salary_structure_assignment: + frappe.throw( + _("Please assign a Salary Structure for Employee {0} " + "applicable from or before {1} first").format( + frappe.bold(self.employee_name), + frappe.bold(self.start_date) + ) + ) + data.update(frappe.get_doc("Salary Structure Assignment", - {"employee": self.employee, "salary_structure": self.salary_structure}).as_dict()) + salary_structure_assignment).as_dict()) data.update(frappe.get_doc("Employee", self.employee).as_dict()) data.update(self.as_dict()) From 74818c7b624534a39b647476587abb02f84b1e88 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 29 May 2021 00:04:26 +0530 Subject: [PATCH 148/159] fix: improve filter for `from_date`; validation for joining and relieving date --- .../doctype/salary_slip/salary_slip.py | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index cc9e8d1043..2b35d94dfc 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -115,10 +115,23 @@ class SalarySlip(TransactionBase): status = "Cancelled" return status - def validate_dates(self): + def validate_dates(self, joining_date=None, relieving_date=None): if date_diff(self.end_date, self.start_date) < 0: frappe.throw(_("To date cannot be before From date")) + if not joining_date: + joining_date, relieving_date = frappe.get_cached_value( + "Employee", + self.employee, + ("date_of_joining", "relieving_date") + ) + + if date_diff(self.end_date, joining_date) < 0: + frappe.throw(_("Cannot create Salary Slip for Employee joining after Payroll Period")) + + if relieving_date and date_diff(relieving_date, self.start_date) < 0: + frappe.throw(_("Cannot create Salary Slip for Employee who has left before Payroll Period")) + def is_rounding_total_disabled(self): return cint(frappe.db.get_single_value("Payroll Settings", "disable_rounded_total")) @@ -154,9 +167,14 @@ class SalarySlip(TransactionBase): if not self.salary_slip_based_on_timesheet: self.get_date_details() - self.validate_dates() - joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, - ["date_of_joining", "relieving_date"]) + + joining_date, relieving_date = frappe.get_cached_value( + "Employee", + self.employee, + ("date_of_joining", "relieving_date") + ) + + self.validate_dates(joining_date, relieving_date) #getin leave details self.get_working_days_details(joining_date, relieving_date) @@ -492,13 +510,21 @@ class SalarySlip(TransactionBase): def get_data_for_eval(self): '''Returns data for evaluating formula''' data = frappe._dict() + employee = frappe.get_doc("Employee", self.employee).as_dict() + + start_date = getdate(self.start_date) + date_to_validate = ( + employee.date_of_joining + if employee.date_of_joining > start_date + else start_date + ) salary_structure_assignment = frappe.get_value( "Salary Structure Assignment", { "employee": self.employee, "salary_structure": self.salary_structure, - "from_date": ("<=", self.start_date), + "from_date": ("<=", date_to_validate), "docstatus": 1, }, order_by="from_date desc", @@ -509,14 +535,14 @@ class SalarySlip(TransactionBase): _("Please assign a Salary Structure for Employee {0} " "applicable from or before {1} first").format( frappe.bold(self.employee_name), - frappe.bold(self.start_date) + frappe.bold(formatdate(date_to_validate)), ) ) data.update(frappe.get_doc("Salary Structure Assignment", salary_structure_assignment).as_dict()) - data.update(frappe.get_doc("Employee", self.employee).as_dict()) + data.update(employee) data.update(self.as_dict()) # set values for components From 0e5e1350b241e9ac0b1c93b92da1d8999cd9723c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 4 Jun 2021 13:51:45 +0530 Subject: [PATCH 149/159] perf: use frappe.get_value with wildcard instead of another frappe.get_doc call --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 2b35d94dfc..877503b41c 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -527,7 +527,9 @@ class SalarySlip(TransactionBase): "from_date": ("<=", date_to_validate), "docstatus": 1, }, + "*", order_by="from_date desc", + as_dict=True, ) if not salary_structure_assignment: @@ -539,9 +541,7 @@ class SalarySlip(TransactionBase): ) ) - data.update(frappe.get_doc("Salary Structure Assignment", - salary_structure_assignment).as_dict()) - + data.update(salary_structure_assignment) data.update(employee) data.update(self.as_dict()) From ca205be5ac62ff25815df81bde6e2a8a7852b26c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jun 2021 18:40:54 +0530 Subject: [PATCH 150/159] fix: tests --- .../doctype/salary_slip/test_salary_slip.py | 51 +++++++++++-------- .../salary_structure/test_salary_structure.py | 39 +++++++++----- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 01e4170d31..9e7db977ab 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -8,7 +8,6 @@ import erpnext import calendar import random from erpnext.accounts.utils import get_fiscal_year -from frappe.utils.make_random import get_random from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details @@ -155,12 +154,14 @@ class TestSalarySlip(unittest.TestCase): self.assertEqual(ss.gross_pay, 78000) def test_payment_days(self): + from erpnext.payroll.doctype.salary_structure.test_salary_structure import create_salary_structure_assignment + no_of_days = self.get_no_of_days() # Holidays not included in working days frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1) # set joinng date in the same month - make_employee("test_payment_days@salary.com") + employee = make_employee("test_payment_days@salary.com") if getdate(nowdate()).day >= 15: relieving_date = getdate(add_days(nowdate(),-10)) date_of_joining = getdate(add_days(nowdate(),-10)) @@ -174,25 +175,30 @@ class TestSalarySlip(unittest.TestCase): date_of_joining = getdate(nowdate()) relieving_date = getdate(nowdate()) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", date_of_joining) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active") + frappe.db.set_value("Employee", employee, { + "date_of_joining": date_of_joining, + "relieving_date": None, + "status": "Active" + }) - ss = make_employee_salary_slip("test_payment_days@salary.com", "Monthly", "Test Payment Days") + salary_structure = "Test Payment Days" + ss = make_employee_salary_slip("test_payment_days@salary.com", "Monthly", salary_structure) self.assertEqual(ss.total_working_days, no_of_days[0]) self.assertEqual(ss.payment_days, (no_of_days[0] - getdate(date_of_joining).day + 1)) # set relieving date in the same month - frappe.db.set_value("Employee",frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60))) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", relieving_date) - frappe.db.set_value("Employee", frappe.get_value("Employee", - {"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Left") + frappe.db.set_value("Employee", employee, { + "date_of_joining": add_days(nowdate(),-60), + "relieving_date": relieving_date, + "status": "Left" + }) + + if date_of_joining.day > 1: + self.assertRaises(frappe.ValidationError, ss.save) + + create_salary_structure_assignment(employee, salary_structure) + ss.reload() ss.save() self.assertEqual(ss.total_working_days, no_of_days[0]) @@ -285,6 +291,7 @@ class TestSalarySlip(unittest.TestCase): def test_multi_currency_salary_slip(self): from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + applicant = make_employee("test_multi_currency_salary_slip@salary.com", company="_Test Company") frappe.db.sql("""delete from `tabSalary Structure` where name='Test Multi Currency Salary Slip'""") salary_structure = make_salary_structure("Test Multi Currency Salary Slip", "Monthly", employee=applicant, company="_Test Company", currency='USD') @@ -325,7 +332,8 @@ class TestSalarySlip(unittest.TestCase): def test_component_wise_year_to_date_computation(self): from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure - applicant = make_employee("test_ytd@salary.com", company="_Test Company") + employee_name = "test_component_wise_ytd@salary.com" + applicant = make_employee(employee_name, company="_Test Company") payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company") @@ -336,13 +344,13 @@ class TestSalarySlip(unittest.TestCase): "Monthly", employee=applicant, company="_Test Company", currency="INR", payroll_period=payroll_period) # clear salary slip for this employee - frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'") + frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = '%s'" % employee_name) create_salary_slips_for_payroll_period(applicant, salary_structure.name, payroll_period, deduct_random=False, num=3) salary_slips = frappe.get_all("Salary Slip", fields=["name"], filters={"employee_name": - "test_ytd@salary.com"}, order_by = "posting_date") + employee_name}, order_by="posting_date") year_to_date = dict() for slip in salary_slips: @@ -380,10 +388,10 @@ class TestSalarySlip(unittest.TestCase): from erpnext.payroll.doctype.salary_structure.test_salary_structure import \ make_salary_structure, create_salary_structure_assignment + salary_structure = make_salary_structure("Stucture to test tax", "Monthly", - other_details={"max_benefits": 100000}, test_tax=True) - create_salary_structure_assignment(employee, salary_structure.name, - payroll_period.start_date) + other_details={"max_benefits": 100000}, test_tax=True, + employee=employee, payroll_period=payroll_period) # create salary slip for whole period deducting tax only on last period # to find the total tax amount paid @@ -469,6 +477,7 @@ class TestSalarySlip(unittest.TestCase): def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + if not salary_structure: salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index 36387f23df..26cd9922e4 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -6,7 +6,7 @@ import frappe import unittest import erpnext from frappe.utils.make_random import get_random -from frappe.utils import nowdate, add_days, add_years, getdate, add_months +from frappe.utils import nowdate, add_days, add_years, getdate, add_months, get_first_day, date_diff from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\ make_deduction_salary_component, make_employee_salary_slip, create_tax_slab @@ -113,8 +113,9 @@ class TestSalaryStructure(unittest.TestCase): sal_struct = make_salary_structure("Salary Structure Multi Currency", "Monthly", currency='USD') self.assertEqual(sal_struct.currency, 'USD') -def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None, - test_tax=False, company=None, currency=erpnext.get_default_currency(), payroll_period=None): +def make_salary_structure(salary_structure, payroll_frequency, employee=None, + from_date=None, dont_submit=False, other_details=None,test_tax=False, + company=None, currency=erpnext.get_default_currency(), payroll_period=None): if test_tax: frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) @@ -139,10 +140,23 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do else: salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure) + filters = {'employee':employee, 'docstatus': 1} + if not from_date and payroll_period: + from_date = payroll_period.start_date + + if from_date: + filters['from_date'] = from_date + if employee and not frappe.db.get_value("Salary Structure Assignment", - {'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1: - create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency, - payroll_period=payroll_period) + filters) and salary_structure_doc.docstatus==1: + create_salary_structure_assignment( + employee, + salary_structure, + from_date=from_date, + company=company, + currency=currency, + payroll_period=payroll_period + ) return salary_structure_doc @@ -165,12 +179,13 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non salary_structure_assignment.base = 50000 salary_structure_assignment.variable = 5000 - if getdate(nowdate()).day == 1: - date = from_date or nowdate() - else: - date = from_date or add_days(nowdate(), -1) + if not from_date: + from_date = get_first_day(nowdate()) + joining_date = frappe.get_cached_value("Employee", employee, "date_of_joining") + if date_diff(joining_date, from_date) > 0: + from_date = joining_date - salary_structure_assignment.from_date = date + salary_structure_assignment.from_date = from_date salary_structure_assignment.salary_structure = salary_structure salary_structure_assignment.currency = currency salary_structure_assignment.payroll_payable_account = get_payable_account(company) @@ -183,4 +198,4 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non def get_payable_account(company=None): if not company: company = erpnext.get_default_company() - return frappe.db.get_value("Company", company, "default_payroll_payable_account") \ No newline at end of file + return frappe.db.get_value("Company", company, "default_payroll_payable_account") From 062e247353a2277727050c648a4633a2d24fb1b8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jun 2021 19:34:02 +0530 Subject: [PATCH 151/159] test: remove unused imports --- .../payroll/doctype/salary_structure/test_salary_structure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index 26cd9922e4..dce6b7aa3d 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -6,7 +6,7 @@ import frappe import unittest import erpnext from frappe.utils.make_random import get_random -from frappe.utils import nowdate, add_days, add_years, getdate, add_months, get_first_day, date_diff +from frappe.utils import nowdate, add_years, get_first_day, date_diff from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\ make_deduction_salary_component, make_employee_salary_slip, create_tax_slab From bbf07d9214bf86eb120a7ad14989b2bd21a5d9d7 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 8 Jun 2021 17:05:44 +0530 Subject: [PATCH 152/159] fix: quiz timer issues --- erpnext/education/utils.py | 13 +++++++------ erpnext/public/js/education/lms/quiz.js | 10 ++++------ erpnext/www/lms/content.html | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 8f51fef847..9d5653c170 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -219,7 +219,6 @@ def get_quiz(quiz_name, course): try: quiz = frappe.get_doc("Quiz", quiz_name) questions = quiz.get_questions() - duration = quiz.duration except: frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError) return None @@ -236,15 +235,17 @@ def get_quiz(quiz_name, course): return { 'questions': questions, 'activity': None, - 'duration':duration + 'is_time_bound': quiz.is_time_bound, + 'duration': quiz.duration } student = get_current_student() course_enrollment = get_enrollment("course", course, student.name) status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment) return { - 'questions': questions, + 'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken}, + 'is_time_bound': quiz.is_time_bound, 'duration': quiz.duration } @@ -372,9 +373,9 @@ def check_content_completion(content_name, content_type, enrollment_name): def check_quiz_completion(quiz, enrollment_name): attempts = frappe.get_all("Quiz Activity", filters={ - 'enrollment': enrollment_name, + 'enrollment': enrollment_name, 'quiz': quiz.name - }, + }, fields=["name", "activity_date", "score", "status", "time_taken"] ) status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) @@ -389,4 +390,4 @@ def check_quiz_completion(quiz, enrollment_name): time_taken = attempts[0]['time_taken'] if result == 'Pass': status = True - return status, score, result, time_taken \ No newline at end of file + return status, score, result, time_taken diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index 32fa4ab1ec..5bb8425c85 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -20,10 +20,8 @@ class Quiz { } make(data) { - if (data.duration) { - const timer_display = document.createElement("div"); - timer_display.classList.add("lms-timer", "float-right", "font-weight-bold"); - document.getElementsByClassName("lms-title")[0].appendChild(timer_display); + if (data.is_time_bound) { + $(".lms-timer").removeClass("hide") if (!data.activity || (data.activity && !data.activity.is_complete)) { this.initialiseTimer(data.duration); this.is_time_bound = true; @@ -118,7 +116,7 @@ class Quiz { quiz_response: this.get_selected(), course: this.course, program: this.program, - time_taken: this.is_time_bound ? this.time_taken : "" + time_taken: this.is_time_bound ? this.time_taken : 0 }).then(res => { this.submit_btn.remove() if (!res.message) { @@ -237,4 +235,4 @@ class Question { this.options = option_list this.wrapper.appendChild(options_wrapper) } -} \ No newline at end of file +} diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index 15afb097b9..d22ef66d2a 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -64,6 +64,7 @@

{{ content.name }} ({{ position + 1 }}/{{length}})

+
{% endmacro %} From 0ea4d850e1c3870c750072e0ce89a6cd73605266 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 8 Jun 2021 17:23:44 +0530 Subject: [PATCH 153/159] fix: Allow all System Managers to delete company transactions (#25834) --- .../transaction_deletion_record.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 38f8de7a66..ece9fb5699 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -12,10 +12,6 @@ from frappe.desk.notifications import clear_notifications class TransactionDeletionRecord(Document): def validate(self): frappe.only_for('System Manager') - company_obj = frappe.get_doc('Company', self.company) - if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator': - frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'), - frappe.PermissionError) doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in self.doctypes_to_be_ignored: if doctype.doctype_name not in doctypes_to_be_ignored_list: From 7ace06ac21c7d29d8470df139979c15f46a82ed1 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 8 Jun 2021 18:26:23 +0530 Subject: [PATCH 154/159] fix: sider --- erpnext/education/utils.py | 2 +- erpnext/public/js/education/lms/quiz.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 9d5653c170..9db8a4a90d 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -245,7 +245,7 @@ def get_quiz(quiz_name, course): return { 'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken}, - 'is_time_bound': quiz.is_time_bound, + 'is_time_bound': quiz.is_time_bound, 'duration': quiz.duration } diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index 5bb8425c85..66160a7610 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -21,7 +21,7 @@ class Quiz { make(data) { if (data.is_time_bound) { - $(".lms-timer").removeClass("hide") + $(".lms-timer").removeClass("hide"); if (!data.activity || (data.activity && !data.activity.is_complete)) { this.initialiseTimer(data.duration); this.is_time_bound = true; From 18eed58fc5afa1ef95037a1c297b308c34de58ee Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 8 Jun 2021 20:52:14 +0530 Subject: [PATCH 155/159] fix(e-invoicing): service item check --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 843fb012b9..a1179ff9b6 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -199,7 +199,7 @@ def get_item_list(invoice): item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None - item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' + item.is_service_item = 'Y' if item.gst_hsn_code[:2] == "99" else 'N' item.serial_no = "" item = update_item_taxes(invoice, item) From 21b8e2f0d8f18972b05783eface42404cc104c30 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 9 Jun 2021 12:57:21 +0530 Subject: [PATCH 156/159] fix: hiding Rounding Adjustment field (#25380) * fix: hiding Rounding Adjustment field * fix: updating purchase_invoice.json --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 4 +++- erpnext/buying/doctype/purchase_order/purchase_order.json | 4 +++- .../buying/doctype/supplier_quotation/supplier_quotation.json | 4 +++- erpnext/stock/doctype/purchase_receipt/purchase_receipt.json | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d3d3ffa17f..9157821520 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -837,6 +837,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -883,6 +884,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -1380,7 +1382,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-04-30 22:45:58.334107", + "modified": "2021-06-09 12:30:25.632109", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ee2beea67f..8677c71bc5 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -765,6 +765,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -810,6 +811,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -1124,7 +1126,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-01-20 22:07:23.487138", + "modified": "2021-04-19 00:55:30.781375", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 40fbe2c26e..0a51a8e9a1 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -576,6 +576,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency", @@ -620,6 +621,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -802,7 +804,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-03 15:18:29.073368", + "modified": "2021-04-19 00:58:20.995491", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 32d349f303..ad350d344f 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -762,6 +762,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -805,6 +806,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -1147,7 +1149,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-12-26 20:49:39.106049", + "modified": "2021-04-19 01:01:00.754119", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From b57ebba3fd8af3cb70b16a340d41961ba6eb7223 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 9 Jun 2021 17:56:41 +0530 Subject: [PATCH 157/159] fix: Validate negative allocated amount in Payment Entry (#25799) --- .../invoice_discounting/invoice_discounting.py | 7 +++++-- .../doctype/payment_entry/payment_entry.py | 14 ++++++++++---- .../doctype/payment_request/payment_request.py | 3 +-- .../accounts_receivable/accounts_receivable.py | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 7b62b617f9..95d2ee56d9 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -49,11 +49,11 @@ class InvoiceDiscounting(AccountsController): self.make_gl_entries() def on_cancel(self): - self.set_status() + self.set_status(cancel=1) self.update_sales_invoice() self.make_gl_entries() - def set_status(self, status=None): + def set_status(self, status=None, cancel=0): if status: self.status = status self.db_set("status", status) @@ -66,6 +66,9 @@ class InvoiceDiscounting(AccountsController): elif self.docstatus == 2: self.status = "Cancelled" + if cancel: + self.db_set('status', self.status, update_modified = True) + def update_sales_invoice(self): for d in self.invoices: if self.docstatus == 1: diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 62ab76c323..e01c651a93 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -65,7 +65,6 @@ class PaymentEntry(AccountsController): self.set_status() def on_submit(self): - self.setup_party_account_field() if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) self.make_gl_entries() @@ -78,7 +77,6 @@ class PaymentEntry(AccountsController): def on_cancel(self): self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') - self.setup_party_account_field() self.make_gl_entries(cancel=1) self.update_outstanding_amounts() self.update_advance_paid() @@ -122,6 +120,11 @@ class PaymentEntry(AccountsController): if flt(d.allocated_amount) > flt(d.outstanding_amount): frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)) + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0: + if flt(d.allocated_amount) < flt(d.outstanding_amount): + frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)) + def delink_advance_entry_references(self): for reference in self.references: if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"): @@ -177,7 +180,7 @@ class PaymentEntry(AccountsController): for field, value in iteritems(ref_details): if field == 'exchange_rate' or not d.get(field) or force: - d.set(field, value) + d.db_set(field, value) def validate_payment_type(self): if self.payment_type not in ("Receive", "Pay", "Internal Transfer"): @@ -386,6 +389,8 @@ class PaymentEntry(AccountsController): else: self.status = 'Draft' + self.db_set('status', self.status, update_modified = True) + def set_amounts(self): self.set_amounts_in_company_currency() self.set_total_allocated_amount() @@ -791,7 +796,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): outstanding_invoices.pop(idx - 1) outstanding_invoices += invoice_ref_based_on_payment_terms[idx] - + return outstanding_invoices def get_orders_to_be_billed(posting_date, party_type, party, @@ -989,6 +994,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre outstanding_amount = ref_doc.get("outstanding_amount") elif reference_doctype == "Donation": total_amount = ref_doc.get("amount") + outstanding_amount = total_amount exchange_rate = 1 elif reference_doctype == "Dunning": total_amount = ref_doc.get("dunning_amount") diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 53ac996290..468978785b 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -101,7 +101,7 @@ class PaymentRequest(Document): controller.validate_transaction_currency(self.currency) controller.request_for_payment(**payment_record) - + def get_request_amount(self): data_of_completed_requests = frappe.get_all("Integration Request", filters={ 'reference_doctype': self.doctype, @@ -492,7 +492,6 @@ def update_payment_req_status(doc, method): status = 'Requested' pay_req_doc.db_set('status', status) - frappe.db.commit() def get_dummy_message(doc): return frappe.render_template("""{% if doc.contact_person -%} diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index db605f7285..a11b77a6f6 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -584,6 +584,7 @@ class ReceivablePayableReport(object): `tabGL Entry` where docstatus < 2 + and is_cancelled = 0 and party_type=%s and (party is not null and party != '') {1} {2} {3}""" From 26f06093903036bdb222e523d3dba12888869086 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 9 Jun 2021 19:47:28 +0530 Subject: [PATCH 158/159] feat: enable/disable gl entry posting for change given in pos (#25822) --- .../accounts_settings/accounts_settings.json | 9 +++- .../doctype/sales_invoice/sales_invoice.py | 12 ++++- .../sales_invoice/test_sales_invoice.py | 44 +++++++++++++++++-- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 781f94e203..2735b1ccee 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -18,6 +18,7 @@ "delete_linked_ledger_entries", "book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order", + "post_change_gl_entries", "tax_settings_section", "determine_address_tax_category_from", "column_break_19", @@ -253,6 +254,12 @@ { "fieldname": "column_break_19", "fieldtype": "Column Break" + }, + { + "default": "1", + "fieldname": "post_change_gl_entries", + "fieldtype": "Check", + "label": "Post Ledger Entries for Given Change" } ], "icon": "icon-cog", @@ -260,7 +267,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-30 15:25:10.381008", + "modified": "2021-05-25 12:34:05.858669", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f8b5179d2c..0b8d28aef0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -849,7 +849,6 @@ class SalesInvoice(SellingController): self.make_loyalty_point_redemption_gle(gl_entries) self.make_pos_gl_entries(gl_entries) - self.make_gle_for_change_amount(gl_entries) self.make_write_off_gl_entry(gl_entries) self.make_gle_for_rounding_adjustment(gl_entries) @@ -983,7 +982,13 @@ class SalesInvoice(SellingController): def make_pos_gl_entries(self, gl_entries): if cint(self.is_pos): + + skip_change_gl_entries = not cint(frappe.db.get_single_value('Accounts Settings', 'post_change_gl_entries')) + for payment_mode in self.payments: + if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount: + payment_mode.base_amount -= self.change_amount + if payment_mode.amount: # POS, make payment entries gl_entries.append( @@ -1015,8 +1020,11 @@ class SalesInvoice(SellingController): }, payment_mode_account_currency, item=self) ) + if not skip_change_gl_entries: + self.make_gle_for_change_amount(gl_entries) + def make_gle_for_change_amount(self, gl_entries): - if cint(self.is_pos) and self.change_amount: + if self.change_amount: if self.account_for_change_amount: gl_entries.append( self.get_gl_dict({ diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index df6d483904..5409a6f192 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -713,7 +713,7 @@ class TestSalesInvoice(unittest.TestCase): si.submit() self.assertEqual(si.paid_amount, 100.0) - self.pos_gl_entry(si, pos, 50) + self.validate_pos_gl_entry(si, pos, 50) def test_pos_returns_with_repayment(self): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return @@ -749,7 +749,7 @@ class TestSalesInvoice(unittest.TestCase): make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1") - pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", + make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1") pos = create_sales_invoice(company= "_Test Company with perpetual inventory", @@ -770,7 +770,45 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(pos.grand_total, 100.0) self.assertEqual(pos.write_off_amount, -5) - def pos_gl_entry(self, si, pos, cash_amount): + def test_pos_with_no_gl_entry_for_change_amount(self): + frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 0) + + make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", + expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1") + + make_purchase_receipt(company= "_Test Company with perpetual inventory", + item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1") + + pos = create_sales_invoice(company= "_Test Company with perpetual inventory", + debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", + income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", + cost_center = "Main - TCP1", do_not_save=True) + + pos.is_pos = 1 + pos.update_stock = 1 + + taxes = get_taxes_and_charges() + pos.taxes = [] + for tax in taxes: + pos.append("taxes", tax) + + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50}) + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60}) + + pos.insert() + pos.submit() + + self.assertEqual(pos.grand_total, 100.0) + self.assertEqual(pos.change_amount, 10) + + self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True) + + frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 1) + + def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False): + if validate_without_change_gle: + cash_amount -= pos.change_amount + # check stock ledger entries sle = frappe.db.sql("""select * from `tabStock Ledger Entry` where voucher_type = 'Sales Invoice' and voucher_no = %s""", From 42557c4ad387db7ea4956ac80dfaea085614481d Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 9 Jun 2021 19:48:31 +0530 Subject: [PATCH 159/159] feat: cost-center wise period closing entry (#25766) --- .../period_closing_voucher.json | 452 +++++------------- .../period_closing_voucher.py | 111 +++-- .../test_period_closing_voucher.py | 85 ++++ 3 files changed, 277 insertions(+), 371 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json index 47546c07a4..84c941ecc1 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -1,350 +1,138 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "ACC-PCV-.YYYY.-.#####", - "beta": 0, - "creation": "2013-01-10 16:34:07", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, - "engine": "InnoDB", + "actions": [], + "autoname": "ACC-PCV-.YYYY.-.#####", + "creation": "2013-01-10 16:34:07", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "transaction_date", + "posting_date", + "fiscal_year", + "amended_from", + "company", + "cost_center_wise_pnl", + "column_break1", + "closing_account_head", + "remarks" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transaction Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "transaction_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "transaction_date", + "fieldtype": "Date", + "label": "Transaction Date", + "oldfieldname": "transaction_date", + "oldfieldtype": "Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Closing Fiscal Year", - "length": 0, - "no_copy": 0, - "oldfieldname": "fiscal_year", - "oldfieldtype": "Select", - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "fiscal_year", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Closing Fiscal Year", + "oldfieldname": "fiscal_year", + "oldfieldtype": "Select", + "options": "Fiscal Year", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Period Closing Voucher", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Period Closing Voucher", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Select", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Select", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "The account head under Liability or Equity, in which Profit/Loss will be booked", - "fieldname": "closing_account_head", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Closing Account Head", - "length": 0, - "no_copy": 0, - "oldfieldname": "closing_account_head", - "oldfieldtype": "Link", - "options": "Account", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "description": "The account head under Liability or Equity, in which Profit/Loss will be booked", + "fieldname": "closing_account_head", + "fieldtype": "Link", + "label": "Closing Account Head", + "oldfieldname": "closing_account_head", + "oldfieldtype": "Link", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "remarks", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Remarks", - "length": 0, - "no_copy": 0, - "oldfieldname": "remarks", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "remarks", + "fieldtype": "Small Text", + "label": "Remarks", + "oldfieldname": "remarks", + "oldfieldtype": "Small Text", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "cost_center_wise_pnl", + "fieldtype": "Check", + "label": "Book Cost Center Wise Profit/Loss" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-file-text", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Period Closing Voucher", - "owner": "Administrator", + ], + "icon": "fa fa-file-text", + "idx": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-05-20 15:27:37.210458", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Period Closing Voucher", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "posting_date, fiscal_year", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "closing_account_head", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "posting_date, fiscal_year", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "closing_account_head" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index a74fa062b6..b0a5b04de6 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -51,63 +51,96 @@ class PeriodClosingVoucher(AccountsController): def make_gl_entries(self): gl_entries = [] - net_pl_balance = 0 - dimension_fields = ['t1.cost_center'] + net_pl_balance = 0 - accounting_dimensions = get_accounting_dimensions() - for dimension in accounting_dimensions: - dimension_fields.append('t1.{0}'.format(dimension)) - - dimension_filters, default_dimensions = get_dimensions() - - pl_accounts = self.get_pl_balances(dimension_fields) + pl_accounts = self.get_pl_balances() for acc in pl_accounts: - if flt(acc.balance_in_company_currency): + if flt(acc.bal_in_company_currency): gl_entries.append(self.get_gl_dict({ "account": acc.account, "cost_center": acc.cost_center, "account_currency": acc.account_currency, - "debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \ - if flt(acc.balance_in_account_currency) < 0 else 0, - "debit": abs(flt(acc.balance_in_company_currency)) \ - if flt(acc.balance_in_company_currency) < 0 else 0, - "credit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \ - if flt(acc.balance_in_account_currency) > 0 else 0, - "credit": abs(flt(acc.balance_in_company_currency)) \ - if flt(acc.balance_in_company_currency) > 0 else 0 + "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0, + "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0, + "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0, + "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0 }, item=acc)) - net_pl_balance += flt(acc.balance_in_company_currency) + net_pl_balance += flt(acc.bal_in_company_currency) if net_pl_balance: - cost_center = frappe.db.get_value("Company", self.company, "cost_center") - gl_entry = self.get_gl_dict({ - "account": self.closing_account_head, - "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, - "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, - "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, - "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0, - "cost_center": cost_center - }) - - for dimension in accounting_dimensions: - gl_entry.update({ - dimension: default_dimensions.get(self.company, {}).get(dimension) - }) - - gl_entries.append(gl_entry) + if self.cost_center_wise_pnl: + costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts) + gl_entries += costcenter_wise_gl_entries + else: + gl_entry = self.get_pnl_gl_entry(net_pl_balance) + gl_entries.append(gl_entry) from erpnext.accounts.general_ledger import make_gl_entries make_gl_entries(gl_entries) + + def get_pnl_gl_entry(self, net_pl_balance): + cost_center = frappe.db.get_value("Company", self.company, "cost_center") + gl_entry = self.get_gl_dict({ + "account": self.closing_account_head, + "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, + "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, + "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, + "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0, + "cost_center": cost_center + }) + + self.update_default_dimensions(gl_entry) + + return gl_entry + + def get_costcenter_wise_pnl_gl_entries(self, pl_accounts): + company_cost_center = frappe.db.get_value("Company", self.company, "cost_center") + gl_entries = [] + + for acc in pl_accounts: + if flt(acc.bal_in_company_currency): + gl_entry = self.get_gl_dict({ + "account": self.closing_account_head, + "cost_center": acc.cost_center or company_cost_center, + "account_currency": acc.account_currency, + "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0, + "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0, + "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0, + "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0 + }, item=acc) + + self.update_default_dimensions(gl_entry) + + gl_entries.append(gl_entry) + + return gl_entries + + def update_default_dimensions(self, gl_entry): + if not self.accounting_dimensions: + self.accounting_dimensions = get_accounting_dimensions() + + _, default_dimensions = get_dimensions() + for dimension in self.accounting_dimensions: + gl_entry.update({ + dimension: default_dimensions.get(self.company, {}).get(dimension) + }) + + def get_pl_balances(self): + """Get balance for dimension-wise pl accounts""" + + dimension_fields = ['t1.cost_center'] + + self.accounting_dimensions = get_accounting_dimensions() + for dimension in self.accounting_dimensions: + dimension_fields.append('t1.{0}'.format(dimension)) - def get_pl_balances(self, dimension_fields): - """Get balance for pl accounts""" return frappe.db.sql(""" select t1.account, t2.account_currency, {dimension_fields}, - sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency, - sum(t1.debit) - sum(t1.credit) as balance_in_company_currency + sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency, + sum(t1.debit) - sum(t1.credit) as bal_in_company_currency from `tabGL Entry` t1, `tabAccount` t2 where t1.account = t2.name and t2.report_type = 'Profit and Loss' and t2.docstatus < 2 and t2.company = %s diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index eb02d97b78..2f29372b01 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -8,6 +8,7 @@ import frappe from frappe.utils import flt, today from erpnext.accounts.utils import get_fiscal_year, now from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice class TestPeriodClosingVoucher(unittest.TestCase): def test_closing_entry(self): @@ -65,6 +66,58 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency, -1*random_expense_account[0].balance_in_account_currency) + def test_cost_center_wise_posting(self): + frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") + + company = create_company() + surplus_account = create_account() + + cost_center1 = create_cost_center("Test Cost Center 1") + cost_center2 = create_cost_center("Test Cost Center 2") + + create_sales_invoice( + company=company, + cost_center=cost_center1, + income_account="Sales - TPC", + expense_account="Cost of Goods Sold - TPC", + rate=400, + debit_to="Debtors - TPC" + ) + create_sales_invoice( + company=company, + cost_center=cost_center2, + income_account="Sales - TPC", + expense_account="Cost of Goods Sold - TPC", + rate=200, + debit_to="Debtors - TPC" + ) + + pcv = frappe.get_doc({ + "transaction_date": today(), + "posting_date": today(), + "fiscal_year": get_fiscal_year(today())[0], + "company": "Test PCV Company", + "cost_center_wise_pnl": 1, + "closing_account_head": surplus_account, + "remarks": "Test", + "doctype": "Period Closing Voucher" + }) + pcv.insert() + pcv.submit() + + expected_gle = ( + ('Sales - TPC', 200.0, 0.0, cost_center2), + (surplus_account, 0.0, 200.0, cost_center2), + ('Sales - TPC', 400.0, 0.0, cost_center1), + (surplus_account, 0.0, 400.0, cost_center1) + ) + + pcv_gle = frappe.db.sql(""" + select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s + """, (pcv.name)) + + self.assertTrue(pcv_gle, expected_gle) + def make_period_closing_voucher(self): pcv = frappe.get_doc({ "doctype": "Period Closing Voucher", @@ -80,6 +133,38 @@ class TestPeriodClosingVoucher(unittest.TestCase): return pcv +def create_company(): + company = frappe.get_doc({ + 'doctype': 'Company', + 'company_name': "Test PCV Company", + 'country': 'United States', + 'default_currency': 'USD' + }) + company.insert(ignore_if_duplicate = True) + return company.name + +def create_account(): + account = frappe.get_doc({ + "account_name": "Reserve and Surplus", + "is_group": 0, + "company": "Test PCV Company", + "root_type": "Liability", + "report_type": "Balance Sheet", + "account_currency": "USD", + "parent_account": "Current Liabilities - TPC", + "doctype": "Account" + }).insert(ignore_if_duplicate = True) + return account.name + +def create_cost_center(cc_name): + costcenter = frappe.get_doc({ + "company": "Test PCV Company", + "cost_center_name": cc_name, + "doctype": "Cost Center", + "parent_cost_center": "Test PCV Company - TPC" + }) + costcenter.insert(ignore_if_duplicate = True) + return costcenter.name test_dependencies = ["Customer", "Cost Center"] test_records = frappe.get_test_records("Period Closing Voucher")