stock.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from odoo import api, fields, models, _
  4. from odoo.osv.expression import AND
  5. from dateutil.relativedelta import relativedelta
  6. class StockPicking(models.Model):
  7. _inherit = 'stock.picking'
  8. purchase_id = fields.Many2one(
  9. 'purchase.order', related='move_ids.purchase_line_id.order_id',
  10. string="Purchase Orders", readonly=True)
  11. class StockWarehouse(models.Model):
  12. _inherit = 'stock.warehouse'
  13. buy_to_resupply = fields.Boolean('Buy to Resupply', default=True,
  14. help="When products are bought, they can be delivered to this warehouse")
  15. buy_pull_id = fields.Many2one('stock.rule', 'Buy rule')
  16. def _get_global_route_rules_values(self):
  17. rules = super(StockWarehouse, self)._get_global_route_rules_values()
  18. location_id = self.in_type_id.default_location_dest_id
  19. rules.update({
  20. 'buy_pull_id': {
  21. 'depends': ['reception_steps', 'buy_to_resupply'],
  22. 'create_values': {
  23. 'action': 'buy',
  24. 'picking_type_id': self.in_type_id.id,
  25. 'group_propagation_option': 'none',
  26. 'company_id': self.company_id.id,
  27. 'route_id': self._find_global_route('purchase_stock.route_warehouse0_buy', _('Buy')).id,
  28. 'propagate_cancel': self.reception_steps != 'one_step',
  29. },
  30. 'update_values': {
  31. 'active': self.buy_to_resupply,
  32. 'name': self._format_rulename(location_id, False, 'Buy'),
  33. 'location_dest_id': location_id.id,
  34. 'propagate_cancel': self.reception_steps != 'one_step',
  35. }
  36. }
  37. })
  38. return rules
  39. def _get_all_routes(self):
  40. routes = super(StockWarehouse, self)._get_all_routes()
  41. routes |= self.filtered(lambda self: self.buy_to_resupply and self.buy_pull_id and self.buy_pull_id.route_id).mapped('buy_pull_id').mapped('route_id')
  42. return routes
  43. def get_rules_dict(self):
  44. result = super(StockWarehouse, self).get_rules_dict()
  45. for warehouse in self:
  46. result[warehouse.id].update(warehouse._get_receive_rules_dict())
  47. return result
  48. def _get_routes_values(self):
  49. routes = super(StockWarehouse, self)._get_routes_values()
  50. routes.update(self._get_receive_routes_values('buy_to_resupply'))
  51. return routes
  52. def _update_name_and_code(self, name=False, code=False):
  53. res = super(StockWarehouse, self)._update_name_and_code(name, code)
  54. warehouse = self[0]
  55. #change the buy stock rule name
  56. if warehouse.buy_pull_id and name:
  57. warehouse.buy_pull_id.write({'name': warehouse.buy_pull_id.name.replace(warehouse.name, name, 1)})
  58. return res
  59. class ReturnPicking(models.TransientModel):
  60. _inherit = "stock.return.picking"
  61. def _prepare_move_default_values(self, return_line, new_picking):
  62. vals = super(ReturnPicking, self)._prepare_move_default_values(return_line, new_picking)
  63. vals['purchase_line_id'] = return_line.move_id.purchase_line_id.id
  64. return vals
  65. class Orderpoint(models.Model):
  66. _inherit = "stock.warehouse.orderpoint"
  67. show_supplier = fields.Boolean('Show supplier column', compute='_compute_show_suppplier')
  68. supplier_id = fields.Many2one(
  69. 'product.supplierinfo', string='Product Supplier', check_company=True,
  70. domain="['|', ('product_id', '=', product_id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]")
  71. vendor_id = fields.Many2one(related='supplier_id.partner_id', string="Vendor", store=True)
  72. purchase_visibility_days = fields.Float(default=0.0, help="Visibility Days applied on the purchase routes.")
  73. @api.depends('product_id.purchase_order_line_ids.product_qty', 'product_id.purchase_order_line_ids.state')
  74. def _compute_qty(self):
  75. """ Extend to add more depends values """
  76. return super()._compute_qty()
  77. @api.depends('supplier_id')
  78. def _compute_lead_days(self):
  79. return super()._compute_lead_days()
  80. def _compute_visibility_days(self):
  81. res = super()._compute_visibility_days()
  82. for orderpoint in self:
  83. if 'buy' in orderpoint.rule_ids.mapped('action'):
  84. orderpoint.visibility_days = orderpoint.purchase_visibility_days
  85. return res
  86. def _set_visibility_days(self):
  87. res = super()._set_visibility_days()
  88. for orderpoint in self:
  89. if 'buy' in orderpoint.rule_ids.mapped('action'):
  90. orderpoint.purchase_visibility_days = orderpoint.visibility_days
  91. return res
  92. def _compute_days_to_order(self):
  93. res = super()._compute_days_to_order()
  94. for orderpoint in self:
  95. if 'buy' in orderpoint.rule_ids.mapped('action'):
  96. orderpoint.days_to_order = orderpoint.company_id.days_to_purchase
  97. return res
  98. @api.depends('route_id')
  99. def _compute_show_suppplier(self):
  100. buy_route = []
  101. for res in self.env['stock.rule'].search_read([('action', '=', 'buy')], ['route_id']):
  102. buy_route.append(res['route_id'][0])
  103. for orderpoint in self:
  104. orderpoint.show_supplier = orderpoint.route_id.id in buy_route
  105. def action_view_purchase(self):
  106. """ This function returns an action that display existing
  107. purchase orders of given orderpoint.
  108. """
  109. result = self.env['ir.actions.act_window']._for_xml_id('purchase.purchase_rfq')
  110. # Remvove the context since the action basically display RFQ and not PO.
  111. result['context'] = {}
  112. order_line_ids = self.env['purchase.order.line'].search([('orderpoint_id', '=', self.id)])
  113. purchase_ids = order_line_ids.mapped('order_id')
  114. result['domain'] = "[('id','in',%s)]" % (purchase_ids.ids)
  115. return result
  116. def _get_lead_days_values(self):
  117. values = super()._get_lead_days_values()
  118. if self.supplier_id:
  119. values['supplierinfo'] = self.supplier_id
  120. return values
  121. def _get_replenishment_order_notification(self):
  122. self.ensure_one()
  123. domain = [('orderpoint_id', 'in', self.ids)]
  124. if self.env.context.get('written_after'):
  125. domain = AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
  126. order = self.env['purchase.order.line'].search(domain, limit=1).order_id
  127. if order:
  128. action = self.env.ref('purchase.action_rfq_form')
  129. return {
  130. 'type': 'ir.actions.client',
  131. 'tag': 'display_notification',
  132. 'params': {
  133. 'title': _('The following replenishment order has been generated'),
  134. 'message': '%s',
  135. 'links': [{
  136. 'label': order.display_name,
  137. 'url': f'#action={action.id}&id={order.id}&model=purchase.order',
  138. }],
  139. 'sticky': False,
  140. }
  141. }
  142. return super()._get_replenishment_order_notification()
  143. def _prepare_procurement_values(self, date=False, group=False):
  144. values = super()._prepare_procurement_values(date=date, group=group)
  145. values['supplierinfo_id'] = self.supplier_id
  146. return values
  147. def _quantity_in_progress(self):
  148. res = super()._quantity_in_progress()
  149. qty_by_product_location, dummy = self.product_id._get_quantity_in_progress(self.location_id.ids)
  150. for orderpoint in self:
  151. product_qty = qty_by_product_location.get((orderpoint.product_id.id, orderpoint.location_id.id), 0.0)
  152. product_uom_qty = orderpoint.product_id.uom_id._compute_quantity(product_qty, orderpoint.product_uom, round=False)
  153. res[orderpoint.id] += product_uom_qty
  154. return res
  155. def _set_default_route_id(self):
  156. route_id = self.env['stock.rule'].search([
  157. ('action', '=', 'buy')
  158. ], limit=1).route_id
  159. orderpoint_wh_supplier = self.filtered(lambda o: o.product_id.seller_ids)
  160. if route_id and orderpoint_wh_supplier and (not self.product_id.route_ids or route_id in self.product_id.route_ids):
  161. orderpoint_wh_supplier.route_id = route_id[0].id
  162. return super()._set_default_route_id()
  163. class StockLot(models.Model):
  164. _inherit = 'stock.lot'
  165. purchase_order_ids = fields.Many2many('purchase.order', string="Purchase Orders", compute='_compute_purchase_order_ids', readonly=True, store=False)
  166. purchase_order_count = fields.Integer('Purchase order count', compute='_compute_purchase_order_ids')
  167. @api.depends('name')
  168. def _compute_purchase_order_ids(self):
  169. for lot in self:
  170. stock_moves = self.env['stock.move.line'].search([
  171. ('lot_id', '=', lot.id),
  172. ('state', '=', 'done')
  173. ]).mapped('move_id')
  174. stock_moves = stock_moves.search([('id', 'in', stock_moves.ids)]).filtered(
  175. lambda move: move.picking_id.location_id.usage == 'supplier' and move.state == 'done')
  176. lot.purchase_order_ids = stock_moves.mapped('purchase_line_id.order_id')
  177. lot.purchase_order_count = len(lot.purchase_order_ids)
  178. def action_view_po(self):
  179. self.ensure_one()
  180. action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_form_action")
  181. action['domain'] = [('id', 'in', self.mapped('purchase_order_ids.id'))]
  182. action['context'] = dict(self._context, create=False)
  183. return action
  184. class ProcurementGroup(models.Model):
  185. _inherit = 'procurement.group'
  186. @api.model
  187. def run(self, procurements, raise_user_error=True):
  188. wh_by_comp = dict()
  189. for procurement in procurements:
  190. routes = procurement.values.get('route_ids')
  191. if routes and any(r.action == 'buy' for r in routes.rule_ids):
  192. company = procurement.company_id
  193. if company not in wh_by_comp:
  194. wh_by_comp[company] = self.env['stock.warehouse'].search([('company_id', '=', company.id)])
  195. wh = wh_by_comp[company]
  196. procurement.values['route_ids'] |= wh.reception_route_id
  197. return super().run(procurements, raise_user_error=raise_user_error)