diff --git a/erpnext/__init__.py b/erpnext/__init__.py index eb7f91d2ca6f..641b9b6f0092 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe -__version__ = '8.0.40' +__version__ = '8.0.41' def get_default_company(user=None): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 007afe4b63bc..1f8aa1d03c3d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -299,6 +299,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.calculate_write_off_amount() }else { this.frm.set_value("change_amount", 0.0) + this.frm.set_value("base_change_amount", 0.0) } this.frm.refresh_fields(); diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index cede8f4d342d..42327b94acab 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -19,10 +19,10 @@ def validate_status(status, options): ["Converted", "has_customer"], ], "Opportunity": [ - ["Quotation", "has_active_quotation"], - ["Converted", "has_ordered_quotation"], ["Lost", "eval:self.status=='Lost'"], ["Lost", "has_lost_quotation"], + ["Quotation", "has_active_quotation"], + ["Converted", "has_ordered_quotation"], ["Closed", "eval:self.status=='Closed'"] ], "Quotation": [ diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 822d50bfdf87..50f6b61c32ba 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -440,17 +440,17 @@ def calculate_outstanding_amount(self): self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total")) - if self.doc.doctype == "Sales Invoice": + if self.doc.doctype == "Sales Invoice": self.doc.round_floats_in(self.doc, ["paid_amount"]) + self.calculate_write_off_amount() + self.calculate_change_amount() + paid_amount = self.doc.paid_amount \ if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount - + change_amount = self.doc.change_amount \ if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount - self.calculate_write_off_amount() - self.calculate_change_amount() - self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount), self.doc.precision("outstanding_amount")) @@ -475,7 +475,9 @@ def calculate_paid_amount(self): def calculate_change_amount(self): self.doc.change_amount = 0.0 self.doc.base_change_amount = 0.0 - if self.doc.paid_amount > self.doc.grand_total: + if self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \ + and any([d.type == "Cash" for d in self.doc.payments]): + self.doc.change_amount = flt(self.doc.paid_amount - self.doc.grand_total + self.doc.write_off_amount, self.doc.precision("change_amount")) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 974b23e69ad4..0e14c85b2c0d 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -35,6 +35,7 @@ frappe.ui.form.on("Opportunity", { var doc = frm.doc; frm.events.enquiry_from(frm); frm.trigger('set_contact_link'); + erpnext.toggle_naming_series(); if(!doc.__islocal && doc.status!=="Lost") { if(doc.with_items){ @@ -53,6 +54,20 @@ frappe.ui.form.on("Opportunity", { frm.add_custom_button(__('Lost'), cur_frm.cscript['Declare Opportunity Lost']); } + }, + + if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) { + if(frm.doc.status==="Open") { + frm.add_custom_button(__("Close"), function() { + frm.set_value("status", "Closed"); + frm.save(); + }); + } else { + frm.add_custom_button(__("Reopen"), function() { + frm.set_value("status", "Open"); + frm.save(); + }); + } } }, @@ -122,25 +137,6 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({ $.extend(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm})); -cur_frm.cscript.refresh = function(doc, cdt, cdn) { - erpnext.toggle_naming_series(); - - var frm = cur_frm; - if(!doc.__islocal && frm.perm[0].write && doc.docstatus==0) { - if(frm.doc.status==="Open") { - frm.add_custom_button(__("Close"), function() { - frm.set_value("status", "Closed"); - frm.save(); - }); - } else { - frm.add_custom_button(__("Reopen"), function() { - frm.set_value("status", "Open"); - frm.save(); - }); - } - } -} - cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) { if(doc.enquiry_from == 'Lead' && doc.lead) cur_frm.cscript.lead(doc, cdt, cdn); diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index eebf464047ab..58f26fe54616 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -31,7 +31,6 @@ def validate(self): if not self.enquiry_from: frappe.throw(_("Opportunity From field is mandatory")) - self.set_status() self.validate_item_details() self.validate_uom_is_integer("uom", "qty") self.validate_lead_cust() diff --git a/erpnext/hooks.py b/erpnext/hooks.py index af4dd3b68e92..d64409d9564e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -178,7 +178,8 @@ "erpnext.hr.doctype.employee.employee.send_birthday_reminders", "erpnext.projects.doctype.task.task.set_tasks_as_overdue", "erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries", - 'erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary' + "erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary", + "erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status" ] } diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 01dc341fd594..1cd725734fa3 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -11,6 +11,7 @@ from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account from erpnext.controllers.accounts_controller import AccountsController +from frappe.utils.csvutils import getlink class InvalidExpenseApproverError(frappe.ValidationError): pass @@ -146,7 +147,7 @@ def validate_account_details(self): frappe.throw(_("Cost center is required to book an expense claim")) if not self.payable_account: - frappe.throw(_("Please set default payable account in the employee {0}").format(self.employee)) + frappe.throw(_("Please set default payable account for the company {0}").format(getlink("Company",self.company))) if self.is_paid: if not self.mode_of_payment: diff --git a/erpnext/hr/doctype/training_event/training_event.json b/erpnext/hr/doctype/training_event/training_event.json index 1f79d231adf1..03b58b48021e 100644 --- a/erpnext/hr/doctype/training_event/training_event.json +++ b/erpnext/hr/doctype/training_event/training_event.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:event_name", @@ -12,6 +13,7 @@ "editable_grid": 1, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -27,7 +29,7 @@ "in_standard_filter": 0, "label": "Event Name", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", "print_hide": 0, @@ -41,6 +43,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 1, "bold": 0, "collapsible": 0, @@ -71,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -99,6 +103,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -129,6 +134,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -157,6 +163,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -186,6 +193,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -216,6 +224,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -244,6 +253,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -274,6 +284,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -303,6 +314,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -331,6 +343,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -361,6 +374,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -390,6 +404,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -418,6 +433,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -447,6 +463,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -476,6 +493,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -504,6 +522,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -533,6 +552,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -562,6 +582,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -592,6 +613,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 1, "bold": 0, "collapsible": 0, @@ -622,6 +644,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -651,17 +674,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-17 16:51:35.141403", + "modified": "2017-05-29 06:13:38.411039", "modified_by": "Administrator", "module": "HR", "name": "Training Event", diff --git a/erpnext/hr/doctype/training_result/training_result.py b/erpnext/hr/doctype/training_result/training_result.py index 16b76a7e19a7..36c3cb93bc1a 100644 --- a/erpnext/hr/doctype/training_result/training_result.py +++ b/erpnext/hr/doctype/training_result/training_result.py @@ -13,7 +13,9 @@ def on_submit(self): def send_result(self): for emp in self.employees: - message = "Thank You for attending {0}. You grade is {1}".format(self.training_event, emp.grade) + message = "Thank You for attending {0}.".format(self.training_event) + if emp.grade: + message = message + "Your grade: {0}".format(emp.grade) frappe.sendmail(frappe.db.get_value("Employee", emp.employee, "company_email"), \ subject=_("{0} Results".format(self.training_event)), \ content=message) diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index ff1082d0b811..68da3362502f 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -20,6 +20,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict from erpnext.stock.utils import get_bin +from frappe.utils.csvutils import getlink class OverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass @@ -311,7 +312,7 @@ def make_time_logs(self, open_new=False): if timesheet and timesheet.get("time_logs"): timesheet.save() - timesheets.append(timesheet.name) + timesheets.append(getlink("Timesheet", timesheet.name)) self.planned_end_date = self.operations[-1].planned_end_time if timesheets: diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 154f0d0f2023..8844a483d776 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -368,7 +368,8 @@ def get_events(start, end, filters=None): conditions = get_conditions(filters) return frappe.db.sql("""select `tabTimesheet Detail`.name as name, `tabTimesheet Detail`.docstatus as status, `tabTimesheet Detail`.parent as parent, - from_time as start_date, hours, activity_type, project, to_time as end_date, + from_time as start_date, hours, activity_type, + `tabTimesheet Detail`.project, to_time as end_date, CONCAT(`tabTimesheet Detail`.parent, ' (', ROUND(hours,2),' hrs)') as title from `tabTimesheet Detail`, `tabTimesheet` where `tabTimesheet Detail`.parent = `tabTimesheet`.name diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 849275f02cd3..dc1db3554b4d 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -604,11 +604,18 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ calculate_change_amount: function(){ this.frm.doc.change_amount = 0.0; - if(this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return){ - this.frm.doc.change_amount = flt(this.frm.doc.paid_amount - this.frm.doc.grand_total + - this.frm.doc.write_off_amount, precision("change_amount")); - this.frm.doc.base_change_amount = flt(this.frm.doc.base_paid_amount - this.frm.doc.base_grand_total + - this.frm.doc.base_write_off_amount, precision("base_change_amount")); + this.frm.doc.base_change_amount = 0.0; + if(this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) { + var payment_types = $.map(cur_frm.doc.payments, function(d) { return d.type }); + if (in_list(payment_types, 'Cash')) { + this.frm.doc.change_amount = flt(this.frm.doc.paid_amount - this.frm.doc.grand_total + + this.frm.doc.write_off_amount, precision("change_amount")); + + this.frm.doc.base_change_amount = flt(this.frm.doc.base_paid_amount - + this.frm.doc.base_grand_total + this.frm.doc.base_write_off_amount, + precision("base_change_amount")); + + } } }, diff --git a/erpnext/schools/doctype/student_group/student_group.py b/erpnext/schools/doctype/student_group/student_group.py index e86142106ef1..9cdf9c7f016f 100644 --- a/erpnext/schools/doctype/student_group/student_group.py +++ b/erpnext/schools/doctype/student_group/student_group.py @@ -14,7 +14,7 @@ def validate(self): self.validate_strength() if frappe.defaults.get_defaults().student_validation_setting: self.validate_students() - self.validate_roll_no() + self.validate_and_set_child_table_fields() validate_duplicate_student(self.students) def validate_mandatory_fields(self): @@ -38,9 +38,16 @@ def validate_students(self): if not frappe.db.get_value("Student", d.student, "enabled") and d.active: frappe.throw(_("{0} - {1} is inactive student".format(d.group_roll_number, d.student_name))) - def validate_roll_no(self): + def validate_and_set_child_table_fields(self): + roll_numbers = [d.group_roll_number for d in self.students if d.group_roll_number] + max_roll_no = max(roll_numbers) if roll_numbers else 0 roll_no_list = [] for d in self.students: + if not d.student_name: + d.student_name = frappe.db.get_value("Student", d.student, "title") + if not d.group_roll_number: + max_roll_no += 1 + d.group_roll_number = max_roll_no if d.group_roll_number in roll_no_list: frappe.throw(_("Duplicate roll number for student {0}".format(d.student_name))) else: diff --git a/erpnext/schools/doctype/student_group/test_student_group.py b/erpnext/schools/doctype/student_group/test_student_group.py index 6b599aa26551..18a6b14f5098 100644 --- a/erpnext/schools/doctype/student_group/test_student_group.py +++ b/erpnext/schools/doctype/student_group/test_student_group.py @@ -5,8 +5,23 @@ import frappe import unittest - -# test_records = frappe.get_test_records('Student Group') +from frappe.utils.make_random import get_random class TestStudentGroup(unittest.TestCase): - pass + def test_student_roll_no(self): + doc = frappe.get_doc({ + "doctype": "Student Group", + "student_group_name": "_Test Student Group R", + "group_based_on": "Activity" + }).insert() + + student_list = [] + while len(student_list) < 3: + s = get_random("Student") + if s not in student_list: + student_list.append(s) + + doc.extend("students", [{"student":d} for d in student_list]) + doc.save() + self.assertEquals(max([d.group_roll_number for d in doc.students]), 3) + diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index bc9b89395024..03d4d73a981a 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -324,3 +324,12 @@ def update_serial_nos_after_submit(controller, parentfield): update_rejected_serial_nos = False if accepted_serial_nos_updated: break + +def update_maintenance_status(): + serial_nos = frappe.db.sql('''select name from `tabSerial No` where (amc_expiry_date<%s or + warranty_expiry_date<%s) and maintenance_status not in ('Out of Warranty', 'Out of AMC')''', + (nowdate(), nowdate())) + for serial_no in serial_nos: + doc = frappe.get_doc("Serial No", serial_no[0]) + doc.set_maintenance_status() + frappe.db.set_value('Serial No', doc.name, 'maintenance_status', doc.maintenance_status)