From cea5479f4d6066ac066178fb45846c6fda3ee844 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Thu, 12 Nov 2015 18:39:57 +0530 Subject: [PATCH 01/13] [fix] Don't show Receive for a Purchase Order having non-stock items, show Close button only for users with Submit rights --- .../doctype/purchase_order/purchase_order.js | 16 ++++++++++------ .../doctype/purchase_order/purchase_order.py | 3 +++ .../selling/doctype/sales_order/sales_order.js | 18 +++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 2a76f8a2b1..e1b01a128a 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -21,11 +21,13 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( if(doc.docstatus == 1 && !in_list(["Stopped", "Closed", "Delivered"], doc.status)) { - if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100) { - cur_frm.add_custom_button(__('Stop'), this.stop_purchase_order); - } + if (this.frm.has_perm("submit")) { + if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100) { + cur_frm.add_custom_button(__('Stop'), this.stop_purchase_order); + } - cur_frm.add_custom_button(__('Close'), this.close_purchase_order); + cur_frm.add_custom_button(__('Close'), this.close_purchase_order); + } if(doc.delivered_by_supplier && doc.status!="Delivered"){ cur_frm.add_custom_button(__('Mark as Delivered'), this.delivered_by_supplier); @@ -35,7 +37,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry); } - if(flt(doc.per_received, 2) < 100) { + if(flt(doc.per_received, 2) < 100 && this.frm.doc.__onload.has_stock_item) { cur_frm.add_custom_button(__('Receive'), this.make_purchase_receipt).addClass("btn-primary"); if(doc.is_subcontracted==="Yes") { @@ -53,7 +55,9 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( } if(doc.docstatus == 1 && in_list(["Stopped", "Closed", "Delivered"], doc.status)) { - cur_frm.add_custom_button(__('Re-open'), this.unstop_purchase_order); + if (this.frm.has_perm("submit")) { + cur_frm.add_custom_button(__('Re-open'), this.unstop_purchase_order); + } } }, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 01ebcb9f07..d069bc0112 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -33,6 +33,9 @@ class PurchaseOrder(BuyingController): 'overflow_type': 'order' }] + def onload(self): + self.set_onload("has_stock_item", len(self.get_stock_items()) > 0) + def validate(self): super(PurchaseOrder, self).validate() diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 5926b25db8..56c8da0578 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -56,13 +56,15 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry); } - // stop - if(flt(doc.per_delivered, 2) < 100 || flt(doc.per_billed) < 100) { - cur_frm.add_custom_button(__('Stop'), this.stop_sales_order) - } + if (this.frm.has_perm("submit")) { + // stop + if(flt(doc.per_delivered, 2) < 100 || flt(doc.per_billed) < 100) { + cur_frm.add_custom_button(__('Stop'), this.stop_sales_order) + } - cur_frm.add_custom_button(__('Close'), this.close_sales_order) + cur_frm.add_custom_button(__('Close'), this.close_sales_order) + } // maintenance if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) { @@ -82,8 +84,10 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } else { - // un-stop - cur_frm.add_custom_button(__('Re-open'), cur_frm.cscript['Unstop Sales Order']); + if (this.frm.has_perm("submit")) { + // un-stop + cur_frm.add_custom_button(__('Re-open'), cur_frm.cscript['Unstop Sales Order']); + } } } From 26c54bb4fbf3f427c505c084423e2201a2280342 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 13 Nov 2015 10:39:54 +0530 Subject: [PATCH 02/13] [minor] new quote --- erpnext/setup/doctype/email_digest/quotes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/setup/doctype/email_digest/quotes.py b/erpnext/setup/doctype/email_digest/quotes.py index d56117a2c6..4b996d9675 100644 --- a/erpnext/setup/doctype/email_digest/quotes.py +++ b/erpnext/setup/doctype/email_digest/quotes.py @@ -27,6 +27,7 @@ def get_random_quote(): ("There is more to life than increasing its speed.", "Mahatma Gandhi"), ("A small body of determined spirits fired by an unquenchable faith in their mission can alter the course of history.", "Mahatma Gandhi"), ("If two wrongs don't make a right, try three.", "Laurence J. Peter"), + ("Inspiration exists, but it has to find you working.", "Pablo Picasso"), ] return random.choice(quotes) From 511421b6a3321865f69b5be49dcaf0062264f206 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 13 Nov 2015 11:04:50 +0530 Subject: [PATCH 03/13] [minor] [enhancement] ability to make web-form standard, reload web-forms, merge after https://github.com/frappe/frappe/pull/1405 --- erpnext/patches.txt | 3 +- erpnext/patches/v6_8/__init__.py | 0 erpnext/patches/v6_8/make_webform_standard.py | 9 + .../setup/fixtures/web_form/addresses.json | 271 +++++++++--------- erpnext/setup/fixtures/web_form/issues.json | 109 +++---- 5 files changed, 202 insertions(+), 190 deletions(-) create mode 100644 erpnext/patches/v6_8/__init__.py create mode 100644 erpnext/patches/v6_8/make_webform_standard.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 37a6b27042..05723caaad 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -231,4 +231,5 @@ erpnext.patches.v6_4.fix_expense_included_in_valuation execute:frappe.delete_doc_if_exists("Report", "Item-wise Last Purchase Rate") erpnext.patches.v6_6.fix_website_image erpnext.patches.v6_6.remove_fiscal_year_from_leave_allocation -execute:frappe.delete_doc_if_exists("DocType", "Stock UOM Replace Utility") \ No newline at end of file +execute:frappe.delete_doc_if_exists("DocType", "Stock UOM Replace Utility") +erpnext.patches.v6_8.make_webform_standard diff --git a/erpnext/patches/v6_8/__init__.py b/erpnext/patches/v6_8/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/patches/v6_8/make_webform_standard.py b/erpnext/patches/v6_8/make_webform_standard.py new file mode 100644 index 0000000000..d4e1484b2a --- /dev/null +++ b/erpnext/patches/v6_8/make_webform_standard.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + frappe.reload_doctype("Web Form") + frappe.delete_doc("Web Form", "Issues") + frappe.delete_doc("Web Form", "Addresses") + + from erpnext.setup.install import add_web_forms + add_web_forms() diff --git a/erpnext/setup/fixtures/web_form/addresses.json b/erpnext/setup/fixtures/web_form/addresses.json index 8051c726e0..1830dab6e4 100644 --- a/erpnext/setup/fixtures/web_form/addresses.json +++ b/erpnext/setup/fixtures/web_form/addresses.json @@ -1,167 +1,168 @@ [ { - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_multiple": 1, - "breadcrumbs": null, - "doc_type": "Address", - "docstatus": 0, - "doctype": "Web Form", - "introduction_text": null, - "login_required": 1, - "modified": "2015-06-01 06:53:43.699336", - "name": "addresses", - "page_name": "addresses", - "published": 1, - "success_message": null, - "success_url": "/addresses", - "title": "Addresses", + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 1, + "allow_multiple": 1, + "breadcrumbs": null, + "doc_type": "Address", + "docstatus": 0, + "doctype": "Web Form", + "introduction_text": null, + "is_standard": 1, + "login_required": 1, + "modified": "2015-06-01 06:53:43.699336", + "name": "addresses", + "page_name": "addresses", + "published": 1, + "success_message": null, + "success_url": "/addresses", + "title": "Addresses", "web_form_fields": [ { - "default": null, - "description": "", - "fieldname": "address_title", - "fieldtype": "Data", - "hidden": 0, - "label": "Address Title", - "options": null, - "read_only": 0, + "default": null, + "description": "", + "fieldname": "address_title", + "fieldtype": "Data", + "hidden": 0, + "label": "Address Title", + "options": null, + "read_only": 0, "reqd": 0 - }, + }, { - "default": null, - "description": null, - "fieldname": "address_type", - "fieldtype": "Select", - "hidden": 0, - "label": "Address Type", - "options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nOther", - "read_only": 0, + "default": null, + "description": null, + "fieldname": "address_type", + "fieldtype": "Select", + "hidden": 0, + "label": "Address Type", + "options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nOther", + "read_only": 0, "reqd": 1 - }, + }, { - "default": null, - "description": null, - "fieldname": "address_line1", - "fieldtype": "Data", - "hidden": 0, - "label": "Address Line 1", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "address_line1", + "fieldtype": "Data", + "hidden": 0, + "label": "Address Line 1", + "options": null, + "read_only": 0, "reqd": 1 - }, + }, { - "default": null, - "description": null, - "fieldname": "address_line2", - "fieldtype": "Data", - "hidden": 0, - "label": "Address Line 2", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "address_line2", + "fieldtype": "Data", + "hidden": 0, + "label": "Address Line 2", + "options": null, + "read_only": 0, "reqd": 0 - }, + }, { - "default": null, - "description": null, - "fieldname": "city", - "fieldtype": "Data", - "hidden": 0, - "label": "City/Town", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "city", + "fieldtype": "Data", + "hidden": 0, + "label": "City/Town", + "options": null, + "read_only": 0, "reqd": 1 - }, + }, { - "default": null, - "description": null, - "fieldname": "state", - "fieldtype": "Data", - "hidden": 0, - "label": "State", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "state", + "fieldtype": "Data", + "hidden": 0, + "label": "State", + "options": null, + "read_only": 0, "reqd": 0 - }, + }, { - "default": null, - "description": null, - "fieldname": "pincode", - "fieldtype": "Data", - "hidden": 0, - "label": "Pincode", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "pincode", + "fieldtype": "Data", + "hidden": 0, + "label": "Pincode", + "options": null, + "read_only": 0, "reqd": 0 - }, + }, { - "default": null, - "description": null, - "fieldname": "country", - "fieldtype": "Data", - "hidden": 0, - "label": "Country", - "options": "Country", - "read_only": 0, + "default": null, + "description": null, + "fieldname": "country", + "fieldtype": "Data", + "hidden": 0, + "label": "Country", + "options": "Country", + "read_only": 0, "reqd": 1 - }, + }, { - "default": null, - "description": null, - "fieldname": null, - "fieldtype": "Column Break", - "hidden": null, - "label": null, - "options": null, - "read_only": null, + "default": null, + "description": null, + "fieldname": null, + "fieldtype": "Column Break", + "hidden": null, + "label": null, + "options": null, + "read_only": null, "reqd": null - }, + }, { - "default": null, - "description": null, - "fieldname": "email_id", - "fieldtype": "Data", - "hidden": 0, - "label": "Email Id", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "email_id", + "fieldtype": "Data", + "hidden": 0, + "label": "Email Id", + "options": null, + "read_only": 0, "reqd": 0 - }, + }, { - "default": null, - "description": null, - "fieldname": "phone", - "fieldtype": "Data", - "hidden": 0, - "label": "Phone", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "phone", + "fieldtype": "Data", + "hidden": 0, + "label": "Phone", + "options": null, + "read_only": 0, "reqd": 1 - }, + }, { - "default": "0", - "description": "", - "fieldname": "is_primary_address", - "fieldtype": "Check", - "hidden": 0, - "label": "Preferred Billing Address", - "options": null, - "read_only": 0, + "default": "0", + "description": "", + "fieldname": "is_primary_address", + "fieldtype": "Check", + "hidden": 0, + "label": "Preferred Billing Address", + "options": null, + "read_only": 0, "reqd": 0 - }, + }, { - "default": "0", - "description": "", - "fieldname": "is_shipping_address", - "fieldtype": "Check", - "hidden": 0, - "label": "Preferred Shipping Address", - "options": null, - "read_only": 0, + "default": "0", + "description": "", + "fieldname": "is_shipping_address", + "fieldtype": "Check", + "hidden": 0, + "label": "Preferred Shipping Address", + "options": null, + "read_only": 0, "reqd": 0 } - ], + ], "web_page_link_text": null } -] \ No newline at end of file +] diff --git a/erpnext/setup/fixtures/web_form/issues.json b/erpnext/setup/fixtures/web_form/issues.json index 84ff1c2001..b452affdbf 100644 --- a/erpnext/setup/fixtures/web_form/issues.json +++ b/erpnext/setup/fixtures/web_form/issues.json @@ -1,68 +1,69 @@ [ { - "allow_comments": 1, - "allow_delete": 1, - "allow_edit": 1, - "allow_multiple": 1, - "breadcrumbs": "[{\"title\":\"Issues\", \"name\":\"issues\"}]", - "doc_type": "Issue", - "docstatus": 0, - "doctype": "Web Form", - "introduction_text": null, - "login_required": 1, - "modified": "2015-06-01 08:14:26.350792", - "name": "issues", - "page_name": "issues", - "published": 1, - "success_message": "", - "success_url": "/issues", - "title": "Issues", + "allow_comments": 1, + "allow_delete": 1, + "allow_edit": 1, + "allow_multiple": 1, + "breadcrumbs": "[{\"title\":\"Issues\", \"name\":\"issues\"}]", + "doc_type": "Issue", + "docstatus": 0, + "doctype": "Web Form", + "is_standard": 1, + "introduction_text": null, + "login_required": 1, + "modified": "2015-06-01 08:14:26.350792", + "name": "issues", + "page_name": "issues", + "published": 1, + "success_message": "", + "success_url": "/issues", + "title": "Issues", "web_form_fields": [ { - "default": null, - "description": null, - "fieldname": "subject", - "fieldtype": "Data", - "hidden": 0, - "label": "Subject", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "subject", + "fieldtype": "Data", + "hidden": 0, + "label": "Subject", + "options": null, + "read_only": 0, "reqd": 1 - }, + }, { - "default": "Open", - "description": null, - "fieldname": "status", - "fieldtype": "Select", - "hidden": null, - "label": "Status", - "options": "Open\nReplied\nHold\nClosed", - "read_only": 1, + "default": "Open", + "description": null, + "fieldname": "status", + "fieldtype": "Select", + "hidden": null, + "label": "Status", + "options": "Open\nReplied\nHold\nClosed", + "read_only": 1, "reqd": 0 - }, + }, { - "default": null, - "description": null, - "fieldname": "description", - "fieldtype": "Text", - "hidden": 0, - "label": "Description", - "options": null, - "read_only": 0, + "default": null, + "description": null, + "fieldname": "description", + "fieldtype": "Text", + "hidden": 0, + "label": "Description", + "options": null, + "read_only": 0, "reqd": 0 - }, + }, { - "default": null, - "description": null, - "fieldname": "attachment", - "fieldtype": "Attach", - "hidden": null, - "label": "Attachment", - "options": null, - "read_only": null, + "default": null, + "description": null, + "fieldname": "attachment", + "fieldtype": "Attach", + "hidden": null, + "label": "Attachment", + "options": null, + "read_only": null, "reqd": null } - ], + ], "web_page_link_text": null } -] \ No newline at end of file +] From c1531e700849d23f2952c4cc8a31b15b8ed1d142 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 13 Nov 2015 11:39:38 +0530 Subject: [PATCH 04/13] [minor] catch SSL error on make_thumbnail, via support --- erpnext/stock/doctype/item/item.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a795de874c..c02d9f10cd 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -105,7 +105,11 @@ class Item(WebsiteGenerator): frappe.local.message_log.pop() except requests.exceptions.HTTPError: - frappe.msgprint(_("Warning: Invalid Attachment {0}").format(self.website_image)) + frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image)) + self.website_image = None + + except requests.exceptions.SSLError: + frappe.msgprint(_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image)) self.website_image = None # for CSV import From 90c6d7bb473c0602a7df6ae620e3ee5054b6e234 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 13 Nov 2015 13:03:10 +0530 Subject: [PATCH 05/13] [fix] Invoice Outstanding calculation related to advance --- erpnext/controllers/taxes_and_totals.py | 21 ++++++++-------- .../public/js/controllers/taxes_and_totals.js | 25 ++++++++++--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 27ec6181b0..b348da67d9 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -398,17 +398,18 @@ class calculate_taxes_and_totals(object): return self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"]) - total_amount_to_pay = flt(self.doc.grand_total - self.doc.total_advance - self.doc.write_off_amount, - self.doc.precision("grand_total")) + if self.doc.party_account_currency == self.doc.currency: + total_amount_to_pay = flt(self.doc.grand_total - self.doc.total_advance + - flt(self.doc.write_off_amount), self.doc.precision("grand_total")) + else: + total_amount_to_pay = flt(self.doc.base_grand_total - self.doc.total_advance + - flt(self.doc.base_write_off_amount), self.doc.precision("grand_total")) if self.doc.doctype == "Sales Invoice": self.doc.round_floats_in(self.doc, ["paid_amount"]) - outstanding_amount = flt(total_amount_to_pay - self.doc.paid_amount, self.doc.precision("outstanding_amount")) + paid_amount = self.doc.paid_amount \ + if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount + self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount), + self.doc.precision("outstanding_amount")) elif self.doc.doctype == "Purchase Invoice": - outstanding_amount = flt(total_amount_to_pay, self.doc.precision("outstanding_amount")) - - if self.doc.party_account_currency == self.doc.currency: - self.doc.outstanding_amount = outstanding_amount - else: - self.doc.outstanding_amount = flt(outstanding_amount * self.doc.conversion_rate, - self.doc.precision("outstanding_amount")) \ No newline at end of file + self.doc.outstanding_amount = flt(total_amount_to_pay, self.doc.precision("outstanding_amount")) \ No newline at end of file diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b1b24cf38a..413c7ae5f9 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -500,9 +500,13 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return; frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); - - var total_amount_to_pay = flt((this.frm.doc.grand_total - this.frm.doc.total_advance - - this.frm.doc.write_off_amount), precision("grand_total")); + if(this.frm.doc.party_account_currency == this.frm.doc.currency) { + var total_amount_to_pay = flt((this.frm.doc.grand_total - this.frm.doc.total_advance + - this.frm.doc.write_off_amount), precision("grand_total")); + else { + var total_amount_to_pay = flt((this.frm.doc.base_grand_total - this.frm.doc.total_advance + - this.frm.doc.base_write_off_amount), precision("base_grand_total")); + } if(this.frm.doc.doctype == "Sales Invoice") { frappe.model.round_floats_in(this.frm.doc, ["paid_amount"]); @@ -518,18 +522,15 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({ this.frm.refresh_field("paid_amount"); this.frm.refresh_field("base_paid_amount"); - var outstanding_amount = flt(total_amount_to_pay - this.frm.doc.paid_amount, + var paid_amount = (this.frm.doc.party_account_currency == this.frm.doc.currency) ? + this.frm.doc.paid_amount : this.frm.doc.base_paid_amount; + + var outstanding_amount = flt(total_amount_to_pay - flt(paid_amount), precision("outstanding_amount")); } else if(this.frm.doc.doctype == "Purchase Invoice") { var outstanding_amount = flt(total_amount_to_pay, precision("outstanding_amount")); - } - - if(this.frm.doc.party_account_currency == this.frm.doc.currency) { - this.frm.set_value("outstanding_amount", outstanding_amount); - } else { - this.frm.set_value("outstanding_amount", - flt(outstanding_amount * this.frm.doc.conversion_rate, precision("outstanding_amount"))); - } + } + this.frm.set_value("outstanding_amount", outstanding_amount); } }) From 95d025f9aa7fda5acffdefc3aecfaaa4c5c661dc Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 13 Nov 2015 14:22:32 +0530 Subject: [PATCH 06/13] Show Close button in Delivery Note and Purchase Receipt, only if it is returned and user has submit permission --- erpnext/stock/doctype/delivery_note/delivery_note.js | 11 ++++++----- erpnext/stock/doctype/delivery_note/delivery_note.py | 5 ++++- .../doctype/purchase_receipt/purchase_receipt.js | 10 +++++++--- .../doctype/purchase_receipt/purchase_receipt.py | 5 ++++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 9cd4936ba5..4bef0d86a3 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -46,13 +46,14 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( if (cint(frappe.defaults.get_default("auto_accounting_for_stock"))) { this.show_general_ledger(); } - - if(doc.status !== "Closed") { - cur_frm.add_custom_button(__("Close"), this.close_delivery_note) + if (this.frm.has_perm("submit") && (doc.status !== "Closed") + && this.frm.doc.__onload && this.frm.doc.__onload.has_return_entry) { + cur_frm.add_custom_button(__("Close"), this.close_delivery_note) } } - if(doc.__onload && !doc.__onload.billing_complete && doc.docstatus==1 && !doc.is_return && doc.status!="Closed") { + if(doc.__onload && !doc.__onload.billing_complete && doc.docstatus==1 + && !doc.is_return && doc.status!="Closed") { // show Make Invoice button only if Delivery Note is not created from Sales Invoice var from_sales_invoice = false; from_sales_invoice = cur_frm.doc.items.some(function(item) { @@ -63,7 +64,7 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary"); } - if(doc.docstatus==1 && doc.status === "Closed") { + if(doc.docstatus==1 && doc.status === "Closed" && this.frm.has_perm("submit")) { cur_frm.add_custom_button(__('Re-open'), this.reopen_delivery_note) } erpnext.stock.delivery_note.set_print_hide(doc, dt, dn); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 6346c6353e..60cc430c8a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -69,7 +69,10 @@ class DeliveryNote(SellingController): where docstatus=1 and delivery_note=%s""", self.name) if billed_qty: total_qty = sum((item.qty for item in self.get("items"))) - self.get("__onload").billing_complete = (billed_qty[0][0] == total_qty) + self.set_onload("billing_complete", (billed_qty[0][0] == total_qty)) + + self.set_onload("has_return_entry", len(frappe.db.exists({"doctype": "Delivery Note", + "is_return": 1, "return_against": self.name, "docstatus": 1}))) def before_print(self): def toggle_print_hide(meta, fieldname): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index acf6809ae9..af29cb8828 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -57,14 +57,18 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend if(this.frm.doc.docstatus == 1 && this.frm.doc.status!="Closed") { cur_frm.add_custom_button(__('Return'), this.make_purchase_return); if(this.frm.doc.__onload && !this.frm.doc.__onload.billing_complete) { - cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice).addClass("btn-primary"); + cur_frm.add_custom_button(__('Invoice'), + this.make_purchase_invoice).addClass("btn-primary"); + } + if (this.frm.has_perm("submit") && + this.frm.doc.__onload && this.frm.doc.__onload.has_return_entry) { + cur_frm.add_custom_button(__("Close"), this.close_purchase_receipt) } - cur_frm.add_custom_button(__("Close"), this.close_purchase_receipt) } } - if(this.frm.doc.docstatus==1 && this.frm.doc.status === "Closed") { + if(this.frm.doc.docstatus==1 && this.frm.doc.status === "Closed" && this.frm.has_perm("submit")) { cur_frm.add_custom_button(__('Re-open'), this.reopen_purchase_receipt) } diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c1316f949b..edd7be8d36 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -51,7 +51,10 @@ class PurchaseReceipt(BuyingController): where purchase_receipt=%s and docstatus=1""", self.name) if billed_qty: total_qty = sum((item.qty for item in self.get("items"))) - self.get("__onload").billing_complete = (billed_qty[0][0] == total_qty) + self.set_onload("billing_complete", (billed_qty[0][0] == total_qty)) + + self.set_onload("has_return_entry", len(frappe.db.exists({"doctype": "Purchase Receipt", + "is_return": 1, "return_against": self.name, "docstatus": 1}))) def validate(self): super(PurchaseReceipt, self).validate() From 834b3e6f83306e684fc9eda9b446dc0813410498 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 13 Nov 2015 14:37:52 +0530 Subject: [PATCH 07/13] [fix] added missing address hook for website permissions --- erpnext/hooks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 3b33e65c8c..7e333f7df0 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -96,7 +96,8 @@ has_website_permission = { "Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission", "Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission", "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", - "Issue": "erpnext.support.doctype.issue.issue.has_website_permission" + "Issue": "erpnext.support.doctype.issue.issue.has_website_permission", + "Address": "erpnext.utilities.doctype.address.address.has_website_permission" } permission_query_conditions = { From cd87e76d88ea61cc74d9f80c8fdbc02a9236666f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sun, 15 Nov 2015 11:06:43 +0530 Subject: [PATCH 08/13] [fix] Check Salary Structure validity --- .../doctype/expense_claim/expense_claim.json | 9 +++-- .../process_payroll/process_payroll.py | 39 ++++++++++--------- erpnext/hr/doctype/salary_slip/salary_slip.py | 13 +++++-- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index a8d56fbf5f..a5e3972f3d 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -19,7 +19,7 @@ "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Series", + "label": "Series", "no_copy": 1, "options": "EXP", "permlevel": 0, @@ -44,7 +44,7 @@ "ignore_user_permissions": 0, "in_filter": 1, "in_list_view": 0, - "label": "Approval Status", + "label": "Approval Status", "no_copy": 1, "oldfieldname": "approval_status", "oldfieldtype": "Select", @@ -536,7 +536,7 @@ "is_submittable": 1, "issingle": 0, "istable": 0, - "modified": "2015-10-02 07:38:50.191920", + "modified": "2015-11-14 12:11:13.213073", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", @@ -564,7 +564,7 @@ }, { "amend": 0, - "apply_user_permissions": 0, + "apply_user_permissions": 1, "cancel": 0, "create": 1, "delete": 0, @@ -580,6 +580,7 @@ "set_user_permissions": 0, "share": 1, "submit": 0, + "user_permission_doctypes": "[\"Employee\"]", "write": 1 }, { diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.py b/erpnext/hr/doctype/process_payroll/process_payroll.py index 3658e650f4..b6b76c9d3a 100644 --- a/erpnext/hr/doctype/process_payroll/process_payroll.py +++ b/erpnext/hr/doctype/process_payroll/process_payroll.py @@ -41,7 +41,7 @@ class ProcessPayroll(Document): def get_joining_releiving_condition(self): - m = self.get_month_details(self.fiscal_year, self.month) + m = get_month_details(self.fiscal_year, self.month) cond = """ and ifnull(t1.date_of_joining, '0000-00-00') <= '%(month_end_date)s' and ifnull(t1.relieving_date, '2199-12-31') >= '%(month_start_date)s' @@ -54,24 +54,6 @@ class ProcessPayroll(Document): if not self.get(f): frappe.throw(_("Please set {0}").format(f)) - def get_month_details(self, year, month): - ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") - if ysd: - from dateutil.relativedelta import relativedelta - import calendar, datetime - diff_mnt = cint(month)-cint(ysd.month) - if diff_mnt<0: - diff_mnt = 12-int(ysd.month)+cint(month) - msd = ysd + relativedelta(months=diff_mnt) # month start date - month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month - med = datetime.date(msd.year, cint(month), month_days) # month end date - return { - 'year': msd.year, - 'month_start_date': msd, - 'month_end_date': med, - 'month_days': month_days - } - def create_sal_slip(self): """ Creates salary slip for selected employees if already not created @@ -205,3 +187,22 @@ class ProcessPayroll(Document): ]) return journal_entry.as_dict() + + +def get_month_details(year, month): + ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") + if ysd: + from dateutil.relativedelta import relativedelta + import calendar, datetime + diff_mnt = cint(month)-cint(ysd.month) + if diff_mnt<0: + diff_mnt = 12-int(ysd.month)+cint(month) + msd = ysd + relativedelta(months=diff_mnt) # month start date + month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month + med = datetime.date(msd.year, cint(month), month_days) # month end date + return frappe._dict({ + 'year': msd.year, + 'month_start_date': msd, + 'month_end_date': med, + 'month_days': month_days + }) \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 12fdc205a4..9a06c36e64 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -10,6 +10,7 @@ from frappe.model.naming import make_autoname from frappe import msgprint, _ from erpnext.setup.utils import get_company_currency from erpnext.hr.utils import set_employee_name +from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details from erpnext.utilities.transaction_base import TransactionBase @@ -25,11 +26,17 @@ class SalarySlip(TransactionBase): self.pull_sal_struct(struct) def check_sal_struct(self): + m = get_month_details(self.fiscal_year, self.month) struct = frappe.db.sql("""select name from `tabSalary Structure` - where employee=%s and is_active = 'Yes'""", self.employee) + where employee=%s and is_active = 'Yes' + and from_date <= %s and (to_date is null or to_date >= %s)""", + (self.employee, m.month_start_date, m.month_end_date)) + if not struct: - msgprint(_("Please create Salary Structure for employee {0}").format(self.employee)) + msgprint(_("No active Salary Structure found for employee {0} and the month") + .format(self.employee)) self.employee = None + return struct and struct[0][0] or '' def pull_sal_struct(self, struct): @@ -49,7 +56,7 @@ class SalarySlip(TransactionBase): if not self.month: self.month = "%02d" % getdate(nowdate()).month - m = frappe.get_doc('Process Payroll').get_month_details(self.fiscal_year, self.month) + m = get_month_details(self.fiscal_year, self.month) holidays = self.get_holidays_for_employee(m) if not cint(frappe.db.get_value("HR Settings", "HR Settings", From 1ed82834ce3eb2b3459fc84fc617445b82d066a7 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 16 Nov 2015 12:58:14 +0530 Subject: [PATCH 09/13] [fix] re-make setup wizard slides on change of language --- erpnext/public/js/setup_wizard.js | 703 +++++++++--------- .../welcome_to_erpnext.html | 2 +- 2 files changed, 354 insertions(+), 351 deletions(-) diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index e877737629..bb3d272ac4 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -5,404 +5,407 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) { frappe.set_route("desk"); return; } -} +}; -$.extend(erpnext.wiz, { - region: { - title: __("Region"), - icon: "icon-flag", - help: __("Select your Country, Time Zone and Currency"), - fields: [ - { fieldname: "country", label: __("Country"), reqd:1, - fieldtype: "Select" }, - { fieldname: "timezone", label: __("Time Zone"), reqd:1, - fieldtype: "Select" }, - { fieldname: "currency", label: __("Currency"), reqd:1, - fieldtype: "Select" }, - ], +function load_erpnext_slides() { + $.extend(erpnext.wiz, { + region: { + title: __("Region"), + icon: "icon-flag", + help: __("Select your Country, Time Zone and Currency"), + fields: [ + { fieldname: "country", label: __("Country"), reqd:1, + fieldtype: "Select" }, + { fieldname: "timezone", label: __("Time Zone"), reqd:1, + fieldtype: "Select" }, + { fieldname: "currency", label: __("Currency"), reqd:1, + fieldtype: "Select" }, + ], - onload: function(slide) { - frappe.call({ - method:"frappe.geo.country_info.get_country_timezone_info", - callback: function(data) { - erpnext.wiz.region.data = data.message; - erpnext.wiz.region.setup_fields(slide); - erpnext.wiz.region.bind_events(slide); - } - }); - }, - css_class: "single-column", - setup_fields: function(slide) { - var data = erpnext.wiz.region.data; - - slide.get_input("country").empty() - .add_options([""].concat(keys(data.country_info).sort())); - - slide.get_input("currency").empty() - .add_options(frappe.utils.unique([""].concat($.map(data.country_info, - function(opts, country) { return opts.currency; }))).sort()); - - slide.get_input("timezone").empty() - .add_options([""].concat(data.all_timezones)); - - if (data.default_country) { - slide.set_input("country", data.default_country); - } - }, - - bind_events: function(slide) { - slide.get_input("country").on("change", function() { - var country = slide.get_input("country").val(); - var $timezone = slide.get_input("timezone"); + onload: function(slide) { + frappe.call({ + method:"frappe.geo.country_info.get_country_timezone_info", + callback: function(data) { + erpnext.wiz.region.data = data.message; + erpnext.wiz.region.setup_fields(slide); + erpnext.wiz.region.bind_events(slide); + } + }); + }, + css_class: "single-column", + setup_fields: function(slide) { var data = erpnext.wiz.region.data; - $timezone.empty(); + slide.get_input("country").empty() + .add_options([""].concat(keys(data.country_info).sort())); - // add country specific timezones first - if(country) { - var timezone_list = data.country_info[country].timezones || []; - $timezone.add_options(timezone_list.sort()); - slide.get_field("currency").set_input(data.country_info[country].currency); - slide.get_field("currency").$input.trigger("change"); + slide.get_input("currency").empty() + .add_options(frappe.utils.unique([""].concat($.map(data.country_info, + function(opts, country) { return opts.currency; }))).sort()); + + slide.get_input("timezone").empty() + .add_options([""].concat(data.all_timezones)); + + if (data.default_country) { + slide.set_input("country", data.default_country); } + }, - // add all timezones at the end, so that user has the option to change it to any timezone - $timezone.add_options([""].concat(data.all_timezones)); + bind_events: function(slide) { + slide.get_input("country").on("change", function() { + var country = slide.get_input("country").val(); + var $timezone = slide.get_input("timezone"); + var data = erpnext.wiz.region.data; - slide.get_field("timezone").set_input($timezone.val()); + $timezone.empty(); - // temporarily set date format - frappe.boot.sysdefaults.date_format = (data.country_info[country].date_format - || "dd-mm-yyyy"); - }); - - slide.get_input("currency").on("change", function() { - var currency = slide.get_input("currency").val(); - if (!currency) return; - frappe.model.with_doc("Currency", currency, function() { - frappe.provide("locals.:Currency." + currency); - var currency_doc = frappe.model.get_doc("Currency", currency); - var number_format = currency_doc.number_format; - if (number_format==="#.###") { - number_format = "#.###,##"; - } else if (number_format==="#,###") { - number_format = "#,###.##" + // add country specific timezones first + if(country) { + var timezone_list = data.country_info[country].timezones || []; + $timezone.add_options(timezone_list.sort()); + slide.get_field("currency").set_input(data.country_info[country].currency); + slide.get_field("currency").$input.trigger("change"); } - frappe.boot.sysdefaults.number_format = number_format; - locals[":Currency"][currency] = $.extend({}, currency_doc); + // add all timezones at the end, so that user has the option to change it to any timezone + $timezone.add_options([""].concat(data.all_timezones)); + + slide.get_field("timezone").set_input($timezone.val()); + + // temporarily set date format + frappe.boot.sysdefaults.date_format = (data.country_info[country].date_format + || "dd-mm-yyyy"); }); - }); - } - }, - user: { - title: __("The First User: You"), - icon: "icon-user", - fields: [ - {"fieldname": "first_name", "label": __("First Name"), "fieldtype": "Data", - reqd:1}, - {"fieldname": "last_name", "label": __("Last Name"), "fieldtype": "Data"}, - {"fieldname": "email", "label": __("Email Address"), "fieldtype": "Data", - reqd:1, "description": __("You will use it to Login"), "options":"Email"}, - {"fieldname": "password", "label": __("Password"), "fieldtype": "Password", - reqd:1}, - {fieldtype:"Attach Image", fieldname:"attach_user", - label: __("Attach Your Picture")}, - ], - help: __('The first user will become the System Manager (you can change this later).'), - onload: function(slide) { - if(user!=="Administrator") { - slide.form.fields_dict.password.$wrapper.toggle(false); - slide.form.fields_dict.email.$wrapper.toggle(false); - slide.form.fields_dict.first_name.set_input(frappe.boot.user.first_name); - slide.form.fields_dict.last_name.set_input(frappe.boot.user.last_name); + slide.get_input("currency").on("change", function() { + var currency = slide.get_input("currency").val(); + if (!currency) return; + frappe.model.with_doc("Currency", currency, function() { + frappe.provide("locals.:Currency." + currency); + var currency_doc = frappe.model.get_doc("Currency", currency); + var number_format = currency_doc.number_format; + if (number_format==="#.###") { + number_format = "#.###,##"; + } else if (number_format==="#,###") { + number_format = "#,###.##" + } - var user_image = frappe.get_cookie("user_image"); - if(user_image) { - var $attach_user = slide.form.fields_dict.attach_user.$wrapper; - $attach_user.find(".missing-image").toggle(false); - $attach_user.find("img").attr("src", decodeURIComponent(user_image)).toggle(true); - } - - delete slide.form.fields_dict.email; - delete slide.form.fields_dict.password; + frappe.boot.sysdefaults.number_format = number_format; + locals[":Currency"][currency] = $.extend({}, currency_doc); + }); + }); } }, - css_class: "single-column" - }, - org: { - title: __("The Organization"), - icon: "icon-building", - fields: [ - {fieldname:'company_name', label: __('Company Name'), fieldtype:'Data', reqd:1, - placeholder: __('e.g. "My Company LLC"')}, - {fieldname:'company_abbr', label: __('Company Abbreviation'), fieldtype:'Data', - description: __('Max 5 characters'), placeholder: __('e.g. "MC"'), reqd:1}, - {fieldname:'company_tagline', label: __('What does it do?'), fieldtype:'Data', - placeholder:__('e.g. "Build tools for builders"'), reqd:1}, - {fieldname:'bank_account', label: __('Bank Account'), fieldtype:'Data', - placeholder: __('e.g. "XYZ National Bank"'), reqd:1 }, - {fieldname:'chart_of_accounts', label: __('Chart of Accounts'), - options: "", fieldtype: 'Select'}, + user: { + title: __("The First User: You"), + icon: "icon-user", + fields: [ + {"fieldname": "first_name", "label": __("First Name"), "fieldtype": "Data", + reqd:1}, + {"fieldname": "last_name", "label": __("Last Name"), "fieldtype": "Data"}, + {"fieldname": "email", "label": __("Email Address"), "fieldtype": "Data", + reqd:1, "description": __("You will use it to Login"), "options":"Email"}, + {"fieldname": "password", "label": __("Password"), "fieldtype": "Password", + reqd:1}, + {fieldtype:"Attach Image", fieldname:"attach_user", + label: __("Attach Your Picture")}, + ], + help: __('The first user will become the System Manager (you can change this later).'), + onload: function(slide) { + if(user!=="Administrator") { + slide.form.fields_dict.password.$wrapper.toggle(false); + slide.form.fields_dict.email.$wrapper.toggle(false); + slide.form.fields_dict.first_name.set_input(frappe.boot.user.first_name); + slide.form.fields_dict.last_name.set_input(frappe.boot.user.last_name); - // TODO remove this - {fieldtype: "Section Break"}, - {fieldname:'fy_start_date', label:__('Financial Year Start Date'), fieldtype:'Date', - description: __('Your financial year begins on'), reqd:1}, - {fieldname:'fy_end_date', label:__('Financial Year End Date'), fieldtype:'Date', - description: __('Your financial year ends on'), reqd:1}, - ], - help: __('The name of your company for which you are setting up this system.'), + var user_image = frappe.get_cookie("user_image"); + if(user_image) { + var $attach_user = slide.form.fields_dict.attach_user.$wrapper; + $attach_user.find(".missing-image").toggle(false); + $attach_user.find("img").attr("src", decodeURIComponent(user_image)).toggle(true); + } - onload: function(slide) { - erpnext.wiz.org.load_chart_of_accounts(slide); - erpnext.wiz.org.bind_events(slide); - erpnext.wiz.org.set_fy_dates(slide); + delete slide.form.fields_dict.email; + delete slide.form.fields_dict.password; + } + }, + css_class: "single-column" }, - css_class: "single-column", + org: { + title: __("The Organization"), + icon: "icon-building", + fields: [ + {fieldname:'company_name', label: __('Company Name'), fieldtype:'Data', reqd:1, + placeholder: __('e.g. "My Company LLC"')}, + {fieldname:'company_abbr', label: __('Company Abbreviation'), fieldtype:'Data', + description: __('Max 5 characters'), placeholder: __('e.g. "MC"'), reqd:1}, + {fieldname:'company_tagline', label: __('What does it do?'), fieldtype:'Data', + placeholder:__('e.g. "Build tools for builders"'), reqd:1}, + {fieldname:'bank_account', label: __('Bank Account'), fieldtype:'Data', + placeholder: __('e.g. "XYZ National Bank"'), reqd:1 }, + {fieldname:'chart_of_accounts', label: __('Chart of Accounts'), + options: "", fieldtype: 'Select'}, - set_fy_dates: function(slide) { - var country = slide.wiz.get_values().country; + // TODO remove this + {fieldtype: "Section Break"}, + {fieldname:'fy_start_date', label:__('Financial Year Start Date'), fieldtype:'Date', + description: __('Your financial year begins on'), reqd:1}, + {fieldname:'fy_end_date', label:__('Financial Year End Date'), fieldtype:'Date', + description: __('Your financial year ends on'), reqd:1}, + ], + help: __('The name of your company for which you are setting up this system.'), - if(country) { - var fy = erpnext.wiz.fiscal_years[country]; - var current_year = moment(new Date()).year(); - var next_year = current_year + 1; - if(!fy) { - fy = ["01-01", "12-31"]; - next_year = current_year; + onload: function(slide) { + erpnext.wiz.org.load_chart_of_accounts(slide); + erpnext.wiz.org.bind_events(slide); + erpnext.wiz.org.set_fy_dates(slide); + }, + + css_class: "single-column", + + set_fy_dates: function(slide) { + var country = slide.wiz.get_values().country; + + if(country) { + var fy = erpnext.wiz.fiscal_years[country]; + var current_year = moment(new Date()).year(); + var next_year = current_year + 1; + if(!fy) { + fy = ["01-01", "12-31"]; + next_year = current_year; + } + + slide.get_field("fy_start_date").set_input(current_year + "-" + fy[0]); + slide.get_field("fy_end_date").set_input(next_year + "-" + fy[1]); } - slide.get_field("fy_start_date").set_input(current_year + "-" + fy[0]); - slide.get_field("fy_end_date").set_input(next_year + "-" + fy[1]); - } + }, - }, + load_chart_of_accounts: function(slide) { + var country = slide.wiz.get_values().country; - load_chart_of_accounts: function(slide) { - var country = slide.wiz.get_values().country; + if(country) { + frappe.call({ + method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.get_charts_for_country", + args: {"country": country}, + callback: function(r) { + if(r.message) { + slide.get_input("chart_of_accounts").empty() + .add_options(r.message); - if(country) { - frappe.call({ - method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.get_charts_for_country", - args: {"country": country}, - callback: function(r) { - if(r.message) { - slide.get_input("chart_of_accounts").empty() - .add_options(r.message); - - if (r.message.length===1) { - var field = slide.get_field("chart_of_accounts"); - field.set_value(r.message[0]); - field.df.hidden = 1; - field.refresh(); + if (r.message.length===1) { + var field = slide.get_field("chart_of_accounts"); + field.set_value(r.message[0]); + field.df.hidden = 1; + field.refresh(); + } } } - } - }) - } - }, - - bind_events: function(slide) { - slide.get_input("company_name").on("change", function() { - var parts = slide.get_input("company_name").val().split(" "); - var abbr = $.map(parts, function(p) { return p ? p.substr(0,1) : null }).join(""); - slide.get_field("company_abbr").set_input(abbr.slice(0, 5).toUpperCase()); - }).val(frappe.boot.sysdefaults.company_name || "").trigger("change"); - - slide.get_input("company_abbr").on("change", function() { - if(slide.get_input("company_abbr").val().length > 5) { - msgprint("Company Abbreviation cannot have more than 5 characters"); - slide.get_field("company_abbr").set_input(""); + }) } - }); - - // TODO remove this - slide.get_input("fy_start_date").on("change", function() { - var year_end_date = - frappe.datetime.add_days(frappe.datetime.add_months( - frappe.datetime.user_to_obj(slide.get_input("fy_start_date").val()), 12), -1); - slide.get_input("fy_end_date").val(frappe.datetime.obj_to_user(year_end_date)); - - }); - } - }, - - branding: { - icon: "icon-bookmark", - title: __("The Brand"), - help: __('Upload your letter head and logo. (you can edit them later).'), - fields: [ - {fieldtype:"Attach Image", fieldname:"attach_letterhead", - label: __("Attach Letterhead"), - description: __("Keep it web friendly 900px (w) by 100px (h)") }, - {fieldtype: "Column Break"}, - {fieldtype:"Attach Image", fieldname:"attach_logo", - label:__("Attach Logo"), - description: __("100px by 100px")}, - ], - css_class: "two-column" - }, + bind_events: function(slide) { + slide.get_input("company_name").on("change", function() { + var parts = slide.get_input("company_name").val().split(" "); + var abbr = $.map(parts, function(p) { return p ? p.substr(0,1) : null }).join(""); + slide.get_field("company_abbr").set_input(abbr.slice(0, 5).toUpperCase()); + }).val(frappe.boot.sysdefaults.company_name || "").trigger("change"); - users: { - icon: "icon-money", - "title": __("Add Users"), - "help": __("Add users to your organization, other than yourself"), - "fields": [], - before_load: function(slide) { - slide.fields = []; - for(var i=1; i<5; i++) { - slide.fields = slide.fields.concat([ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"user_fullname_"+ i, - label:__("Full Name")}, - {fieldtype:"Data", fieldname:"user_email_" + i, - label:__("Email ID"), placeholder:__("user@example.com"), - options: "Email"}, - {fieldtype:"Column Break"}, - {fieldtype: "Check", fieldname: "user_sales_" + i, - label:__("Sales"), default: 1}, - {fieldtype: "Check", fieldname: "user_purchaser_" + i, - label:__("Purchaser"), default: 1}, - {fieldtype: "Check", fieldname: "user_accountant_" + i, - label:__("Accountant"), default: 1}, - ]); + slide.get_input("company_abbr").on("change", function() { + if(slide.get_input("company_abbr").val().length > 5) { + msgprint("Company Abbreviation cannot have more than 5 characters"); + slide.get_field("company_abbr").set_input(""); + } + }); + + // TODO remove this + slide.get_input("fy_start_date").on("change", function() { + var year_end_date = + frappe.datetime.add_days(frappe.datetime.add_months( + frappe.datetime.user_to_obj(slide.get_input("fy_start_date").val()), 12), -1); + slide.get_input("fy_end_date").val(frappe.datetime.obj_to_user(year_end_date)); + + }); } }, - css_class: "two-column" - }, - taxes: { - icon: "icon-money", - "title": __("Add Taxes"), - "help": __("List your tax heads (e.g. VAT, Customs etc; they should have unique names) and their standard rates. This will create a standard template, which you can edit and add more later."), - "fields": [], - before_load: function(slide) { - slide.fields = []; - for(var i=1; i<4; i++) { - slide.fields = slide.fields.concat([ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"tax_"+ i, label:__("Tax") + " " + i, - placeholder:__("e.g. VAT") + " " + i}, - {fieldtype:"Column Break"}, - {fieldtype:"Float", fieldname:"tax_rate_" + i, label:__("Rate (%)"), placeholder:__("e.g. 5")}, - ]); - } + branding: { + icon: "icon-bookmark", + title: __("The Brand"), + help: __('Upload your letter head and logo. (you can edit them later).'), + fields: [ + {fieldtype:"Attach Image", fieldname:"attach_letterhead", + label: __("Attach Letterhead"), + description: __("Keep it web friendly 900px (w) by 100px (h)") + }, + {fieldtype: "Column Break"}, + {fieldtype:"Attach Image", fieldname:"attach_logo", + label:__("Attach Logo"), + description: __("100px by 100px")}, + ], + + css_class: "two-column" }, - css_class: "two-column" - }, - customers: { - icon: "icon-group", - "title": __("Your Customers"), - "help": __("List a few of your customers. They could be organizations or individuals."), - "fields": [], - before_load: function(slide) { - slide.fields = []; - for(var i=1; i<6; i++) { - slide.fields = slide.fields.concat([ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"customer_" + i, label:__("Customer") + " " + i, - placeholder:__("Customer Name")}, - {fieldtype:"Column Break"}, - {fieldtype:"Data", fieldname:"customer_contact_" + i, - label:__("Contact Name") + " " + i, placeholder:__("Contact Name")} - ]) - } - slide.fields[1].reqd = 1; + users: { + icon: "icon-money", + "title": __("Add Users"), + "help": __("Add users to your organization, other than yourself"), + "fields": [], + before_load: function(slide) { + slide.fields = []; + for(var i=1; i<5; i++) { + slide.fields = slide.fields.concat([ + {fieldtype:"Section Break"}, + {fieldtype:"Data", fieldname:"user_fullname_"+ i, + label:__("Full Name")}, + {fieldtype:"Data", fieldname:"user_email_" + i, + label:__("Email ID"), placeholder:__("user@example.com"), + options: "Email"}, + {fieldtype:"Column Break"}, + {fieldtype: "Check", fieldname: "user_sales_" + i, + label:__("Sales"), default: 1}, + {fieldtype: "Check", fieldname: "user_purchaser_" + i, + label:__("Purchaser"), default: 1}, + {fieldtype: "Check", fieldname: "user_accountant_" + i, + label:__("Accountant"), default: 1}, + ]); + } + }, + css_class: "two-column" }, - css_class: "two-column" - }, - suppliers: { - icon: "icon-group", - "title": __("Your Suppliers"), - "help": __("List a few of your suppliers. They could be organizations or individuals."), - "fields": [], - before_load: function(slide) { - slide.fields = []; - for(var i=1; i<6; i++) { - slide.fields = slide.fields.concat([ - {fieldtype:"Section Break"}, - {fieldtype:"Data", fieldname:"supplier_" + i, label:__("Supplier")+" " + i, - placeholder:__("Supplier Name")}, - {fieldtype:"Column Break"}, - {fieldtype:"Data", fieldname:"supplier_contact_" + i, - label:__("Contact Name") + " " + i, placeholder:__("Contact Name")}, - ]) - } - slide.fields[1].reqd = 1; + taxes: { + icon: "icon-money", + "title": __("Add Taxes"), + "help": __("List your tax heads (e.g. VAT, Customs etc; they should have unique names) and their standard rates. This will create a standard template, which you can edit and add more later."), + "fields": [], + before_load: function(slide) { + slide.fields = []; + for(var i=1; i<4; i++) { + slide.fields = slide.fields.concat([ + {fieldtype:"Section Break"}, + {fieldtype:"Data", fieldname:"tax_"+ i, label:__("Tax") + " " + i, + placeholder:__("e.g. VAT") + " " + i}, + {fieldtype:"Column Break"}, + {fieldtype:"Float", fieldname:"tax_rate_" + i, label:__("Rate (%)"), placeholder:__("e.g. 5")}, + ]); + } + }, + css_class: "two-column" }, - css_class: "two-column" - }, - items: { - icon: "icon-barcode", - "title": __("Your Products or Services"), - "help": __("List your products or services that you buy or sell. Make sure to check the Item Group, Unit of Measure and other properties when you start."), - "fields": [], - before_load: function(slide) { - slide.fields = []; - for(var i=1; i<6; i++) { - slide.fields = slide.fields.concat([ - {fieldtype:"Section Break", show_section_border: true}, - {fieldtype:"Data", fieldname:"item_" + i, label:__("Item") + " " + i, - placeholder:__("A Product or Service")}, - {fieldtype:"Select", label:__("Group"), fieldname:"item_group_" + i, - options:[__("Products"), __("Services"), - __("Raw Material"), __("Consumable"), __("Sub Assemblies")], - "default": __("Products")}, - {fieldtype:"Select", fieldname:"item_uom_" + i, label:__("UOM"), - options:[__("Unit"), __("Nos"), __("Box"), __("Pair"), __("Kg"), __("Set"), - __("Hour"), __("Minute")], - "default": __("Unit")}, - {fieldtype: "Check", fieldname: "is_sales_item_" + i, label:__("We sell this Item"), default: 1}, - {fieldtype: "Check", fieldname: "is_purchase_item_" + i, label:__("We buy this Item")}, - {fieldtype:"Column Break"}, - {fieldtype:"Currency", fieldname:"item_price_" + i, label:__("Rate")}, - {fieldtype:"Attach Image", fieldname:"item_img_" + i, label:__("Attach Image")}, - ]) - } - slide.fields[1].reqd = 1; - - // dummy data - slide.fields.push({fieldtype: "Section Break"}); - slide.fields.push({fieldtype: "Check", fieldname: "add_sample_data", - label: __("Add a few sample records"), "default": 1}); + customers: { + icon: "icon-group", + "title": __("Your Customers"), + "help": __("List a few of your customers. They could be organizations or individuals."), + "fields": [], + before_load: function(slide) { + slide.fields = []; + for(var i=1; i<6; i++) { + slide.fields = slide.fields.concat([ + {fieldtype:"Section Break"}, + {fieldtype:"Data", fieldname:"customer_" + i, label:__("Customer") + " " + i, + placeholder:__("Customer Name")}, + {fieldtype:"Column Break"}, + {fieldtype:"Data", fieldname:"customer_contact_" + i, + label:__("Contact Name") + " " + i, placeholder:__("Contact Name")} + ]) + } + slide.fields[1].reqd = 1; + }, + css_class: "two-column" }, - css_class: "two-column" - }, -}); -// Source: https://en.wikipedia.org/wiki/Fiscal_year -// default 1st Jan - 31st Dec + suppliers: { + icon: "icon-group", + "title": __("Your Suppliers"), + "help": __("List a few of your suppliers. They could be organizations or individuals."), + "fields": [], + before_load: function(slide) { + slide.fields = []; + for(var i=1; i<6; i++) { + slide.fields = slide.fields.concat([ + {fieldtype:"Section Break"}, + {fieldtype:"Data", fieldname:"supplier_" + i, label:__("Supplier")+" " + i, + placeholder:__("Supplier Name")}, + {fieldtype:"Column Break"}, + {fieldtype:"Data", fieldname:"supplier_contact_" + i, + label:__("Contact Name") + " " + i, placeholder:__("Contact Name")}, + ]) + } + slide.fields[1].reqd = 1; + }, + css_class: "two-column" + }, -erpnext.wiz.fiscal_years = { - "Afghanistan": ["12-20", "12-21"], - "Australia": ["07-01", "06-30"], - "Bangladesh": ["07-01", "06-30"], - "Canada": ["04-01", "03-31"], - "Costa Rica": ["10-01", "09-30"], - "Egypt": ["07-01", "06-30"], - "Hong Kong": ["04-01", "03-31"], - "India": ["04-01", "03-31"], - "Iran": ["06-23", "06-22"], - "Italy": ["07-01", "06-30"], - "Myanmar": ["04-01", "03-31"], - "New Zealand": ["04-01", "03-31"], - "Pakistan": ["07-01", "06-30"], - "Singapore": ["04-01", "03-31"], - "South Africa": ["03-01", "02-28"], - "Thailand": ["10-01", "09-30"], - "United Kingdom": ["04-01", "03-31"], -} + items: { + icon: "icon-barcode", + "title": __("Your Products or Services"), + "help": __("List your products or services that you buy or sell. Make sure to check the Item Group, Unit of Measure and other properties when you start."), + "fields": [], + before_load: function(slide) { + slide.fields = []; + for(var i=1; i<6; i++) { + slide.fields = slide.fields.concat([ + {fieldtype:"Section Break", show_section_border: true}, + {fieldtype:"Data", fieldname:"item_" + i, label:__("Item") + " " + i, + placeholder:__("A Product or Service")}, + {fieldtype:"Select", label:__("Group"), fieldname:"item_group_" + i, + options:[__("Products"), __("Services"), + __("Raw Material"), __("Consumable"), __("Sub Assemblies")], + "default": __("Products")}, + {fieldtype:"Select", fieldname:"item_uom_" + i, label:__("UOM"), + options:[__("Unit"), __("Nos"), __("Box"), __("Pair"), __("Kg"), __("Set"), + __("Hour"), __("Minute")], + "default": __("Unit")}, + {fieldtype: "Check", fieldname: "is_sales_item_" + i, label:__("We sell this Item"), default: 1}, + {fieldtype: "Check", fieldname: "is_purchase_item_" + i, label:__("We buy this Item")}, + {fieldtype:"Column Break"}, + {fieldtype:"Currency", fieldname:"item_price_" + i, label:__("Rate")}, + {fieldtype:"Attach Image", fieldname:"item_img_" + i, label:__("Attach Image")}, + ]) + } + slide.fields[1].reqd = 1; + + // dummy data + slide.fields.push({fieldtype: "Section Break"}); + slide.fields.push({fieldtype: "Check", fieldname: "add_sample_data", + label: __("Add a few sample records"), "default": 1}); + }, + css_class: "two-column" + }, + }); + + // Source: https://en.wikipedia.org/wiki/Fiscal_year + // default 1st Jan - 31st Dec + + erpnext.wiz.fiscal_years = { + "Afghanistan": ["12-20", "12-21"], + "Australia": ["07-01", "06-30"], + "Bangladesh": ["07-01", "06-30"], + "Canada": ["04-01", "03-31"], + "Costa Rica": ["10-01", "09-30"], + "Egypt": ["07-01", "06-30"], + "Hong Kong": ["04-01", "03-31"], + "India": ["04-01", "03-31"], + "Iran": ["06-23", "06-22"], + "Italy": ["07-01", "06-30"], + "Myanmar": ["04-01", "03-31"], + "New Zealand": ["04-01", "03-31"], + "Pakistan": ["07-01", "06-30"], + "Singapore": ["04-01", "03-31"], + "South Africa": ["03-01", "02-28"], + "Thailand": ["10-01", "09-30"], + "United Kingdom": ["04-01", "03-31"], + }; +}; frappe.wiz.on("before_load", function() { + load_erpnext_slides(); frappe.wiz.add_slide(erpnext.wiz.user); frappe.wiz.add_slide(erpnext.wiz.org); frappe.wiz.add_slide(erpnext.wiz.branding); diff --git a/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html b/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html index 032eab9230..5fe7a140fb 100644 --- a/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html +++ b/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html @@ -18,7 +18,7 @@

-

Next Steps

+

{%= __("Next Steps") %}

  • {%= __("Go to the Desktop and start using ERPNext") %}
  • {%= __("View a list of all the help videos") %}
  • From 4fbf01fd9d7732c6d093ff7670a21cf106ec4106 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 16 Nov 2015 15:05:49 +0530 Subject: [PATCH 10/13] [fix] urlencode address list's link --- erpnext/templates/includes/address_row.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/address_row.html b/erpnext/templates/includes/address_row.html index fd287addf4..717ca7500e 100644 --- a/erpnext/templates/includes/address_row.html +++ b/erpnext/templates/includes/address_row.html @@ -1,5 +1,5 @@
    - +

    {{ doc.address_title }}

    {{ frappe.get_doc(doc).get_display() }} From 58c4646199d2848bf621d48501618a3532769e55 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 16 Nov 2015 14:50:14 +0530 Subject: [PATCH 11/13] [fix][cleanup] Leave allocation, application and balance report cleanup and fixes --- .../leave_allocation/leave_allocation.js | 98 ++++---- .../leave_allocation/leave_allocation.py | 140 ++++++----- .../leave_application/leave_application.js | 32 ++- .../leave_application/leave_application.json | 26 +- .../leave_application/leave_application.py | 232 +++++++++++------- .../employee_leave_balance.js | 3 + .../employee_leave_balance.py | 93 +++---- erpnext/projects/doctype/time_log/time_log.js | 12 +- test_sites/apps.txt | 1 - test_sites/languages.txt | 1 - test_sites/test_site/site_config.json | 12 - 11 files changed, 342 insertions(+), 308 deletions(-) delete mode 100644 test_sites/apps.txt delete mode 100644 test_sites/languages.txt delete mode 100644 test_sites/test_site/site_config.json diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 994dc3cdd4..06c7693501 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -1,48 +1,62 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.cscript.onload = function(doc, dt, dn) { - if(!doc.posting_date) set_multiple(dt,dn,{posting_date:get_today()}); -} - - cur_frm.add_fetch('employee','employee_name','employee_name'); -cur_frm.cscript.employee = function(doc, dt, dn) { - calculate_total_leaves_allocated(doc, dt, dn); -} - -cur_frm.cscript.leave_type = function(doc, dt, dn) { - calculate_total_leaves_allocated(doc, dt, dn); -} - -cur_frm.cscript.fiscal_year = function(doc, dt, dn) { - calculate_total_leaves_allocated(doc, dt, dn); -} - -cur_frm.cscript.carry_forward = function(doc, dt, dn) { - calculate_total_leaves_allocated(doc, dt, dn); -} - -cur_frm.cscript.carry_forwarded_leaves = function(doc, dt, dn) { - set_multiple(dt,dn,{total_leaves_allocated : flt(doc.carry_forwarded_leaves)+flt(doc.new_leaves_allocated)}); -} - -cur_frm.cscript.new_leaves_allocated = function(doc, dt, dn) { - set_multiple(dt,dn,{total_leaves_allocated : flt(doc.carry_forwarded_leaves)+flt(doc.new_leaves_allocated)}); -} - -calculate_total_leaves_allocated = function(doc, dt, dn) { - if(cint(doc.carry_forward) == 1 && doc.leave_type && doc.fiscal_year && doc.employee){ - return get_server_fields('get_carry_forwarded_leaves','','', doc, dt, dn, 1); +frappe.ui.form.on("Leave Allocation", { + onload: function(frm) { + if(!frm.doc.from_date) frm.set_value("from_date", get_today()); + + frm.set_query("employee", function() { + return { + query: "erpnext.controllers.queries.employee_query" + } + }) + }, + + employee: function(frm) { + frm.trigger("calculate_total_leaves_allocated"); + }, + + leave_type: function(frm) { + frm.trigger("calculate_total_leaves_allocated"); + }, + + carry_forward: function(frm) { + frm.trigger("calculate_total_leaves_allocated"); + }, + + carry_forwarded_leaves: function(frm) { + frm.set_value("total_leaves_allocated", + flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + }, + + new_leaves_allocated: function(frm) { + frm.set_value("total_leaves_allocated", + flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + }, + + calculate_total_leaves_allocated: function(frm) { + if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) { + return frappe.call({ + method: "erpnext.hr.doctype.leave_allocation.leave_allocation.get_carry_forwarded_leaves", + args: { + "employee": frm.doc.employee, + "date": frm.doc.from_date, + "leave_type": frm.doc.leave_type, + "carry_forward": frm.doc.carry_forward + }, + callback: function(r) { + if (!r.exc && r.message) { + frm.set_value('carry_forwarded_leaves', r.message); + frm.set_value("total_leaves_allocated", + flt(r.message) + flt(frm.doc.new_leaves_allocated)); + } + } + }) + } else if (cint(frm.doc.carry_forward) == 0) { + frm.set_value("carry_forwarded_leaves", 0); + frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); + } } - else if(cint(doc.carry_forward) == 0){ - set_multiple(dt,dn,{carry_forwarded_leaves : 0,total_leaves_allocated : flt(doc.new_leaves_allocated)}); - } -} - -cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) { - return{ - query: "erpnext.controllers.queries.employee_query" - } -} +}) \ 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 f8461241ad..43facf6dcc 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -3,93 +3,105 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, flt, date_diff +from frappe.utils import flt, date_diff, formatdate from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name +from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period class LeaveAllocation(Document): def validate(self): self.validate_period() self.validate_new_leaves_allocated_value() - self.check_existing_leave_allocation() - if not self.total_leaves_allocated: - self.total_leaves_allocated = self.new_leaves_allocated - + self.validate_allocation_overlap() + self.validate_back_dated_allocation() + self.set_total_leaves_allocated() + self.validate_total_leaves_allocated() set_employee_name(self) def on_update_after_submit(self): self.validate_new_leaves_allocated_value() - - def on_update(self): - self.get_total_allocated_leaves() + self.set_total_leaves_allocated() + + frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves)) + frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated)) + + self.validate_against_leave_applications() def validate_period(self): if date_diff(self.to_date, self.from_date) <= 0: - frappe.throw(_("Invalid period")) + frappe.throw(_("To date cannot be before from date")) def validate_new_leaves_allocated_value(self): """validate that leave allocation is in multiples of 0.5""" if flt(self.new_leaves_allocated) % 0.5: frappe.throw(_("Leaves must be allocated in multiples of 0.5")) - def check_existing_leave_allocation(self): - """check whether leave for same type is already allocated or not""" - leave_allocation = frappe.db.sql("""select name from `tabLeave Allocation` - where employee='%s' and leave_type='%s' and to_date >= '%s' and from_date <= '%s' and docstatus=1 - """%(self.employee, self.leave_type, self.from_date, self.to_date)) + 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)) if leave_allocation: - frappe.msgprint(_("Leaves for type {0} already allocated for Employee {1} for period {2} - {3}").format(self.leave_type, - self.employee, self.from_date, self.to_date)) - frappe.throw(_('Reference') + ': {0}'.format(leave_allocation[0][0])) + frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} - {3}") + .format(self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date))) + + frappe.throw(_('Reference') + ': {0}' + .format(leave_allocation[0][0])) + + def validate_back_dated_allocation(self): + future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation` + where employee=%s and leave_type=%s and docstatus=1 and from_date > %s + and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1) + + if future_allocation: + frappe.throw(_("Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}") + .format(formatdate(future_allocation[0].from_date), future_allocation[0].name)) - def get_leave_bal(self): - return self.get_leaves_allocated() - self.get_leaves_applied() + def set_total_leaves_allocated(self): + self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, + self.leave_type, self.from_date, self.carry_forward) + + self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated) + + if not self.total_leaves_allocated: + frappe.throw(_("Total leaves allocated is mandatory")) - def get_leaves_applied(self): - leaves_applied = frappe.db.sql("""select SUM(ifnull(total_leave_days, 0)) - from `tabLeave Application` where employee=%s and leave_type=%s - and to_date<=%s and docstatus=1""", - (self.employee, self.leave_type, self.from_date)) - return leaves_applied and flt(leaves_applied[0][0]) or 0 + def validate_total_leaves_allocated(self): + if date_diff(self.to_date, self.from_date) <= flt(self.total_leaves_allocated): + frappe.throw(_("Total allocated leaves are more than days in the period")) + + def validate_against_leave_applications(self): + leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type, + self.from_date, self.to_date) + + if flt(leaves_taken) > flt(self.total_leaves_allocated): + frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken)) - def get_leaves_allocated(self): - leaves_allocated = frappe.db.sql("""select SUM(ifnull(total_leaves_allocated, 0)) - from `tabLeave Allocation` where employee=%s and leave_type=%s - and to_date<=%s and docstatus=1 and name!=%s""", - (self.employee, self.leave_type, self.from_date, self.name)) - return leaves_allocated and flt(leaves_allocated[0][0]) or 0 - - def allow_carry_forward(self): - """check whether carry forward is allowed or not for this leave type""" - cf = frappe.db.sql("""select is_carry_forward from `tabLeave Type` where name = %s""", - self.leave_type) - cf = cf and cint(cf[0][0]) or 0 - if not cf: - frappe.db.set(self,'carry_forward',0) - frappe.throw(_("Cannot carry forward {0}").format(self.leave_type)) - - def get_carry_forwarded_leaves(self): - if self.carry_forward: - self.allow_carry_forward() - - prev_bal = 0 - if cint(self.carry_forward) == 1: - prev_bal = self.get_leave_bal() - - ret = { - 'carry_forwarded_leaves': prev_bal, - 'total_leaves_allocated': flt(prev_bal) + flt(self.new_leaves_allocated) - } - return ret - - def get_total_allocated_leaves(self): - leave_det = self.get_carry_forwarded_leaves() - self.validate_total_leaves_allocated(leave_det) - frappe.db.set(self,'carry_forwarded_leaves',flt(leave_det['carry_forwarded_leaves'])) - frappe.db.set(self,'total_leaves_allocated',flt(leave_det['total_leaves_allocated'])) - - def validate_total_leaves_allocated(self, leave_det): - if date_diff(self.to_date, self.from_date) <= leave_det['total_leaves_allocated']: - frappe.throw(_("Total allocated leaves are more than period")) +@frappe.whitelist() +def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): + carry_forwarded_leaves = 0 + + if carry_forward: + validate_carry_forward(leave_type) + + previous_allocation = frappe.db.sql(""" + select name, from_date, to_date, total_leaves_allocated + from `tabLeave Allocation` + where employee=%s and leave_type=%s and docstatus=1 and to_date < %s + order by to_date desc limit 1 + """, (employee, leave_type, date), as_dict=1) + if previous_allocation: + leaves_taken = get_approved_leaves_for_period(employee, leave_type, + previous_allocation[0].from_date, previous_allocation[0].to_date) + + carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken) + + return carry_forwarded_leaves + +def validate_carry_forward(leave_type): + if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): + frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) + \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 835170a154..e708b77911 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -66,14 +66,18 @@ frappe.ui.form.on("Leave Application", { }, get_leave_balance: function(frm) { - if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) { - return frm.call({ - method: "get_leave_balance", + if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) { + return frappe.call({ + method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on", args: { employee: frm.doc.employee, - from_date: frm.doc.from_date, - to_date: frm.doc.to_date, + date: frm.doc.from_date, leave_type: frm.doc.leave_type + }, + callback: function(r) { + if (!r.exc && r.message) { + frm.set_value('leave_balance', r.message); + } } }); } @@ -83,14 +87,20 @@ frappe.ui.form.on("Leave Application", { if(frm.doc.from_date && frm.doc.to_date) { if (cint(frm.doc.half_day)==1) { frm.set_value("total_leave_days", 0.5); - } else { + } else if (frm.doc.employee && frm.doc.leave_type){ // server call is done to include holidays in leave days calculations return frappe.call({ - method: 'erpnext.hr.doctype.leave_application.leave_application.get_total_leave_days', - args: { leave_app: frm.doc }, - callback: function(response) { - if (response && response.message) { - frm.set_value('total_leave_days', response.message.total_leave_days); + method: 'erpnext.hr.doctype.leave_application.leave_application.get_number_of_leave_days', + args: { + "employee": frm.doc.employee, + "leave_type": frm.doc.leave_type, + "from_date": frm.doc.from_date, + "to_date": frm.doc.to_date, + "half_day": frm.doc.half_day + }, + callback: function(r) { + if (r && r.message) { + frm.set_value('total_leave_days', r.message); frm.trigger("get_leave_balance"); } } diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json index 62e4cd8b58..10f6594ec6 100644 --- a/erpnext/hr/doctype/leave_application/leave_application.json +++ b/erpnext/hr/doctype/leave_application/leave_application.json @@ -418,28 +418,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 1, - "in_list_view": 0, - "label": "Fiscal Year", - "no_copy": 0, - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 1, "bold": 0, @@ -559,7 +537,7 @@ "issingle": 0, "istable": 0, "max_attachments": 3, - "modified": "2015-10-28 16:14:25.640730", + "modified": "2015-11-15 19:32:32.258397", "modified_by": "Administrator", "module": "HR", "name": "Leave Application", @@ -711,7 +689,7 @@ ], "read_only": 0, "read_only_onload": 0, - "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days,fiscal_year", + "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days", "sort_field": "modified", "sort_order": "DESC", "title_field": "employee_name" diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 8c91173d36..40f2136b70 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -2,13 +2,13 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, json +import frappe from frappe import _ - from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ comma_or, get_fullname -from frappe import msgprint from erpnext.hr.utils import set_employee_name +from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates + class LeaveDayBlockedError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass @@ -18,8 +18,7 @@ class LeaveApproverIdentityError(frappe.ValidationError): pass from frappe.model.document import Document class LeaveApplication(Document): def get_feed(self): - return _("{0}: From {0} of type {1}").format(self.status, - self.employee_name, self.leave_type) + return _("{0}: From {0} of type {1}").format(self.status, self.employee_name, self.leave_type) def validate(self): if not getattr(self, "__islocal", None) and frappe.db.exists(self.doctype, self.name): @@ -29,7 +28,7 @@ class LeaveApplication(Document): set_employee_name(self) - self.validate_to_date() + self.validate_dates() self.validate_balance_leaves() self.validate_leave_overlap() self.validate_max_days() @@ -57,10 +56,41 @@ class LeaveApplication(Document): def on_cancel(self): # notify leave applier about cancellation self.notify_employee("cancelled") + + def validate_dates(self): + if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): + frappe.throw(_("To date cannot be before from date")) + + self.validate_dates_acorss_allocation() + self.validate_back_dated_application() + + def validate_dates_acorss_allocation(self): + def _get_leave_alloction_record(date): + allocation = frappe.db.sql("""select name from `tabLeave Allocation` + where employee=%s and leave_type=%s and docstatus=1 + and %s between from_date and to_date""", (self.employee, self.leave_type, date)) + + return allocation and allocation[0][0] + + allocation_based_on_from_date = _get_leave_alloction_record(self.from_date) + allocation_based_on_to_date = _get_leave_alloction_record(self.to_date) + + if not (allocation_based_on_from_date or allocation_based_on_to_date): + frappe.throw(_("Application period cannot be outside leave allocation period")) + + elif allocation_based_on_from_date != allocation_based_on_to_date: + frappe.throw(_("Application period cannot be across two alocation records")) + + def validate_back_dated_application(self): + future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation` + where employee=%s and leave_type=%s and docstatus=1 and from_date > %s + and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1) + + if future_allocation: + frappe.throw(_("Leave cannot be applied before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}") + .format(formatdate(future_allocation[0].from_date), future_allocation[0].name)) def show_block_day_warning(self): - from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates - block_dates = get_applicable_block_dates(self.from_date, self.to_date, self.employee, self.company, all_lists=True) @@ -70,60 +100,39 @@ class LeaveApplication(Document): frappe.msgprint(formatdate(d.block_date) + ": " + d.reason) def validate_block_days(self): - from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates - block_dates = get_applicable_block_dates(self.from_date, self.to_date, self.employee, self.company) - if block_dates: - if self.status == "Approved": - frappe.throw(_("Cannot approve leave as you are not authorized to approve leaves on Block Dates"), - LeaveDayBlockedError) - - def get_holidays(self): - return get_holidays(self) - - def get_total_leave_days(self): - return get_total_leave_days(self) - - def validate_to_date(self): - if self.from_date and self.to_date and \ - (getdate(self.to_date) < getdate(self.from_date)): - frappe.throw(_("To date cannot be before from date")) + if block_dates and self.status == "Approved": + frappe.throw(_("You are not authorized to approve leaves on Block Dates"), LeaveDayBlockedError) def validate_balance_leaves(self): if self.from_date and self.to_date: - self.total_leave_days = self.get_total_leave_days()["total_leave_days"] + self.total_leave_days = get_number_of_leave_days(self.employee, self.leave_type, + self.from_date, self.to_date, self.half_day) if self.total_leave_days == 0: - frappe.throw(_("The day(s) on which you are applying for leave are holiday. You need not apply for leave.")) + frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave.")) if not is_lwp(self.leave_type): - self.leave_balance = get_leave_balance(self.employee, - self.leave_type, self.from_date, self.to_date)["leave_balance"] + self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date) - if self.status != "Rejected" \ - and self.leave_balance - self.total_leave_days < 0: - #check if this leave type allow the remaining balance to be in negative. If yes then warn the user and continue to save else warn the user and don't save. + if self.status != "Rejected" and self.leave_balance < self.total_leave_days: if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): - frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}").format(self.leave_type)) + frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}") + .format(self.leave_type)) else: - frappe.throw(_("There is not enough leave balance for Leave Type {0}").format(self.leave_type)) - + frappe.throw(_("There is not enough leave balance for Leave Type {0}") + .format(self.leave_type)) def validate_leave_overlap(self): if not self.name: self.name = "New Leave Application" - for d in frappe.db.sql("""select name, leave_type, posting_date, - from_date, to_date + for d in frappe.db.sql("""select name, leave_type, posting_date, from_date, to_date from `tabLeave Application` - where - employee = %(employee)s - and docstatus < 2 - and status in ("Open", "Approved") - and to_date >= %(from_date)s - and from_date <= %(to_date)s + where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved") + and to_date >= %(from_date)s and from_date <= %(to_date)s and name != %(name)s""", { "employee": self.employee, "from_date": self.from_date, @@ -131,9 +140,12 @@ class LeaveApplication(Document): "name": self.name }, as_dict = 1): - frappe.msgprint(_("Employee {0} has already applied for {1} between {2} and {3}").format(self.employee, - cstr(d['leave_type']), formatdate(d['from_date']), formatdate(d['to_date']))) - frappe.throw('{0}'.format(d["name"]), OverlapError) + frappe.msgprint(_("Employee {0} has already applied for {1} between {2} and {3}") + .format(self.employee, cstr(d['leave_type']), + formatdate(d['from_date']), formatdate(d['to_date']))) + + frappe.throw("""Exising Application: {0}""" + .format(d["name"]), OverlapError) def validate_max_days(self): max_days = frappe.db.get_value("Leave Type", self.leave_type, "max_days_allowed") @@ -145,7 +157,8 @@ class LeaveApplication(Document): leave_approvers = [l.leave_approver for l in employee.get("leave_approvers")] if len(leave_approvers) and self.leave_approver not in leave_approvers: - frappe.throw(_("Leave approver must be one of {0}").format(comma_or(leave_approvers)), InvalidLeaveApproverError) + frappe.throw(_("Leave approver must be one of {0}") + .format(comma_or(leave_approvers)), InvalidLeaveApproverError) elif self.leave_approver and not frappe.db.sql("""select name from `tabUserRole` where parent=%s and role='Leave Approver'""", self.leave_approver): @@ -153,8 +166,8 @@ class LeaveApplication(Document): .format(get_fullname(self.leave_approver), self.leave_approver), InvalidLeaveApproverError) elif self.docstatus==1 and len(leave_approvers) and self.leave_approver != frappe.session.user: - msgprint(_("Only the selected Leave Approver can submit this Leave Application"), - raise_exception=LeaveApproverIdentityError) + frappe.throw(_("Only the selected Leave Approver can submit this Leave Application"), + LeaveApproverIdentityError) def notify_employee(self, status): employee = frappe.get_doc("Employee", self.employee) @@ -213,60 +226,89 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): approver.parent = %s and user.name like %s and approver.leave_approver=user.name""", (filters.get("employee"), "%" + txt + "%")) + +@frappe.whitelist() +def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day=None): + if half_day: + return 0.5 + number_of_days = date_diff(to_date, from_date) + 1 + if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"): + number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date)) + + return number_of_days -def get_holidays(leave_app): +@frappe.whitelist() +def get_leave_balance_on(employee, leave_type, date, allocation_records=None): + if allocation_records == None: + allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + + allocation = allocation_records.get(leave_type, frappe._dict()) + + leaves_taken = get_approved_leaves_for_period(employee, leave_type, allocation.from_date, date) + + return flt(allocation.total_leaves_allocated) - flt(leaves_taken) + +def get_approved_leaves_for_period(employee, leave_type, from_date, to_date): + leave_applications = frappe.db.sql(""" + select employee, leave_type, from_date, to_date, total_leave_days + from `tabLeave Application` + where employee=%(employee)s and leave_type=%(leave_type)s + and status="Approved" and docstatus=1 + 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, + "employee": employee, + "leave_type": leave_type + }, as_dict=1) + + leave_days = 0 + for leave_app in leave_applications: + if leave_app.from_date >= getdate(from_date) and leave_app.to_date <= getdate(to_date): + leave_days += leave_app.total_leave_days + else: + if leave_app.from_date < getdate(from_date): + leave_app.from_date = from_date + if leave_app.to_date > getdate(to_date): + leave_app.to_date = to_date + + leave_days += get_number_of_leave_days(employee, leave_type, + leave_app.from_date, leave_app.to_date) + + return leave_days + +def get_leave_allocation_records(date, employee=None): + conditions = (" and employee='%s'" % employee) if employee else "" + + leave_allocation_records = frappe.db.sql(""" + select employee, leave_type, total_leaves_allocated, from_date + from `tabLeave Allocation` + where %s between from_date and to_date and docstatus=1 {0}""".format(conditions), (date), as_dict=1) + + allocated_leaves = frappe._dict() + for d in leave_allocation_records: + allocated_leaves.setdefault(d.employee, frappe._dict()).setdefault(d.leave_type, frappe._dict({ + "from_date": d.from_date, + "total_leaves_allocated": d.total_leaves_allocated + })) + + return allocated_leaves + + +def get_holidays(employee, from_date, to_date): tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2, `tabEmployee` e1 where e1.name = %s and h1.parent = h2.name and e1.holiday_list = h2.name - and h1.holiday_date between %s and %s""", (leave_app.employee, leave_app.from_date, - leave_app.to_date))[0][0] - # below line is needed. If an employee hasn't been assigned with any holiday list then above will return 0 rows. + and h1.holiday_date between %s and %s""", (employee, from_date, to_date))[0][0] + if not tot_hol: tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2 where h1.parent = h2.name and h1.holiday_date between %s and %s - and ifnull(h2.is_default,0) = 1 and h2.fiscal_year = %s""", - (leave_app.from_date, leave_app.to_date, leave_app.fiscal_year))[0][0] + and ifnull(h2.is_default,0) = 1""", (from_date, to_date))[0][0] + return tot_hol -@frappe.whitelist() -def get_total_leave_days(leave_app): - # Parse Leave Application if neccessary - if isinstance(leave_app, str) or isinstance(leave_app, unicode): - leave_app = frappe.get_doc(json.loads(leave_app)) - - """Calculates total leave days based on input and holidays""" - ret = {'total_leave_days' : 0.5} - if not leave_app.half_day: - tot_days = date_diff(leave_app.to_date, leave_app.from_date) + 1 - if frappe.db.get_value("Leave Type", leave_app.leave_type, "include_holiday"): - ret = { - 'total_leave_days' : flt(tot_days) - } - else: - holidays = leave_app.get_holidays() - ret = { - 'total_leave_days' : flt(tot_days)-flt(holidays) - } - - return ret - -@frappe.whitelist() -def get_leave_balance(employee, leave_type, from_date, to_date): - leave_all = frappe.db.sql("""select total_leaves_allocated - from `tabLeave Allocation` where employee = %s and leave_type = %s - and from_date<=%s and to_date>=%s and docstatus = 1""", (employee, - leave_type, from_date, to_date)) - - leave_all = leave_all and flt(leave_all[0][0]) or 0 - - leave_app = frappe.db.sql("""select SUM(total_leave_days) - from `tabLeave Application` - where employee = %s and leave_type = %s and to_date>=%s and from_date<=%s - and status="Approved" and docstatus = 1""", (employee, leave_type, from_date, to_date)) - leave_app = leave_app and flt(leave_app[0][0]) or 0 - - ret = {'leave_balance': leave_all - leave_app} - return ret - def is_lwp(leave_type): lwp = frappe.db.sql("select is_lwp from `tabLeave Type` where name = %s", leave_type) return lwp and cint(lwp[0][0]) or 0 diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js index 41b1421b71..5df4554dec 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js @@ -7,12 +7,14 @@ frappe.query_reports["Employee Leave Balance"] = { "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", + "reqd": 1, "default": frappe.datetime.year_start() }, { "fieldname":"to_date", "label": __("To Date"), "fieldtype": "Date", + "reqd": 1, "default": frappe.datetime.year_end() }, { @@ -20,6 +22,7 @@ frappe.query_reports["Employee Leave Balance"] = { "label": __("Company"), "fieldtype": "Link", "options": "Company", + "reqd": 1, "default": frappe.defaults.get_user_default("company") } ] diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 0aa88a82b0..7f1c442a10 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -4,67 +4,54 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.desk.reportview import execute as runreport +from erpnext.hr.doctype.leave_application.leave_application \ + import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period + def execute(filters=None): - if not filters: filters = {} - - employee_filters = { - "status": "Active" - } + leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc") - if filters.get("company"): - filters["company"] = filters.company - - employees = runreport(doctype="Employee", fields=["name", "employee_name", "department"], - filters=employee_filters, limit_page_length=None) - - if not employees: - frappe.throw(_("No employee found!")) - - leave_types = frappe.db.sql_list("select name from `tabLeave Type`") - - employee_names = [d.name for d in employees] - - allocations = frappe.db.sql("""select employee, leave_type, sum(new_leaves_allocated) as leaves_allocated - from `tabLeave Allocation` - where docstatus=1 and employee in (%s) and from_date >= '%s' and to_date <= '%s'""" % - (','.join(['%s']*len(employee_names)), filters.get("from_date"), - filters.get("to_date")), employee_names, as_dict=True) - - applications = frappe.db.sql("""select employee, leave_type, - SUM(total_leave_days) as leaves - from `tabLeave Application` - where status="Approved" and docstatus = 1 and employee in (%s) - and from_date >= '%s' and to_date <= '%s' - group by employee, leave_type""" % - (','.join(['%s']*len(employee_names)), filters.get("from_date"), - filters.get("to_date")), employee_names, as_dict=True) - + columns = get_columns(leave_types) + data = get_data(filters, leave_types) + + return columns, data + +def get_columns(leave_types): columns = [ - _("Employee") + ":Link/Employee:150", _("Employee Name") + "::200", _("Department") +"::150" + _("Employee") + ":Link/Employee:150", + _("Employee Name") + "::200", + _("Department") +"::150" ] for leave_type in leave_types: - columns.append(_(leave_type) + " " + _("Opening") + ":Float") - columns.append(_(leave_type) + " " + _("Taken") + ":Float") - columns.append(_(leave_type) + " " + _("Balance") + ":Float") + columns.append(_(leave_type) + " " + _("Taken") + ":Float:160") + columns.append(_(leave_type) + " " + _("Balance") + ":Float:160") + + return columns + +def get_data(filters, leave_types): - data = {} - for d in allocations: - data.setdefault((d.employee,d.leave_type), frappe._dict()).allocation = d.leaves_allocated + allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date) - for d in applications: - data.setdefault((d.employee, d.leave_type), frappe._dict()).leaves = d.leaves - - result = [] - for employee in employees: + active_employees = frappe.get_all("Employee", + filters = { "status": "Active", "company": filters.company}, + fields = ["name", "employee_name", "department"]) + + data = [] + for employee in active_employees: row = [employee.name, employee.employee_name, employee.department] - result.append(row) - for leave_type in leave_types: - tmp = data.get((employee.name, leave_type), frappe._dict()) - row.append(tmp.allocation or 0) - row.append(tmp.leaves or 0) - row.append((tmp.allocation or 0) - (tmp.leaves or 0)) - return columns, result + for leave_type in leave_types: + # leaves taken + leaves_taken = get_approved_leaves_for_period(employee.name, leave_type, + filters.from_date, filters.to_date) + + # closing balance + closing = get_leave_balance_on(employee.name, leave_type, filters.to_date, + allocation_records_based_on_to_date.get(employee.name, frappe._dict())) + + row += [leaves_taken, closing] + + data.append(row) + + return data \ No newline at end of file diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index 5fce97009d..7b2faf9d2b 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -4,11 +4,13 @@ frappe.provide("erpnext.projects"); frappe.ui.form.on("Time Log", "onload", function(frm) { - if (frm.doc.for_manufacturing) { - frappe.ui.form.trigger("Time Log", "production_order"); - } - if (frm.doc.from_time && frm.doc.to_time) { - frappe.ui.form.trigger("Time Log", "to_time"); + if (frm.doc.__islocal) { + if (frm.doc.for_manufacturing) { + frappe.ui.form.trigger("Time Log", "production_order"); + } + if (frm.doc.from_time && frm.doc.to_time) { + frappe.ui.form.trigger("Time Log", "to_time"); + } } }); diff --git a/test_sites/apps.txt b/test_sites/apps.txt deleted file mode 100644 index 37967291f6..0000000000 --- a/test_sites/apps.txt +++ /dev/null @@ -1 +0,0 @@ -erpnext diff --git a/test_sites/languages.txt b/test_sites/languages.txt deleted file mode 100644 index 0163b22e4a..0000000000 --- a/test_sites/languages.txt +++ /dev/null @@ -1 +0,0 @@ -en english diff --git a/test_sites/test_site/site_config.json b/test_sites/test_site/site_config.json deleted file mode 100644 index 7d1194af75..0000000000 --- a/test_sites/test_site/site_config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "test_frappe", - "db_password": "test_frappe", - "auto_email_id": "test@example.com", - "mail_server": "smtp.example.com", - "mail_login": "test@example.com", - "mail_password": "test", - "admin_password": "admin", - "run_selenium_tests": 1, - "host_name": "http://localhost:8000", - "install_apps": ["erpnext"] -} From b6de519571a962f358851ef75422cc5205d58853 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 16 Nov 2015 15:35:09 +0530 Subject: [PATCH 12/13] [fix] Test case fixed --- .../leave_allocation/leave_allocation.py | 17 ++++++++++++----- .../leave_allocation/test_leave_allocation.py | 4 ++-- .../leave_application/leave_application.py | 4 +++- .../hr/doctype/salary_slip/test_salary_slip.py | 16 ++++++++++++++-- test_sites/apps.txt | 1 + test_sites/languages.txt | 1 + test_sites/test_site/site_config.json | 12 ++++++++++++ 7 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 test_sites/apps.txt create mode 100644 test_sites/languages.txt create mode 100644 test_sites/test_site/site_config.json diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 43facf6dcc..57eb146654 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -9,6 +9,12 @@ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period +class OverlapError(frappe.ValidationError): pass +class BackDatedAllocationError(frappe.ValidationError): pass +class OverAllocationError(frappe.ValidationError): pass +class LessAllocationError(frappe.ValidationError): pass +class ValueMultiplierError(frappe.ValidationError): pass + class LeaveAllocation(Document): def validate(self): self.validate_period() @@ -35,7 +41,7 @@ class LeaveAllocation(Document): def validate_new_leaves_allocated_value(self): """validate that leave allocation is in multiples of 0.5""" if flt(self.new_leaves_allocated) % 0.5: - frappe.throw(_("Leaves must be allocated in multiples of 0.5")) + frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" @@ -49,7 +55,7 @@ class LeaveAllocation(Document): .format(self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date))) frappe.throw(_('Reference') + ': {0}' - .format(leave_allocation[0][0])) + .format(leave_allocation[0][0]), OverlapError) def validate_back_dated_allocation(self): future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation` @@ -58,7 +64,8 @@ class LeaveAllocation(Document): if future_allocation: frappe.throw(_("Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}") - .format(formatdate(future_allocation[0].from_date), future_allocation[0].name)) + .format(formatdate(future_allocation[0].from_date), future_allocation[0].name), + BackDatedAllocationError) def set_total_leaves_allocated(self): self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, @@ -71,14 +78,14 @@ class LeaveAllocation(Document): def validate_total_leaves_allocated(self): if date_diff(self.to_date, self.from_date) <= flt(self.total_leaves_allocated): - frappe.throw(_("Total allocated leaves are more than days in the period")) + frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError) def validate_against_leave_applications(self): leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type, self.from_date, self.to_date) if flt(leaves_taken) > flt(self.total_leaves_allocated): - frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken)) + frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) @frappe.whitelist() def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index d36fb2cb69..b3eee31121 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -13,7 +13,7 @@ class TestLeaveAllocation(unittest.TestCase): "employee": employee.name, "employee_name": employee.employee_name, "leave_type": "_Test Leave Type", - "from_date": getdate("2015-10-1"), + "from_date": getdate("2015-10-01"), "to_date": getdate("2015-10-31"), "new_leaves_allocated": 5, "docstatus": 1 @@ -24,7 +24,7 @@ class TestLeaveAllocation(unittest.TestCase): "employee": employee.name, "employee_name": employee.employee_name, "leave_type": "_Test Leave Type", - "from_date": getdate("2015-09-1"), + "from_date": getdate("2015-09-01"), "to_date": getdate("2015-11-30"), "new_leaves_allocated": 5 } diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 40f2136b70..1d84a4013b 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -49,6 +49,8 @@ class LeaveApplication(Document): def on_submit(self): if self.status != "Approved": frappe.throw(_("Only Leave Applications with status 'Approved' can be submitted")) + + self.validate_back_dated_application() # notify leave applier about approval self.notify_employee(self.status) @@ -87,7 +89,7 @@ class LeaveApplication(Document): and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1) if future_allocation: - frappe.throw(_("Leave cannot be applied before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}") + frappe.throw(_("Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}") .format(formatdate(future_allocation[0].from_date), future_allocation[0].name)) def show_block_day_warning(self): diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index 2bb7f52f63..fb69440cab 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -10,8 +10,20 @@ from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_sli class TestSalarySlip(unittest.TestCase): def setUp(self): - frappe.db.sql("""delete from `tabLeave Application`""") - frappe.db.sql("""delete from `tabSalary Slip`""") + for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: + frappe.db.sql("delete from `tab%s`" % dt) + + allocation = frappe.get_doc({ + "doctype": "Leave Allocation", + "employee": "_T-Employee-0001", + "leave_type": "_Test Leave Type LWP", + "from_date": "2013-01-01", + "to_date": "2015-12-31", + "new_leaves_allocated": 5 + }) + + allocation.insert() + allocation.submit() frappe.db.set_value("Holiday List", "_Test Holiday List", "is_default", 1) diff --git a/test_sites/apps.txt b/test_sites/apps.txt new file mode 100644 index 0000000000..ee6454915c --- /dev/null +++ b/test_sites/apps.txt @@ -0,0 +1 @@ +erpnext \ No newline at end of file diff --git a/test_sites/languages.txt b/test_sites/languages.txt new file mode 100644 index 0000000000..cf2b150830 --- /dev/null +++ b/test_sites/languages.txt @@ -0,0 +1 @@ +en english \ No newline at end of file diff --git a/test_sites/test_site/site_config.json b/test_sites/test_site/site_config.json new file mode 100644 index 0000000000..48b330bc72 --- /dev/null +++ b/test_sites/test_site/site_config.json @@ -0,0 +1,12 @@ +{ + "db_name": "test_frappe", + "db_password": "test_frappe", + "auto_email_id": "test@example.com", + "mail_server": "smtp.example.com", + "mail_login": "test@example.com", + "mail_password": "test", + "admin_password": "admin", + "run_selenium_tests": 1, + "host_name": "http://localhost:8000", + "install_apps": ["erpnext"] +} \ No newline at end of file From 8e2d76bd6311e7f957d47ba824f2d8f2196d79db Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 16 Nov 2015 17:12:56 +0600 Subject: [PATCH 13/13] bumped to version 6.9.0 --- erpnext/__version__.py | 2 +- erpnext/hooks.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/__version__.py b/erpnext/__version__.py index f2ac3756b1..ce580166c8 100644 --- a/erpnext/__version__.py +++ b/erpnext/__version__.py @@ -1,2 +1,2 @@ from __future__ import unicode_literals -__version__ = '6.8.4' +__version__ = '6.9.0' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 7e333f7df0..7dd0628f19 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -29,7 +29,7 @@ blogs. """ app_icon = "icon-th" app_color = "#e74c3c" -app_version = "6.8.4" +app_version = "6.9.0" app_email = "info@erpnext.com" app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" diff --git a/setup.py b/setup.py index 51dd9e4ea9..600693f9fc 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = "6.8.4" +version = "6.9.0" with open("requirements.txt", "r") as f: install_requires = f.readlines()