shopinvader/odoo-shopinvader

View on GitHub
shopinvader/models/sale.py

Summary

Maintainability
A
3 hrs
Test Coverage
# -*- coding: utf-8 -*-
# Copyright 2017 Akretion (http://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).


import logging

from odoo import api, fields, models

from odoo.addons.queue_job.job import job

_logger = logging.getLogger(__name__)


class ShopinvaderCartStep(models.Model):
    _name = "shopinvader.cart.step"
    _description = "Shopinvader Cart Step"

    name = fields.Char(required=True)
    code = fields.Char(required=True)


class SaleOrder(models.Model):
    _name = "sale.order"
    _inherit = ["sale.order", "track.external.mixin"]

    typology = fields.Selection(
        [("sale", "Sale"), ("cart", "Cart")], default="sale"
    )
    shopinvader_backend_id = fields.Many2one("shopinvader.backend", "Backend")
    current_step_id = fields.Many2one(
        "shopinvader.cart.step", "Current Cart Step", readonly=True
    )
    done_step_ids = fields.Many2many(
        comodel_name="shopinvader.cart.step",
        string="Done Cart Step",
        readonly=True,
    )
    # TODO move this in an extra OCA module
    shopinvader_state = fields.Selection(
        [
            ("cancel", "Cancel"),
            ("pending", "Pending"),
            ("processing", "Processing"),
            ("shipped", "Shipped"),
        ],
        compute="_compute_shopinvader_state",
        store=True,
    )
    shopinvader_to_be_recomputed = fields.Boolean(
        help="Technical field that will intend to check if the sale order has"
        "to be recomputed due to a modification (asynchronous)"
    )

    def _shopinvader_fields_recompute(self):
        """
        Call recompute_todo to build the list of fields to recompute
        TODO: Limit the real changed fields and built the job identity_key
            with them to ensure we forget no field
        :return:
        """
        self.order_line.shopinvader_recompute()
        for _name, field in self._fields.items():
            if field.compute and field.store:
                self._recompute_todo(field)

    @job(default_channel="root.shopinvader")
    def _shopinvader_delayed_recompute(self):
        self.shopinvader_recompute()

    @api.multi
    def _shopinvader_recompute(self):
        """
        Method called if shopinvader_to_be_recomputed is True
        Can be inherited
        :return:
        """
        self._shopinvader_fields_recompute()
        self.recompute()
        self.shopinvader_to_be_recomputed = False

    @api.multi
    def shopinvader_recompute(self):
        self.ensure_one()
        if self.shopinvader_to_be_recomputed:
            self._shopinvader_recompute()

    def _get_shopinvader_state(self):
        self.ensure_one()
        if self.state == "cancel":
            return "cancel"
        elif self.state == "done":
            return "shipped"
        elif self.state == "draft":
            return "pending"
        else:
            return "processing"

    @api.depends("state")
    def _compute_shopinvader_state(self):
        # simple way to have more human friendly name for
        # the sale order on the website
        for record in self:
            record.shopinvader_state = record._get_shopinvader_state()

    def _prepare_invoice(self):
        res = super(SaleOrder, self)._prepare_invoice()
        res["shopinvader_backend_id"] = self.shopinvader_backend_id.id
        return res

    @api.multi
    def action_confirm_cart(self):
        for record in self:
            if record.typology == "sale":
                # cart is already confirmed
                continue
            record.write({"typology": "sale"})
            if record.shopinvader_backend_id:
                record.shopinvader_backend_id._send_notification(
                    "cart_confirmation", record
                )
        return True

    @api.multi
    def action_confirm(self):
        res = super(SaleOrder, self).action_confirm()
        for record in self:
            if record.state != "draft" and record.shopinvader_backend_id:
                # If we confirm a cart directly we change the typology
                if record.typology != "sale":
                    record.typology = "sale"
                record.shopinvader_backend_id._send_notification(
                    "sale_confirmation", record
                )
        return res

    def reset_price_tax(self):
        for record in self:
            record.order_line.reset_price_tax()

    def _need_price_update(self, vals):
        for field in ["fiscal_position_id", "pricelist_id"]:
            if field in vals and self[field].id != vals[field]:
                return True
        return False

    @api.multi
    def write_with_onchange(self, vals):
        self.ensure_one()
        # Playing onchange on one2many is not really really clean
        # all value are returned even if their are not modify
        # Moreover "convert_to_onchange" in field.py add (5,) that
        # will drop the order_line
        # so it's better to drop the key order_line and run the onchange
        # on line manually
        reset_price = False
        new_vals = self.play_onchanges(vals, vals.keys())
        new_vals.pop("order_line", None)
        vals.update(new_vals)
        reset_price = self._need_price_update(vals)
        self.write(vals)
        if reset_price:
            self.reset_price_tax()
        return True

    @api.multi
    def _update_pricelist_and_update_line_prices(self):
        """
        On current sale orders (self):
        - Update the pricelist with the one set on the backend;
            => If no pricelist, use the default define on the partner.
        - Then launch the price update (module sale_order_price_recalculation).
        :return: bool
        """
        backends = self.mapped("shopinvader_backend_id").filtered(
            lambda b: b.pricelist_id
        )
        other_sales = self
        for backend in backends:
            pricelist = backend.pricelist_id
            sales = self.filtered(
                lambda s, b=backend: s.shopinvader_backend_id == b
            )
            other_sales -= sales
            sales.write({"pricelist_id": pricelist.id})
        # We don't have a pricelist on the backend so use the one set
        # on the partner
        partner_pricelists = other_sales.mapped(
            "partner_id.property_product_pricelist"
        )
        # Group by pricelist to do less write as possible.
        for pricelist in partner_pricelists:
            sales = other_sales.filtered(
                lambda s, p=pricelist: s.partner_id.property_product_pricelist
                == p
            )
            sales.write({"pricelist_id": pricelist.id})
        # Even if the pricelist is not updated on the SO, we have to launch
        # the price recalculation in case of pricelist content is updated.
        self.recalculate_prices()
        return True


class SaleOrderLine(models.Model):
    _inherit = "sale.order.line"

    shopinvader_variant_id = fields.Many2one(
        "shopinvader.variant",
        compute="_compute_shopinvader_variant",
        string="Shopinvader Variant",
    )

    @api.multi
    def shopinvader_recompute(self):
        for line in self:
            for _name, field in line._fields.items():
                if field.compute and field.store:
                    line._recompute_todo(field)
        self.recompute()

    def reset_price_tax(self):
        for line in self:
            line.product_id_change()
            line._onchange_discount()

    @api.depends("order_id.shopinvader_backend_id", "product_id")
    def _compute_shopinvader_variant(self):
        lang = self._context.get("lang")
        if not lang:
            _logger.warning(
                "No lang specified for getting the shopinvader variant "
                "take the first binding"
            )
        for record in self:
            bindings = record.product_id.shopinvader_bind_ids
            for binding in bindings:
                if (
                    binding.backend_id
                    != record.order_id.shopinvader_backend_id
                ):
                    continue
                if lang and binding.lang_id.code != lang:
                    continue
                record.shopinvader_variant_id = binding