blob: 3b6a5881ca061ab04e8dee21655e24141719874a [file] [log] [blame]
Zarrare83ff382018-09-21 15:45:40 +05301from __future__ import unicode_literals
2
3import frappe
4from frappe import _
Nabin Hait0a90ce52019-03-28 19:43:02 +05305from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day
Zarrare83ff382018-09-21 15:45:40 +05306from erpnext.accounts.utils import get_account_currency
Nabin Hait0a90ce52019-03-28 19:43:02 +05307from frappe.email import sendmail_to_system_managers
Zarrare83ff382018-09-21 15:45:40 +05308
9def validate_service_stop_date(doc):
10 ''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
11
12 enable_check = "enable_deferred_revenue" \
13 if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
14
15 old_stop_dates = {}
16 old_doc = frappe.db.get_all("{0} Item".format(doc.doctype),
17 {"parent": doc.name}, ["name", "service_stop_date"])
18
19 for d in old_doc:
20 old_stop_dates[d.name] = d.service_stop_date or ""
21
22 for item in doc.items:
23 if not item.get(enable_check): continue
24
25 if item.service_stop_date:
26 if date_diff(item.service_stop_date, item.service_start_date) < 0:
27 frappe.throw(_("Service Stop Date cannot be before Service Start Date"))
28
29 if date_diff(item.service_stop_date, item.service_end_date) > 0:
30 frappe.throw(_("Service Stop Date cannot be after Service End Date"))
31
Rohit Waghchaurec699b2a2018-09-24 11:57:48 +053032 if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
Suraj Shetty48e9bc32020-01-29 15:06:18 +053033 frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
Zarrare83ff382018-09-21 15:45:40 +053034
35def convert_deferred_expense_to_expense(start_date=None, end_date=None):
Nabin Hait0a90ce52019-03-28 19:43:02 +053036 # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
37 if not start_date:
38 start_date = add_months(today(), -1)
39 if not end_date:
40 end_date = add_days(today(), -1)
41
Zarrare83ff382018-09-21 15:45:40 +053042 # check for the purchase invoice for which GL entries has to be done
43 invoices = frappe.db.sql_list('''
Nabin Hait0a90ce52019-03-28 19:43:02 +053044 select distinct parent from `tabPurchase Invoice Item`
45 where service_start_date<=%s and service_end_date>=%s
rohitwaghchaure90f9f9d2018-09-30 21:12:50 +053046 and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0
Nabin Hait0a90ce52019-03-28 19:43:02 +053047 ''', (end_date, start_date))
Zarrare83ff382018-09-21 15:45:40 +053048
49 # For each invoice, book deferred expense
50 for invoice in invoices:
51 doc = frappe.get_doc("Purchase Invoice", invoice)
Nabin Hait0a90ce52019-03-28 19:43:02 +053052 book_deferred_income_or_expense(doc, end_date)
Zarrare83ff382018-09-21 15:45:40 +053053
54def convert_deferred_revenue_to_income(start_date=None, end_date=None):
Nabin Hait0a90ce52019-03-28 19:43:02 +053055 # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
56 if not start_date:
57 start_date = add_months(today(), -1)
58 if not end_date:
59 end_date = add_days(today(), -1)
60
Zarrare83ff382018-09-21 15:45:40 +053061 # check for the sales invoice for which GL entries has to be done
62 invoices = frappe.db.sql_list('''
Nabin Hait0a90ce52019-03-28 19:43:02 +053063 select distinct parent from `tabSales Invoice Item`
64 where service_start_date<=%s and service_end_date>=%s
rohitwaghchaure90f9f9d2018-09-30 21:12:50 +053065 and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0
Nabin Hait0a90ce52019-03-28 19:43:02 +053066 ''', (end_date, start_date))
Zarrare83ff382018-09-21 15:45:40 +053067
Zarrare83ff382018-09-21 15:45:40 +053068 for invoice in invoices:
69 doc = frappe.get_doc("Sales Invoice", invoice)
Nabin Hait0a90ce52019-03-28 19:43:02 +053070 book_deferred_income_or_expense(doc, end_date)
Zarrare83ff382018-09-21 15:45:40 +053071
Nabin Hait0a90ce52019-03-28 19:43:02 +053072def get_booking_dates(doc, item, posting_date=None):
73 if not posting_date:
74 posting_date = add_days(today(), -1)
75
76 last_gl_entry = False
77
Zarrare83ff382018-09-21 15:45:40 +053078 deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
Zarrare83ff382018-09-21 15:45:40 +053079
80 prev_gl_entry = frappe.db.sql('''
81 select name, posting_date from `tabGL Entry` where company=%s and account=%s and
82 voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
83 order by posting_date desc limit 1
84 ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
85
Nabin Hait0a90ce52019-03-28 19:43:02 +053086 if prev_gl_entry:
87 start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
88 else:
89 start_date = item.service_start_date
Zarrare83ff382018-09-21 15:45:40 +053090
Nabin Hait0a90ce52019-03-28 19:43:02 +053091 end_date = get_last_day(start_date)
92 if end_date >= item.service_end_date:
93 end_date = item.service_end_date
94 last_gl_entry = True
Nabin Hait66d07c22019-03-29 13:25:11 +053095 elif item.service_stop_date and end_date >= item.service_stop_date:
96 end_date = item.service_stop_date
97 last_gl_entry = True
Zarrare83ff382018-09-21 15:45:40 +053098
Nabin Hait0a90ce52019-03-28 19:43:02 +053099 if end_date > getdate(posting_date):
100 end_date = posting_date
Zarrare83ff382018-09-21 15:45:40 +0530101
Nabin Hait0a90ce52019-03-28 19:43:02 +0530102 if getdate(start_date) <= getdate(end_date):
103 return start_date, end_date, last_gl_entry
104 else:
105 return None, None, None
106
107def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
Zarrare83ff382018-09-21 15:45:40 +0530108 if doc.doctype == "Sales Invoice":
109 total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
110 deferred_account = "deferred_revenue_account"
111 else:
112 total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
113 deferred_account = "deferred_expense_account"
114
115 amount, base_amount = 0, 0
116 if not last_gl_entry:
117 base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
118 if account_currency==doc.company_currency:
119 amount = base_amount
120 else:
121 amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
122 else:
123 gl_entries_details = frappe.db.sql('''
124 select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
125 from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
126 group by voucher_detail_no
127 '''.format(total_credit_debit, total_credit_debit_currency),
128 (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
Zarrare83ff382018-09-21 15:45:40 +0530129 already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
130 base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
131 if account_currency==doc.company_currency:
132 amount = base_amount
133 else:
134 already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
135 amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
136
137 return amount, base_amount
138
Nabin Hait0a90ce52019-03-28 19:43:02 +0530139def book_deferred_income_or_expense(doc, posting_date=None):
Nabin Hait27af6b32019-02-08 16:52:13 +0530140 enable_check = "enable_deferred_revenue" \
141 if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
Zarrare83ff382018-09-21 15:45:40 +0530142
Nabin Hait0a90ce52019-03-28 19:43:02 +0530143 def _book_deferred_revenue_or_expense(item):
144 start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
145 if not (start_date and end_date): return
Zarrare83ff382018-09-21 15:45:40 +0530146
147 account_currency = get_account_currency(item.expense_account)
Zarrare83ff382018-09-21 15:45:40 +0530148 if doc.doctype == "Sales Invoice":
149 against, project = doc.customer, doc.project
150 credit_account, debit_account = item.income_account, item.deferred_revenue_account
151 else:
152 against, project = doc.supplier, item.project
153 credit_account, debit_account = item.deferred_expense_account, item.expense_account
154
Nabin Hait0a90ce52019-03-28 19:43:02 +0530155 total_days = date_diff(item.service_end_date, item.service_start_date) + 1
156 total_booking_days = date_diff(end_date, start_date) + 1
157
158 amount, base_amount = calculate_amount(doc, item, last_gl_entry,
159 total_days, total_booking_days, account_currency)
160
161 make_gl_entries(doc, credit_account, debit_account, against,
162 amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
163
164 if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
165 _book_deferred_revenue_or_expense(item)
166
167
168 for item in doc.get('items'):
169 if item.get(enable_check):
170 _book_deferred_revenue_or_expense(item)
171
172def make_gl_entries(doc, credit_account, debit_account, against,
173 amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
174 # GL Entry for crediting the amount in the deferred expense
175 from erpnext.accounts.general_ledger import make_gl_entries
176
rohitwaghchauree123ec62019-11-06 15:25:00 +0530177 if amount == 0: return
178
Nabin Hait0a90ce52019-03-28 19:43:02 +0530179 gl_entries = []
180 gl_entries.append(
181 doc.get_gl_dict({
182 "account": credit_account,
183 "against": against,
184 "credit": base_amount,
185 "credit_in_account_currency": amount,
186 "cost_center": cost_center,
187 "voucher_detail_no": voucher_detail_no,
188 'posting_date': posting_date,
189 'project': project
190 }, account_currency)
191 )
192 # GL Entry to debit the amount from the expense
193 gl_entries.append(
194 doc.get_gl_dict({
195 "account": debit_account,
196 "against": against,
197 "debit": base_amount,
198 "debit_in_account_currency": amount,
199 "cost_center": cost_center,
200 "voucher_detail_no": voucher_detail_no,
201 'posting_date': posting_date,
202 'project': project
203 }, account_currency)
204 )
205
Zarrare83ff382018-09-21 15:45:40 +0530206 if gl_entries:
Nabin Hait29fcb142019-02-13 17:18:12 +0530207 try:
208 make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
209 frappe.db.commit()
210 except:
211 frappe.db.rollback()
Nabin Hait0a90ce52019-03-28 19:43:02 +0530212 title = _("Error while processing deferred accounting for {0}").format(doc.name)
213 traceback = frappe.get_traceback()
214 frappe.log_error(message=traceback , title=title)
215 sendmail_to_system_managers(title, traceback)