diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index acfc660c4f7..997937738b6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -332,6 +332,7 @@ "label": "Reference" }, { + "depends_on": "eval:doc.docstatus==0", "fieldname": "get_outstanding_invoice", "fieldtype": "Button", "label": "Get Outstanding Invoice" @@ -575,7 +576,7 @@ } ], "is_submittable": 1, - "modified": "2019-11-06 12:59:43.151721", + "modified": "2019-12-08 13:02:30.016610", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3bb3df8dbd9..7b2061ac168 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -830,7 +830,11 @@ class PurchaseInvoice(BuyingController): ) def make_gle_for_rounding_adjustment(self, gl_entries): - if self.rounding_adjustment: + # if rounding adjustment in small and conversion rate is also small then + # base_rounding_adjustment may become zero due to small precision + # eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2 + # then base_rounding_adjustment becomes zero and error is thrown in GL Entry + if self.rounding_adjustment and self.base_rounding_adjustment: round_off_account, round_off_cost_center = \ get_round_off_account_and_cost_center(self.company) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index d024a31162f..c4d64a72fd1 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -953,7 +953,7 @@ class SalesInvoice(SellingController): ) def make_gle_for_rounding_adjustment(self, gl_entries): - if flt(self.rounding_adjustment, self.precision("rounding_adjustment")): + if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment: round_off_account, round_off_cost_center = \ get_round_off_account_and_cost_center(self.company) diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 15bc97c2a4d..6768dfabfea 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-05-24 19:29:06", "doctype": "DocType", @@ -43,6 +44,7 @@ "base_amount", "pricing_rules", "is_free_item", + "is_fixed_asset", "section_break_29", "net_rate", "net_amount", @@ -708,11 +710,20 @@ "fieldname": "against_blanket_order", "fieldtype": "Check", "label": "Against Blanket Order" + }, + { + "default": "0", + "fetch_from": "item_code.is_fixed_asset", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "label": "Is Fixed Asset", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-11-19 14:10:52.865006", + "links": [], + "modified": "2019-12-06 13:17:12.142799", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json b/erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json similarity index 78% rename from erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json rename to erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json index 006d139eb02..d3adcb79812 100644 --- a/erpnext/buying/setup_wizard_slide/add_a_few_suppliers/add_a_few_suppliers.json +++ b/erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json @@ -3,18 +3,18 @@ "app": "ERPNext", "creation": "2019-11-15 14:45:32.626641", "docstatus": 0, - "doctype": "Setup Wizard Slide", + "doctype": "Onboarding Slide", "domains": [], "help_links": [ { - "label": "Supplier", + "label": "Learn More", "video_id": "zsrrVDk6VBs" } ], "idx": 0, - "image_src": "/assets/erpnext/images/illustrations/supplier.png", + "image_src": "/assets/erpnext/images/illustrations/supplier-onboard.png", "max_count": 3, - "modified": "2019-11-26 18:26:25.498325", + "modified": "2019-12-03 22:53:50.552445", "modified_by": "Administrator", "name": "Add A Few Suppliers", "owner": "Administrator", @@ -44,6 +44,5 @@ ], "slide_order": 50, "slide_title": "Add A Few Suppliers", - "slide_type": "Create", - "submit_method": "" + "slide_type": "Create" } \ No newline at end of file diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index bc4a1b4034b..7a9727f18c4 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -5,9 +5,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, add_days, getdate +from frappe.utils import date_diff, add_days, getdate, cint from frappe.model.document import Document -from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, get_holidays_for_employee +from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \ + get_holidays_for_employee, create_additional_leave_ledger_entry class CompensatoryLeaveRequest(Document): @@ -25,16 +26,14 @@ class CompensatoryLeaveRequest(Document): frappe.throw(_("Leave Type is madatory")) def validate_attendance(self): - query = """select attendance_date, status - from `tabAttendance` where - attendance_date between %(work_from_date)s and %(work_end_date)s - and docstatus=1 and status = 'Present' and employee=%(employee)s""" + attendance = frappe.get_all('Attendance', + filters={ + 'attendance_date': ['between', (self.work_from_date, self.work_end_date)], + 'status': 'Present', + 'docstatus': 1, + 'employee': self.employee + }, fields=['attendance_date', 'status']) - attendance = frappe.db.sql(query, { - "work_from_date": self.work_from_date, - "work_end_date": self.work_end_date, - "employee": self.employee - }, as_dict=True) if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1: frappe.throw(_("You are not present all day(s) between compensatory leave request days")) @@ -50,13 +49,19 @@ class CompensatoryLeaveRequest(Document): date_difference -= 0.5 leave_period = get_leave_period(self.work_from_date, self.work_end_date, company) if leave_period: - leave_allocation = self.exists_allocation_for_period(leave_period) + leave_allocation = self.get_existing_allocation_for_period(leave_period) if leave_allocation: leave_allocation.new_leaves_allocated += date_difference - leave_allocation.submit() + leave_allocation.validate() + leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated) + leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated) + + # generate additional ledger entry for the new compensatory leaves off + create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1)) + else: leave_allocation = self.create_leave_allocation(leave_period, date_difference) - self.db_set("leave_allocation", leave_allocation.name) + self.leave_allocation=leave_allocation.name else: frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date)) @@ -68,11 +73,16 @@ class CompensatoryLeaveRequest(Document): leave_allocation = frappe.get_doc("Leave Allocation", self.leave_allocation) if leave_allocation: leave_allocation.new_leaves_allocated -= date_difference - if leave_allocation.total_leaves_allocated - date_difference <= 0: - leave_allocation.total_leaves_allocated = 0 - leave_allocation.submit() + if leave_allocation.new_leaves_allocated - date_difference <= 0: + leave_allocation.new_leaves_allocated = 0 + leave_allocation.validate() + leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated) + leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated) - def exists_allocation_for_period(self, leave_period): + # create reverse entry on cancelation + create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1)) + + def get_existing_allocation_for_period(self, leave_period): leave_allocation = frappe.db.sql(""" select name from `tabLeave Allocation` @@ -95,17 +105,18 @@ class CompensatoryLeaveRequest(Document): def create_leave_allocation(self, leave_period, date_difference): is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward") - allocation = frappe.new_doc("Leave Allocation") - allocation.employee = self.employee - allocation.employee_name = self.employee_name - allocation.leave_type = self.leave_type - allocation.from_date = add_days(self.work_end_date, 1) - allocation.to_date = leave_period[0].to_date - allocation.new_leaves_allocated = date_difference - allocation.total_leaves_allocated = date_difference - allocation.description = self.reason - if is_carry_forward == 1: - allocation.carry_forward = True - allocation.save(ignore_permissions = True) + allocation = frappe.get_doc(dict( + doctype="Leave Allocation", + employee=self.employee, + employee_name=self.employee_name, + leave_type=self.leave_type, + from_date=add_days(self.work_end_date, 1), + to_date=leave_period[0].to_date, + carry_forward=cint(is_carry_forward), + new_leaves_allocated=date_difference, + total_leaves_allocated=date_difference, + description=self.reason + )) + allocation.insert(ignore_permissions=True) allocation.submit() - return allocation + return allocation \ No newline at end of file diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index f2ca1f4f5f0..1615ab30f1d 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -5,37 +5,128 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.utils import today, add_months, add_days +from erpnext.hr.doctype.attendance_request.test_attendance_request import get_employee +from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period +from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on -# class TestCompensatoryLeaveRequest(unittest.TestCase): -# def get_compensatory_leave_request(self): -# return frappe.get_doc('Compensatory Leave Request', dict( -# employee = employee, -# work_from_date = today, -# work_to_date = today, -# reason = 'test' -# )).insert() -# -# def test_creation_of_leave_allocation(self): -# employee = get_employee() -# today = get_today() -# -# compensatory_leave_request = self.get_compensatory_leave_request(today) -# -# before = get_leave_balance(employee, compensatory_leave_request.leave_type) -# -# compensatory_leave_request.submit() -# -# self.assertEqual(get_leave_balance(employee, compensatory_leave_request.leave_type), before + 1) -# -# def test_max_compensatory_leave(self): -# employee = get_employee() -# today = get_today() -# -# compensatory_leave_request = self.get_compensatory_leave_request() -# -# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 0) -# -# self.assertRaises(MaxLeavesLimitCrossed, compensatory_leave_request.submit) -# -# frappe.db.set_value('Leave Type', compensatory_leave_request.leave_type, 'max_leaves_allowed', 10) -# +class TestCompensatoryLeaveRequest(unittest.TestCase): + def setUp(self): + frappe.db.sql(''' delete from `tabCompensatory Leave Request`''') + frappe.db.sql(''' delete from `tabLeave Ledger Entry`''') + frappe.db.sql(''' delete from `tabLeave Allocation`''') + frappe.db.sql(''' delete from `tabAttendance` where attendance_date in {0} '''.format((today(), add_days(today(), -1)))) #nosec + create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company") + create_holiday_list() + + employee = get_employee() + employee.holiday_list = "_Test Compensatory Leave" + employee.save() + + def test_leave_balance_on_submit(self): + ''' check creation of leave allocation on submission of compensatory leave request ''' + employee = get_employee() + mark_attendance(employee) + compensatory_leave_request = get_compensatory_leave_request(employee.name) + + before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today()) + compensatory_leave_request.submit() + + self.assertEqual(get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)), before + 1) + + def test_leave_allocation_update_on_submit(self): + employee = get_employee() + mark_attendance(employee, date=add_days(today(), -1)) + compensatory_leave_request = get_compensatory_leave_request(employee.name, leave_date=add_days(today(), -1)) + compensatory_leave_request.submit() + + # leave allocation creation on submit + leaves_allocated = frappe.db.get_value('Leave Allocation', { + 'name': compensatory_leave_request.leave_allocation + }, ['total_leaves_allocated']) + self.assertEqual(leaves_allocated, 1) + + mark_attendance(employee) + compensatory_leave_request = get_compensatory_leave_request(employee.name) + compensatory_leave_request.submit() + + # leave allocation updates on submission of second compensatory leave request + leaves_allocated = frappe.db.get_value('Leave Allocation', { + 'name': compensatory_leave_request.leave_allocation + }, ['total_leaves_allocated']) + self.assertEqual(leaves_allocated, 2) + + def test_creation_of_leave_ledger_entry_on_submit(self): + ''' check creation of leave ledger entry on submission of leave request ''' + employee = get_employee() + mark_attendance(employee) + compensatory_leave_request = get_compensatory_leave_request(employee.name) + compensatory_leave_request.submit() + + filters = dict(transaction_name=compensatory_leave_request.leave_allocation) + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters) + + self.assertEquals(len(leave_ledger_entry), 1) + self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, 1) + + # check reverse leave ledger entry on cancellation + compensatory_leave_request.cancel() + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc') + + self.assertEquals(len(leave_ledger_entry), 2) + self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, -1) + +def get_compensatory_leave_request(employee, leave_date=today()): + prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request', + dict(leave_type='Compensatory Off', + work_from_date=leave_date, + work_end_date=leave_date, + employee=employee), 'name') + if prev_comp_leave_req: + return frappe.get_doc('Compensatory Leave Request', prev_comp_leave_req) + + return frappe.get_doc(dict( + doctype='Compensatory Leave Request', + employee=employee, + leave_type='Compensatory Off', + work_from_date=leave_date, + work_end_date=leave_date, + reason='test' + )).insert() + +def mark_attendance(employee, date=today(), status='Present'): + if not frappe.db.exists(dict(doctype='Attendance', employee=employee.name, attendance_date=date, status='Present')): + attendance = frappe.get_doc({ + "doctype": "Attendance", + "employee": employee.name, + "attendance_date": date, + "status": status + }) + attendance.save() + attendance.submit() + +def create_holiday_list(): + if frappe.db.exists("Holiday List", "_Test Compensatory Leave"): + return + + holiday_list = frappe.get_doc({ + "doctype": "Holiday List", + "from_date": add_months(today(), -3), + "to_date": add_months(today(), 3), + "holidays": [ + { + "description": "Test Holiday", + "holiday_date": today() + }, + { + "description": "Test Holiday 1", + "holiday_date": add_days(today(), -1) + } + ], + "holiday_list_name": "_Test Compensatory Leave" + }) + holiday_list.save() \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 874ae7a1bc2..d13bb4577cd 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -69,10 +69,14 @@ class LeaveAllocation(Document): def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" - select name from `tabLeave Allocation` - where employee=%s and leave_type=%s and docstatus=1 - and to_date >= %s and from_date <= %s""", - (self.employee, self.leave_type, self.from_date, self.to_date)) + SELECT + name + FROM `tabLeave Allocation` + WHERE + employee=%s AND leave_type=%s + AND name <> %s AND docstatus=1 + AND to_date >= %s AND from_date <= %s""", + (self.employee, self.leave_type, self.name, self.from_date, self.to_date)) if leave_allocation: frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}") diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index db3819eff2d..e32d57011d8 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -170,7 +170,7 @@ frappe.ui.form.on("Leave Application", { frm.set_value('to_date', ''); return; } - // server call is done to include holidays in leave days calculations + // server call is done to include holidays in leave days calculations return frappe.call({ method: 'erpnext.hr.doctype.leave_application.leave_application.get_number_of_leave_days', args: { @@ -193,7 +193,7 @@ frappe.ui.form.on("Leave Application", { set_leave_approver: function(frm) { if(frm.doc.employee) { - // server call is done to include holidays in leave days calculations + // server call is done to include holidays in leave days calculations return frappe.call({ method: 'erpnext.hr.doctype.leave_application.leave_application.get_leave_approver', args: { diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 0e6630541c4..65fcbf7a999 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -549,10 +549,10 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date): leave_days += leave_entry.leaves elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \ - and not skip_expiry_leaves(leave_entry, to_date): + and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date): leave_days += leave_entry.leaves - else: + elif leave_entry.transaction_type == 'Leave Application': if leave_entry.from_date < getdate(from_date): leave_entry.from_date = from_date if leave_entry.to_date > getdate(to_date): @@ -579,14 +579,15 @@ def skip_expiry_leaves(leave_entry, date): def get_leave_entries(employee, leave_type, from_date, to_date): ''' Returns leave entries between from_date and to_date ''' return frappe.db.sql(""" - select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward, transaction_name - from `tabLeave Ledger Entry` - where employee=%(employee)s and leave_type=%(leave_type)s - and docstatus=1 - and leaves<0 - and (from_date between %(from_date)s and %(to_date)s - or to_date between %(from_date)s and %(to_date)s - or (from_date < %(from_date)s and to_date > %(to_date)s)) + SELECT + employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, + is_carry_forward, is_expired + FROM `tabLeave Ledger Entry` + WHERE employee=%(employee)s AND leave_type=%(leave_type)s + AND docstatus=1 AND leaves<0 + AND (from_date between %(from_date)s AND %(to_date)s + OR to_date between %(from_date)s AND %(to_date)s + OR (from_date < %(from_date)s AND to_date > %(to_date)s)) """, { "from_date": from_date, "to_date": to_date, @@ -773,4 +774,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 + return leave_approver \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 38ae808f27e..b9c02101f1b 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -301,7 +301,7 @@ class TestLeaveApplication(unittest.TestCase): to_date = add_days(date, 2), company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) leave_application.submit() @@ -314,7 +314,7 @@ class TestLeaveApplication(unittest.TestCase): to_date = add_days(date, 8), company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) self.assertRaises(frappe.ValidationError, leave_application.insert) diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py index 850a08dd536..1762cf917a2 100644 --- a/erpnext/hr/doctype/leave_period/test_leave_period.py +++ b/erpnext/hr/doctype/leave_period/test_leave_period.py @@ -43,10 +43,18 @@ class TestLeavePeriod(unittest.TestCase): leave_period.grant_leave_allocation(employee=employee_doc_name) self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20) -def create_leave_period(from_date, to_date): +def create_leave_period(from_date, to_date, company=None): + leave_period = frappe.db.get_value('Leave Period', + dict(company=company or erpnext.get_default_company(), + from_date=from_date, + to_date=to_date, + is_active=1), 'name') + if leave_period: + return frappe.get_doc("Leave Period", leave_period) + leave_period = frappe.get_doc({ "doctype": "Leave Period", - "company": erpnext.get_default_company(), + "company": company or erpnext.get_default_company(), "from_date": from_date, "to_date": to_date, "is_active": 1 diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 1464a779368..c3e8d27557d 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -321,11 +321,11 @@ def allocate_earned_leaves(): if new_allocation == allocation.total_leaves_allocated: continue allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False) - create_earned_leave_ledger_entry(allocation, earned_leaves, today) + create_additional_leave_ledger_entry(allocation, earned_leaves, today) -def create_earned_leave_ledger_entry(allocation, earned_leaves, date): - ''' Create leave ledger entry based on the earned leave frequency ''' - allocation.new_leaves_allocated = earned_leaves +def create_additional_leave_ledger_entry(allocation, leaves, date): + ''' Create leave ledger entry for leave types ''' + allocation.new_leaves_allocated = leaves allocation.from_date = date allocation.unused_leaves = 0 allocation.create_leave_ledger_entry() @@ -389,6 +389,7 @@ def get_sal_slip_total_benefit_given(employee, payroll_period, component=False): def get_holidays_for_employee(employee, start_date, end_date): holiday_list = get_holiday_list_for_employee(employee) + holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%(holiday_list)s diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 55799544982..e3ece569641 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -606,6 +606,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite item.image, bom.project, item.stock_uom, + item.item_group, item.allow_alternative_item, item_default.default_warehouse, item_default.expense_account as expense_account, diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 25c385fb1e5..8876253e8e6 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -615,6 +615,9 @@ def get_items_for_material_requests(doc, ignore_existing_ordered_qty=None): doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') + if not po_items: + frappe.throw(_("Items are required to pull the raw materials which is associated with it.")) + company = doc.get('company') warehouse = doc.get('for_warehouse') diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 25c97d1fb84..3570a0f2be4 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -4,20 +4,16 @@ frappe.ui.form.on("Project", { setup(frm) { frm.make_methods = { 'Timesheet': () => { - let doctype = 'Timesheet'; - frappe.model.with_doctype(doctype, () => { - let new_doc = frappe.model.get_new_doc(doctype); - - // add a new row and set the project - let time_log = frappe.model.get_new_doc('Timesheet Detail'); - time_log.project = frm.doc.name; - time_log.parent = new_doc.name; - time_log.parentfield = 'time_logs'; - time_log.parenttype = 'Timesheet'; - new_doc.time_logs = [time_log]; - - frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); - }); + open_form(frm, "Timesheet", "Timesheet Detail", "time_logs"); + }, + 'Purchase Order': () => { + open_form(frm, "Purchase Order", "Purchase Order Item", "items"); + }, + 'Purchase Receipt': () => { + open_form(frm, "Purchase Receipt", "Purchase Receipt Item", "items"); + }, + 'Purchase Invoice': () => { + open_form(frm, "Purchase Invoice", "Purchase Invoice Item", "items"); }, }; }, @@ -80,7 +76,7 @@ frappe.ui.form.on("Project", { frm.events.set_status(frm, 'Cancelled'); }, __('Set Status')); } - + if (frappe.model.can_read("Task")) { frm.add_custom_button(__("Gantt Chart"), function () { frappe.route_options = { @@ -123,3 +119,20 @@ frappe.ui.form.on("Project", { }, }); + +function open_form(frm, doctype, child_doctype, parentfield) { + frappe.model.with_doctype(doctype, () => { + let new_doc = frappe.model.get_new_doc(doctype); + + // add a new row and set the project + let new_child_doc = frappe.model.get_new_doc(child_doctype); + new_child_doc.project = frm.doc.name; + new_child_doc.parent = new_doc.name; + new_child_doc.parentfield = parentfield; + new_child_doc.parenttype = doctype; + new_doc[parentfield] = [new_child_doc]; + + frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); + }); + +} \ No newline at end of file diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index c4481c9aa0e..e90821689bd 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -188,7 +188,8 @@ class Timesheet(Document): }, as_dict=True) # check internal overlap for time_log in self.time_logs: - if not (time_log.from_time or time_log.to_time): continue + if not (time_log.from_time and time_log.to_time + and args.from_time and args.to_time): continue if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \ args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or diff --git a/erpnext/public/images/illustrations/collaboration.png b/erpnext/public/images/illustrations/collaboration.png deleted file mode 100644 index 12c67e394cc..00000000000 Binary files a/erpnext/public/images/illustrations/collaboration.png and /dev/null differ diff --git a/erpnext/public/images/illustrations/customer.png b/erpnext/public/images/illustrations/customer.png deleted file mode 100644 index b2ddbf3bb47..00000000000 Binary files a/erpnext/public/images/illustrations/customer.png and /dev/null differ diff --git a/erpnext/public/images/illustrations/customers-onboard.png b/erpnext/public/images/illustrations/customers-onboard.png new file mode 100644 index 00000000000..4a517bde29c Binary files /dev/null and b/erpnext/public/images/illustrations/customers-onboard.png differ diff --git a/erpnext/public/images/illustrations/desk-onboard.png b/erpnext/public/images/illustrations/desk-onboard.png new file mode 100644 index 00000000000..74b632dc30b Binary files /dev/null and b/erpnext/public/images/illustrations/desk-onboard.png differ diff --git a/erpnext/public/images/illustrations/letterhead-onboard.png b/erpnext/public/images/illustrations/letterhead-onboard.png new file mode 100644 index 00000000000..fdfd16ad9da Binary files /dev/null and b/erpnext/public/images/illustrations/letterhead-onboard.png differ diff --git a/erpnext/public/images/illustrations/letterhead.png b/erpnext/public/images/illustrations/letterhead.png deleted file mode 100644 index 37df6d7f6fc..00000000000 Binary files a/erpnext/public/images/illustrations/letterhead.png and /dev/null differ diff --git a/erpnext/public/images/illustrations/onboard.png b/erpnext/public/images/illustrations/onboard.png deleted file mode 100644 index 094aa3f8ddf..00000000000 Binary files a/erpnext/public/images/illustrations/onboard.png and /dev/null differ diff --git a/erpnext/public/images/illustrations/product.png b/erpnext/public/images/illustrations/product.png deleted file mode 100644 index f864b7af60e..00000000000 Binary files a/erpnext/public/images/illustrations/product.png and /dev/null differ diff --git a/erpnext/public/images/illustrations/products-onboard.png b/erpnext/public/images/illustrations/products-onboard.png new file mode 100644 index 00000000000..2dee2039e7d Binary files /dev/null and b/erpnext/public/images/illustrations/products-onboard.png differ diff --git a/erpnext/public/images/illustrations/supplier-onboard.png b/erpnext/public/images/illustrations/supplier-onboard.png new file mode 100644 index 00000000000..30335f2b63b Binary files /dev/null and b/erpnext/public/images/illustrations/supplier-onboard.png differ diff --git a/erpnext/public/images/illustrations/supplier.png b/erpnext/public/images/illustrations/supplier.png deleted file mode 100644 index 87f7789600f..00000000000 Binary files a/erpnext/public/images/illustrations/supplier.png and /dev/null differ diff --git a/erpnext/public/images/illustrations/user.png b/erpnext/public/images/illustrations/user.png deleted file mode 100644 index 7dd7db210d4..00000000000 Binary files a/erpnext/public/images/illustrations/user.png and /dev/null differ diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue index f151add8d5a..54c359766d3 100644 --- a/erpnext/public/js/hub/PageContainer.vue +++ b/erpnext/public/js/hub/PageContainer.vue @@ -24,7 +24,7 @@ import NotFound from './pages/NotFound.vue'; function get_route_map() { const read_only_routes = { 'marketplace/home': Home, - 'marketplace/search/:keyword': Search, + 'marketplace/search/:category/:keyword': Search, 'marketplace/category/:category': Category, 'marketplace/item/:item': Item, 'marketplace/seller/:seller': Seller, diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue index 3a0e6bfab83..057fe8bc617 100644 --- a/erpnext/public/js/hub/pages/Category.vue +++ b/erpnext/public/js/hub/pages/Category.vue @@ -3,6 +3,12 @@ class="marketplace-page" :data-page-name="page_name" > + +
{{ page_title }}
diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue index 353656957df..aaeaa7eb7c1 100644 --- a/erpnext/public/js/hub/pages/Home.vue +++ b/erpnext/public/js/hub/pages/Home.vue @@ -98,7 +98,7 @@ export default { }, set_search_route() { - frappe.set_route('marketplace', 'search', this.search_value); + frappe.set_route('marketplace', 'search', 'All', this.search_value); }, } } diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue index 5118a814e07..103284289bb 100644 --- a/erpnext/public/js/hub/pages/Search.vue +++ b/erpnext/public/js/hub/pages/Search.vue @@ -29,8 +29,10 @@ export default { return { page_name: frappe.get_route()[1], items: [], - search_value: frappe.get_route()[2], + category: frappe.get_route()[2], + search_value: frappe.get_route()[3], item_id_fieldname: 'name', + filters: {}, // Constants search_placeholder: __('Search for anything ...'), @@ -40,7 +42,7 @@ export default { computed: { page_title() { return this.items.length - ? __(`Results for "${this.search_value}"`) + ? __(`Results for "${this.search_value}" ${this.category !== 'All'? `in category ${this.category}` : ''}`) : __('No Items found.'); } }, @@ -49,14 +51,20 @@ export default { }, methods: { get_items() { - hub.call('get_items', { keyword: this.search_value }) + if (this.category !== 'All') { + this.filters['hub_category'] = this.category; + } + hub.call('get_items', { + keyword: this.search_value, + filters: this.filters + }) .then((items) => { this.items = items; }) }, set_route_and_get_items() { - frappe.set_route('marketplace', 'search', this.search_value); + frappe.set_route('marketplace', 'search', this.category, this.search_value); this.get_items(); }, diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index ce559218cbd..1a7ff2bf5a6 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -55,14 +55,25 @@ frappe.query_reports["GSTR-1"] = { report.page.add_inner_button(__("Download as JSON"), function () { var filters = report.get_values(); - const args = { - cmd: 'erpnext.regional.report.gstr_1.gstr_1.get_json', - data: report.data, - report_name: report.report_name, - filters: filters - }; - - open_url_post(frappe.request.url, args); + frappe.call({ + method: 'erpnext.regional.report.gstr_1.gstr_1.get_json', + args: { + data: report.data, + report_name: report.report_name, + filters: filters + }, + callback: function(r) { + if (r.message) { + const args = { + cmd: 'erpnext.regional.report.gstr_1.gstr_1.download_json_file', + data: r.message.data, + report_name: r.message.report_name, + report_type: r.message.report_type + }; + open_url_post(frappe.request.url, args); + } + } + }); }); } } diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 090616b0777..4f9cc7ff7a0 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -532,16 +532,9 @@ class Gstr1Report(object): self.columns = self.invoice_columns + self.tax_columns + self.other_columns @frappe.whitelist() -def get_json(): - data = frappe._dict(frappe.local.form_dict) - - del data["cmd"] - if "csrf_token" in data: - del data["csrf_token"] - - filters = json.loads(data["filters"]) - report_data = json.loads(data["data"]) - report_name = data["report_name"] +def get_json(filters, report_name, data): + filters = json.loads(filters) + report_data = json.loads(data) gstin = get_company_gstin_number(filters["company"]) fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) @@ -575,7 +568,11 @@ def get_json(): out = get_export_json(res) gst_json["exp"] = out - download_json_file(report_name, filters["type_of_business"], gst_json) + return { + 'report_name': report_name, + 'report_type': filters['type_of_business'], + 'data': gst_json + } def get_b2b_json(res, gstin): inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, [] @@ -722,11 +719,15 @@ def get_company_gstin_number(company): if gstin: return gstin[0]["gstin"] else: - frappe.throw(_("Please set valid GSTIN No. in Company Address")) + frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}".format( + frappe.bold(company) + ))) -def download_json_file(filename, report_type, data): +@frappe.whitelist() +def download_json_file(): ''' download json content in a file ''' - frappe.response['filename'] = frappe.scrub("{0} {1}".format(filename, report_type)) + '.json' - frappe.response['filecontent'] = json.dumps(data) + data = frappe._dict(frappe.local.form_dict) + frappe.response['filename'] = frappe.scrub("{0} {1}".format(data['report_name'], data['report_type'])) + '.json' + frappe.response['filecontent'] = data['data'] frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 7dc58b582ac..2dae0d8063d 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -112,7 +112,6 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( let allow_delivery = false; if (doc.docstatus==1) { - this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); if(this.frm.has_perm("submit")) { if(doc.status === 'On Hold') { @@ -136,7 +135,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( if(doc.status !== 'Closed') { if(doc.status !== 'On Hold') { - allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty)) + allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty)) && !this.frm.doc.skip_delivery_note if (this.frm.has_perm("submit")) { @@ -148,6 +147,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } } + this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); + // delivery note if(flt(doc.per_delivered, 6) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) { this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create')); @@ -361,7 +362,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( }, toggle_delivery_date: function() { - this.frm.fields_dict.items.grid.toggle_reqd("delivery_date", + this.frm.fields_dict.items.grid.toggle_reqd("delivery_date", (this.frm.doc.order_type == "Sales" && !this.frm.doc.skip_delivery_note)); }, diff --git a/erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json b/erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json similarity index 77% rename from erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json rename to erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json index a0bb6fe26df..f39fea48960 100644 --- a/erpnext/selling/setup_wizard_slide/add_a_few_customers/add_a_few_customers.json +++ b/erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json @@ -3,18 +3,18 @@ "app": "ERPNext", "creation": "2019-11-15 14:44:10.065014", "docstatus": 0, - "doctype": "Setup Wizard Slide", + "doctype": "Onboarding Slide", "domains": [], "help_links": [ { - "label": "Customers", + "label": "Learn More", "video_id": "zsrrVDk6VBs" } ], "idx": 0, - "image_src": "/assets/erpnext/images/illustrations/customer.png", + "image_src": "/assets/erpnext/images/illustrations/customers-onboard.png", "max_count": 3, - "modified": "2019-11-26 18:26:15.888794", + "modified": "2019-12-03 22:54:28.959549", "modified_by": "Administrator", "name": "Add A Few Customers", "owner": "Administrator", @@ -44,6 +44,5 @@ ], "slide_order": 40, "slide_title": "Add A Few Customers", - "slide_type": "Create", - "submit_method": "" + "slide_type": "Create" } \ No newline at end of file diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 0bcddc21517..4d2d540bbc8 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -283,7 +283,7 @@ class EmailDigest(Document): card.value = card.value *-1 card.value = self.fmt_money(card.value,False if key in ("bank_balance", "credit_balance") else True) - cache.setex(cache_key, card, 24 * 60 * 60) + cache.set_value(cache_key, card, expires_in_sec=24 * 60 * 60) context.cards.append(card) diff --git a/erpnext/setup/onboarding_slide/welcome_back_to_erpnext!/welcome_back_to_erpnext!.json b/erpnext/setup/onboarding_slide/welcome_back_to_erpnext!/welcome_back_to_erpnext!.json new file mode 100644 index 00000000000..bf330d09de4 --- /dev/null +++ b/erpnext/setup/onboarding_slide/welcome_back_to_erpnext!/welcome_back_to_erpnext!.json @@ -0,0 +1,23 @@ +{ + "add_more_button": 0, + "app": "ERPNext", + "creation": "2019-12-04 19:21:39.995776", + "docstatus": 0, + "doctype": "Onboarding Slide", + "domains": [], + "help_links": [], + "idx": 0, + "image_src": "/assets/erpnext/images/illustrations/desk-onboard.png", + "is_completed": 0, + "max_count": 3, + "modified": "2019-12-04 19:21:39.995776", + "modified_by": "Administrator", + "name": "Welcome back to ERPNext!", + "owner": "Administrator", + "slide_desc": "

Let's continue where you left from!

", + "slide_fields": [], + "slide_module": "Setup", + "slide_order": 0, + "slide_title": "Welcome back to ERPNext!", + "slide_type": "Continue" +} \ No newline at end of file diff --git a/erpnext/setup/setup_wizard_slide/welcome_to_erpnext!/welcome_to_erpnext!.json b/erpnext/setup/onboarding_slide/welcome_to_erpnext!/welcome_to_erpnext!.json similarity index 60% rename from erpnext/setup/setup_wizard_slide/welcome_to_erpnext!/welcome_to_erpnext!.json rename to erpnext/setup/onboarding_slide/welcome_to_erpnext!/welcome_to_erpnext!.json index 1da9dd44e2b..4ea69852afa 100644 --- a/erpnext/setup/setup_wizard_slide/welcome_to_erpnext!/welcome_to_erpnext!.json +++ b/erpnext/setup/onboarding_slide/welcome_to_erpnext!/welcome_to_erpnext!.json @@ -3,20 +3,20 @@ "app": "ERPNext", "creation": "2019-11-26 17:01:26.671859", "docstatus": 0, - "doctype": "Setup Wizard Slide", + "doctype": "Onboarding Slide", "domains": [], "help_links": [], "idx": 0, - "image_src": "/assets/erpnext/images/illustrations/onboard.png", + "image_src": "/assets/erpnext/images/illustrations/desk-onboard.png", "max_count": 0, - "modified": "2019-11-26 17:17:29.813299", + "modified": "2019-12-03 22:49:12.871260", "modified_by": "Administrator", "name": "Welcome to ERPNext!", "owner": "Administrator", - "slide_desc": "Setting up an ERP can be overwhelming. But don't worry, we have got your back!
\nLet's setup your company.\nThis wizard will help you onboard to ERPNext in a short time!", + "slide_desc": "Setting up an ERP can be overwhelming. But don't worry, we have got your back!\nLet's setup your company.\nThis wizard will help you onboard to ERPNext in a short time!", "slide_fields": [], "slide_module": "Setup", - "slide_order": 10, + "slide_order": 1, "slide_title": "Welcome to ERPNext!", "slide_type": "Information" } \ No newline at end of file diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index d1c206d8b1d..1a86b79ad10 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -106,7 +106,8 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No # expire in 6 hours response.raise_for_status() value = response.json()["rates"][to_currency] - cache.setex(key, value, 6 * 60 * 60) + + cache.set_value(key, value, expires_in_sec=6 * 60 * 60) return flt(value) except: frappe.log_error(title="Get Exchange Rate") diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 831381c86ad..5341f298531 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -65,7 +65,7 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip bin = get_bin_qty(packing_item_code, pi.warehouse) pi.actual_qty = flt(bin.get("actual_qty")) pi.projected_qty = flt(bin.get("projected_qty")) - if old_packed_items_map: + if old_packed_items_map and old_packed_items_map.get((packing_item_code, main_item_row.item_code)): pi.batch_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].batch_no pi.serial_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].serial_no pi.warehouse = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].warehouse diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index d9c94fced7d..47704712594 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -249,6 +249,8 @@ frappe.ui.form.on('Stock Entry', { }, __("Get items from")); } + frm.events.show_bom_custom_button(frm); + if (frm.doc.company) { frm.trigger("toggle_display_account_head"); } @@ -262,6 +264,11 @@ frappe.ui.form.on('Stock Entry', { frm.trigger("setup_quality_inspection"); }, + stock_entry_type: function(frm){ + frm.remove_custom_button('Bill of Materials', "Get items from"); + frm.events.show_bom_custom_button(frm); + }, + purpose: function(frm) { frm.trigger('validate_purpose_consumption'); frm.fields_dict.items.grid.refresh(); @@ -398,6 +405,85 @@ frappe.ui.form.on('Stock Entry', { } }, + show_bom_custom_button: function(frm){ + if (frm.doc.docstatus === 0 && + ['Material Issue', 'Material Receipt', 'Material Transfer', 'Send to Subcontractor'].includes(frm.doc.purpose)) { + frm.add_custom_button(__('Bill of Materials'), function() { + frm.events.get_items_from_bom(frm); + }, __("Get items from")); + } + }, + + get_items_from_bom: function(frm) { + let filters = function(){ + return {filters: { docstatus:1 }}; + } + + let fields = [ + {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), + options:"BOM", reqd: 1, get_query: filters()}, + {"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"), + options:"Warehouse"}, + {"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"), + options:"Warehouse"}, + {"fieldname":"qty", "fieldtype":"Float", "label":__("Quantity"), + reqd: 1, "default": 1}, + {"fieldname":"fetch_exploded", "fieldtype":"Check", + "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1}, + {"fieldname":"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"} + ] + + // Exclude field 'Target Warehouse' in case of Material Issue + if (frm.doc.purpose == 'Material Issue'){ + fields.splice(2,1); + } + // Exclude field 'Source Warehouse' in case of Material Receipt + else if(frm.doc.purpose == 'Material Receipt'){ + fields.splice(1,1); + } + + let d = new frappe.ui.Dialog({ + title: __("Get Items from BOM"), + fields: fields + }); + d.get_input("fetch").on("click", function() { + let values = d.get_values(); + if(!values) return; + values["company"] = frm.doc.company; + if(!frm.doc.company) frappe.throw(__("Company field is required")); + frappe.call({ + method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", + args: values, + callback: function(r) { + if (!r.message) { + frappe.throw(__("BOM does not contain any stock item")); + } else { + erpnext.utils.remove_empty_first_row(frm, "items"); + $.each(r.message, function(i, item) { + let d = frappe.model.add_child(cur_frm.doc, "Stock Entry Detail", "items"); + d.item_code = item.item_code; + d.item_name = item.item_name; + d.item_group = item.item_group; + d.s_warehouse = values.source_warehouse; + d.t_warehouse = values.target_warehouse; + d.uom = item.stock_uom; + d.stock_uom = item.stock_uom; + d.conversion_factor = item.conversion_factor ? item.conversion_factor : 1; + d.qty = item.qty; + d.expense_account = item.expense_account; + d.project = item.project; + frm.events.set_basic_rate(frm, d.doctype, d.name); + }); + } + d.hide(); + refresh_field("items"); + } + }); + + }); + d.show(); + }, + calculate_basic_amount: function(frm, item) { item.basic_amount = flt(flt(item.transfer_qty) * flt(item.basic_rate), precision("basic_amount", item)); diff --git a/erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json b/erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json similarity index 76% rename from erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json rename to erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json index c536f7b2cab..27a30627eec 100644 --- a/erpnext/stock/setup_wizard_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json +++ b/erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json @@ -3,13 +3,13 @@ "app": "ERPNext", "creation": "2019-11-15 14:41:12.007359", "docstatus": 0, - "doctype": "Setup Wizard Slide", + "doctype": "Onboarding Slide", "domains": [], "help_links": [], "idx": 0, - "image_src": "/assets/erpnext/images/illustrations/product.png", + "image_src": "/assets/erpnext/images/illustrations/products-onboard.png", "max_count": 3, - "modified": "2019-11-26 18:26:35.305755", + "modified": "2019-12-03 22:54:07.558632", "modified_by": "Administrator", "name": "Add A Few Products You Buy Or Sell", "owner": "Administrator", @@ -26,15 +26,9 @@ }, { "align": "", - "fieldtype": "Column Break", - "reqd": 1 - }, - { - "align": "", - "fieldname": "uom", - "fieldtype": "Link", - "label": "UOM", - "options": "UOM", + "fieldname": "item_price", + "fieldtype": "Currency", + "label": "Item Price", "reqd": 1 }, { @@ -44,14 +38,14 @@ }, { "align": "", - "fieldname": "item_price", - "fieldtype": "Currency", - "label": "Item Price", + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", "reqd": 1 } ], "slide_order": 30, "slide_title": "Add A Few Products You Buy Or Sell", - "slide_type": "Create", - "submit_method": "" + "slide_type": "Create" } \ No newline at end of file