# Copyright 2018-2019 ForgeFlow, S.L. # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) from odoo import _, api, fields, models from odoo.exceptions import UserError _STATES = [ ("draft", "Draft"), ("to_approve", "To be approved"), ("approved", "Approved"), ("in_progress", "In progress"), ("done", "Done"), ("rejected", "Rejected"), ] class PurchaseRequest(models.Model): _name = "purchase.request" _description = "Purchase Request" _mail_post_access = "read" _inherit = ["mail.thread", "mail.activity.mixin"] _order = "id desc" @api.model def _company_get(self): return self.env["res.company"].browse(self.env.company.id) @api.model def _get_default_requested_by(self): return self.env["res.users"].browse(self.env.uid) @api.model def _get_default_name(self): return self.env["ir.sequence"].next_by_code("purchase.request") @api.model def _default_picking_type(self): type_obj = self.env["stock.picking.type"] company_id = self.env.context.get("company_id") or self.env.company.id types = type_obj.search( [("code", "=", "incoming"), ("warehouse_id.company_id", "=", company_id)] ) if not types: types = type_obj.search( [("code", "=", "incoming"), ("warehouse_id", "=", False)] ) return types[:1] @api.depends("state") def _compute_is_editable(self): for rec in self: if rec.state in ( "to_approve", "approved", "rejected", "in_progress", "done", ): rec.is_editable = False else: rec.is_editable = True name = fields.Char( string="Request Reference", required=True, default=lambda self: _("New"), tracking=True, ) is_name_editable = fields.Boolean( default=lambda self: self.env.user.has_group("base.group_no_one"), ) origin = fields.Char(string="Source Document") date_start = fields.Date( string="Creation date", help="Date when the user initiated the request.", default=fields.Date.context_today, tracking=True, ) requested_by = fields.Many2one( comodel_name="res.users", required=True, copy=False, tracking=True, default=_get_default_requested_by, index=True, ) assigned_to = fields.Many2one( comodel_name="res.users", string="Approver", tracking=True, domain=lambda self: [ ( "groups_id", "in", self.env.ref("purchase_request.group_purchase_request_manager").id, ) ], index=True, ) description = fields.Text() company_id = fields.Many2one( comodel_name="res.company", required=False, default=_company_get, tracking=True, ) line_ids = fields.One2many( comodel_name="purchase.request.line", inverse_name="request_id", string="Products to Purchase", readonly=True, copy=True, tracking=True, states={"draft": [("readonly", False)]}, ) product_id = fields.Many2one( comodel_name="product.product", related="line_ids.product_id", string="Product", readonly=True, ) state = fields.Selection( selection=_STATES, string="Status", index=True, tracking=True, required=True, copy=False, default="draft", ) is_editable = fields.Boolean(compute="_compute_is_editable", readonly=True) to_approve_allowed = fields.Boolean(compute="_compute_to_approve_allowed") picking_type_id = fields.Many2one( comodel_name="stock.picking.type", string="Picking Type", required=True, default=_default_picking_type, ) group_id = fields.Many2one( comodel_name="procurement.group", string="Procurement Group", copy=False, index=True, ) line_count = fields.Integer( string="Purchase Request Line count", compute="_compute_line_count", readonly=True, ) move_count = fields.Integer( string="Stock Move count", compute="_compute_move_count", readonly=True ) purchase_count = fields.Integer( string="Purchases count", compute="_compute_purchase_count", readonly=True ) currency_id = fields.Many2one(related="company_id.currency_id", readonly=True) estimated_cost = fields.Monetary( compute="_compute_estimated_cost", string="Total Estimated Cost", store=True, ) @api.depends("line_ids", "line_ids.estimated_cost") def _compute_estimated_cost(self): for rec in self: rec.estimated_cost = sum(rec.line_ids.mapped("estimated_cost")) @api.depends("line_ids") def _compute_purchase_count(self): for rec in self: rec.purchase_count = len(rec.mapped("line_ids.purchase_lines.order_id")) def action_view_purchase_order(self): action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq") lines = self.mapped("line_ids.purchase_lines.order_id") if len(lines) > 1: action["domain"] = [("id", "in", lines.ids)] elif lines: action["views"] = [ (self.env.ref("purchase.purchase_order_form").id, "form") ] action["res_id"] = lines.id return action @api.depends("line_ids") def _compute_move_count(self): for rec in self: rec.move_count = len( rec.mapped("line_ids.purchase_request_allocation_ids.stock_move_id") ) def action_view_stock_picking(self): action = self.env["ir.actions.actions"]._for_xml_id( "stock.action_picking_tree_all" ) # remove default filters action["context"] = {} lines = self.mapped( "line_ids.purchase_request_allocation_ids.stock_move_id.picking_id" ) if len(lines) > 1: action["domain"] = [("id", "in", lines.ids)] elif lines: action["views"] = [(self.env.ref("stock.view_picking_form").id, "form")] action["res_id"] = lines.id return action @api.depends("line_ids") def _compute_line_count(self): for rec in self: rec.line_count = len(rec.mapped("line_ids")) def action_view_purchase_request_line(self): action = ( self.env.ref("purchase_request.purchase_request_line_form_action") .sudo() .read()[0] ) lines = self.mapped("line_ids") if len(lines) > 1: action["domain"] = [("id", "in", lines.ids)] elif lines: action["views"] = [ (self.env.ref("purchase_request.purchase_request_line_form").id, "form") ] action["res_id"] = lines.ids[0] return action @api.depends("state", "line_ids.product_qty", "line_ids.cancelled") def _compute_to_approve_allowed(self): for rec in self: rec.to_approve_allowed = rec.state == "draft" and any( not line.cancelled and line.product_qty for line in rec.line_ids ) def copy(self, default=None): default = dict(default or {}) self.ensure_one() default.update({"state": "draft", "name": self._get_default_name()}) return super(PurchaseRequest, self).copy(default) @api.model def _get_partner_id(self, request): user_id = request.assigned_to or self.env.user return user_id.partner_id.id @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get("name", _("New")) == _("New"): vals["name"] = self._get_default_name() requests = super(PurchaseRequest, self).create(vals_list) for vals, request in zip(vals_list, requests): if vals.get("assigned_to"): partner_id = self._get_partner_id(request) request.message_subscribe(partner_ids=[partner_id]) return requests def write(self, vals): res = super(PurchaseRequest, self).write(vals) for request in self: if vals.get("assigned_to"): partner_id = self._get_partner_id(request) request.message_subscribe(partner_ids=[partner_id]) return res def _can_be_deleted(self): self.ensure_one() return self.state == "draft" def unlink(self): for request in self: if not request._can_be_deleted(): raise UserError( _("You cannot delete a purchase request which is not draft.") ) return super(PurchaseRequest, self).unlink() def button_draft(self): self.mapped("line_ids").do_uncancel() return self.write({"state": "draft"}) def button_to_approve(self): self.to_approve_allowed_check() return self.write({"state": "to_approve"}) def button_approved(self): return self.write({"state": "approved"}) def button_rejected(self): self.mapped("line_ids").do_cancel() return self.write({"state": "rejected"}) def button_in_progress(self): return self.write({"state": "in_progress"}) def button_done(self): return self.write({"state": "done"}) def check_auto_reject(self): """When all lines are cancelled the purchase request should be auto-rejected.""" for pr in self: if not pr.line_ids.filtered(lambda l: l.cancelled is False): pr.write({"state": "rejected"}) def to_approve_allowed_check(self): for rec in self: if not rec.to_approve_allowed: raise UserError( _( "You can't request an approval for a purchase request " "which is empty. (%s)" ) % rec.name )