blob: 41e0a81859b5a03f627b7c96937441dd1ab0e508 [file] [log] [blame]
Rushabh Mehtae67d1fb2013-08-05 14:59:54 +05301# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
2# License: GNU General Public License v3. See license.txt
Nabin Hait23941aa2013-01-29 11:32:38 +05303
4from __future__ import unicode_literals
5
6import webnotes
Nabin Haitcac622e2013-08-02 11:44:29 +05307from webnotes.utils import nowdate, nowtime, cstr, flt, now
Nabin Haitfb3fd6e2013-01-30 19:16:13 +05308from webnotes.model.doc import addchild
9from webnotes import msgprint, _
Rushabh Mehta2e8d0782013-02-05 11:31:27 +053010from webnotes.utils import formatdate
Nabin Haita76a0682013-02-08 14:04:13 +053011from utilities import build_filter_conditions
12
Nabin Hait23941aa2013-01-29 11:32:38 +053013
14class FiscalYearError(webnotes.ValidationError): pass
15
Nabin Haitcfecd2b2013-07-11 17:49:18 +053016def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1):
17 return get_fiscal_years(date, fiscal_year, label, verbose=1)[0]
Rushabh Mehtac2563ef2013-02-05 23:25:37 +053018
Nabin Haitcfecd2b2013-07-11 17:49:18 +053019def get_fiscal_years(date=None, fiscal_year=None, label="Date", verbose=1):
Nabin Hait23941aa2013-01-29 11:32:38 +053020 # if year start date is 2012-04-01, year end date should be 2013-03-31 (hence subdate)
Nabin Hait0930b942013-06-20 16:35:09 +053021 cond = ""
22 if fiscal_year:
23 cond = "name = '%s'" % fiscal_year
24 else:
25 cond = "'%s' >= year_start_date and '%s' < adddate(year_start_date, interval 1 year)" % \
26 (date, date)
Nabin Hait23941aa2013-01-29 11:32:38 +053027 fy = webnotes.conn.sql("""select name, year_start_date,
28 subdate(adddate(year_start_date, interval 1 year), interval 1 day)
29 as year_end_date
30 from `tabFiscal Year`
Nabin Hait0930b942013-06-20 16:35:09 +053031 where %s
32 order by year_start_date desc""" % cond)
Nabin Hait23941aa2013-01-29 11:32:38 +053033
34 if not fy:
Nabin Haitcfecd2b2013-07-11 17:49:18 +053035 error_msg = """%s %s not in any Fiscal Year""" % (label, formatdate(date))
Nabin Hait23941aa2013-01-29 11:32:38 +053036 if verbose: webnotes.msgprint(error_msg)
37 raise FiscalYearError, error_msg
38
Rushabh Mehtac2563ef2013-02-05 23:25:37 +053039 return fy
Rushabh Mehta2e8d0782013-02-05 11:31:27 +053040
41def validate_fiscal_year(date, fiscal_year, label="Date"):
Nabin Haitcfecd2b2013-07-11 17:49:18 +053042 years = [f[0] for f in get_fiscal_years(date, label=label)]
Rushabh Mehtac2563ef2013-02-05 23:25:37 +053043 if fiscal_year not in years:
Rushabh Mehta96075822013-02-05 11:39:49 +053044 webnotes.msgprint(("%(label)s '%(posting_date)s': " + _("not within Fiscal Year") + \
Rushabh Mehta2e8d0782013-02-05 11:31:27 +053045 ": '%(fiscal_year)s'") % {
46 "label": label,
47 "posting_date": formatdate(date),
48 "fiscal_year": fiscal_year
49 }, raise_exception=1)
Nabin Hait23941aa2013-01-29 11:32:38 +053050
51@webnotes.whitelist()
52def get_balance_on(account=None, date=None):
53 if not account and webnotes.form_dict.get("account"):
54 account = webnotes.form_dict.get("account")
55 date = webnotes.form_dict.get("date")
56
57 cond = []
58 if date:
59 cond.append("posting_date <= '%s'" % date)
60 else:
61 # get balance of all entries that exist
62 date = nowdate()
63
64 try:
65 year_start_date = get_fiscal_year(date, verbose=0)[1]
66 except FiscalYearError, e:
67 from webnotes.utils import getdate
68 if getdate(date) > getdate(nowdate()):
69 # if fiscal year not found and the date is greater than today
70 # get fiscal year for today's date and its corresponding year start date
71 year_start_date = get_fiscal_year(nowdate(), verbose=1)[1]
72 else:
73 # this indicates that it is a date older than any existing fiscal year.
74 # hence, assuming balance as 0.0
75 return 0.0
76
77 acc = webnotes.conn.get_value('Account', account, \
78 ['lft', 'rgt', 'debit_or_credit', 'is_pl_account', 'group_or_ledger'], as_dict=1)
79
80 # for pl accounts, get balance within a fiscal year
81 if acc.is_pl_account == 'Yes':
82 cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
83 % year_start_date)
84
85 # different filter for group and ledger - improved performance
86 if acc.group_or_ledger=="Group":
87 cond.append("""exists (
88 select * from `tabAccount` ac where ac.name = gle.account
89 and ac.lft >= %s and ac.rgt <= %s
90 )""" % (acc.lft, acc.rgt))
91 else:
92 cond.append("""gle.account = "%s" """ % (account, ))
93
94 # join conditional conditions
95 cond = " and ".join(cond)
96 if cond:
97 cond += " and "
98
99 bal = webnotes.conn.sql("""
100 SELECT sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
101 FROM `tabGL Entry` gle
102 WHERE %s ifnull(is_cancelled, 'No') = 'No' """ % (cond, ))[0][0]
103
104 # if credit account, it should calculate credit - debit
105 if bal and acc.debit_or_credit == 'Credit':
106 bal = -bal
107
108 # if bal is None, return 0
Anand Doshi6fa5e422013-07-19 15:33:11 +0530109 return flt(bal)
Nabin Haitfb3fd6e2013-01-30 19:16:13 +0530110
111@webnotes.whitelist()
112def add_ac(args=None):
113 if not args:
114 args = webnotes.form_dict
115 args.pop("cmd")
Rushabh Mehtaaae45532013-02-15 14:39:34 +0530116
Rushabh Mehtac53231a2013-02-18 18:24:28 +0530117 ac = webnotes.bean(args)
Nabin Haitfb3fd6e2013-01-30 19:16:13 +0530118 ac.doc.doctype = "Account"
119 ac.doc.old_parent = ""
120 ac.doc.freeze_account = "No"
Nabin Haitfb3fd6e2013-01-30 19:16:13 +0530121 ac.insert()
122 return ac.doc.name
123
124@webnotes.whitelist()
125def add_cc(args=None):
126 if not args:
127 args = webnotes.form_dict
128 args.pop("cmd")
129
Rushabh Mehtac53231a2013-02-18 18:24:28 +0530130 cc = webnotes.bean(args)
Nabin Haitfb3fd6e2013-01-30 19:16:13 +0530131 cc.doc.doctype = "Cost Center"
132 cc.doc.old_parent = ""
Nabin Haitfb3fd6e2013-01-30 19:16:13 +0530133 cc.insert()
134 return cc.doc.name
135
136def reconcile_against_document(args):
137 """
138 Cancel JV, Update aginst document, split if required and resubmit jv
139 """
140 for d in args:
141 check_if_jv_modified(d)
142
143 against_fld = {
144 'Journal Voucher' : 'against_jv',
145 'Sales Invoice' : 'against_invoice',
146 'Purchase Invoice' : 'against_voucher'
147 }
148
149 d['against_fld'] = against_fld[d['against_voucher_type']]
150
151 # cancel JV
152 jv_obj = webnotes.get_obj('Journal Voucher', d['voucher_no'], with_children=1)
153 jv_obj.make_gl_entries(cancel=1, adv_adj=1)
154
155 # update ref in JV Detail
156 update_against_doc(d, jv_obj)
157
158 # re-submit JV
159 jv_obj = webnotes.get_obj('Journal Voucher', d['voucher_no'], with_children =1)
160 jv_obj.make_gl_entries(cancel = 0, adv_adj =1)
161
162
163def check_if_jv_modified(args):
164 """
165 check if there is already a voucher reference
166 check if amount is same
167 check if jv is submitted
168 """
169 ret = webnotes.conn.sql("""
170 select t2.%(dr_or_cr)s from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
171 where t1.name = t2.parent and t2.account = '%(account)s'
172 and ifnull(t2.against_voucher, '')=''
173 and ifnull(t2.against_invoice, '')='' and ifnull(t2.against_jv, '')=''
174 and t1.name = '%(voucher_no)s' and t2.name = '%(voucher_detail_no)s'
175 and t1.docstatus=1 and t2.%(dr_or_cr)s = %(unadjusted_amt)s""" % args)
176
177 if not ret:
178 msgprint(_("""Payment Entry has been modified after you pulled it.
179 Please pull it again."""), raise_exception=1)
180
181def update_against_doc(d, jv_obj):
182 """
183 Updates against document, if partial amount splits into rows
184 """
185
186 webnotes.conn.sql("""
187 update `tabJournal Voucher Detail` t1, `tabJournal Voucher` t2
188 set t1.%(dr_or_cr)s = '%(allocated_amt)s',
189 t1.%(against_fld)s = '%(against_voucher)s', t2.modified = now()
190 where t1.name = '%(voucher_detail_no)s' and t1.parent = t2.name""" % d)
191
192 if d['allocated_amt'] < d['unadjusted_amt']:
193 jvd = webnotes.conn.sql("""select cost_center, balance, against_account, is_advance
194 from `tabJournal Voucher Detail` where name = %s""", d['voucher_detail_no'])
195 # new entry with balance amount
196 ch = addchild(jv_obj.doc, 'entries', 'Journal Voucher Detail')
197 ch.account = d['account']
198 ch.cost_center = cstr(jvd[0][0])
199 ch.balance = cstr(jvd[0][1])
200 ch.fields[d['dr_or_cr']] = flt(d['unadjusted_amt']) - flt(d['allocated_amt'])
201 ch.fields[d['dr_or_cr']== 'debit' and 'credit' or 'debit'] = 0
202 ch.against_account = cstr(jvd[0][2])
203 ch.is_advance = cstr(jvd[0][3])
204 ch.docstatus = 1
Nabin Haita76a0682013-02-08 14:04:13 +0530205 ch.save(1)
206
207def get_account_list(doctype, txt, searchfield, start, page_len, filters):
208 if not filters.get("group_or_ledger"):
209 filters["group_or_ledger"] = "Ledger"
210
211 conditions, filter_values = build_filter_conditions(filters)
212
213 return webnotes.conn.sql("""select name, parent_account from `tabAccount`
214 where docstatus < 2 %s and %s like %s order by name limit %s, %s""" %
215 (conditions, searchfield, "%s", "%s", "%s"),
216 tuple(filter_values + ["%%%s%%" % txt, start, page_len]))
217
218def get_cost_center_list(doctype, txt, searchfield, start, page_len, filters):
219 if not filters.get("group_or_ledger"):
220 filters["group_or_ledger"] = "Ledger"
221
222 conditions, filter_values = build_filter_conditions(filters)
223
224 return webnotes.conn.sql("""select name, parent_cost_center from `tabCost Center`
225 where docstatus < 2 %s and %s like %s order by name limit %s, %s""" %
226 (conditions, searchfield, "%s", "%s", "%s"),
Anand Doshi7da72dd2013-03-20 17:00:41 +0530227 tuple(filter_values + ["%%%s%%" % txt, start, page_len]))
228
229def remove_against_link_from_jv(ref_type, ref_no, against_field):
230 webnotes.conn.sql("""update `tabJournal Voucher Detail` set `%s`=null,
231 modified=%s, modified_by=%s
232 where `%s`=%s and docstatus < 2""" % (against_field, "%s", "%s", against_field, "%s"),
233 (now(), webnotes.session.user, ref_no))
234
235 webnotes.conn.sql("""update `tabGL Entry`
236 set against_voucher_type=null, against_voucher=null,
237 modified=%s, modified_by=%s
238 where against_voucher_type=%s and against_voucher=%s
239 and voucher_no != ifnull(against_voucher, "")
240 and ifnull(is_cancelled, "No")="No" """,
241 (now(), webnotes.session.user, ref_type, ref_no))
Nabin Hait0fc24542013-03-25 11:06:00 +0530242
243@webnotes.whitelist()
244def get_company_default(company, fieldname):
245 value = webnotes.conn.get_value("Company", company, fieldname)
246
247 if not value:
248 msgprint(_("Please mention default value for '") +
249 _(webnotes.get_doctype("company").get_label(fieldname) +
250 _("' in Company: ") + company), raise_exception=True)
251
252 return value
253
254def create_stock_in_hand_jv(reverse=False):
255 from webnotes.utils import nowdate
256 today = nowdate()
257 fiscal_year = get_fiscal_year(today)[0]
Nabin Hait787c02e2013-03-29 16:42:33 +0530258 jv_list = []
Nabin Hait0fc24542013-03-25 11:06:00 +0530259
260 for company in webnotes.conn.sql_list("select name from `tabCompany`"):
261 stock_rbnb_value = get_stock_rbnb_value(company)
Nabin Hait787c02e2013-03-29 16:42:33 +0530262 stock_rbnb_value = reverse and -1*stock_rbnb_value or stock_rbnb_value
263 if stock_rbnb_value:
264 jv = webnotes.bean([
265 {
266 "doctype": "Journal Voucher",
Anand Doshi72edc3d2013-05-29 12:13:16 +0530267 "naming_series": "JV-AUTO-",
Nabin Hait787c02e2013-03-29 16:42:33 +0530268 "company": company,
269 "posting_date": today,
270 "fiscal_year": fiscal_year,
271 "voucher_type": "Journal Entry",
Anand Doshi72edc3d2013-05-29 12:13:16 +0530272 "user_remark": (_("Auto Inventory Accounting") + ": " +
273 (_("Disabled") if reverse else _("Enabled")) + ". " +
274 _("Journal Entry for inventory that is received but not yet invoiced"))
Nabin Hait787c02e2013-03-29 16:42:33 +0530275 },
276 {
277 "doctype": "Journal Voucher Detail",
278 "parentfield": "entries",
279 "account": get_company_default(company, "stock_received_but_not_billed"),
Anand Doshi72edc3d2013-05-29 12:13:16 +0530280 (stock_rbnb_value > 0 and "credit" or "debit"): abs(stock_rbnb_value)
Nabin Hait787c02e2013-03-29 16:42:33 +0530281 },
282 {
283 "doctype": "Journal Voucher Detail",
284 "parentfield": "entries",
285 "account": get_company_default(company, "stock_adjustment_account"),
Anand Doshi72edc3d2013-05-29 12:13:16 +0530286 (stock_rbnb_value > 0 and "debit" or "credit"): abs(stock_rbnb_value),
Nabin Hait787c02e2013-03-29 16:42:33 +0530287 "cost_center": get_company_default(company, "stock_adjustment_cost_center")
288 },
289 ])
290 jv.insert()
Nabin Hait787c02e2013-03-29 16:42:33 +0530291
292 jv_list.append(jv.doc.name)
293
294 if jv_list:
Anand Doshi72edc3d2013-05-29 12:13:16 +0530295 msgprint(_("Following Journal Vouchers have been created automatically") + \
296 ":\n%s" % ("\n".join([("<a href=\"#Form/Journal Voucher/%s\">%s</a>" % (jv, jv)) for jv in jv_list]),))
297
298 msgprint(_("""These adjustment vouchers book the difference between \
299 the total value of received items and the total value of invoiced items, \
300 as a required step to use Auto Inventory Accounting.
301 This is an approximation to get you started.
302 You will need to submit these vouchers after checking if the values are correct.
303 For more details, read: \
304 <a href="http://erpnext.com/auto-inventory-accounting" target="_blank">\
305 Auto Inventory Accounting</a>"""))
Nabin Hait787c02e2013-03-29 16:42:33 +0530306
307 webnotes.msgprint("""Please refresh the system to get effect of Auto Inventory Accounting""")
308
Nabin Hait0fc24542013-03-25 11:06:00 +0530309
Nabin Hait787c02e2013-03-29 16:42:33 +0530310def get_stock_rbnb_value(company):
311 total_received_amount = webnotes.conn.sql("""select sum(valuation_rate*qty*conversion_factor)
Nabin Hait0fc24542013-03-25 11:06:00 +0530312 from `tabPurchase Receipt Item` pr_item where docstatus=1
313 and exists(select name from `tabItem` where name = pr_item.item_code
314 and is_stock_item='Yes')
Nabin Hait41fe3562013-03-28 15:54:11 +0530315 and exists(select name from `tabPurchase Receipt`
Nabin Hait0fc24542013-03-25 11:06:00 +0530316 where name = pr_item.parent and company = %s)""", company)
317
Nabin Hait787c02e2013-03-29 16:42:33 +0530318 total_billed_amount = webnotes.conn.sql("""select sum(valuation_rate*qty*conversion_factor)
Nabin Hait0fc24542013-03-25 11:06:00 +0530319 from `tabPurchase Invoice Item` pi_item where docstatus=1
320 and exists(select name from `tabItem` where name = pi_item.item_code
321 and is_stock_item='Yes')
Nabin Hait41fe3562013-03-28 15:54:11 +0530322 and exists(select name from `tabPurchase Invoice`
Nabin Hait0fc24542013-03-25 11:06:00 +0530323 where name = pi_item.parent and company = %s)""", company)
Nabin Hait0fc24542013-03-25 11:06:00 +0530324 return flt(total_received_amount[0][0]) - flt(total_billed_amount[0][0])
Nabin Hait8b509f52013-05-28 16:52:30 +0530325
326
327def fix_total_debit_credit():
328 vouchers = webnotes.conn.sql("""select voucher_type, voucher_no,
329 sum(debit) - sum(credit) as diff
330 from `tabGL Entry`
331 group by voucher_type, voucher_no
332 having sum(ifnull(debit, 0)) != sum(ifnull(credit, 0))""", as_dict=1)
333
334 for d in vouchers:
335 if abs(d.diff) > 0:
336 dr_or_cr = d.voucher_type == "Sales Invoice" and "credit" or "debit"
337
338 webnotes.conn.sql("""update `tabGL Entry` set %s = %s + %s
339 where voucher_type = %s and voucher_no = %s and %s > 0 limit 1""" %
340 (dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
Nabin Haitcac622e2013-08-02 11:44:29 +0530341 (d.diff, d.voucher_type, d.voucher_no))
342
343def validate_stock_and_account_balance():
344 difference = get_stock_and_account_difference()
345 if difference:
346 msgprint(_("Account balance must be synced with stock balance, \
347 to enable perpetual accounting." +
348 _(" Following accounts are not synced with stock balance") + ": \n" +
349 "\n".join(difference.keys())), raise_exception=1)
350
351def get_stock_and_account_difference(warehouse_list=None):
352 from stock.utils import get_latest_stock_balance
353
354 if not warehouse_list:
355 warehouse_list = webnotes.conn.sql_list("""select name from tabWarehouse
356 where docstatus<2""")
Nabin Hait94d39632013-08-06 15:58:44 +0530357
Nabin Haitcac622e2013-08-02 11:44:29 +0530358 account_warehouse_map = {}
359 warehouse_with_no_account = []
360 difference = {}
Nabin Haitcac622e2013-08-02 11:44:29 +0530361 warehouse_account = webnotes.conn.sql("""select name, account from tabWarehouse
362 where name in (%s)""" % ', '.join(['%s']*len(warehouse_list)), warehouse_list, as_dict=1)
363
364 for wh in warehouse_account:
365 if not wh.account: warehouse_with_no_account.append(wh.name)
366 account_warehouse_map.setdefault(wh.account, []).append(wh.name)
367
368 if warehouse_with_no_account:
369 msgprint(_("Please mention Perpetual Account in warehouse master for following warehouses")
370 + ": " + '\n'.join(warehouse_with_no_account), raise_exception=1)
371
Nabin Hait94d39632013-08-06 15:58:44 +0530372 bin_map = get_latest_stock_balance()
373 for account, warehouse_list in account_warehouse_map.items():
Nabin Haitcac622e2013-08-02 11:44:29 +0530374 account_balance = get_balance_on(account)
Nabin Hait94d39632013-08-06 15:58:44 +0530375 stock_value = sum([sum(bin_map.get(warehouse, {}).values())
376 for warehouse in warehouse_list])
Nabin Haitcac622e2013-08-02 11:44:29 +0530377
Nabin Hait73d04b12013-08-05 12:19:38 +0530378 if stock_value - account_balance:
379 difference.setdefault(account, (stock_value - account_balance))
Nabin Haitcac622e2013-08-02 11:44:29 +0530380
Nabin Haitcc0e2d12013-08-06 16:19:18 +0530381 return difference