From e83ff38c1092f472f8f1b10e5c8e1da95efc7272 Mon Sep 17 00:00:00 2001 From: Zarrar Date: Fri, 21 Sep 2018 15:45:40 +0530 Subject: [PATCH] [Enhance] Deferred Expense (#15437) * added section for deferred expense in item master * added default expense account field in Company master * added deferred expense section in purchase invoice item * validation and getter code added * scheduler event to book expense every month * codacy, import fix and other minor fixes * rectify debit credit logic for expense * commonify js code for deferred expense and revenue * remove deferred calculation and validation * common file to calculate deferred revenue and expense * codacy fixes * expense account root_type - Asset, specific method naming --- erpnext/accounts/deferred_revenue.py | 179 ++++++++++++++ .../purchase_invoice/purchase_invoice.js | 10 + .../purchase_invoice/purchase_invoice.py | 6 +- .../purchase_invoice_item.json | 232 +++++++++++++++++- .../doctype/sales_invoice/sales_invoice.js | 33 --- .../doctype/sales_invoice/sales_invoice.py | 126 +--------- erpnext/hooks.py | 3 +- erpnext/public/js/controllers/transaction.js | 32 +++ erpnext/setup/doctype/company/company.json | 60 ++++- erpnext/stock/doctype/item/item.json | 184 +++++++++++++- erpnext/stock/get_item_details.py | 18 +- 11 files changed, 692 insertions(+), 191 deletions(-) create mode 100644 erpnext/accounts/deferred_revenue.py diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py new file mode 100644 index 0000000000..61fc211aa4 --- /dev/null +++ b/erpnext/accounts/deferred_revenue.py @@ -0,0 +1,179 @@ +from __future__ import unicode_literals + +import frappe +from frappe import _ +from frappe.utils import date_diff, add_months, today, getdate, add_days, flt +from erpnext.accounts.utils import get_account_currency +from erpnext.accounts.general_ledger import make_gl_entries + +def validate_service_stop_date(doc): + ''' Validates service_stop_date for Purchase Invoice and Sales Invoice ''' + + enable_check = "enable_deferred_revenue" \ + if doc.doctype=="Sales Invoice" else "enable_deferred_expense" + + old_stop_dates = {} + old_doc = frappe.db.get_all("{0} Item".format(doc.doctype), + {"parent": doc.name}, ["name", "service_stop_date"]) + + for d in old_doc: + old_stop_dates[d.name] = d.service_stop_date or "" + + for item in doc.items: + if not item.get(enable_check): continue + + if item.service_stop_date: + if date_diff(item.service_stop_date, item.service_start_date) < 0: + frappe.throw(_("Service Stop Date cannot be before Service Start Date")) + + if date_diff(item.service_stop_date, item.service_end_date) > 0: + frappe.throw(_("Service Stop Date cannot be after Service End Date")) + + if old_stop_dates and old_stop_dates[item.name] and item.service_stop_date!=old_stop_dates[item.name]: + frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx))) + +def convert_deferred_expense_to_expense(start_date=None, end_date=None): + # check for the purchase invoice for which GL entries has to be done + invoices = frappe.db.sql_list(''' + select parent from `tabPurchase Invoice Item` where service_start_date<=%s and service_end_date>=%s + and enable_deferred_expense = 1 and docstatus = 1 + ''', (end_date or today(), start_date or add_months(today(), -1))) + + # For each invoice, book deferred expense + for invoice in invoices: + doc = frappe.get_doc("Purchase Invoice", invoice) + book_deferred_income_or_expense(doc, start_date, end_date) + +def convert_deferred_revenue_to_income(start_date=None, end_date=None): + # check for the sales invoice for which GL entries has to be done + invoices = frappe.db.sql_list(''' + select parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s + and enable_deferred_revenue = 1 and docstatus = 1 + ''', (end_date or today(), start_date or add_months(today(), -1))) + + # For each invoice, book deferred revenue + for invoice in invoices: + doc = frappe.get_doc("Sales Invoice", invoice) + book_deferred_income_or_expense(doc, start_date, end_date) + +def get_booking_dates(doc, item, start_date=None, end_date=None): + deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account" + last_gl_entry, skip = False, False + + booking_end_date = getdate(add_days(today(), -1)) if not end_date else end_date + if booking_end_date < item.service_start_date or \ + (item.service_stop_date and booking_end_date.month > item.service_stop_date.month): + return None, None, None, True + elif booking_end_date >= item.service_end_date: + last_gl_entry = True + booking_end_date = item.service_end_date + elif item.service_stop_date and item.service_stop_date <= booking_end_date: + last_gl_entry = True + booking_end_date = item.service_stop_date + + booking_start_date = getdate(add_months(today(), -1)) if not start_date else start_date + booking_start_date = booking_start_date \ + if booking_start_date > item.service_start_date else item.service_start_date + + prev_gl_entry = frappe.db.sql(''' + select name, posting_date from `tabGL Entry` where company=%s and account=%s and + voucher_type=%s and voucher_no=%s and voucher_detail_no=%s + order by posting_date desc limit 1 + ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True) + + if not prev_gl_entry and item.service_start_date < booking_start_date: + booking_start_date = item.service_start_date + elif prev_gl_entry: + booking_start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1)) + skip = True if booking_start_date > booking_end_date else False + + return last_gl_entry, booking_start_date, booking_end_date, skip + +def calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total_booking_days): + account_currency = get_account_currency(item.expense_account) + + if doc.doctype == "Sales Invoice": + total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency" + deferred_account = "deferred_revenue_account" + else: + total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency" + deferred_account = "deferred_expense_account" + + amount, base_amount = 0, 0 + if not last_gl_entry: + base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount")) + if account_currency==doc.company_currency: + amount = base_amount + else: + amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount")) + else: + gl_entries_details = frappe.db.sql(''' + select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no + from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s + group by voucher_detail_no + '''.format(total_credit_debit, total_credit_debit_currency), + (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True) + + already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0 + base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount")) + if account_currency==doc.company_currency: + amount = base_amount + else: + already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0 + amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")) + + return amount, base_amount + +def book_deferred_income_or_expense(doc, start_date=None, end_date=None): + # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM + # start_date: 1st of the last month or the start date + # end_date: end_date or today-1 + + gl_entries = [] + for item in doc.get('items'): + skip = False + last_gl_entry, booking_start_date, booking_end_date, skip = \ + get_booking_dates(doc, item, start_date, end_date) + + if skip: continue + total_days = date_diff(item.service_end_date, item.service_start_date) + total_booking_days = date_diff(booking_end_date, booking_start_date) + 1 + + account_currency = get_account_currency(item.expense_account) + amount, base_amount = calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total_booking_days) + + if doc.doctype == "Sales Invoice": + against, project = doc.customer, doc.project + credit_account, debit_account = item.income_account, item.deferred_revenue_account + else: + against, project = doc.supplier, item.project + credit_account, debit_account = item.deferred_expense_account, item.expense_account + + # GL Entry for crediting the amount in the deferred expense + gl_entries.append( + doc.get_gl_dict({ + "account": credit_account, + "against": against, + "credit": base_amount, + "credit_in_account_currency": amount, + "cost_center": item.cost_center, + 'posting_date': booking_end_date, + 'project': project + }, account_currency) + ) + # GL Entry to debit the amount from the expense + gl_entries.append( + doc.get_gl_dict({ + "account": debit_account, + "against": against, + "debit": base_amount, + "debit_in_account_currency": amount, + "cost_center": item.cost_center, + "voucher_detail_no": item.name, + 'posting_date': booking_end_date, + 'project': project + }, account_currency) + ) + + if gl_entries: + make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 5154b90361..c37fd15ab9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -496,6 +496,16 @@ frappe.ui.form.on("Purchase Invoice", { 'Purchase Invoice': 'Debit Note', 'Payment Entry': 'Payment' } + + frm.fields_dict['items'].grid.get_field('deferred_expense_account').get_query = function(doc) { + return { + filters: { + 'root_type': 'Asset', + 'company': doc.company, + "is_group": 0 + } + } + } }, onload: function(frm) { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 273a6e4686..11e48db8a3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -22,6 +22,7 @@ from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_invoice,\ unlink_inter_company_invoice from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details +from erpnext.accounts.deferred_revenue import validate_service_stop_date form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -74,6 +75,9 @@ class PurchaseInvoice(BuyingController): if (self.is_paid == 1): self.validate_cash() + # validate service stop date to lie in between start and end date + validate_service_stop_date(self) + if self._action=="submit" and self.update_stock: self.make_batches('warehouse') @@ -448,7 +452,7 @@ class PurchaseInvoice(BuyingController): elif not item.is_fixed_asset: gl_entries.append( self.get_gl_dict({ - "account": item.expense_account, + "account": item.expense_account if not item.enable_deferred_expense else item.deferred_expense_account, "against": self.supplier, "debit": flt(item.base_net_amount, item.precision("base_net_amount")), "debit_in_account_currency": (flt(item.base_net_amount, diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index eaafdc0a08..936be10276 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -1680,8 +1680,229 @@ "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, - "columns": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "deferred_expense_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Deferred Expense", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Deferred Expense Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enable_deferred_expense", + "fieldname": "service_stop_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Service Stop Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enable_deferred_expense", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Enable Deferred Expense", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_58", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enable_deferred_expense", + "fieldname": "service_start_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Service Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enable_deferred_expense", + "fieldname": "service_end_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Service End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "reference", "fieldtype": "Section Break", "hidden": 0, @@ -2323,7 +2544,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-08-06 05:18:38.205356", + "modified": "2018-09-04 10:11:28.246395", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -2336,5 +2557,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 0, - "track_seen": 0 -} + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 216d3e603a..ca74d4da15 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -786,39 +786,6 @@ frappe.ui.form.on('Sales Invoice Timesheet', { } }) -frappe.ui.form.on('Sales Invoice Item', { - service_stop_date: function(frm, cdt, cdn) { - var child = locals[cdt][cdn]; - - if(child.service_stop_date) { - let start_date = Date.parse(child.service_start_date); - let end_date = Date.parse(child.service_end_date); - let stop_date = Date.parse(child.service_stop_date); - - if(stop_date < start_date) { - frappe.model.set_value(cdt, cdn, "service_stop_date", ""); - frappe.throw(__("Service Stop Date cannot be before Service Start Date")); - } else if (stop_date > end_date) { - frappe.model.set_value(cdt, cdn, "service_stop_date", ""); - frappe.throw(__("Service Stop Date cannot be after Service End Date")); - } - } - }, - service_start_date: function(frm, cdt, cdn) { - var child = locals[cdt][cdn]; - - if(child.service_start_date) { - frappe.call({ - "method": "erpnext.stock.get_item_details.calculate_service_end_date", - args: {"args": child}, - callback: function(r) { - frappe.model.set_value(cdt, cdn, "service_end_date", r.message.service_end_date); - } - }) - } - } -}) - var calculate_total_billing_amount = function(frm) { var doc = frm.doc; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4708edfbeb..d085d2a85e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -23,6 +23,7 @@ from erpnext.setup.doctype.company.company import update_company_current_month_s from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center from erpnext.accounts.doctype.loyalty_program.loyalty_program import \ get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points +from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.healthcare.utils import manage_invoice_submit_cancel @@ -92,7 +93,7 @@ class SalesInvoice(SellingController): self.validate_delivery_note() # validate service stop date to lie in between start and end date - self.validate_service_stop_date() + validate_service_stop_date(self) if not self.is_opening: self.is_opening = 'No' @@ -558,24 +559,6 @@ class SalesInvoice(SellingController): if frappe.db.get_value("Sales Order Item", item.so_detail, "delivered_by_supplier"): frappe.throw(_("Could not update stock, invoice contains drop shipping item.")) - def validate_service_stop_date(self): - old_doc = frappe.db.get_all("Sales Invoice Item", {"parent": self.name}, ["name", "service_stop_date"]) - old_stop_dates = {} - for d in old_doc: - old_stop_dates[d.name] = d.service_stop_date or "" - - for item in self.items: - if item.enable_deferred_revenue: - if item.service_stop_date: - if date_diff(item.service_stop_date, item.service_start_date) < 0: - frappe.throw(_("Service Stop Date cannot be before Service Start Date")) - - if date_diff(item.service_stop_date, item.service_end_date) > 0: - frappe.throw(_("Service Stop Date cannot be after Service End Date")) - - if old_stop_dates and old_stop_dates[item.name] and item.service_stop_date!=old_stop_dates[item.name]: - frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx))) - def update_current_stock(self): for d in self.get('items'): if d.item_code and d.warehouse: @@ -1059,9 +1042,11 @@ class SalesInvoice(SellingController): # valdite the redemption and then delete the loyalty points earned on cancel of the invoice def delete_loyalty_point_entry(self): lp_entry = frappe.db.sql("select name from `tabLoyalty Point Entry` where sales_invoice=%s", - (self.name), as_dict=1)[0] + (self.name), as_dict=1) + + if not lp_entry: return against_lp_entry = frappe.db.sql('''select name, sales_invoice from `tabLoyalty Point Entry` - where redeem_against=%s''', (lp_entry.name), as_dict=1) + where redeem_against=%s''', (lp_entry[0].name), as_dict=1) if against_lp_entry: invoice_list = ", ".join([d.sales_invoice for d in against_lp_entry]) frappe.throw(_('''Sales Invoice can't be cancelled since the Loyalty Points earned has been redeemed. @@ -1119,92 +1104,6 @@ class SalesInvoice(SellingController): if points_to_redeem < 1: # since points_to_redeem is integer break - def book_income_for_deferred_revenue(self, start_date=None, end_date=None): - # book the income on the last day, but it will be trigger on the 1st of month at 12:00 AM - # start_date: 1st of the last month or the start date - # end_date: end_date or today-1 - - gl_entries = [] - for item in self.get('items'): - last_gl_entry = False - - booking_end_date = getdate(add_days(today(), -1)) if not end_date else end_date - if booking_end_date < item.service_start_date or (item.service_stop_date and booking_end_date.month > item.service_stop_date.month): - continue - elif booking_end_date>=item.service_end_date: - last_gl_entry = True - booking_end_date = item.service_end_date - elif item.service_stop_date and item.service_stop_date<=booking_end_date: - last_gl_entry = True - booking_end_date = item.service_stop_date - - booking_start_date = getdate(add_months(today(), -1)) if not start_date else start_date - booking_start_date = booking_start_date if booking_start_date>item.service_start_date else item.service_start_date - - if item.service_start_date < booking_start_date: - prev_gl_entry = frappe.db.sql(''' - select name, posting_date from `tabGL Entry` where company=%s and account=%s and - voucher_type=%s and voucher_no=%s and voucher_detail_no=%s - order by posting_date desc limit 1 - ''', (self.company, item.deferred_revenue_account, "Sales Invoice", self.name, item.name), as_dict=True) - - if not prev_gl_entry: - booking_start_date = item.service_start_date - else: - booking_start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1)) - - total_days = date_diff(item.service_end_date, item.service_start_date) - total_booking_days = date_diff(booking_end_date, booking_start_date) + 1 - - account_currency = get_account_currency(item.income_account) - if not last_gl_entry: - base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount")) - if account_currency==self.company_currency: - amount = base_amount - else: - amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount")) - else: - gl_entries_details = frappe.db.sql(''' - select sum(debit) as total_debit, sum(debit_in_account_currency) as total_debit_in_account_currency, voucher_detail_no - from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s - group by voucher_detail_no - ''', (self.company, item.deferred_revenue_account, "Sales Invoice", self.name, item.name), as_dict=True) - already_booked_amount = gl_entries_details[0].total_debit if gl_entries_details else 0 - base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount")) - if account_currency==self.company_currency: - amount = base_amount - else: - already_booked_amount_in_account_currency = gl_entries_details[0].total_debit_in_account_currency if gl_entries_details else 0 - amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")) - - # GL Entry for crediting the amount in the income - gl_entries.append( - self.get_gl_dict({ - "account": item.income_account, - "against": self.customer, - "credit": base_amount, - "credit_in_account_currency": amount, - "cost_center": item.cost_center, - 'posting_date': booking_end_date - }, account_currency) - ) - # GL Entry to debit the amount from the deferred account - gl_entries.append( - self.get_gl_dict({ - "account": item.deferred_revenue_account, - "against": self.customer, - "debit": base_amount, - "debit_in_account_currency": amount, - "cost_center": item.cost_center, - "voucher_detail_no": item.name, - 'posting_date': booking_end_date - }, account_currency) - ) - - if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries - make_gl_entries(gl_entries, cancel=(self.docstatus == 2), merge_entries=True) - # Healthcare def set_healthcare_services(self, checked_values): self.set("items", []) @@ -1243,19 +1142,6 @@ class SalesInvoice(SellingController): self.set_missing_values(for_validate = True) -def booked_deferred_revenue(start_date=None, end_date=None): - # check for the sales invoice for which GL entries has to be done - invoices = frappe.db.sql_list(''' - select parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s - and enable_deferred_revenue = 1 and docstatus = 1 - ''', (end_date or today(), start_date or add_months(today(), -1))) - - # ToDo also find the list on the basic of the GL entry, and make another list - for invoice in invoices: - doc = frappe.get_doc("Sales Invoice", invoice) - doc.book_income_for_deferred_revenue(start_date, end_date) - - def validate_inter_company_party(doctype, party, company, inter_company_invoice_reference): if doctype == "Sales Invoice": partytype, ref_partytype, internal = "Customer", "Supplier", "is_internal_customer" diff --git a/erpnext/hooks.py b/erpnext/hooks.py index d944874437..83c45347eb 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -250,7 +250,8 @@ scheduler_events = { "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms" ], "monthly": [ - "erpnext.accounts.doctype.sales_invoice.sales_invoice.booked_deferred_revenue", + "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income", + "erpnext.accounts.deferred_revenue.convert_deferred_expense_to_expense", "erpnext.hr.utils.allocate_earned_leaves" ] } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index cd4a7b26d4..3b6f169d36 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -796,6 +796,38 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.apply_pricing_rule(frappe.get_doc(cdt, cdn), true); }, + service_stop_date: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + + if(child.service_stop_date) { + let start_date = Date.parse(child.service_start_date); + let end_date = Date.parse(child.service_end_date); + let stop_date = Date.parse(child.service_stop_date); + + if(stop_date < start_date) { + frappe.model.set_value(cdt, cdn, "service_stop_date", ""); + frappe.throw(__("Service Stop Date cannot be before Service Start Date")); + } else if (stop_date > end_date) { + frappe.model.set_value(cdt, cdn, "service_stop_date", ""); + frappe.throw(__("Service Stop Date cannot be after Service End Date")); + } + } + }, + + service_start_date: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + + if(child.service_start_date) { + frappe.call({ + "method": "erpnext.stock.get_item_details.calculate_service_end_date", + args: {"args": child}, + callback: function(r) { + frappe.model.set_value(cdt, cdn, "service_end_date", r.message.service_end_date); + } + }) + } + }, + calculate_net_weight: function(){ /* Calculate Total Net Weight then further applied shipping rule to calculate shipping charges.*/ var me = this; diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 9a245e203c..9377cad143 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -837,7 +837,7 @@ "label": "Create Chart Of Accounts Based On", "length": 0, "no_copy": 0, - "options": "\nStandard Template\nExisting Company", + "options": "\nStandard Template\nExisting Company", "permlevel": 0, "precision": "", "print_hide": 0, @@ -871,7 +871,7 @@ "label": "Chart Of Accounts Template", "length": 0, "no_copy": 1, - "options": "", + "options": "", "permlevel": 0, "precision": "", "print_hide": 0, @@ -1158,6 +1158,39 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fieldname": "round_off_cost_center", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Round Off Cost Center", + "length": 0, + "no_copy": 0, + "options": "Cost Center", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "write_off_account", "fieldtype": "Link", "hidden": 0, @@ -1458,7 +1491,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", - "fieldname": "default_payroll_payable_account", + "fieldname": "default_deferred_expense_account", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 1, @@ -1467,7 +1500,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Default Payroll Payable Account", + "label": "Default Deferred Expense Account", "length": 0, "no_copy": 1, "options": "Account", @@ -1492,7 +1525,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", - "fieldname": "default_expense_claim_payable_account", + "fieldname": "default_payroll_payable_account", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 1, @@ -1501,7 +1534,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Default Expense Claim Payable Account", + "label": "Default Payroll Payable Account", "length": 0, "no_copy": 1, "options": "Account", @@ -1525,19 +1558,20 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "round_off_cost_center", + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_expense_claim_payable_account", "fieldtype": "Link", "hidden": 0, - "ignore_user_permissions": 0, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Round Off Cost Center", + "label": "Default Expense Claim Payable Account", "length": 0, - "no_copy": 0, - "options": "Cost Center", + "no_copy": 1, + "options": "Account", "permlevel": 0, "precision": "", "print_hide": 0, @@ -2836,8 +2870,8 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-09-07 16:03:30.716918", - "modified_by": "cave@aperture.com", + "modified": "2018-09-13 10:00:47.915706", + "modified_by": "Administrator", "module": "Setup", "name": "Company", "owner": "Administrator", diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index e78e8d5e09..7d1bb6d27a 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -2690,18 +2690,20 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "enable_deferred_revenue", - "fieldtype": "Check", + "depends_on": "enable_deferred_revenue", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", "hidden": 0, - "ignore_user_permissions": 0, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Enable Deferred Revenue", + "label": "Deferred Revenue Account", "length": 0, "no_copy": 0, + "options": "Account", "permlevel": 0, "precision": "", "print_hide": 0, @@ -2722,20 +2724,18 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_deferred_revenue", - "fieldname": "deferred_revenue_account", - "fieldtype": "Link", + "fieldname": "enable_deferred_revenue", + "fieldtype": "Check", "hidden": 0, - "ignore_user_permissions": 1, + "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Deferred Revenue Account", + "label": "Enable Deferred Revenue", "length": 0, "no_copy": 0, - "options": "Account", "permlevel": 0, "precision": "", "print_hide": 0, @@ -2813,6 +2813,168 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "deferred_expense_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Deferred Expense", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Deferred Expense Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enable_deferred_expense", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Enable Deferred Expense", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_88", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enable_deferred_expense", + "fieldname": "no_of_months_exp", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "No of Months", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -3951,7 +4113,7 @@ "issingle": 0, "istable": 0, "max_attachments": 1, - "modified": "2018-09-19 16:17:41.039657", + "modified": "2018-09-20 11:14:21.031369", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index be138d7359..d90db56e2c 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -310,14 +310,18 @@ def calculate_service_end_date(args, item=None): if not item: item = frappe.get_cached_doc("Item", args.item_code) + enable_deferred = "enable_deferred_revenue" if args.doctype=="Sales Invoice" else "enable_deferred_expense" + no_of_months = "no_of_months" if args.doctype=="Sales Invoice" else "no_of_months_exp" + account = "deferred_revenue_account" if args.doctype=="Sales Invoice" else "deferred_expense_account" + service_start_date = args.service_start_date if args.service_start_date else args.transaction_date - service_end_date = add_months(service_start_date, item.no_of_months) + service_end_date = add_months(service_start_date, item.get(no_of_months)) deferred_detail = { - "enable_deferred_revenue": item.enable_deferred_revenue, - "deferred_revenue_account": get_default_deferred_revenue_account(args, item), "service_start_date": service_start_date, "service_end_date": service_end_date } + deferred_detail[enable_deferred] = item.get(enable_deferred) + deferred_detail[account] = get_default_deferred_account(args, item, fieldname=account) return deferred_detail @@ -331,11 +335,11 @@ def get_default_expense_account(args, item, item_group): or item_group.get("expense_account") or args.expense_account) -def get_default_deferred_revenue_account(args, item): +def get_default_deferred_account(args, item, fieldname=None): if item.enable_deferred_revenue: - return (item.deferred_revenue_account - or args.deferred_revenue_account - or frappe.get_cached_value('Company', args.company, "default_deferred_revenue_account")) + return (item.get(fieldname) + or args.get(fieldname) + or frappe.get_cached_value('Company', args.company, "default_"+fieldname)) else: return None