/erpnext/accounts/doctype/sales_invoice/sales_invoice.py

http://github.com/webnotes/erpnext · Python · 720 lines · 464 code · 130 blank · 126 comment · 202 complexity · df3a71ee0845286beeab1202b3d4dd6b MD5 · raw file

  1. # ERPNext - web based ERP (http://erpnext.com)
  2. # Copyright (C) 2012 Web Notes Technologies Pvt Ltd
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. # Please edit this list and import only required elements
  17. import webnotes
  18. from webnotes.utils import add_days, add_months, add_years, cint, cstr,date_diff, default_fields, flt, fmt_money, formatdate, generate_hash,getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common,month_name, now, nowdate, replace_newlines, sendmail, set_default,str_esc_quote, user_format, validate_email_add
  19. from webnotes.model import db_exists
  20. from webnotes.model.doc import Document, addchild, getchildren, make_autoname
  21. from webnotes.model.doclist import getlist, copy_doclist
  22. from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax
  23. from webnotes import session, form, is_testing, msgprint, errprint
  24. in_transaction = webnotes.conn.in_transaction
  25. convert_to_lists = webnotes.conn.convert_to_lists
  26. session = webnotes.session
  27. # -----------------------------------------------------------------------------------------
  28. from utilities.transaction_base import TransactionBase
  29. class DocType(TransactionBase):
  30. def __init__(self,d,dl):
  31. self.doc, self.doclist = d, dl
  32. self.log = []
  33. self.tname = 'Sales Invoice Item'
  34. self.fname = 'entries'
  35. # Autoname
  36. # ---------
  37. def autoname(self):
  38. self.doc.name = make_autoname(self.doc.naming_series+ '.#####')
  39. # ********************************* Trigger Functions ******************************
  40. #Set retail related fields from pos settings
  41. #-------------------------------------------------------------------------
  42. def set_pos_fields(self):
  43. pos = webnotes.conn.sql("select * from `tabPOS Setting` where ifnull(user,'') = '%s' and company = '%s'" % (session['user'], self.doc.company), as_dict=1)
  44. if not pos:
  45. pos = webnotes.conn.sql("select * from `tabPOS Setting` where ifnull(user,'') = '' and company = '%s'" % (self.doc.company), as_dict=1)
  46. if pos:
  47. val = webnotes.conn.sql("select name from `tabAccount` where name = %s and docstatus != 2", (cstr(self.doc.customer) + " - " + self.get_company_abbr()))
  48. val = val and val[0][0] or ''
  49. if not val: val = pos and pos[0]['customer_account'] or ''
  50. if not self.doc.debit_to:
  51. webnotes.conn.set(self.doc,'debit_to',val)
  52. lst = ['territory','naming_series','currency','charge','letter_head','tc_name','price_list_name','company','select_print_heading','cash_bank_account']
  53. for i in lst:
  54. val = pos and pos[0][i] or ''
  55. self.doc.fields[i] = val
  56. self.set_pos_item_values()
  57. val = pos and flt(pos[0]['conversion_rate']) or 0
  58. self.doc.conversion_rate = val
  59. #fetch terms
  60. if self.doc.tc_name: self.get_tc_details()
  61. #fetch charges
  62. if self.doc.charge: self.get_other_charges()
  63. # Set default values related to pos for previously created sales invoice.
  64. # --------------------------------------------------------------------------
  65. def set_pos_item_values(self):
  66. if cint(self.doc.is_pos) ==1:
  67. dtl = webnotes.conn.sql("select income_account, warehouse, cost_center from `tabPOS Setting` where ifnull(user,'') = '%s' and company = '%s'" % (session['user'], self.doc.company), as_dict=1)
  68. if not dtl:
  69. dtl = webnotes.conn.sql("select income_account, warehouse, cost_center from `tabPOS Setting` where ifnull(user,'') = '' and company = '%s'" % (self.doc.company), as_dict=1)
  70. for d in getlist(self.doclist,'entries'):
  71. # overwrite if mentioned in item
  72. item = webnotes.conn.sql("select default_income_account, default_sales_cost_center, default_warehouse from tabItem where name = '%s'" %(d.item_code), as_dict=1)
  73. d.income_account = item and item[0]['default_income_account'] or dtl and dtl[0]['income_account'] or d.income_account
  74. d.cost_center = item and item[0]['default_sales_cost_center'] or dtl and dtl[0]['cost_center'] or d.cost_center
  75. d.warehouse = item and item[0]['default_warehouse'] or dtl and dtl[0]['warehouse'] or d.warehouse
  76. # Get Account Head to which amount needs to be Debited based on Customer
  77. # ----------------------------------------------------------------------
  78. def get_customer_account(self):
  79. if not self.doc.company:
  80. msgprint("Please select company first and re-select the customer after doing so", raise_exception=1)
  81. acc_head = webnotes.conn.sql("""select name from `tabAccount`
  82. where (name = %s or (master_name = %s and master_type = 'customer')) and docstatus != 2""",
  83. (cstr(self.doc.customer) + " - " + self.get_company_abbr(),self.doc.customer))
  84. if acc_head and acc_head[0][0]:
  85. return acc_head[0][0]
  86. else:
  87. msgprint("%s does not have an Account Head in %s. You must first create it from the Customer Master" % (self.doc.customer, self.doc.company))
  88. def get_debit_to(self):
  89. acc_head = self.get_customer_account()
  90. return acc_head and {'debit_to' : acc_head} or {}
  91. # Set Due Date = Posting Date + Credit Days
  92. # -----------------------------------------
  93. def get_cust_and_due_date(self):
  94. credit_days = 0
  95. if self.doc.debit_to:
  96. credit_days = webnotes.conn.sql("select credit_days from `tabAccount` where name='%s' and docstatus != 2" % self.doc.debit_to)
  97. credit_days = credit_days and cint(credit_days[0][0]) or 0
  98. if self.doc.company and not credit_days:
  99. credit_days = webnotes.conn.sql("select credit_days from `tabCompany` where name='%s'" % self.doc.company)
  100. credit_days = credit_days and cint(credit_days[0][0]) or 0
  101. # Customer has higher priority than company
  102. # i.e.if not entered in customer will take credit days from company
  103. self.doc.due_date = add_days(cstr(self.doc.posting_date), credit_days)
  104. if self.doc.debit_to:
  105. self.doc.customer = webnotes.conn.get_value('Account',self.doc.debit_to,'master_name')
  106. # Pull Details of Delivery Note or Sales Order Selected
  107. # ------------------------------------------------------
  108. def pull_details(self):
  109. # Delivery Note
  110. if self.doc.delivery_note_main:
  111. self.validate_prev_docname('delivery note')
  112. self.doc.clear_table(self.doclist,'other_charges')
  113. self.doclist = get_obj('DocType Mapper', 'Delivery Note-Sales Invoice').dt_map('Delivery Note', 'Sales Invoice', self.doc.delivery_note_main, self.doc, self.doclist, "[['Delivery Note', 'Sales Invoice'],['Delivery Note Item', 'Sales Invoice Item'],['Sales Taxes and Charges','Sales Taxes and Charges'],['Sales Team','Sales Team']]")
  114. self.get_income_account('entries')
  115. # Sales Order
  116. elif self.doc.sales_order_main:
  117. self.validate_prev_docname('sales order')
  118. self.doc.clear_table(self.doclist,'other_charges')
  119. get_obj('DocType Mapper', 'Sales Order-Sales Invoice').dt_map('Sales Order', 'Sales Invoice', self.doc.sales_order_main, self.doc, self.doclist, "[['Sales Order', 'Sales Invoice'],['Sales Order Item', 'Sales Invoice Item'],['Sales Taxes and Charges','Sales Taxes and Charges'], ['Sales Team', 'Sales Team']]")
  120. self.get_income_account('entries')
  121. ret = self.get_debit_to()
  122. self.doc.debit_to = ret.get('debit_to')
  123. # onload pull income account
  124. # --------------------------
  125. def load_default_accounts(self):
  126. """
  127. Loads default accounts from items, customer when called from mapper
  128. """
  129. self.get_income_account('entries')
  130. def get_income_account(self,doctype):
  131. for d in getlist(self.doclist, doctype):
  132. if d.item_code:
  133. item = webnotes.conn.sql("select default_income_account, default_sales_cost_center from tabItem where name = '%s'" %(d.item_code), as_dict=1)
  134. d.income_account = item and item[0]['default_income_account'] or ''
  135. d.cost_center = item and item[0]['default_sales_cost_center'] or ''
  136. # Item Details
  137. # -------------
  138. def get_item_details(self, args=None):
  139. import json
  140. args = args and json.loads(args) or {}
  141. if args.get('item_code'):
  142. ret = get_obj('Sales Common').get_item_details(args, self)
  143. return self.get_pos_details(args, ret)
  144. else:
  145. obj = get_obj('Sales Common')
  146. for doc in self.doclist:
  147. if doc.fields.get('item_code'):
  148. arg = {'item_code':doc.fields.get('item_code'), 'income_account':doc.fields.get('income_account'),
  149. 'cost_center': doc.fields.get('cost_center'), 'warehouse': doc.fields.get('warehouse')};
  150. ret = self.get_pos_details(arg)
  151. for r in ret:
  152. if not doc.fields.get(r):
  153. doc.fields[r] = ret[r]
  154. def get_pos_details(self, args, ret = {}):
  155. if args['item_code'] and cint(self.doc.is_pos) == 1:
  156. dtl = webnotes.conn.sql("select income_account, warehouse, cost_center from `tabPOS Setting` where user = '%s' and company = '%s'" % (session['user'], self.doc.company), as_dict=1)
  157. if not dtl:
  158. dtl = webnotes.conn.sql("select income_account, warehouse, cost_center from `tabPOS Setting` where ifnull(user,'') = '' and company = '%s'" % (self.doc.company), as_dict=1)
  159. item = webnotes.conn.sql("select default_income_account, default_sales_cost_center, default_warehouse from tabItem where name = '%s'" %(args['item_code']), as_dict=1)
  160. ret['income_account'] = item and item[0].get('default_income_account') \
  161. or (dtl and dtl[0].get('income_account') or args.get('income_account'))
  162. ret['cost_center'] = item and item[0].get('default_sales_cost_center') \
  163. or (dtl and dtl[0].get('cost_center') or args.get('cost_center'))
  164. ret['warehouse'] = item and item[0].get('default_warehouse') \
  165. or (dtl and dtl[0].get('warehouse') or args.get('warehouse'))
  166. if ret['warehouse']:
  167. actual_qty = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = '%s' and warehouse = '%s'" % (args['item_code'], ret['warehouse']))
  168. ret['actual_qty']= actual_qty and flt(actual_qty[0][0]) or 0
  169. return ret
  170. def get_barcode_details(self, barcode):
  171. return get_obj('Sales Common').get_barcode_details(barcode)
  172. # Fetch ref rate from item master as per selected price list
  173. def get_adj_percent(self, arg=''):
  174. get_obj('Sales Common').get_adj_percent(self)
  175. # Get tax rate if account type is tax
  176. # ------------------------------------
  177. def get_rate(self,arg):
  178. get_obj('Sales Common').get_rate(arg)
  179. # Get Commission rate of Sales Partner
  180. # -------------------------------------
  181. def get_comm_rate(self, sales_partner):
  182. return get_obj('Sales Common').get_comm_rate(sales_partner, self)
  183. # GET TERMS & CONDITIONS
  184. # -------------------------------------
  185. def get_tc_details(self):
  186. return get_obj('Sales Common').get_tc_details(self)
  187. # Load Default Charges
  188. # ----------------------------------------------------------
  189. def load_default_taxes(self):
  190. return get_obj('Sales Common').load_default_taxes(self)
  191. # Get Sales Taxes and Charges Master Details
  192. # --------------------------
  193. def get_other_charges(self):
  194. return get_obj('Sales Common').get_other_charges(self)
  195. # Get Advances
  196. # -------------
  197. def get_advances(self):
  198. get_obj('GL Control').get_advances(self, self.doc.debit_to, 'Sales Invoice Advance', 'advance_adjustment_details', 'credit')
  199. #pull project customer
  200. #-------------------------
  201. def pull_project_customer(self):
  202. res = webnotes.conn.sql("select customer from `tabProject` where name = '%s'"%self.doc.project_name)
  203. if res:
  204. get_obj('DocType Mapper', 'Project-Sales Invoice').dt_map('Project', 'Sales Invoice', self.doc.project_name, self.doc, self.doclist, "[['Project', 'Sales Invoice']]")
  205. # ********************************** Server Utility Functions ******************************
  206. # Get Company Abbr.
  207. # ------------------
  208. def get_company_abbr(self):
  209. return webnotes.conn.sql("select abbr from tabCompany where name=%s", self.doc.company)[0][0]
  210. # Check whether sales order / delivery note items already pulled
  211. #----------------------------------------------------------------
  212. def validate_prev_docname(self,doctype):
  213. for d in getlist(self.doclist, 'entries'):
  214. if doctype == 'delivery note' and self.doc.delivery_note_main == d.delivery_note:
  215. msgprint(cstr(self.doc.delivery_note_main) + " delivery note details have already been pulled.")
  216. raise Exception , "Validation Error. Delivery note details have already been pulled."
  217. elif doctype == 'sales order' and self.doc.sales_order_main == d.sales_order and not d.delivery_note:
  218. msgprint(cstr(self.doc.sales_order_main) + " sales order details have already been pulled.")
  219. raise Exception , "Validation Error. Sales order details have already been pulled."
  220. #-----------------------------------------------------------------
  221. def update_against_document_in_jv(self):
  222. """
  223. Links invoice and advance voucher:
  224. 1. cancel advance voucher
  225. 2. split into multiple rows if partially adjusted, assign against voucher
  226. 3. submit advance voucher
  227. """
  228. lst = []
  229. for d in getlist(self.doclist, 'advance_adjustment_details'):
  230. if flt(d.allocated_amount) > 0:
  231. args = {
  232. 'voucher_no' : d.journal_voucher,
  233. 'voucher_detail_no' : d.jv_detail_no,
  234. 'against_voucher_type' : 'Sales Invoice',
  235. 'against_voucher' : self.doc.name,
  236. 'account' : self.doc.debit_to,
  237. 'is_advance' : 'Yes',
  238. 'dr_or_cr' : 'credit',
  239. 'unadjusted_amt' : flt(d.advance_amount),
  240. 'allocated_amt' : flt(d.allocated_amount)
  241. }
  242. lst.append(args)
  243. if lst:
  244. get_obj('GL Control').reconcile_against_document(lst)
  245. # ------------------------------------------------------------------------
  246. def validate_customer(self):
  247. """
  248. Validate customer name with SO and DN
  249. """
  250. for d in getlist(self.doclist,'entries'):
  251. dt = d.delivery_note and 'Delivery Note' or d.sales_order and 'Sales Order' or ''
  252. if dt:
  253. dt_no = d.delivery_note or d.sales_order
  254. cust = webnotes.conn.sql("select customer from `tab%s` where name = %s" % (dt, '%s'), dt_no)
  255. if cust and cstr(cust[0][0]) != cstr(self.doc.customer):
  256. msgprint("Customer %s does not match with customer of %s: %s." %(self.doc.customer, dt, dt_no), raise_exception=1)
  257. # Validates Debit To Account and Customer Matches
  258. # ------------------------------------------------
  259. def validate_debit_to_acc(self):
  260. if self.doc.customer and self.doc.debit_to and not cint(self.doc.is_pos):
  261. acc_head = webnotes.conn.sql("select master_name from `tabAccount` where name = %s and docstatus != 2", self.doc.debit_to)
  262. if (acc_head and cstr(acc_head[0][0]) != cstr(self.doc.customer)) or \
  263. (not acc_head and (self.doc.debit_to != cstr(self.doc.customer) + " - " + self.get_company_abbr())):
  264. msgprint("Debit To: %s do not match with Customer: %s for Company: %s.\n If both correctly entered, please select Master Type \
  265. and Master Name in account master." %(self.doc.debit_to, self.doc.customer,self.doc.company), raise_exception=1)
  266. # Validate Debit To Account
  267. # 1. Account Exists
  268. # 2. Is a Debit Account
  269. # 3. Is a PL Account
  270. # ---------------------------
  271. def validate_debit_acc(self):
  272. acc = webnotes.conn.sql("select debit_or_credit, is_pl_account from tabAccount where name = '%s' and docstatus != 2" % self.doc.debit_to)
  273. if not acc:
  274. msgprint("Account: "+ self.doc.debit_to + " does not exist")
  275. raise Exception
  276. elif acc[0][0] and acc[0][0] != 'Debit':
  277. msgprint("Account: "+ self.doc.debit_to + " is not a debit account")
  278. raise Exception
  279. elif acc[0][1] and acc[0][1] != 'No':
  280. msgprint("Account: "+ self.doc.debit_to + " is a pl account")
  281. raise Exception
  282. # Validate Fixed Asset Account and whether Income Account Entered Exists
  283. # -----------------------------------------------------------------------
  284. def validate_fixed_asset_account(self):
  285. for d in getlist(self.doclist,'entries'):
  286. item = webnotes.conn.sql("select name,is_asset_item,is_sales_item from `tabItem` where name = '%s' and (ifnull(end_of_life,'')='' or end_of_life = '0000-00-00' or end_of_life > now())"% d.item_code)
  287. acc = webnotes.conn.sql("select account_type from `tabAccount` where name = '%s' and docstatus != 2" % d.income_account)
  288. if not acc:
  289. msgprint("Account: "+d.income_account+" does not exist in the system")
  290. raise Exception
  291. elif item and item[0][1] == 'Yes' and not acc[0][0] == 'Fixed Asset Account':
  292. msgprint("Please select income head with account type 'Fixed Asset Account' as Item %s is an asset item" % d.item_code)
  293. raise Exception
  294. # Set totals in words
  295. #--------------------
  296. def set_in_words(self):
  297. dcc = TransactionBase().get_company_currency(self.doc.company)
  298. self.doc.in_words = get_obj('Sales Common').get_total_in_words(dcc, self.doc.rounded_total)
  299. self.doc.in_words_export = get_obj('Sales Common').get_total_in_words(self.doc.currency, self.doc.rounded_total_export)
  300. # Clear Advances
  301. # --------------
  302. def clear_advances(self):
  303. get_obj('GL Control').clear_advances(self, 'Sales Invoice Advance','advance_adjustment_details')
  304. # set aging date
  305. #-------------------
  306. def set_aging_date(self):
  307. if self.doc.is_opening != 'Yes':
  308. self.doc.aging_date = self.doc.posting_date
  309. elif not self.doc.aging_date:
  310. msgprint("Aging Date is mandatory for opening entry")
  311. raise Exception
  312. # Set against account for debit to account
  313. #------------------------------------------
  314. def set_against_income_account(self):
  315. against_acc = []
  316. for d in getlist(self.doclist, 'entries'):
  317. if d.income_account not in against_acc:
  318. against_acc.append(d.income_account)
  319. self.doc.against_income_account = ','.join(against_acc)
  320. def add_remarks(self):
  321. if not self.doc.remarks: self.doc.remarks = 'No Remarks'
  322. #check in manage account if sales order / delivery note required or not.
  323. def so_dn_required(self):
  324. dict = {'Sales Order':'so_required','Delivery Note':'dn_required'}
  325. for i in dict:
  326. res = webnotes.conn.sql("select value from `tabSingles` where doctype = 'Global Defaults' and field = '%s'"%dict[i])
  327. if res and res[0][0] == 'Yes':
  328. for d in getlist(self.doclist,'entries'):
  329. if not d.fields[i.lower().replace(' ','_')]:
  330. msgprint("%s No. required against item %s"%(i,d.item_code))
  331. raise Exception
  332. #check for does customer belong to same project as entered..
  333. #-------------------------------------------------------------------------------------------------
  334. def validate_proj_cust(self):
  335. if self.doc.project_name and self.doc.customer:
  336. res = webnotes.conn.sql("select name from `tabProject` where name = '%s' and (customer = '%s' or ifnull(customer,'')='')"%(self.doc.project_name, self.doc.customer))
  337. if not res:
  338. msgprint("Customer - %s does not belong to project - %s. \n\nIf you want to use project for multiple customers then please make customer details blank in that project."%(self.doc.customer,self.doc.project_name))
  339. raise Exception
  340. def validate_pos(self):
  341. if not self.doc.cash_bank_account and flt(self.doc.paid_amount):
  342. msgprint("Cash/Bank Account is mandatory for POS, for making payment entry")
  343. raise Exception
  344. if (flt(self.doc.paid_amount) + flt(self.doc.write_off_amount) - round(flt(self.doc.grand_total), 2))>0.001:
  345. msgprint("(Paid amount + Write Off Amount) can not be greater than Grand Total")
  346. raise Exception
  347. # ********* UPDATE CURRENT STOCK *****************************
  348. def update_current_stock(self):
  349. for d in getlist(self.doclist, 'entries'):
  350. bin = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
  351. d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
  352. def validate_item_code(self):
  353. for d in getlist(self.doclist, 'entries'):
  354. if not d.item_code:
  355. msgprint("Please enter Item Code at line no : %s to update stock for POS or remove check from Update Stock in Basic Info Tab." % (d.idx))
  356. raise Exception
  357. # Validate Write Off Account
  358. # -------------------------------
  359. def validate_write_off_account(self):
  360. if flt(self.doc.write_off_amount) and not self.doc.write_off_account:
  361. msgprint("Please enter Write Off Account", raise_exception=1)
  362. def validate_c_form(self):
  363. """ Blank C-form no if C-form applicable marked as 'No'"""
  364. if self.doc.amended_from and self.doc.c_form_applicable == 'No' and self.doc.c_form_no:
  365. webnotes.conn.sql("""delete from `tabC-Form Invoice Detail` where invoice_no = %s
  366. and parent = %s""", (self.doc.amended_from, self.doc.c_form_no))
  367. webnotes.conn.set(self.doc, 'c_form_no', '')
  368. # VALIDATE
  369. # ====================================================================================
  370. def validate(self):
  371. self.so_dn_required()
  372. #self.dn_required()
  373. self.validate_proj_cust()
  374. sales_com_obj = get_obj('Sales Common')
  375. sales_com_obj.check_stop_sales_order(self)
  376. sales_com_obj.check_active_sales_items(self)
  377. sales_com_obj.check_conversion_rate(self)
  378. sales_com_obj.validate_max_discount(self, 'entries') #verify whether rate is not greater than tolerance
  379. sales_com_obj.get_allocated_sum(self) # this is to verify that the allocated % of sales persons is 100%
  380. sales_com_obj.validate_fiscal_year(self.doc.fiscal_year,self.doc.posting_date,'Posting Date')
  381. self.validate_customer()
  382. self.validate_debit_to_acc()
  383. self.validate_debit_acc()
  384. self.validate_fixed_asset_account()
  385. self.add_remarks()
  386. if cint(self.doc.is_pos):
  387. self.validate_pos()
  388. self.validate_write_off_account()
  389. if cint(self.doc.update_stock):
  390. get_obj('Stock Ledger').validate_serial_no(self, 'entries')
  391. self.validate_item_code()
  392. self.update_current_stock()
  393. self.set_in_words()
  394. if not self.doc.is_opening:
  395. self.doc.is_opening = 'No'
  396. self.set_aging_date()
  397. self.clear_advances()
  398. # Set against account
  399. self.set_against_income_account()
  400. self.validate_c_form()
  401. # *************************************************** ON SUBMIT **********************************************
  402. # Check Ref Document's docstatus
  403. # -------------------------------
  404. def check_prev_docstatus(self):
  405. for d in getlist(self.doclist,'entries'):
  406. if d.sales_order:
  407. submitted = webnotes.conn.sql("select name from `tabSales Order` where docstatus = 1 and name = '%s'" % d.sales_order)
  408. if not submitted:
  409. msgprint("Sales Order : "+ cstr(d.sales_order) +" is not submitted")
  410. raise Exception , "Validation Error."
  411. if d.delivery_note:
  412. submitted = webnotes.conn.sql("select name from `tabDelivery Note` where docstatus = 1 and name = '%s'" % d.delivery_note)
  413. if not submitted:
  414. msgprint("Delivery Note : "+ cstr(d.delivery_note) +" is not submitted")
  415. raise Exception , "Validation Error."
  416. #Set Actual Qty based on item code and warehouse
  417. #------------------------------------------------------
  418. def set_actual_qty(self):
  419. for d in getlist(self.doclist, 'entries'):
  420. if d.item_code and d.warehouse:
  421. actual_qty = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = '%s' and warehouse = '%s'" % (d.item_code, d.warehouse))
  422. d.actual_qty = actual_qty and flt(actual_qty[0][0]) or 0
  423. # ********************** Make Stock Entry ************************************
  424. def make_sl_entry(self, d, wh, qty, in_value, update_stock):
  425. st_uom = webnotes.conn.sql("select stock_uom from `tabItem` where name = '%s'"%d.item_code)
  426. self.values.append({
  427. 'item_code' : d.item_code,
  428. 'warehouse' : wh,
  429. 'transaction_date' : getdate(self.doc.modified).strftime('%Y-%m-%d'),
  430. 'posting_date' : self.doc.posting_date,
  431. 'posting_time' : self.doc.posting_time,
  432. 'voucher_type' : 'Sales Invoice',
  433. 'voucher_no' : cstr(self.doc.name),
  434. 'voucher_detail_no' : cstr(d.name),
  435. 'actual_qty' : qty,
  436. 'stock_uom' : st_uom and st_uom[0][0] or '',
  437. 'incoming_rate' : in_value,
  438. 'company' : self.doc.company,
  439. 'fiscal_year' : self.doc.fiscal_year,
  440. 'is_cancelled' : (update_stock==1) and 'No' or 'Yes',
  441. 'batch_no' : cstr(d.batch_no),
  442. 'serial_no' : d.serial_no
  443. })
  444. # UPDATE STOCK LEDGER
  445. # ---------------------------------------------------------------------------
  446. def update_stock_ledger(self, update_stock, clear = 0):
  447. self.values = []
  448. for d in getlist(self.doclist, 'entries'):
  449. stock_item = webnotes.conn.sql("SELECT is_stock_item, is_sample_item FROM tabItem where name = '%s'"%(d.item_code), as_dict = 1) # stock ledger will be updated only if it is a stock item
  450. if stock_item[0]['is_stock_item'] == "Yes":
  451. # Reduce actual qty from warehouse
  452. self.make_sl_entry( d, d.warehouse, - flt(d.qty) , 0, update_stock)
  453. get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values, self.doc.amended_from and 'Yes' or 'No')
  454. #-------------------POS Stock Updatation Part----------------------------------------------
  455. def pos_update_stock(self):
  456. self.update_stock_ledger(update_stock = 1)
  457. # ********** Get Actual Qty of item in warehouse selected *************
  458. def get_actual_qty(self,args):
  459. args = eval(args)
  460. actual_qty = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = '%s' and warehouse = '%s'" % (args['item_code'], args['warehouse']), as_dict=1)
  461. ret = {
  462. 'actual_qty' : actual_qty and flt(actual_qty[0]['actual_qty']) or 0
  463. }
  464. return ret
  465. # Make GL Entries
  466. # -------------------------
  467. def make_gl_entries(self, is_cancel=0):
  468. mapper = self.doc.is_pos and self.doc.write_off_account and 'POS with write off' or self.doc.is_pos and not self.doc.write_off_account and 'POS' or ''
  469. update_outstanding = self.doc.is_pos and self.doc.write_off_account and 'No' or 'Yes'
  470. get_obj(dt='GL Control').make_gl_entries(self.doc, self.doclist,cancel = is_cancel, use_mapper = mapper, update_outstanding = update_outstanding, merge_entries = cint(self.doc.is_pos) != 1 and 1 or 0)
  471. # On Submit
  472. # ---------
  473. def on_submit(self):
  474. if cint(self.doc.is_pos) == 1:
  475. if cint(self.doc.update_stock) == 1:
  476. sl_obj = get_obj("Stock Ledger")
  477. sl_obj.validate_serial_no_warehouse(self, 'entries')
  478. sl_obj.update_serial_record(self, 'entries', is_submit = 1, is_incoming = 0)
  479. self.pos_update_stock()
  480. else:
  481. self.check_prev_docstatus()
  482. get_obj("Sales Common").update_prevdoc_detail(1,self)
  483. # Check for Approving Authority
  484. if not self.doc.recurring_id:
  485. get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)
  486. # this sequence because outstanding may get -ve
  487. self.make_gl_entries()
  488. if not cint(self.doc.is_pos) == 1:
  489. self.update_against_document_in_jv()
  490. self.update_c_form()
  491. def update_c_form(self):
  492. """Update amended id in C-form"""
  493. if self.doc.c_form_no and self.doc.amended_from:
  494. webnotes.conn.sql("""update `tabC-Form Invoice Detail` set invoice_no = %s,
  495. invoice_date = %s, territory = %s, net_total = %s,
  496. grand_total = %s where invoice_no = %s and parent = %s""", (self.doc.name, self.doc.amended_from, self.doc.c_form_no))
  497. # *************************************************** ON CANCEL **********************************************
  498. # Check Next Document's docstatus
  499. # --------------------------------
  500. def check_next_docstatus(self):
  501. submit_jv = webnotes.conn.sql("select t1.name from `tabJournal Voucher` t1,`tabJournal Voucher Detail` t2 where t1.name = t2.parent and t2.against_invoice = '%s' and t1.docstatus = 1" % (self.doc.name))
  502. if submit_jv:
  503. msgprint("Journal Voucher : " + cstr(submit_jv[0][0]) + " has been created against " + cstr(self.doc.doctype) + ". So " + cstr(self.doc.doctype) + " cannot be Cancelled.")
  504. raise Exception, "Validation Error."
  505. # On Cancel
  506. # ----------
  507. def on_cancel(self):
  508. if cint(self.doc.is_pos) == 1:
  509. if cint(self.doc.update_stock) == 1:
  510. get_obj('Stock Ledger').update_serial_record(self, 'entries', is_submit = 0, is_incoming = 0)
  511. self.update_stock_ledger(update_stock = -1)
  512. else:
  513. sales_com_obj = get_obj(dt = 'Sales Common')
  514. sales_com_obj.check_stop_sales_order(self)
  515. self.check_next_docstatus()
  516. sales_com_obj.update_prevdoc_detail(0,self)
  517. self.make_gl_entries(is_cancel=1)
  518. # Get Warehouse
  519. def get_warehouse(self):
  520. w = webnotes.conn.sql("select warehouse from `tabPOS Setting` where ifnull(user,'') = '%s' and company = '%s'" % (session['user'], self.doc.company))
  521. w = w and w[0][0] or ''
  522. if not w:
  523. ps = webnotes.conn.sql("select name, warehouse from `tabPOS Setting` where ifnull(user,'') = '' and company = '%s'" % self.doc.company)
  524. if not ps:
  525. msgprint("To make POS entry, please create POS Setting from Setup --> Accounts --> POS Setting and refresh the system.")
  526. raise Exception
  527. elif not ps[0][1]:
  528. msgprint("Please enter warehouse in POS Setting")
  529. else:
  530. w = ps[0][1]
  531. return w
  532. # on update
  533. def on_update(self):
  534. # Set default warehouse from pos setting
  535. #----------------------------------------
  536. if cint(self.doc.is_pos) == 1:
  537. self.set_actual_qty()
  538. w = self.get_warehouse()
  539. if w:
  540. for d in getlist(self.doclist, 'entries'):
  541. if not d.warehouse:
  542. d.warehouse = cstr(w)
  543. if flt(self.doc.paid_amount) == 0:
  544. if self.doc.cash_bank_account:
  545. webnotes.conn.set(self.doc, 'paid_amount',
  546. (flt(self.doc.grand_total) - flt(self.doc.write_off_amount)))
  547. else:
  548. # show message that the amount is not paid
  549. webnotes.conn.set(self.doc,'paid_amount',0)
  550. webnotes.msgprint("Note: Payment Entry not created since 'Cash/Bank Account' was not specified.")
  551. else:
  552. webnotes.conn.set(self.doc,'paid_amount',0)
  553. webnotes.conn.set(self.doc,'outstanding_amount',flt(self.doc.grand_total) - flt(self.doc.total_advance) - flt(self.doc.paid_amount) - flt(self.doc.write_off_amount))
  554. #-------------------------------------------------------------------------------------
  555. def on_update_after_submit(self):
  556. self.convert_into_recurring()
  557. def convert_into_recurring(self):
  558. if self.doc.convert_into_recurring_invoice:
  559. if not self.doc.recurring_type:
  560. msgprint("Please select recurring type", raise_exception=1)
  561. elif not self.doc.invoice_period_from_date or not self.doc.invoice_period_to_date:
  562. msgprint("Invoice period from date and to date is mandatory for recurring invoice", raise_exception=1)
  563. self.set_next_date()
  564. if not self.doc.recurring_id:
  565. webnotes.conn.set(self.doc, 'recurring_id', make_autoname('RECINV/.#####'))
  566. elif self.doc.recurring_id:
  567. webnotes.conn.sql("""update `tabSales Invoice` set convert_into_recurring_invoice = 0 where recurring_id = %s""", self.doc.recurring_id)
  568. def set_next_date(self):
  569. """ Set next date on which auto invoice will be created"""
  570. if not self.doc.repeat_on_day_of_month:
  571. msgprint("""Please enter 'Repeat on Day of Month' field value. \nThe day of the month on which auto invoice
  572. will be generated e.g. 05, 28 etc.""", raise_exception=1)
  573. import datetime
  574. mcount = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
  575. m = getdate(self.doc.posting_date).month + mcount[self.doc.recurring_type]
  576. y = getdate(self.doc.posting_date).year
  577. if m > 12:
  578. m, y = m-12, y+1
  579. try:
  580. next_date = datetime.date(y, m, cint(self.doc.repeat_on_day_of_month))
  581. except:
  582. import calendar
  583. last_day = calendar.monthrange(y, m)[1]
  584. next_date = datetime.date(y, m, last_day)
  585. next_date = next_date.strftime("%Y-%m-%d")
  586. webnotes.conn.set(self.doc, 'next_date', next_date)