Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

401 rader
12 KiB

  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. from __future__ import unicode_literals
  23. """
  24. Transactions are defined as collection of classes, a Bean represents collection of Document
  25. objects for a transaction with main and children.
  26. Group actions like save, etc are performed on doclists
  27. """
  28. import webnotes
  29. from webnotes import _
  30. from webnotes.utils import cint, cstr
  31. from webnotes.model.doc import Document
  32. class DocstatusTransitionError(webnotes.ValidationError): pass
  33. class Bean:
  34. """
  35. Collection of Documents with one parent and multiple children
  36. """
  37. def __init__(self, dt=None, dn=None):
  38. self.docs = []
  39. self.obj = None
  40. self.ignore_permissions = False
  41. self.ignore_children_type = []
  42. self.ignore_check_links = False
  43. self.ignore_validate = False
  44. self.ignore_fields = False
  45. if isinstance(dt, basestring) and not dn:
  46. dn = dt
  47. if dt and dn:
  48. self.load_from_db(dt, dn)
  49. elif isinstance(dt, list):
  50. self.set_doclist(dt)
  51. elif isinstance(dt, dict):
  52. self.set_doclist([dt])
  53. def load_from_db(self, dt=None, dn=None, prefix='tab'):
  54. """
  55. Load doclist from dt
  56. """
  57. from webnotes.model.doc import Document, getchildren
  58. if not dt: dt = self.doc.doctype
  59. if not dn: dn = self.doc.name
  60. doc = Document(dt, dn, prefix=prefix)
  61. # get all children types
  62. tablefields = webnotes.model.meta.get_table_fields(dt)
  63. # load chilren
  64. doclist = webnotes.doclist([doc,])
  65. for t in tablefields:
  66. doclist += getchildren(doc.name, t[0], t[1], dt, prefix=prefix)
  67. self.set_doclist(doclist)
  68. self.run_method("onload")
  69. def __iter__(self):
  70. return self.docs.__iter__()
  71. def from_compressed(self, data, docname):
  72. from webnotes.model.utils import expand
  73. self.docs = expand(data)
  74. self.set_doclist(self.docs)
  75. def set_doclist(self, docs):
  76. for i, d in enumerate(docs):
  77. if isinstance(d, dict):
  78. docs[i] = Document(fielddata=d)
  79. self.docs = self.doclist = webnotes.doclist(docs)
  80. self.doc, self.children = self.doclist[0], self.doclist[1:]
  81. if self.obj:
  82. self.obj.doclist = self.doclist
  83. self.obj.doc = self.doc
  84. def make_obj(self):
  85. if self.obj: return self.obj
  86. self.obj = webnotes.get_obj(doc=self.doc, doclist=self.doclist)
  87. self.controller = self.obj
  88. return self.obj
  89. def to_dict(self):
  90. return [d.fields for d in self.docs]
  91. def check_if_latest(self, method="save"):
  92. from webnotes.model.meta import is_single
  93. conflict = False
  94. if not cint(self.doc.fields.get('__islocal')):
  95. if is_single(self.doc.doctype):
  96. modified = webnotes.conn.get_value(self.doc.doctype, self.doc.name, "modified")
  97. if isinstance(modified, list):
  98. modified = modified[0]
  99. if cstr(modified) and cstr(modified) != cstr(self.doc.modified):
  100. conflict = True
  101. else:
  102. tmp = webnotes.conn.sql("""select modified, docstatus from `tab%s`
  103. where name="%s" for update"""
  104. % (self.doc.doctype, self.doc.name), as_dict=True)
  105. if not tmp:
  106. webnotes.msgprint("""This record does not exist. Please refresh.""", raise_exception=1)
  107. modified = cstr(tmp[0].modified)
  108. if modified and modified != cstr(self.doc.modified):
  109. conflict = True
  110. self.check_docstatus_transition(tmp[0].docstatus, method)
  111. if conflict:
  112. webnotes.msgprint(_("Error: Document has been modified after you have opened it") \
  113. + (" (%s, %s). " % (modified, self.doc.modified)) \
  114. + _("Please refresh to get the latest document."), raise_exception=True)
  115. def check_docstatus_transition(self, db_docstatus, method):
  116. valid = {
  117. "save": [0,0],
  118. "submit": [0,1],
  119. "cancel": [1,2],
  120. "update_after_submit": [1,1]
  121. }
  122. labels = {
  123. 0: _("Draft"),
  124. 1: _("Submitted"),
  125. 2: _("Cancelled")
  126. }
  127. if not hasattr(self, "to_docstatus"):
  128. self.to_docstatus = 0
  129. if method != "runserverobj" and [db_docstatus, self.to_docstatus] != valid[method]:
  130. webnotes.msgprint(_("Cannot change from") + ": " + labels[db_docstatus] + " > " + \
  131. labels[self.to_docstatus], raise_exception=DocstatusTransitionError)
  132. def check_links(self):
  133. if self.ignore_check_links:
  134. return
  135. ref, err_list = {}, []
  136. for d in self.docs:
  137. if not ref.get(d.doctype):
  138. ref[d.doctype] = d.make_link_list()
  139. err_list += d.validate_links(ref[d.doctype])
  140. if err_list:
  141. webnotes.msgprint("""[Link Validation] Could not find the following values: %s.
  142. Please correct and resave. Document Not Saved.""" % ', '.join(err_list), raise_exception=1)
  143. def update_timestamps_and_docstatus(self):
  144. from webnotes.utils import now
  145. ts = now()
  146. user = webnotes.__dict__.get('session', {}).get('user') or 'Administrator'
  147. for d in self.docs:
  148. if self.doc.fields.get('__islocal'):
  149. d.owner = user
  150. d.creation = ts
  151. d.modified_by = user
  152. d.modified = ts
  153. if d.docstatus != 2 and self.to_docstatus >= d.docstatus: # don't update deleted
  154. d.docstatus = self.to_docstatus
  155. def prepare_for_save(self, method):
  156. self.check_if_latest(method)
  157. if method != "cancel":
  158. self.check_links()
  159. self.update_timestamps_and_docstatus()
  160. self.update_parent_info()
  161. def update_parent_info(self):
  162. idx_map = {}
  163. is_local = cint(self.doc.fields.get("__islocal"))
  164. for i, d in enumerate(self.doclist[1:]):
  165. if d.parentfield:
  166. d.parenttype = self.doc.doctype
  167. d.parent = self.doc.name
  168. if not d.idx:
  169. d.idx = idx_map.setdefault(d.parentfield, 0) + 1
  170. if is_local:
  171. # if parent is new, all children should be new
  172. d.fields["__islocal"] = 1
  173. idx_map[d.parentfield] = d.idx
  174. def run_method(self, method):
  175. self.make_obj()
  176. if hasattr(self.obj, method):
  177. getattr(self.obj, method)()
  178. if hasattr(self.obj, 'custom_' + method):
  179. getattr(self.obj, 'custom_' + method)()
  180. notify(self.obj, method)
  181. self.set_doclist(self.obj.doclist)
  182. def get_method(self, method):
  183. self.make_obj()
  184. return getattr(self.obj, method, None)
  185. def save_main(self):
  186. try:
  187. self.doc.save(check_links = False, ignore_fields = self.ignore_fields)
  188. except NameError, e:
  189. webnotes.msgprint('%s "%s" already exists' % (self.doc.doctype, self.doc.name))
  190. # prompt if cancelled
  191. if webnotes.conn.get_value(self.doc.doctype, self.doc.name, 'docstatus')==2:
  192. webnotes.msgprint('[%s "%s" has been cancelled]' % (self.doc.doctype, self.doc.name))
  193. webnotes.errprint(webnotes.utils.getTraceback())
  194. raise e
  195. def save_children(self):
  196. child_map = {}
  197. for d in self.children:
  198. if d.fields.get("parent") or d.fields.get("parentfield"):
  199. d.parent = self.doc.name # rename if reqd
  200. d.parenttype = self.doc.doctype
  201. d.save(check_links=False, ignore_fields = self.ignore_fields)
  202. child_map.setdefault(d.doctype, []).append(d.name)
  203. # delete all children in database that are not in the child_map
  204. # get all children types
  205. tablefields = webnotes.model.meta.get_table_fields(self.doc.doctype)
  206. for dt in tablefields:
  207. if dt[0] not in self.ignore_children_type:
  208. cnames = child_map.get(dt[0]) or []
  209. if cnames:
  210. webnotes.conn.sql("""delete from `tab%s` where parent=%s and parenttype=%s and
  211. name not in (%s)""" % (dt[0], '%s', '%s', ','.join(['%s'] * len(cnames))),
  212. tuple([self.doc.name, self.doc.doctype] + cnames))
  213. else:
  214. webnotes.conn.sql("""delete from `tab%s` where parent=%s and parenttype=%s""" \
  215. % (dt[0], '%s', '%s'), (self.doc.name, self.doc.doctype))
  216. def insert(self):
  217. self.doc.fields["__islocal"] = 1
  218. return self.save()
  219. def has_read_perm(self):
  220. return webnotes.has_permission(self.doc.doctype, "read", self.doc)
  221. def save(self, check_links=1):
  222. if self.ignore_permissions or webnotes.has_permission(self.doc.doctype, "write", self.doc):
  223. self.to_docstatus = 0
  224. self.prepare_for_save("save")
  225. if not self.ignore_validate:
  226. self.run_method('validate')
  227. self.save_main()
  228. self.save_children()
  229. self.run_method('on_update')
  230. else:
  231. self.no_permission_to(_("Write"))
  232. return self
  233. def submit(self):
  234. if self.ignore_permissions or webnotes.has_permission(self.doc.doctype, "submit", self.doc):
  235. self.to_docstatus = 1
  236. self.prepare_for_save("submit")
  237. self.run_method('validate')
  238. self.save_main()
  239. self.save_children()
  240. self.run_method('on_update')
  241. self.run_method('on_submit')
  242. else:
  243. self.no_permission_to(_("Submit"))
  244. return self
  245. def cancel(self):
  246. if self.ignore_permissions or webnotes.has_permission(self.doc.doctype, "cancel", self.doc):
  247. self.to_docstatus = 2
  248. self.prepare_for_save("cancel")
  249. self.run_method('before_cancel')
  250. self.save_main()
  251. self.save_children()
  252. self.run_method('on_cancel')
  253. self.check_no_back_links_exist()
  254. else:
  255. self.no_permission_to(_("Cancel"))
  256. return self
  257. def update_after_submit(self):
  258. if self.doc.docstatus != 1:
  259. webnotes.msgprint("Only to called after submit", raise_exception=1)
  260. if self.ignore_permissions or webnotes.has_permission(self.doc.doctype, "write", self.doc):
  261. self.to_docstatus = 1
  262. self.prepare_for_save("update_after_submit")
  263. self.run_method('before_update_after_submit')
  264. self.save_main()
  265. self.save_children()
  266. self.run_method('on_update_after_submit')
  267. else:
  268. self.no_permission_to(_("Update"))
  269. return self
  270. def delete(self):
  271. webnotes.delete_doc(self.doc.doctype, self.doc.name)
  272. def no_permission_to(self, ptype):
  273. webnotes.msgprint(("%s (%s): " % (self.doc.name, _(self.doc.doctype))) + \
  274. _("No Permission to ") + ptype, raise_exception=True)
  275. def check_no_back_links_exist(self):
  276. from webnotes.model.utils import check_if_doc_is_linked
  277. check_if_doc_is_linked(self.doc.doctype, self.doc.name, method="Cancel")
  278. def clone(source_wrapper):
  279. """ Copy previous invoice and change dates"""
  280. if isinstance(source_wrapper, list):
  281. source_wrapper = Bean(source_wrapper)
  282. new_wrapper = Bean(source_wrapper.doclist.copy())
  283. new_wrapper.doc.fields.update({
  284. "amended_from": None,
  285. "amendment_date": None,
  286. })
  287. for d in new_wrapper.doclist:
  288. d.fields.update({
  289. "name": None,
  290. "__islocal": 1,
  291. "docstatus": 0,
  292. })
  293. return new_wrapper
  294. def notify(controller, caller_method):
  295. try:
  296. from startup.observers import observer_map
  297. except ImportError:
  298. return
  299. doctype = controller.doc.doctype
  300. def call_observers(key):
  301. if key in observer_map:
  302. observer_list = observer_map[key]
  303. if isinstance(observer_list, basestring):
  304. observer_list = [observer_list]
  305. for observer_method in observer_list:
  306. webnotes.get_method(observer_method)(controller, caller_method)
  307. call_observers("*:*")
  308. call_observers(doctype + ":*")
  309. call_observers("*:" + caller_method)
  310. call_observers(doctype + ":" + caller_method)
  311. # for bc
  312. def getlist(doclist, parentfield):
  313. """
  314. Return child records of a particular type
  315. """
  316. import webnotes.model.utils
  317. return webnotes.model.utils.getlist(doclist, parentfield)
  318. def copy_doclist(doclist, no_copy = []):
  319. """
  320. Make a copy of the doclist
  321. """
  322. import webnotes.model.utils
  323. return webnotes.model.utils.copy_doclist(doclist, no_copy)