You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
7.7 KiB

  1. # Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
  2. # Copyright 2018 David Vidal <david.vidal@tecnativa.com>
  3. # Copyright 2018 Lambda IS DOOEL <https://www.lambda-is.com>
  4. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  5. from odoo import _, api, fields, models
  6. from odoo.exceptions import ValidationError
  7. class PosOrder(models.Model):
  8. _inherit = 'pos.order'
  9. returned_order_id = fields.Many2one(
  10. comodel_name='pos.order',
  11. string='Returned Order',
  12. readonly=True,
  13. )
  14. refund_order_ids = fields.One2many(
  15. comodel_name='pos.order',
  16. inverse_name='returned_order_id',
  17. string='Refund Orders',
  18. readonly=True,
  19. )
  20. refund_order_qty = fields.Integer(
  21. compute='_compute_refund_order_qty',
  22. string='Refund Orders Quantity',
  23. )
  24. def _compute_refund_order_qty(self):
  25. order_data = self.env['pos.order'].read_group(
  26. [('returned_order_id', 'in', self.ids)],
  27. ['returned_order_id'], ['returned_order_id']
  28. )
  29. mapped_data = dict(
  30. [(order['returned_order_id'][0], order['returned_order_id_count'])
  31. for order in order_data])
  32. for order in self:
  33. order.refund_order_qty = mapped_data.get(order.id, 0)
  34. def _blank_refund(self, res):
  35. self.ensure_one()
  36. new_order = self.browse(res['res_id'])
  37. new_order.returned_order_id = self
  38. # Remove created lines and recreate and link Lines
  39. new_order.lines.unlink()
  40. return new_order
  41. def _prepare_invoice(self):
  42. res = super(PosOrder, self)._prepare_invoice()
  43. if not self.returned_order_id.invoice_id:
  44. return res
  45. res.update({
  46. 'origin': self.returned_order_id.invoice_id.number,
  47. 'name': _(
  48. 'Return of %s' % self.returned_order_id.invoice_id.number),
  49. 'refund_invoice_id': self.returned_order_id.invoice_id.id,
  50. })
  51. return res
  52. def _action_pos_order_invoice(self):
  53. """Wrap common process"""
  54. self.action_pos_order_invoice()
  55. self.invoice_id.sudo().action_invoice_open()
  56. self.account_move = self.invoice_id.move_id
  57. def refund(self):
  58. # Call super to use original refund algorithm (session management, ...)
  59. ctx = dict(self.env.context, do_not_check_negative_qty=True)
  60. res = super(PosOrder, self.with_context(ctx)).refund()
  61. new_order = self._blank_refund(res)
  62. for line in self.lines:
  63. qty = - line.max_returnable_qty([])
  64. if qty != 0:
  65. copy_line = line.copy(
  66. {
  67. 'order_id': new_order.id,
  68. 'returned_line_id': line.id,
  69. 'qty': qty,
  70. }
  71. )
  72. copy_line._onchange_amount_line_all()
  73. new_order._onchange_amount_all()
  74. return res
  75. def partial_refund(self, partial_return_wizard):
  76. ctx = dict(self.env.context, partial_refund=True)
  77. res = self.with_context(ctx).refund()
  78. new_order = self._blank_refund(res)
  79. for wizard_line in partial_return_wizard.line_ids:
  80. qty = -wizard_line.qty
  81. if qty != 0:
  82. copy_line = wizard_line.pos_order_line_id.copy(
  83. {
  84. 'order_id': new_order.id,
  85. 'returned_line_id': wizard_line.pos_order_line_id.id,
  86. 'qty': qty,
  87. }
  88. )
  89. copy_line._onchange_amount_line_all()
  90. new_order._onchange_amount_all()
  91. return res
  92. def action_pos_order_paid(self):
  93. res = super(PosOrder, self).action_pos_order_paid()
  94. if self.returned_order_id and self.returned_order_id.invoice_id:
  95. self._action_pos_order_invoice()
  96. return res
  97. def _create_picking_return(self):
  98. self.ensure_one()
  99. picking = self.returned_order_id.picking_id
  100. ctx = dict(self.env.context,
  101. active_ids=picking.ids, active_id=picking.id)
  102. wizard = self.env['stock.return.picking'].with_context(ctx).create({})
  103. # Discard not returned lines
  104. wizard.product_return_moves.filtered(
  105. lambda x: x.product_id not in self.mapped(
  106. 'lines.product_id')).unlink()
  107. to_return = {}
  108. for product in self.lines.mapped('product_id'):
  109. to_return[product] = -sum(
  110. self.lines.filtered(
  111. lambda x: x.product_id == product).mapped('qty'))
  112. for move in wizard.product_return_moves:
  113. if to_return[move.product_id] < move.quantity:
  114. move.quantity = to_return[move.product_id]
  115. to_return[move.product_id] -= move.quantity
  116. return wizard
  117. def create_picking(self):
  118. """Odoo bases return picking if the quantities are negative, but it's
  119. not linked to the original one"""
  120. orders = self.filtered(
  121. lambda x: not x.returned_order_id
  122. or not x.returned_order_id.picking_id)
  123. res = super(PosOrder, orders).create_picking()
  124. for order in self - orders:
  125. wizard = order._create_picking_return()
  126. res = wizard.create_returns()
  127. order.write({'picking_id': res['res_id']})
  128. order._force_picking_done(order.picking_id)
  129. return res
  130. class PosOrderLine(models.Model):
  131. _inherit = 'pos.order.line'
  132. returned_line_id = fields.Many2one(
  133. comodel_name='pos.order.line',
  134. string='Returned Order',
  135. readonly=True,
  136. )
  137. refund_line_ids = fields.One2many(
  138. comodel_name='pos.order.line',
  139. inverse_name='returned_line_id',
  140. string='Refund Lines',
  141. readonly=True,
  142. )
  143. @api.model
  144. def max_returnable_qty(self, ignored_line_ids):
  145. qty = self.qty
  146. for refund_line in self.refund_line_ids:
  147. if refund_line.id not in ignored_line_ids:
  148. qty += refund_line.qty
  149. return qty
  150. @api.constrains('returned_line_id', 'qty')
  151. def _check_return_qty(self):
  152. if self.env.context.get('do_not_check_negative_qty', False):
  153. return True
  154. for line in self:
  155. if line.returned_line_id and -line.qty > line.returned_line_id.qty:
  156. raise ValidationError(_(
  157. "You can not return %d %s of %s because the original "
  158. "Order line only mentions %d %s."
  159. ) % (-line.qty, line.product_id.uom_id.name,
  160. line.product_id.name, line.returned_line_id.qty,
  161. line.product_id.uom_id.name))
  162. if (line.returned_line_id and
  163. -line.qty >
  164. line.returned_line_id.max_returnable_qty([line.id])):
  165. raise ValidationError(_(
  166. "You can not return %d %s of %s because some refunds"
  167. " have already been done.\n Maximum quantity allowed :"
  168. " %d %s."
  169. ) % (-line.qty, line.product_id.uom_id.name,
  170. line.product_id.name,
  171. line.returned_line_id.max_returnable_qty([line.id]),
  172. line.product_id.uom_id.name))
  173. if (not line.returned_line_id and
  174. line.qty < 0 and not
  175. line.product_id.product_tmpl_id.pos_allow_negative_qty):
  176. raise ValidationError(_(
  177. "For legal and traceability reasons, you can not set a"
  178. " negative quantity (%d %s of %s), without using "
  179. "return wizard."
  180. ) % (line.qty, line.product_id.uom_id.name,
  181. line.product_id.name))