From 8775154fa0dbeb9561156ce5d95eba48a6d75b61 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 13 Dec 2021 16:50:55 +0530 Subject: [PATCH 01/17] refactor: update_serial_no function and code cleanup (cherry picked from commit 4f52b86d7e577741f65932601c6ff2066bba1553) # Conflicts: # erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js --- .../maintenance_schedule.py | 26 +++++---- .../maintenance_visit/maintenance_visit.js | 55 +++++++++---------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 2ffae1a4f2a..9c35f4f2457 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - import frappe from frappe import _, throw from frappe.utils import add_days, cint, cstr, date_diff, formatdate, getdate @@ -306,13 +305,18 @@ class MaintenanceSchedule(TransactionBase): return schedule.name @frappe.whitelist() -def update_serial_nos(s_id): - serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no') +def update_serial_nos(item_code, schedule=None): + serial_nos = [] + if schedule: + serial_nos = frappe.db.get_value('Maintenance Schedule Item', { + 'parent': schedule, + 'item_code': item_code + }, 'serial_no') + if serial_nos: serial_nos = get_serial_nos(serial_nos) - return serial_nos - else: - return False + + return serial_nos @frappe.whitelist() def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None): @@ -320,12 +324,9 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No 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_sales_and_serial(source, target, parent): - sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person') - target.service_person = sales_person + def update_serial(source, target, parent): serial_nos = get_serial_nos(target.serial_no) if len(serial_nos) == 1: target.serial_no = serial_nos[0] @@ -346,7 +347,10 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No "Maintenance Schedule Item": { "doctype": "Maintenance Visit Purpose", "condition": lambda doc: doc.item_name == item_name, - "postprocess": update_sales_and_serial + "field_map": { + "sales_person": "service_person" + }, + "postprocess": update_serial } }, target_doc) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 78289679744..b3661b7bf38 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -2,52 +2,50 @@ // 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) { - //filters for serial_no based on item_code - frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) { - let item = locals[cdt][cdn]; - 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) { 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, cdt, cdn) { - let item = locals[cdt][cdn]; + onload: function (frm) { + // filters for serial no based on item code if (frm.doc.maintenance_type === "Scheduled") { - const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail; + let item_code = frm.doc.purposes[0].item_code; frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", args: { - s_id: schedule_id - }, - callback: function (r) { - serial_nos = r.message; + schedule: frm.doc.maintenance_schedule, + item_code: item_code } + }).then((r) => { + let serial_nos = r.message; + frm.set_query('serial_no', 'purposes', () => { + if (serial_nos.length > 0) { + return { + filters: { + 'item_code': item_code, + 'name': ["in", serial_nos] + } + }; + } + return { + filters: { + 'item_code': item_code + } + }; + }); }); } if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { +<<<<<<< HEAD frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); +======= + frm.clear_table("purposes"); +>>>>>>> 4f52b86d7e (refactor: update_serial_no function and code cleanup) frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, @@ -60,7 +58,6 @@ frappe.ui.form.on('Maintenance Visit', { contact_person: function (frm) { erpnext.utils.get_contact_details(frm); } - }) // TODO commonify this code From dc0d974144c4aa97c0c846abdf5afd3f7a8282de Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 15 Dec 2021 15:27:41 +0530 Subject: [PATCH 02/17] fix(patch): updates maintenance schedule field for old docs (cherry picked from commit c872aa43f871dbe8fb71b5584fefab52b42a2eda) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 5 +++++ ...ate_maintenance_schedule_field_in_visit.py | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8c9b365fa7e..13f0891b89e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -336,8 +336,13 @@ erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.create_ksa_vat_custom_fields +<<<<<<< HEAD erpnext.patches.v13_0.rename_ksa_qr_field erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 erpnext.patches.v13_0.update_tax_category_for_rcm erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template erpnext.patches.v13_0.agriculture_deprecation_warning +======= +erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit +erpnext.patches.v14_0.migrate_crm_settings +>>>>>>> c872aa43f8 (fix(patch): updates maintenance schedule field for old docs) diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py new file mode 100644 index 00000000000..450c00e4212 --- /dev/null +++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py @@ -0,0 +1,22 @@ + +import frappe + + +def execute(): + # Updates the Maintenance Schedule link to fetch serial nos + from frappe.query_builder.functions import Coalesce + mvp = frappe.qb.DocType('Maintenance Visit Purpose') + mv = frappe.qb.DocType('Maintenance Visit') + + frappe.qb.update( + mv + ).join( + mvp + ).on(mvp.parent == mv.name).set( + mv.maintenance_schedule, + Coalesce(mvp.prevdoc_docname, '') + ).where( + (mv.maintenance_type == "Scheduled") + & (mvp.prevdoc_docname.notnull()) + & (mv.docstatus < 2) + ).run(as_dict=1) From f541b5c780b535e324d9e79911233c8eb89acce1 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 17 Dec 2021 11:57:17 +0530 Subject: [PATCH 03/17] test: added test for serial no. filters (cherry picked from commit 9109660a69056424a96bb9ba6d85ccf5214a45c6) --- .../test_maintenance_schedule.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 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 501712613a8..7539505842d 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -8,7 +8,10 @@ from frappe.utils.data import add_days, formatdate, today from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import ( make_maintenance_visit, + update_serial_nos, ) +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item # test_records = frappe.get_test_records('Maintenance Schedule') @@ -80,6 +83,41 @@ class TestMaintenanceSchedule(unittest.TestCase): #checks if visit status is back updated in schedule self.assertTrue(ms.schedules[1].completion_status, "Partially Completed") + def test_serial_no_filters(self): + # Without serial no. set in schedule -> returns None + item_code = "_Test Serial Item" + make_serial_item_with_serial(item_code) + ms = make_maintenance_schedule(item_code=item_code) + ms.submit() + + s_item = ms.schedules[0] + mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) + mvi = mv.purposes[0] + serial_nos = update_serial_nos(mvi.item_name, ms.name) + self.assertEqual(serial_nos, None) + + # With serial no. set in schedule -> returns serial nos. + make_serial_item_with_serial(item_code) + ms = make_maintenance_schedule(item_code=item_code, serial_no="TEST001, TEST002") + ms.submit() + + s_item = ms.schedules[0] + mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) + mvi = mv.purposes[0] + serial_nos = update_serial_nos(mvi.item_name, ms.name) + self.assertEqual(serial_nos, ["TEST001", "TEST002"]) + + frappe.db.rollback() + +def make_serial_item_with_serial(item_code): + serial_item_doc = create_item(item_code, is_stock_item=1) + if not serial_item_doc.has_serial_no or not serial_item_doc.serial_no_series: + serial_item_doc.has_serial_no = 1 + serial_item_doc.serial_no_series = "TEST.###" + serial_item_doc.save(ignore_permissions=True) + if frappe.db.exists('Serial No', {"status": "Active", "item_code": item_code}): + make_serialized_item(item_code=item_code) + def get_events(ms): return frappe.get_all("Event Participants", filters={ "reference_doctype": ms.doctype, @@ -87,17 +125,18 @@ def get_events(ms): "parenttype": "Event" }) -def make_maintenance_schedule(): +def make_maintenance_schedule(**args): ms = frappe.new_doc("Maintenance Schedule") ms.company = "_Test Company" ms.customer = "_Test Customer" ms.transaction_date = today() ms.append("items", { - "item_code": "_Test Item", + "item_code": args.get("item_code") or "_Test Item", "start_date": today(), "periodicity": "Weekly", "no_of_visits": 4, + "serial_no": args.get("serial_no"), "sales_person": "Sales Team", }) ms.insert(ignore_permissions=True) From 0bb0a9d0328ea8b9739b13f2261dd17da0671148 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 17 Dec 2021 13:32:30 +0530 Subject: [PATCH 04/17] fix(test): fixed test case not creating serials (cherry picked from commit 90d32006f42a95dba37c51924c5f62be7436dd6f) --- .../doctype/maintenance_schedule/test_maintenance_schedule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 7539505842d..378d3238d75 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -115,7 +115,8 @@ def make_serial_item_with_serial(item_code): serial_item_doc.has_serial_no = 1 serial_item_doc.serial_no_series = "TEST.###" serial_item_doc.save(ignore_permissions=True) - if frappe.db.exists('Serial No', {"status": "Active", "item_code": item_code}): + active_serials = frappe.db.get_all('Serial No', {"status": "Active", "item_code": item_code}) + if len(active_serials) < 2: make_serialized_item(item_code=item_code) def get_events(ms): From 93325c72f0e99af5cb2c4a4dd763ee701c597aa1 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 17 Dec 2021 13:46:46 +0530 Subject: [PATCH 05/17] refactor: moved purpose table mandatory check to server (cherry picked from commit b98c61f2a7247d9f27251b8e740664a77a8b51ef) # Conflicts: # erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js --- .../doctype/maintenance_visit/maintenance_visit.js | 3 +++ .../doctype/maintenance_visit/maintenance_visit.json | 6 +++--- .../doctype/maintenance_visit/maintenance_visit.py | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index b3661b7bf38..d08786410c7 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -41,11 +41,14 @@ frappe.ui.form.on('Maintenance Visit', { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { +<<<<<<< HEAD <<<<<<< HEAD frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); ======= frm.clear_table("purposes"); >>>>>>> 4f52b86d7e (refactor: update_serial_no function and code cleanup) +======= +>>>>>>> b98c61f2a7 (refactor: moved purpose table mandatory check to server) frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json index ec32239518f..4a6aa0a34bf 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json @@ -179,8 +179,7 @@ "label": "Purposes", "oldfieldname": "maintenance_visit_details", "oldfieldtype": "Table", - "options": "Maintenance Visit Purpose", - "reqd": 1 + "options": "Maintenance Visit Purpose" }, { "fieldname": "more_info", @@ -294,10 +293,11 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-27 16:06:17.352572", + "modified": "2021-12-17 03:10:27.608112", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 5a87b162af6..d5d87536da5 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -18,6 +18,10 @@ 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_purpose_table(self): + if not self.purposes: + frappe.throw(_("Add Items in the Purpose Table"), title="Purposes Required") + def validate_maintenance_date(self): if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail: item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference') @@ -29,6 +33,7 @@ class MaintenanceVisit(TransactionBase): def validate(self): self.validate_serial_no() self.validate_maintenance_date() + self.validate_purpose_table() def update_completion_status(self): if self.maintenance_schedule_detail: From add36363ab4d91f5b821e0ecbcdb032903d6de31 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 17 Jan 2022 18:25:42 +0530 Subject: [PATCH 06/17] fix: Serial No. filters for unscheduled visits (cherry picked from commit dec751377302e504285e834afed6a64dd0f010bf) --- .../maintenance_schedule/maintenance_schedule.py | 2 +- .../maintenance_schedule/test_maintenance_schedule.py | 6 +++--- .../doctype/maintenance_visit/maintenance_visit.js | 11 ++++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 9c35f4f2457..07d928c221f 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -305,7 +305,7 @@ class MaintenanceSchedule(TransactionBase): return schedule.name @frappe.whitelist() -def update_serial_nos(item_code, schedule=None): +def get_serial_nos_from_schedule(item_code, schedule=None): serial_nos = [] if schedule: serial_nos = frappe.db.get_value('Maintenance Schedule Item', { diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py index 378d3238d75..4d3c3f48f4a 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py @@ -7,8 +7,8 @@ import frappe from frappe.utils.data import add_days, formatdate, today from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import ( + get_serial_nos_from_schedule, make_maintenance_visit, - update_serial_nos, ) from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item @@ -93,7 +93,7 @@ class TestMaintenanceSchedule(unittest.TestCase): s_item = ms.schedules[0] mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) mvi = mv.purposes[0] - serial_nos = update_serial_nos(mvi.item_name, ms.name) + serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name) self.assertEqual(serial_nos, None) # With serial no. set in schedule -> returns serial nos. @@ -104,7 +104,7 @@ class TestMaintenanceSchedule(unittest.TestCase): s_item = ms.schedules[0] mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name) mvi = mv.purposes[0] - serial_nos = update_serial_nos(mvi.item_name, ms.name) + serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name) self.assertEqual(serial_nos, ["TEST001", "TEST002"]) frappe.db.rollback() diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index d08786410c7..746fb765e10 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Maintenance Visit', { if (frm.doc.maintenance_type === "Scheduled") { let item_code = frm.doc.purposes[0].item_code; frappe.call({ - method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", + method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule", args: { schedule: frm.doc.maintenance_schedule, item_code: item_code @@ -36,6 +36,15 @@ frappe.ui.form.on('Maintenance Visit', { }; }); }); + } else { + frm.set_query('serial_no', 'purposes', (frm, cdt, cdn) => { + let row = locals[cdt][cdn]; + return { + filters: { + 'item_code': row.item_code + } + }; + }); } if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); From 3023757a602108e8f006066025a7c918d50e29d5 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 19 Jan 2022 13:06:52 +0530 Subject: [PATCH 07/17] fix: Conflicts --- .../doctype/maintenance_visit/maintenance_visit.js | 8 -------- erpnext/patches.txt | 4 ---- 2 files changed, 12 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 746fb765e10..f4a0d4d399c 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -50,14 +50,6 @@ frappe.ui.form.on('Maintenance Visit', { frm.set_value({ status: 'Draft' }); } if (frm.doc.__islocal) { -<<<<<<< HEAD -<<<<<<< HEAD - frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes"); -======= - frm.clear_table("purposes"); ->>>>>>> 4f52b86d7e (refactor: update_serial_no function and code cleanup) -======= ->>>>>>> b98c61f2a7 (refactor: moved purpose table mandatory check to server) frm.set_value({ mntc_date: frappe.datetime.get_today() }); } }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 13f0891b89e..7110a7e308e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -336,13 +336,9 @@ erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate erpnext.patches.v13_0.create_ksa_vat_custom_fields -<<<<<<< HEAD erpnext.patches.v13_0.rename_ksa_qr_field erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021 erpnext.patches.v13_0.update_tax_category_for_rcm erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template erpnext.patches.v13_0.agriculture_deprecation_warning -======= erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit -erpnext.patches.v14_0.migrate_crm_settings ->>>>>>> c872aa43f8 (fix(patch): updates maintenance schedule field for old docs) From 93d665b38a840037476366682873820fc4ca221a Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 19 Jan 2022 13:53:18 +0530 Subject: [PATCH 08/17] fix: Patch (reload doc) --- .../patches/v13_0/update_maintenance_schedule_field_in_visit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py index 450c00e4212..43096991943 100644 --- a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py +++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py @@ -3,6 +3,8 @@ import frappe def execute(): + frappe.reload_doc("maintenance", "doctype", "maintenance_visit") + # Updates the Maintenance Schedule link to fetch serial nos from frappe.query_builder.functions import Coalesce mvp = frappe.qb.DocType('Maintenance Visit Purpose') From f75d36dcb4f96ad9b9b6759fa93ef199b0f95bac Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:30:21 +0530 Subject: [PATCH 09/17] fix: from time and to time not updated in drag and drop action #29114 (#29361) fix: from time and to time not updated in drag and drop action (cherry picked from commit 8b5827ed6db1041526b6440ca8e4fde19c646e1e) Co-authored-by: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> --- erpnext/education/api.py | 4 ++-- .../doctype/course_schedule/course_schedule.py | 12 +++++++++++- .../course_schedule/course_schedule_calendar.js | 7 +++---- .../doctype/course_schedule/test_course_schedule.py | 6 ++++++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/erpnext/education/api.py b/erpnext/education/api.py index d9013b08161..636b948a1cc 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -201,8 +201,8 @@ def get_course_schedule_events(start, end, filters=None): conditions = get_event_conditions("Course Schedule", filters) data = frappe.db.sql("""select name, course, color, - timestamp(schedule_date, from_time) as from_datetime, - timestamp(schedule_date, to_time) as to_datetime, + timestamp(schedule_date, from_time) as from_time, + timestamp(schedule_date, to_time) as to_time, room, student_group, 0 as 'allDay' from `tabCourse Schedule` where ( schedule_date between %(start)s and %(end)s ) diff --git a/erpnext/education/doctype/course_schedule/course_schedule.py b/erpnext/education/doctype/course_schedule/course_schedule.py index 335b6d28d0c..335cec43527 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule.py +++ b/erpnext/education/doctype/course_schedule/course_schedule.py @@ -3,6 +3,8 @@ # For license information, please see license.txt +from datetime import datetime + import frappe from frappe import _ from frappe.model.document import Document @@ -30,6 +32,14 @@ class CourseSchedule(Document): if self.from_time > self.to_time: frappe.throw(_("From Time cannot be greater than To Time.")) + """Handles specicfic case to update schedule date in calendar """ + if isinstance(self.from_time, str): + try: + datetime_obj = datetime.strptime(self.from_time, '%Y-%m-%d %H:%M:%S') + self.schedule_date = datetime_obj + except ValueError: + pass + def validate_overlap(self): """Validates overlap for Student Group, Instructor, Room""" @@ -47,4 +57,4 @@ class CourseSchedule(Document): validate_overlap_for(self, "Assessment Plan", "student_group") validate_overlap_for(self, "Assessment Plan", "room") - validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) + validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) \ No newline at end of file diff --git a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js index 803527e5480..cacd539b224 100644 --- a/erpnext/education/doctype/course_schedule/course_schedule_calendar.js +++ b/erpnext/education/doctype/course_schedule/course_schedule_calendar.js @@ -1,11 +1,10 @@ frappe.views.calendar["Course Schedule"] = { field_map: { - // from_datetime and to_datetime don't exist as docfields but are used in onload - "start": "from_datetime", - "end": "to_datetime", + "start": "from_time", + "end": "to_time", "id": "name", "title": "course", - "allDay": "allDay" + "allDay": "allDay", }, gantt: false, order_by: "schedule_date", diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.py b/erpnext/education/doctype/course_schedule/test_course_schedule.py index a7324195557..56149affcea 100644 --- a/erpnext/education/doctype/course_schedule/test_course_schedule.py +++ b/erpnext/education/doctype/course_schedule/test_course_schedule.py @@ -6,6 +6,7 @@ import unittest import frappe from frappe.utils import to_timedelta, today +from frappe.utils.data import add_to_date from erpnext.education.utils import OverlapError @@ -39,6 +40,11 @@ class TestCourseSchedule(unittest.TestCase): make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name) + def test_update_schedule_date(self): + doc = make_course_schedule_test_record(schedule_date= add_to_date(today(), days=1)) + doc.schedule_date = add_to_date(doc.schedule_date, days=1) + doc.save() + def make_course_schedule_test_record(**args): args = frappe._dict(args) From 25398d017bca53f14c7ebb6c9f937ef7c1390093 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Jan 2022 10:51:59 +0530 Subject: [PATCH 10/17] fix: Cleanup empty rows on bank statement import --- .../bank_statement_import.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index c57e862892c..57434bdd829 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -17,6 +17,7 @@ from openpyxl.styles import Font from openpyxl.utils import get_column_letter from six import string_types +INVALID_VALUES = ("", None) class BankStatementImport(DataImport): def __init__(self, *args, **kwargs): @@ -96,6 +97,18 @@ def download_errored_template(data_import_name): data_import = frappe.get_doc("Bank Statement Import", data_import_name) data_import.export_errored_rows() +def parse_data_from_template(raw_data): + data = [] + + for i, row in enumerate(raw_data): + if all(v in INVALID_VALUES for v in row): + # empty row + continue + + data.append(row) + + return data + def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options): """This method runs in background job""" @@ -105,7 +118,8 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url, file = import_file_path if import_file_path else google_sheets_url import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records") - data = import_file.raw_data + + data = parse_data_from_template(import_file.raw_data) if import_file_path: add_bank_account(data, bank_account) From 77cb7ebd4085fc4a1acf3b6b0022ca1ba45b376e Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jan 2022 13:36:46 +0530 Subject: [PATCH 11/17] fix: (enhance) BOM Operations Report - Added filters in the Report for BOM ID, Item Code and Workstation - Converted Raw SQL to frappe.qb and added method to get filtered data - Changed fieldtype of 'Time in mins' from Int to Float - Get BOM wise grouped data to keep order and accurate grouping in report (cherry picked from commit a9ff1fc52e1be73fa0d4cbb415607d5d545c545c) --- .../bom_operations_time.js | 35 ++++++++- .../bom_operations_time.py | 78 +++++++++++++------ 2 files changed, 89 insertions(+), 24 deletions(-) diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js index 7468e34020c..705da5a3a69 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js @@ -4,6 +4,39 @@ frappe.query_reports["BOM Operations Time"] = { "filters": [ - + { + "fieldname": "item_code", + "label": __("Item Code"), + "fieldtype": "Link", + "width": "100", + "options": "Item", + "get_query": () =>{ + return { + filters: { "disabled": 0, "is_stock_item": 1 } + } + } + }, + { + "fieldname": "bom_id", + "label": __("BOM ID"), + "fieldtype": "MultiSelectList", + "width": "80", + "options": "BOM", + "get_data": function(txt) { + return frappe.db.get_link_options("BOM", txt); + }, + "get_query": () =>{ + return { + filters: { "docstatus": 1, "is_active": 1 } + } + } + }, + { + "fieldname": "workstation", + "label": __("Workstation"), + "fieldtype": "Link", + "width": "100", + "options": "Workstation" + }, ] }; diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index e7a818abd5d..0bed46ac70b 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -4,7 +4,8 @@ import frappe from frappe import _ - +from frappe.model.meta import get_field_precision +from frappe.utils import flt def execute(filters=None): data = get_data(filters) @@ -12,19 +13,15 @@ def execute(filters=None): return columns, data def get_data(filters): - data = [] + bom_wise_data = {} + bom_data, report_data = [], [] - bom_data = [] - for d in frappe.db.sql(""" - SELECT - bom.name, bom.item, bom.item_name, bom.uom, - bomps.operation, bomps.workstation, bomps.time_in_mins - FROM `tabBOM` bom, `tabBOM Operation` bomps - WHERE - bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent - """, as_dict=1): + bom_operation_data = get_filtered_data(filters) + + for d in bom_operation_data: row = get_args() if d.name not in bom_data: + bom_wise_data[d.name] = [] bom_data.append(d.name) row.update(d) else: @@ -34,14 +31,49 @@ def get_data(filters): "time_in_mins": d.time_in_mins }) - data.append(row) + # maintain BOM wise data for grouping such as: + # {"BOM A": [{Row1}, {Row2}], "BOM B": ...} + bom_wise_data[d.name].append(row) used_as_subassembly_items = get_bom_count(bom_data) - for d in data: - d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0) + for d in bom_wise_data: + for row in bom_wise_data[d]: + row.used_as_subassembly_items = used_as_subassembly_items.get(row.name, 0) + report_data.append(row) - return data + return report_data + +def get_filtered_data(filters): + bom = frappe.qb.DocType("BOM") + bom_ops = frappe.qb.DocType("BOM Operation") + + bom_ops_query = ( + frappe.qb.from_(bom) + .join(bom_ops).on(bom.name == bom_ops.parent) + .select( + bom.name, bom.item, bom.item_name, bom.uom, + bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins + ).where( + (bom.docstatus == 1) + & (bom.is_active == 1) + ) + ) + + if filters.get("item_code"): + bom_ops_query = bom_ops_query.where(bom.item == filters.get("item_code")) + + if filters.get("bom_id"): + bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id"))) + + if filters.get("workstation"): + bom_ops_query = bom_ops_query.where( + bom_ops.workstation == filters.get("workstation") + ) + + bom_operation_data = bom_ops_query.run(as_dict=True) + + return bom_operation_data def get_bom_count(bom_data): data = frappe.get_all("BOM Item", @@ -68,13 +100,13 @@ def get_columns(filters): "options": "BOM", "fieldname": "name", "fieldtype": "Link", - "width": 140 + "width": 220 }, { - "label": _("BOM Item Code"), + "label": _("Item Code"), "options": "Item", "fieldname": "item", "fieldtype": "Link", - "width": 140 + "width": 150 }, { "label": _("Item Name"), "fieldname": "item_name", @@ -85,13 +117,13 @@ def get_columns(filters): "options": "UOM", "fieldname": "uom", "fieldtype": "Link", - "width": 140 + "width": 100 }, { "label": _("Operation"), "options": "Operation", "fieldname": "operation", "fieldtype": "Link", - "width": 120 + "width": 140 }, { "label": _("Workstation"), "options": "Workstation", @@ -101,11 +133,11 @@ def get_columns(filters): }, { "label": _("Time (In Mins)"), "fieldname": "time_in_mins", - "fieldtype": "Int", - "width": 140 + "fieldtype": "Float", + "width": 120 }, { "label": _("Sub-assembly BOM Count"), "fieldname": "used_as_subassembly_items", "fieldtype": "Int", - "width": 180 + "width": 200 }] From e963d0e8e0e62b49ecb4afc228afe6ffee958c59 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jan 2022 14:19:08 +0530 Subject: [PATCH 12/17] fix: Sider (unused imports) and Linter(add extra empty line) (cherry picked from commit 3c7b7721123bb2284a00f84f335a8ab1708831f5) --- .../report/bom_operations_time/bom_operations_time.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py index 0bed46ac70b..eda9eb9d701 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py @@ -4,8 +4,7 @@ import frappe from frappe import _ -from frappe.model.meta import get_field_precision -from frappe.utils import flt + def execute(filters=None): data = get_data(filters) From 655d8980dc60a75f59019f475581c8043de63053 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 20 Jan 2022 14:29:18 +0530 Subject: [PATCH 13/17] fix: Allow only BOM with ops in BOM ID filter & add total row to report (cherry picked from commit af734298c8ac541188de4593a064f962147ea27d) --- .../report/bom_operations_time/bom_operations_time.js | 4 ++-- .../report/bom_operations_time/bom_operations_time.json | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js index 705da5a3a69..0eb22a22f73 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js @@ -20,14 +20,14 @@ frappe.query_reports["BOM Operations Time"] = { "fieldname": "bom_id", "label": __("BOM ID"), "fieldtype": "MultiSelectList", - "width": "80", + "width": "100", "options": "BOM", "get_data": function(txt) { return frappe.db.get_link_options("BOM", txt); }, "get_query": () =>{ return { - filters: { "docstatus": 1, "is_active": 1 } + filters: { "docstatus": 1, "is_active": 1, "with_operations": 1 } } } }, diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json index 665c5b9f79e..8162017ca81 100644 --- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json +++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json @@ -1,14 +1,16 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2020-03-03 01:41:20.862521", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", "letter_head": "", - "modified": "2020-03-03 01:41:20.862521", + "modified": "2022-01-20 14:21:47.771591", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operations Time", From b9a42807f8e3542e908ea53e451836cf9e3098df Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:06:11 +0530 Subject: [PATCH 14/17] fix: show stock UOM for material transfers (#29376) (#29377) (cherry picked from commit 7e407bcc42260e78ed7275946f71e3e7a89b282f) Co-authored-by: Ankush Menat --- .../manufacturing/doctype/production_plan/production_plan.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index e903e9baf84..106777b82ef 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -949,6 +949,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): warehouses, item.get("quantity"), company, ignore_validation=True) required_qty = item.get("quantity") + # get available material by transferring to production warehouse for d in locations: if required_qty <=0: return @@ -959,12 +960,14 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company): new_dict.update({ "quantity": quantity, "material_request_type": "Material Transfer", + "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM "from_warehouse": d.get("warehouse") }) required_qty -= quantity new_mr_items.append(new_dict) + # raise purchase request for remaining qty if required_qty: stock_uom, purchase_uom = frappe.db.get_value( 'Item', From d190ea4ad3b2ed378222894d169fa49f66ecddc8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Jan 2022 14:56:53 +0530 Subject: [PATCH 15/17] ci: disable coverage on v13 branch --- .github/workflows/server-tests.yml | 33 +----------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 6d7324d623b..62576996a0f 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -89,39 +89,8 @@ 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 --use-orchestrator --with-coverage + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator env: 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 8a9a13cb96be74242b01112ea34f0a568b76e3d6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Jan 2022 15:03:22 +0530 Subject: [PATCH 16/17] ci: concurrency control on v13 branches --- .github/workflows/patch.yml | 4 ++++ .github/workflows/server-tests.yml | 4 ++++ .github/workflows/ui-tests.yml | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 8bb44555206..b4421203a15 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -8,6 +8,10 @@ on: workflow_dispatch: +concurrency: + group: patch-mariadb-v13-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 62576996a0f..c62622eecec 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -12,6 +12,10 @@ on: - '**.js' - '**.md' +concurrency: + group: server-mariadb-v13-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 5459e86123d..9f142bd2c2f 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -6,6 +6,10 @@ on: - '**.md' workflow_dispatch: +concurrency: + group: ui-v13-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 From d32f229483cf4681a0873bfc9a4c5d97abecffca Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 20 Jan 2022 15:16:48 +0530 Subject: [PATCH 17/17] ci: fail on merge conflict label --- .github/workflows/patch.yml | 6 ++++++ .github/workflows/server-tests.yml | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index b4421203a15..8d29057b487 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -5,6 +5,7 @@ on: paths-ignore: - '**.js' - '**.md' + types: [opened, unlabeled, synchronize, reopened] workflow_dispatch: @@ -29,6 +30,11 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: + - name: Check for merge conficts label + if: ${{ contains(github.event.pull_request.labels.*.name, 'conflicts') }} + run: | + echo "Remove merge conflicts and remove conflict label to run CI" + exit 1 - name: Clone uses: actions/checkout@v2 diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index c62622eecec..1c9743c5700 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -5,6 +5,7 @@ on: paths-ignore: - '**.js' - '**.md' + types: [opened, unlabeled, synchronize, reopened] workflow_dispatch: push: branches: [ develop ] @@ -39,6 +40,12 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 steps: + - name: Check for merge conficts label + if: ${{ contains(github.event.pull_request.labels.*.name, 'conflicts') }} + run: | + echo "Remove merge conflicts and remove conflict label to run CI" + exit 1 + - name: Clone uses: actions/checkout@v2