shopinvader/odoo-shopinvader

View on GitHub
shopinvader_sale_profile/models/shopinvader_partner.py

Summary

Maintainability
A
50 mins
Test Coverage
# -*- coding: utf-8 -*-
# Copyright 2018 Akretion (http://www.akretion.com).
# Copyright 2018 ACSONE SA/NV (<http://acsone.eu>)
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, exceptions, fields, models
from odoo.fields import first


class ShopinvaderPartner(models.Model):
    _inherit = "shopinvader.partner"

    sale_profile_id = fields.Many2one(
        "shopinvader.sale.profile",
        "Sale profile",
        compute="_compute_sale_profile_id",
        compute_sudo=True,
        store=True,
        help="Sale profile computed, depending every fields who make the "
        "fiscal position (country, zip, vat, account position,...)",
    )

    @api.multi
    @api.depends(
        "record_id.country_id",
        "country_id",
        "record_id.vat",
        "vat",
        "record_id.property_product_pricelist",
        "property_product_pricelist",
        "property_account_position_id",
        "state_id",
        "zip",
        "backend_id.use_sale_profile",
        "backend_id.sale_profile_ids",
        "backend_id.company_id",
    )
    def _compute_sale_profile_id(self):
        """
        Compute function for the field sale_profile_id.
        :return:
        """
        sale_profile_obj = self.env["shopinvader.sale.profile"]
        # Only backends using profiles
        backend_ids = (
            self.mapped("backend_id")
            .filtered(lambda b: b.use_sale_profile)
            .with_prefetch(self._prefetch)
            .ids
        )
        partners = self.mapped("record_id")
        default_sale_profiles = self._get_default_profiles(backend_ids)
        # company_id field is mandatory so we don't have manage empty value
        for company in self.mapped("backend_id.company_id"):
            fposition_by_partner = self._get_fiscal_position_by_partner(
                partners, company_id=company.id
            )
            # Get every fiscal position ids (without duplicates)
            fposition_ids = list(set(fposition_by_partner.values()))
            pricelists = partners.with_context(
                force_company=company.id
            ).mapped("property_product_pricelist")
            sale_profiles = self._get_sale_profiles(
                backend_ids, pricelists, fposition_ids, company
            )
            shopinv_partners = self.filtered(
                lambda p, c=company: p.backend_id.company_id.id == c.id
            )
            shopinv_partners = shopinv_partners.with_context(
                force_company=company.id
            )
            for binding in shopinv_partners:
                sale_profile = sale_profile_obj.browse()
                # Only if the related backend use sale profiles
                if binding.backend_id.use_sale_profile:
                    partner = binding.record_id
                    fposition_id = fposition_by_partner.get(partner.id, False)
                    sale_profile = binding._sale_profile_with_backend(
                        default_sale_profiles, fposition_id, sale_profiles
                    )
                # We change the context during the compute so we have to
                # update values manually (because we have 2 caches;
                # due to with_context())
                record = self.filtered(lambda p, b=binding: p.id == b.id)
                record = record.with_prefetch(self._prefetch)
                record.sale_profile_id = sale_profile

    @api.multi
    def _sale_profile_with_backend(
        self, default_sale_profiles, fposition_id, sale_profiles
    ):
        """
        Get the sale profile of current recordset based on default
        sale profiles given in parameters, fiscal position id and
        every related profile of related backend
        :param default_sale_profiles: shopinvader.sale.profile recordset
        :param fposition_id: int
        :param sale_profiles: shopinvader.sale.profile recordset
        :return: shopinvader.sale.profile recordset
        """
        self.ensure_one()
        sale_profile = self.env["shopinvader.sale.profile"].browse()
        partner = self.record_id
        backend = self.backend_id
        if fposition_id:
            # Get the sale profile using the fiscal position, pricelist,...
            pricelist = partner.property_product_pricelist
            sale_profile = sale_profiles.filtered(
                lambda p, fpos=fposition_id, pl=pricelist, b=backend: fpos
                in p.fiscal_position_ids.ids
                and p.pricelist_id.id == pl.id
                and p.backend_id.id == b.id
            )
            sale_profile = first(sale_profile.with_prefetch(self._prefetch))
        else:
            pricelist = partner.property_product_pricelist
            sale_profile = sale_profiles.filtered(
                lambda p, pl=pricelist, b=backend: not p.fiscal_position_ids
                and p.pricelist_id.id == pl.id
                and p.backend_id.id == b.id
            )
            sale_profile = first(sale_profile.with_prefetch(self._prefetch))
        if not sale_profile:
            # Get the default sale profile
            sale_profile = default_sale_profiles.filtered(
                lambda p, b=backend: p.backend_id.id == b.id
            )
            sale_profile = first(sale_profile.with_prefetch(self._prefetch))
            if not sale_profile:
                message = (
                    _("No default sale profile found for the backend" " %s")
                    % self.backend_id.name
                )
                raise exceptions.UserError(message)
        return sale_profile

    def _get_sale_profiles(
        self, backend_ids, pricelists, fposition_ids, company=False
    ):
        """
        Get every shopinvader.sale.profile related to given backend,
        fiscal position and pricelists.
        :param fposition_ids: list of int
        :param backend_ids: list of int
        :param pricelists: product.pricelist recordset
        :param company: res.company recordset
        :return: shopinvader.sale.profile recordset
        """
        sale_profile_obj = self.env["shopinvader.sale.profile"]
        if company:
            sale_profile_obj = sale_profile_obj.with_context(
                force_company=company.id
            )
        domain = [
            "|",
            ("fiscal_position_ids", "in", fposition_ids),
            ("fiscal_position_ids", "=", False),
            ("pricelist_id", "in", pricelists.ids),
            ("backend_id", "in", backend_ids),
        ]
        sale_profiles = sale_profile_obj.search(domain)
        return sale_profiles

    @api.model
    def _get_default_profiles(self, backend_ids):
        """
        Get every default profile for given backend
        :param backend_ids: list of int
        :return: shopinvader.sale.profile recordset
        """
        sale_profile_obj = self.env["shopinvader.sale.profile"]
        domain_default_profiles = [
            ("default", "=", True),
            ("backend_id", "in", backend_ids),
        ]
        default_sale_profiles = sale_profile_obj.search(
            domain_default_profiles
        )
        return default_sale_profiles

    @api.model
    def _get_fiscal_position_by_partner(self, partners, company_id=False):
        """
        Get every fiscal position related to given partners
        :param partners: res.partner recordset
        :param company_id: int
        :return: account.fiscal.position recordset
        """
        fposition_obj = self.env["account.fiscal.position"]
        if company_id:
            fposition_obj = fposition_obj.with_context(
                force_company=company_id
            )
        fposition_by_partner = {}
        for partner in partners:
            fpos_id = fposition_obj.get_fiscal_position(
                partner.id, delivery_id=partner.id
            )
            if fpos_id:
                fposition_by_partner.update({partner.id: fpos_id})
        return fposition_by_partner