blob: 3dc3e7ae19955c3ef9b429620beea1ad34bb1397 [file] [log] [blame]
Zarrare83ff382018-09-21 15:45:40 +05301import frappe
2from frappe import _
Nabin Hait0a90ce52019-03-28 19:43:02 +05303from frappe.email import sendmail_to_system_managers
Chillar Anand915b3432021-09-02 16:44:59 +05304from frappe.utils import (
5 add_days,
6 add_months,
7 cint,
8 date_diff,
9 flt,
10 get_first_day,
11 get_last_day,
12 get_link_to_form,
13 getdate,
14 rounded,
15 today,
16)
17
18from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
19 get_accounting_dimensions,
20)
21from erpnext.accounts.utils import get_account_currency
22
Zarrare83ff382018-09-21 15:45:40 +053023
24def validate_service_stop_date(doc):
Ankush Menat494bd9e2022-03-28 18:52:46 +053025 """Validates service_stop_date for Purchase Invoice and Sales Invoice"""
Zarrare83ff382018-09-21 15:45:40 +053026
Akhil Narang3effaf22024-03-27 11:37:26 +053027 enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
Zarrare83ff382018-09-21 15:45:40 +053028
29 old_stop_dates = {}
Akhil Narang3effaf22024-03-27 11:37:26 +053030 old_doc = frappe.db.get_all(f"{doc.doctype} Item", {"parent": doc.name}, ["name", "service_stop_date"])
Zarrare83ff382018-09-21 15:45:40 +053031
32 for d in old_doc:
33 old_stop_dates[d.name] = d.service_stop_date or ""
34
35 for item in doc.items:
Ankush Menat494bd9e2022-03-28 18:52:46 +053036 if not item.get(enable_check):
37 continue
Zarrare83ff382018-09-21 15:45:40 +053038
39 if item.service_stop_date:
40 if date_diff(item.service_stop_date, item.service_start_date) < 0:
41 frappe.throw(_("Service Stop Date cannot be before Service Start Date"))
42
43 if date_diff(item.service_stop_date, item.service_end_date) > 0:
44 frappe.throw(_("Service Stop Date cannot be after Service End Date"))
45
Ankush Menat494bd9e2022-03-28 18:52:46 +053046 if (
47 old_stop_dates
48 and old_stop_dates.get(item.name)
49 and item.service_stop_date != old_stop_dates.get(item.name)
50 ):
Suraj Shetty48e9bc32020-01-29 15:06:18 +053051 frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
Zarrare83ff382018-09-21 15:45:40 +053052
Ankush Menat494bd9e2022-03-28 18:52:46 +053053
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053054def build_conditions(process_type, account, company):
Ankush Menat494bd9e2022-03-28 18:52:46 +053055 conditions = ""
56 deferred_account = (
57 "item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
58 )
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053059
60 if account:
Akhil Narang3effaf22024-03-27 11:37:26 +053061 conditions += f"AND {deferred_account}='{account}'"
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053062 elif company:
Ankush Menatf3b3d812021-05-17 16:43:38 +053063 conditions += f"AND p.company = {frappe.db.escape(company)}"
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053064
65 return conditions
66
Ankush Menat494bd9e2022-03-28 18:52:46 +053067
Akhil Narang3effaf22024-03-27 11:37:26 +053068def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=""):
Nabin Hait0a90ce52019-03-28 19:43:02 +053069 # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053070
Nabin Hait0a90ce52019-03-28 19:43:02 +053071 if not start_date:
72 start_date = add_months(today(), -1)
73 if not end_date:
74 end_date = add_days(today(), -1)
75
Zarrare83ff382018-09-21 15:45:40 +053076 # check for the purchase invoice for which GL entries has to be done
Ankush Menat494bd9e2022-03-28 18:52:46 +053077 invoices = frappe.db.sql_list(
Akhil Narang3effaf22024-03-27 11:37:26 +053078 f"""
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053079 select distinct item.parent
80 from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
81 where item.service_start_date<=%s and item.service_end_date>=%s
82 and item.enable_deferred_expense = 1 and item.parent=p.name
83 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
Akhil Narang3effaf22024-03-27 11:37:26 +053084 {conditions}
85 """,
Ankush Menat494bd9e2022-03-28 18:52:46 +053086 (end_date, start_date),
87 ) # nosec
Zarrare83ff382018-09-21 15:45:40 +053088
89 # For each invoice, book deferred expense
90 for invoice in invoices:
91 doc = frappe.get_doc("Purchase Invoice", invoice)
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053092 book_deferred_income_or_expense(doc, deferred_process, end_date)
Zarrare83ff382018-09-21 15:45:40 +053093
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +053094 if frappe.flags.deferred_accounting_error:
95 send_mail(deferred_process)
96
Ankush Menat494bd9e2022-03-28 18:52:46 +053097
Akhil Narang3effaf22024-03-27 11:37:26 +053098def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""):
Nabin Hait0a90ce52019-03-28 19:43:02 +053099 # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530100
Nabin Hait0a90ce52019-03-28 19:43:02 +0530101 if not start_date:
102 start_date = add_months(today(), -1)
103 if not end_date:
104 end_date = add_days(today(), -1)
105
Zarrare83ff382018-09-21 15:45:40 +0530106 # check for the sales invoice for which GL entries has to be done
Ankush Menat494bd9e2022-03-28 18:52:46 +0530107 invoices = frappe.db.sql_list(
Akhil Narang3effaf22024-03-27 11:37:26 +0530108 f"""
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530109 select distinct item.parent
110 from `tabSales Invoice Item` item, `tabSales Invoice` p
111 where item.service_start_date<=%s and item.service_end_date>=%s
112 and item.enable_deferred_revenue = 1 and item.parent=p.name
113 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
Akhil Narang3effaf22024-03-27 11:37:26 +0530114 {conditions}
115 """,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530116 (end_date, start_date),
117 ) # nosec
Zarrare83ff382018-09-21 15:45:40 +0530118
Zarrare83ff382018-09-21 15:45:40 +0530119 for invoice in invoices:
120 doc = frappe.get_doc("Sales Invoice", invoice)
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530121 book_deferred_income_or_expense(doc, deferred_process, end_date)
122
123 if frappe.flags.deferred_accounting_error:
124 send_mail(deferred_process)
Zarrare83ff382018-09-21 15:45:40 +0530125
Ankush Menat494bd9e2022-03-28 18:52:46 +0530126
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530127def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
Nabin Hait0a90ce52019-03-28 19:43:02 +0530128 if not posting_date:
129 posting_date = add_days(today(), -1)
130
131 last_gl_entry = False
132
Ankush Menat494bd9e2022-03-28 18:52:46 +0530133 deferred_account = (
134 "deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
135 )
Zarrare83ff382018-09-21 15:45:40 +0530136
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530137 if not prev_posting_date:
138 prev_gl_entry = frappe.db.sql(
139 """
140 select name, posting_date from `tabGL Entry` where company=%s and account=%s and
141 voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
142 and is_cancelled = 0
143 order by posting_date desc limit 1
144 """,
145 (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
146 as_dict=True,
147 )
Zarrare83ff382018-09-21 15:45:40 +0530148
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530149 prev_gl_via_je = frappe.db.sql(
150 """
151 SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
152 WHERE p.name = c.parent and p.company=%s and c.account=%s
153 and c.reference_type=%s and c.reference_name=%s
154 and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
155 """,
156 (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
157 as_dict=True,
158 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530159
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530160 if prev_gl_via_je:
161 if (not prev_gl_entry) or (
162 prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
163 ):
164 prev_gl_entry = prev_gl_via_je
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530165
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530166 if prev_gl_entry:
167 start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
168 else:
169 start_date = item.service_start_date
170
Nabin Hait0a90ce52019-03-28 19:43:02 +0530171 else:
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530172 start_date = getdate(add_days(prev_posting_date, 1))
Nabin Hait0a90ce52019-03-28 19:43:02 +0530173 end_date = get_last_day(start_date)
174 if end_date >= item.service_end_date:
175 end_date = item.service_end_date
176 last_gl_entry = True
Nabin Hait66d07c22019-03-29 13:25:11 +0530177 elif item.service_stop_date and end_date >= item.service_stop_date:
178 end_date = item.service_stop_date
179 last_gl_entry = True
Zarrare83ff382018-09-21 15:45:40 +0530180
Nabin Hait0a90ce52019-03-28 19:43:02 +0530181 if end_date > getdate(posting_date):
182 end_date = posting_date
Zarrare83ff382018-09-21 15:45:40 +0530183
Nabin Hait0a90ce52019-03-28 19:43:02 +0530184 if getdate(start_date) <= getdate(end_date):
185 return start_date, end_date, last_gl_entry
186 else:
187 return None, None, None
188
Ankush Menat494bd9e2022-03-28 18:52:46 +0530189
190def calculate_monthly_amount(
191 doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency
192):
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530193 amount, base_amount = 0, 0
Zarrare83ff382018-09-21 15:45:40 +0530194
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530195 if not last_gl_entry:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530196 total_months = (
197 (item.service_end_date.year - item.service_start_date.year) * 12
198 + (item.service_end_date.month - item.service_start_date.month)
199 + 1
200 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530201
Ankush Menat494bd9e2022-03-28 18:52:46 +0530202 prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) / flt(
203 date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date))
204 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530205
206 actual_months = rounded(total_months * prorate_factor, 1)
207
Ankush Menat494bd9e2022-03-28 18:52:46 +0530208 already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
209 doc, item
210 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530211 base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
212
213 if base_amount + already_booked_amount > item.base_net_amount:
214 base_amount = item.base_net_amount - already_booked_amount
215
Ankush Menat494bd9e2022-03-28 18:52:46 +0530216 if account_currency == doc.company_currency:
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530217 amount = base_amount
218 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530219 amount = flt(item.net_amount / actual_months, item.precision("net_amount"))
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530220 if amount + already_booked_amount_in_account_currency > item.net_amount:
221 amount = item.net_amount - already_booked_amount_in_account_currency
222
barredterraeb9ee3f2023-12-05 11:22:55 +0100223 if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530224 partial_month = flt(date_diff(end_date, start_date)) / flt(
225 date_diff(get_last_day(end_date), get_first_day(start_date))
226 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530227
228 base_amount = rounded(partial_month, 1) * base_amount
229 amount = rounded(partial_month, 1) * amount
230 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530231 already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
232 doc, item
233 )
Akhil Narang3effaf22024-03-27 11:37:26 +0530234 base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530235 if account_currency == doc.company_currency:
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530236 amount = base_amount
237 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530238 amount = flt(
239 item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
240 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530241
242 return amount, base_amount
243
Ankush Menat494bd9e2022-03-28 18:52:46 +0530244
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530245def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
Zarrare83ff382018-09-21 15:45:40 +0530246 amount, base_amount = 0, 0
247 if not last_gl_entry:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530248 base_amount = flt(
249 item.base_net_amount * total_booking_days / flt(total_days), item.precision("base_net_amount")
250 )
251 if account_currency == doc.company_currency:
Zarrare83ff382018-09-21 15:45:40 +0530252 amount = base_amount
253 else:
Akhil Narang3effaf22024-03-27 11:37:26 +0530254 amount = flt(item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount"))
Zarrare83ff382018-09-21 15:45:40 +0530255 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530256 already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
257 doc, item
258 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530259
Akhil Narang3effaf22024-03-27 11:37:26 +0530260 base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530261 if account_currency == doc.company_currency:
Zarrare83ff382018-09-21 15:45:40 +0530262 amount = base_amount
263 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530264 amount = flt(
265 item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
266 )
Zarrare83ff382018-09-21 15:45:40 +0530267
268 return amount, base_amount
269
Ankush Menat494bd9e2022-03-28 18:52:46 +0530270
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530271def get_already_booked_amount(doc, item):
272 if doc.doctype == "Sales Invoice":
273 total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
274 deferred_account = "deferred_revenue_account"
275 else:
276 total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
277 deferred_account = "deferred_expense_account"
278
Ankush Menat494bd9e2022-03-28 18:52:46 +0530279 gl_entries_details = frappe.db.sql(
280 """
Akhil Narang3effaf22024-03-27 11:37:26 +0530281 select sum({}) as total_credit, sum({}) as total_credit_in_account_currency, voucher_detail_no
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530282 from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
Deepesh Garg366120f2022-03-01 11:56:20 +0530283 and is_cancelled = 0
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530284 group by voucher_detail_no
Akhil Narang3effaf22024-03-27 11:37:26 +0530285 """.format(total_credit_debit, total_credit_debit_currency),
Ankush Menat494bd9e2022-03-28 18:52:46 +0530286 (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
287 as_dict=True,
288 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530289
Ankush Menat494bd9e2022-03-28 18:52:46 +0530290 journal_entry_details = frappe.db.sql(
291 """
Akhil Narang3effaf22024-03-27 11:37:26 +0530292 SELECT sum(c.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530293 FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
294 p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
295 and p.docstatus < 2 group by reference_detail_no
Akhil Narang3effaf22024-03-27 11:37:26 +0530296 """.format(total_credit_debit, total_credit_debit_currency),
Ankush Menat494bd9e2022-03-28 18:52:46 +0530297 (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
298 as_dict=True,
299 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530300
301 already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
302 already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
303
304 if doc.currency == doc.company_currency:
305 already_booked_amount_in_account_currency = already_booked_amount
306 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530307 already_booked_amount_in_account_currency = (
308 gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
309 )
310 already_booked_amount_in_account_currency += (
311 journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
312 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530313
314 return already_booked_amount, already_booked_amount_in_account_currency
315
Ankush Menat494bd9e2022-03-28 18:52:46 +0530316
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530317def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
Akhil Narang3effaf22024-03-27 11:37:26 +0530318 enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
Zarrare83ff382018-09-21 15:45:40 +0530319
ruthra kumarba158102023-08-01 07:58:09 +0530320 accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
Deepesh Garg30a647f2022-01-07 19:52:38 +0530321
Ankush Menat494bd9e2022-03-28 18:52:46 +0530322 def _book_deferred_revenue_or_expense(
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530323 item,
324 via_journal_entry,
325 submit_journal_entry,
326 book_deferred_entries_based_on,
327 prev_posting_date=None,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530328 ):
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530329 start_date, end_date, last_gl_entry = get_booking_dates(
330 doc, item, posting_date=posting_date, prev_posting_date=prev_posting_date
331 )
Ankush Menat494bd9e2022-03-28 18:52:46 +0530332 if not (start_date and end_date):
333 return
Zarrare83ff382018-09-21 15:45:40 +0530334
Deepesh Garg98f294a2022-01-03 19:40:47 +0530335 account_currency = get_account_currency(item.expense_account or item.income_account)
Zarrare83ff382018-09-21 15:45:40 +0530336 if doc.doctype == "Sales Invoice":
337 against, project = doc.customer, doc.project
338 credit_account, debit_account = item.income_account, item.deferred_revenue_account
339 else:
340 against, project = doc.supplier, item.project
341 credit_account, debit_account = item.deferred_expense_account, item.expense_account
342
Nabin Hait0a90ce52019-03-28 19:43:02 +0530343 total_days = date_diff(item.service_end_date, item.service_start_date) + 1
344 total_booking_days = date_diff(end_date, start_date) + 1
345
Ankush Menat494bd9e2022-03-28 18:52:46 +0530346 if book_deferred_entries_based_on == "Months":
347 amount, base_amount = calculate_monthly_amount(
348 doc,
349 item,
350 last_gl_entry,
351 start_date,
352 end_date,
353 total_days,
354 total_booking_days,
355 account_currency,
356 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530357 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530358 amount, base_amount = calculate_amount(
359 doc, item, last_gl_entry, total_days, total_booking_days, account_currency
360 )
Nabin Hait0a90ce52019-03-28 19:43:02 +0530361
Deepesh Gargf79a72d2021-06-24 14:14:46 +0530362 if not amount:
363 return
364
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530365 gl_posting_date = end_date
366 prev_posting_date = None
Deepesh Garg30a647f2022-01-07 19:52:38 +0530367 # check if books nor frozen till endate:
Deepesh Garga3ab8f92023-01-03 17:51:41 +0530368 if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530369 gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
370 prev_posting_date = end_date
Deepesh Garg30a647f2022-01-07 19:52:38 +0530371
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530372 if via_journal_entry:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530373 book_revenue_via_journal_entry(
374 doc,
375 credit_account,
376 debit_account,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530377 amount,
378 base_amount,
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530379 gl_posting_date,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530380 project,
381 account_currency,
382 item.cost_center,
383 item,
384 deferred_process,
385 submit_journal_entry,
386 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530387 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530388 make_gl_entries(
389 doc,
390 credit_account,
391 debit_account,
392 against,
393 amount,
394 base_amount,
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530395 gl_posting_date,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530396 project,
397 account_currency,
398 item.cost_center,
399 item,
400 deferred_process,
401 )
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530402
403 # Returned in case of any errors because it tries to submit the same record again and again in case of errors
404 if frappe.flags.deferred_accounting_error:
405 return
Nabin Hait0a90ce52019-03-28 19:43:02 +0530406
407 if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530408 _book_deferred_revenue_or_expense(
Gursheen Kaur Anand674af152023-07-09 20:41:12 +0530409 item,
410 via_journal_entry,
411 submit_journal_entry,
412 book_deferred_entries_based_on,
413 prev_posting_date,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530414 )
Nabin Hait0a90ce52019-03-28 19:43:02 +0530415
Ankush Menat494bd9e2022-03-28 18:52:46 +0530416 via_journal_entry = cint(
417 frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
418 )
Akhil Narang3effaf22024-03-27 11:37:26 +0530419 submit_journal_entry = cint(frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries"))
Ankush Menat494bd9e2022-03-28 18:52:46 +0530420 book_deferred_entries_based_on = frappe.db.get_singles_value(
421 "Accounts Settings", "book_deferred_entries_based_on"
422 )
Nabin Hait0a90ce52019-03-28 19:43:02 +0530423
Ankush Menat494bd9e2022-03-28 18:52:46 +0530424 for item in doc.get("items"):
Nabin Hait0a90ce52019-03-28 19:43:02 +0530425 if item.get(enable_check):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530426 _book_deferred_revenue_or_expense(
427 item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
428 )
429
Nabin Hait0a90ce52019-03-28 19:43:02 +0530430
Chinmay D. Pai0d147b02020-05-24 00:54:04 +0530431def process_deferred_accounting(posting_date=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530432 """Converts deferred income/expense into income/expense
433 Executed via background jobs on every month end"""
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530434
Chinmay D. Pai0d147b02020-05-24 00:54:04 +0530435 if not posting_date:
436 posting_date = today()
437
Ankush Menat494bd9e2022-03-28 18:52:46 +0530438 if not cint(
Akhil Narang3effaf22024-03-27 11:37:26 +0530439 frappe.db.get_singles_value("Accounts Settings", "automatically_process_deferred_accounting_entry")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530440 ):
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530441 return
442
443 start_date = add_months(today(), -1)
444 end_date = add_days(today(), -1)
445
Ankush Menat494bd9e2022-03-28 18:52:46 +0530446 companies = frappe.get_all("Company")
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530447
Deepesh Garg50690952021-07-01 09:31:31 +0530448 for company in companies:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530449 for record_type in ("Income", "Expense"):
450 doc = frappe.get_doc(
451 dict(
452 doctype="Process Deferred Accounting",
453 company=company.name,
454 posting_date=posting_date,
455 start_date=start_date,
456 end_date=end_date,
457 type=record_type,
458 )
459 )
Deepesh Garg50690952021-07-01 09:31:31 +0530460
461 doc.insert()
462 doc.submit()
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530463
Ankush Menat494bd9e2022-03-28 18:52:46 +0530464
465def make_gl_entries(
466 doc,
467 credit_account,
468 debit_account,
469 against,
470 amount,
471 base_amount,
472 posting_date,
473 project,
474 account_currency,
475 cost_center,
476 item,
477 deferred_process=None,
478):
Nabin Hait0a90ce52019-03-28 19:43:02 +0530479 # GL Entry for crediting the amount in the deferred expense
480 from erpnext.accounts.general_ledger import make_gl_entries
481
Ankush Menat494bd9e2022-03-28 18:52:46 +0530482 if amount == 0:
483 return
rohitwaghchauree123ec62019-11-06 15:25:00 +0530484
Nabin Hait0a90ce52019-03-28 19:43:02 +0530485 gl_entries = []
486 gl_entries.append(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530487 doc.get_gl_dict(
488 {
489 "account": credit_account,
490 "against": against,
491 "credit": base_amount,
492 "credit_in_account_currency": amount,
493 "cost_center": cost_center,
494 "voucher_detail_no": item.name,
495 "posting_date": posting_date,
496 "project": project,
497 "against_voucher_type": "Process Deferred Accounting",
498 "against_voucher": deferred_process,
499 },
500 account_currency,
501 item=item,
502 )
Nabin Hait0a90ce52019-03-28 19:43:02 +0530503 )
504 # GL Entry to debit the amount from the expense
505 gl_entries.append(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530506 doc.get_gl_dict(
507 {
508 "account": debit_account,
509 "against": against,
510 "debit": base_amount,
511 "debit_in_account_currency": amount,
512 "cost_center": cost_center,
513 "voucher_detail_no": item.name,
514 "posting_date": posting_date,
515 "project": project,
516 "against_voucher_type": "Process Deferred Accounting",
517 "against_voucher": deferred_process,
518 },
519 account_currency,
520 item=item,
521 )
Nabin Hait0a90ce52019-03-28 19:43:02 +0530522 )
523
Zarrare83ff382018-09-21 15:45:40 +0530524 if gl_entries:
Nabin Hait29fcb142019-02-13 17:18:12 +0530525 try:
526 make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
527 frappe.db.commit()
Deepesh Gargf1a669c2021-09-29 22:26:33 +0530528 except Exception as e:
529 if frappe.flags.in_test:
Rushabh Mehta548afba2022-05-02 15:04:26 +0530530 doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
Deepesh Garg3c64e202021-12-04 20:05:37 +0530531 raise e
Deepesh Gargf1a669c2021-09-29 22:26:33 +0530532 else:
533 frappe.db.rollback()
Rushabh Mehta548afba2022-05-02 15:04:26 +0530534 doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
Deepesh Gargf1a669c2021-09-29 22:26:33 +0530535 frappe.flags.deferred_accounting_error = True
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530536
Ankush Menat494bd9e2022-03-28 18:52:46 +0530537
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530538def send_mail(deferred_process):
Ankush Menatb6783b12021-05-17 17:06:12 +0530539 title = _("Error while processing deferred accounting for {0}").format(deferred_process)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530540 link = get_link_to_form("Process Deferred Accounting", deferred_process)
Ankush Menatb6783b12021-05-17 17:06:12 +0530541 content = _("Deferred accounting failed for some invoices:") + "\n"
Ankush Menat494bd9e2022-03-28 18:52:46 +0530542 content += _(
543 "Please check Process Deferred Accounting {0} and submit manually after resolving errors."
544 ).format(link)
Mangesh-Khairnar2f7861a2020-05-02 20:09:33 +0530545 sendmail_to_system_managers(title, content)
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530546
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530547
Ankush Menat494bd9e2022-03-28 18:52:46 +0530548def book_revenue_via_journal_entry(
549 doc,
550 credit_account,
551 debit_account,
Ankush Menat494bd9e2022-03-28 18:52:46 +0530552 amount,
553 base_amount,
554 posting_date,
555 project,
556 account_currency,
557 cost_center,
558 item,
559 deferred_process=None,
560 submit="No",
561):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530562 if amount == 0:
563 return
564
565 journal_entry = frappe.new_doc("Journal Entry")
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530566 journal_entry.posting_date = posting_date
567 journal_entry.company = doc.company
Akhil Narang3effaf22024-03-27 11:37:26 +0530568 journal_entry.voucher_type = "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
Deepesh Garg9bf5f762022-04-06 17:33:46 +0530569 journal_entry.process_deferred_accounting = deferred_process
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530570
571 debit_entry = {
Ankush Menat494bd9e2022-03-28 18:52:46 +0530572 "account": credit_account,
573 "credit": base_amount,
574 "credit_in_account_currency": amount,
575 "account_currency": account_currency,
576 "reference_name": doc.name,
577 "reference_type": doc.doctype,
578 "reference_detail_no": item.name,
579 "cost_center": cost_center,
580 "project": project,
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530581 }
582
583 credit_entry = {
Ankush Menat494bd9e2022-03-28 18:52:46 +0530584 "account": debit_account,
585 "debit": base_amount,
586 "debit_in_account_currency": amount,
587 "account_currency": account_currency,
588 "reference_name": doc.name,
589 "reference_type": doc.doctype,
590 "reference_detail_no": item.name,
591 "cost_center": cost_center,
592 "project": project,
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530593 }
594
595 for dimension in get_accounting_dimensions():
Ankush Menat494bd9e2022-03-28 18:52:46 +0530596 debit_entry.update({dimension: item.get(dimension)})
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530597
Ankush Menat494bd9e2022-03-28 18:52:46 +0530598 credit_entry.update({dimension: item.get(dimension)})
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530599
Ankush Menat494bd9e2022-03-28 18:52:46 +0530600 journal_entry.append("accounts", debit_entry)
601 journal_entry.append("accounts", credit_entry)
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530602
603 try:
604 journal_entry.save()
605
606 if submit:
607 journal_entry.submit()
Deepesh Garg0ba4fce2021-12-04 19:25:44 +0530608
609 frappe.db.commit()
Ankush Menat694ae812021-09-01 14:40:56 +0530610 except Exception:
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530611 frappe.db.rollback()
Rushabh Mehta548afba2022-05-02 15:04:26 +0530612 doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530613 frappe.flags.deferred_accounting_error = True
614
Ankush Menat494bd9e2022-03-28 18:52:46 +0530615
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530616def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530617 if doctype == "Sales Invoice":
618 credit_account, debit_account = frappe.db.get_value(
619 "Sales Invoice Item",
620 {"name": voucher_detail_no},
621 ["income_account", "deferred_revenue_account"],
622 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530623 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530624 credit_account, debit_account = frappe.db.get_value(
625 "Purchase Invoice Item",
626 {"name": voucher_detail_no},
627 ["deferred_expense_account", "expense_account"],
628 )
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530629
Ankush Menat494bd9e2022-03-28 18:52:46 +0530630 if dr_or_cr == "Debit":
Deepesh Garg7cc1cf32020-06-23 09:57:56 +0530631 return debit_account
632 else:
633 return credit_account