sale.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # -*- coding: utf-8 -*-
  2. #
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # Authors: Raphaël Valyi, Renato Lima
  6. # Copyright (C) 2011 Akretion LTDA.
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. #
  22. import time
  23. from openerp import api, models, fields
  24. from openerp.exceptions import except_orm
  25. from openerp.tools.safe_eval import safe_eval
  26. from openerp.tools.translate import _
  27. class SaleException(models.Model):
  28. _name = 'sale.exception'
  29. _description = "Sale Exceptions"
  30. _order = 'active desc, sequence asc'
  31. name = fields.Char('Exception Name', required=True, translate=True)
  32. description = fields.Text('Description', translate=True)
  33. sequence = fields.Integer(
  34. string='Sequence',
  35. help="Gives the sequence order when applying the test")
  36. model = fields.Selection(
  37. [('sale.order', 'Sale Order'),
  38. ('sale.order.line', 'Sale Order Line')],
  39. string='Apply on', required=True)
  40. active = fields.Boolean('Active')
  41. code = fields.Text(
  42. 'Python Code',
  43. help="Python code executed to check if the exception apply or "
  44. "not. The code must apply block = True to apply the "
  45. "exception.",
  46. default="""
  47. # Python code. Use failed = True to block the sale order.
  48. # You can use the following variables :
  49. # - self: ORM model of the record which is checked
  50. # - order or line: browse_record of the sale order or sale order line
  51. # - object: same as order or line, browse_record of the sale order or
  52. # sale order line
  53. # - pool: ORM model pool (i.e. self.pool)
  54. # - time: Python time module
  55. # - cr: database cursor
  56. # - uid: current user id
  57. # - context: current context
  58. """)
  59. sale_order_ids = fields.Many2many(
  60. 'sale.order',
  61. 'sale_order_exception_rel', 'exception_id', 'sale_order_id',
  62. string='Sale Orders',
  63. readonly=True)
  64. class SaleOrder(models.Model):
  65. _inherit = 'sale.order'
  66. _order = 'main_exception_id asc, date_order desc, name desc'
  67. main_exception_id = fields.Many2one(
  68. 'sale.exception',
  69. compute='_get_main_error',
  70. string='Main Exception',
  71. store=True)
  72. exception_ids = fields.Many2many(
  73. 'sale.exception',
  74. 'sale_order_exception_rel', 'sale_order_id', 'exception_id',
  75. string='Exceptions')
  76. ignore_exceptions = fields.Boolean('Ignore Exceptions')
  77. @api.one
  78. @api.depends('state', 'exception_ids')
  79. def _get_main_error(self):
  80. if self.state == 'draft' and self.exception_ids:
  81. self.main_exception_id = self.exception_ids[0]
  82. else:
  83. self.main_exception_id = False
  84. @api.model
  85. def test_all_draft_orders(self):
  86. order_set = self.search([('state', '=', 'draft')])
  87. order_set.test_exceptions()
  88. return True
  89. @api.multi
  90. def _popup_exceptions(self):
  91. model_data_model = self.env['ir.model.data']
  92. wizard_model = self.env['sale.exception.confirm']
  93. new_context = {'active_id': self.ids[0], 'active_ids': self.ids}
  94. wizard = wizard_model.with_context(new_context).create({})
  95. view_id = model_data_model.get_object_reference(
  96. 'sale_exceptions', 'view_sale_exception_confirm')[1]
  97. action = {
  98. 'name': _("Blocked in draft due to exceptions"),
  99. 'type': 'ir.actions.act_window',
  100. 'view_type': 'form',
  101. 'view_mode': 'form',
  102. 'res_model': 'sale.exception.confirm',
  103. 'view_id': [view_id],
  104. 'target': 'new',
  105. 'nodestroy': True,
  106. 'res_id': wizard.id,
  107. }
  108. return action
  109. @api.multi
  110. def action_button_confirm(self):
  111. self.ensure_one()
  112. if self.detect_exceptions():
  113. return self._popup_exceptions()
  114. else:
  115. return super(SaleOrder, self).action_button_confirm()
  116. @api.multi
  117. def test_exceptions(self):
  118. """
  119. Condition method for the workflow from draft to confirm
  120. """
  121. if self.detect_exceptions():
  122. return False
  123. return True
  124. @api.multi
  125. def detect_exceptions(self):
  126. """returns the list of exception_ids for all the considered sale orders
  127. as a side effect, the sale order's exception_ids column is updated with
  128. the list of exceptions related to the SO
  129. """
  130. exception_obj = self.env['sale.exception']
  131. order_exceptions = exception_obj.search(
  132. [('model', '=', 'sale.order')])
  133. line_exceptions = exception_obj.search(
  134. [('model', '=', 'sale.order.line')])
  135. all_exception_ids = []
  136. for order in self:
  137. if order.ignore_exceptions:
  138. continue
  139. exception_ids = order._detect_exceptions(order_exceptions,
  140. line_exceptions)
  141. order.exception_ids = [(6, 0, exception_ids)]
  142. all_exception_ids += exception_ids
  143. return all_exception_ids
  144. @api.model
  145. def _exception_rule_eval_context(self, obj_name, rec):
  146. user = self.env['res.users'].browse(self._uid)
  147. return {obj_name: rec,
  148. 'self': self.pool.get(rec._name),
  149. 'object': rec,
  150. 'obj': rec,
  151. 'pool': self.pool,
  152. 'cr': self._cr,
  153. 'uid': self._uid,
  154. 'user': user,
  155. 'time': time,
  156. # copy context to prevent side-effects of eval
  157. 'context': self._context.copy()}
  158. @api.model
  159. def _rule_eval(self, rule, obj_name, rec):
  160. expr = rule.code
  161. space = self._exception_rule_eval_context(obj_name, rec)
  162. try:
  163. safe_eval(expr,
  164. space,
  165. mode='exec',
  166. nocopy=True) # nocopy allows to return 'result'
  167. except Exception, e:
  168. raise except_orm(
  169. _('Error'),
  170. _('Error when evaluating the sale exception '
  171. 'rule:\n %s \n(%s)') % (rule.name, e))
  172. return space.get('failed', False)
  173. @api.multi
  174. def _detect_exceptions(self, order_exceptions,
  175. line_exceptions):
  176. self.ensure_one()
  177. exception_ids = []
  178. for rule in order_exceptions:
  179. if self._rule_eval(rule, 'order', self):
  180. exception_ids.append(rule.id)
  181. for order_line in self.order_line:
  182. for rule in line_exceptions:
  183. if rule.id in exception_ids:
  184. # we do not matter if the exception as already been
  185. # found for an order line of this order
  186. continue
  187. if self._rule_eval(rule, 'line', order_line):
  188. exception_ids.append(rule.id)
  189. return exception_ids
  190. @api.one
  191. def copy(self, default=None):
  192. if default is None:
  193. default = {}
  194. default.update({
  195. 'ignore_exceptions': False,
  196. })
  197. return super(SaleOrder, self).copy(default=default)