diff --git a/addons/delivery/controllers/location_selector.py b/addons/delivery/controllers/location_selector.py index 5222c1f47fc66..9ada6ec1d546f 100644 --- a/addons/delivery/controllers/location_selector.py +++ b/addons/delivery/controllers/location_selector.py @@ -4,21 +4,20 @@ class LocationSelectorController(Controller): - - @route('/delivery/set_pickup_location', type='jsonrpc', auth='user') + @route("/delivery/set_pickup_location", type="jsonrpc", auth="user") def delivery_set_pickup_location(self, order_id, pickup_location_data): - """ Fetch the order and set the pickup location on the current order. + """Fetch the order and set the pickup location on the current order. :param int order_id: The sales order, as a `sale.order` id. :param str pickup_location_data: The JSON-formatted pickup location address. :return: None """ - order = request.env['sale.order'].browse(order_id) + order = request.env["sale.order"].browse(order_id) order._set_pickup_location(pickup_location_data) - @route('/delivery/get_pickup_locations', type='jsonrpc', auth='user') + @route("/delivery/get_pickup_locations", type="jsonrpc", auth="user") def delivery_get_pickup_locations(self, order_id, zip_code=None): - """ Fetch the order and return the pickup locations close to a given zip code. + """Fetch the order and return the pickup locations close to a given zip code. Determine the country based on GeoIP or fallback on the order's delivery address' country. @@ -27,10 +26,10 @@ def delivery_get_pickup_locations(self, order_id, zip_code=None): :return: The close pickup locations data. :rtype: dict """ - order = request.env['sale.order'].browse(order_id) + order = request.env["sale.order"].browse(order_id) if request.geoip.country_code: - country = request.env['res.country'].search( - [('code', '=', request.geoip.country_code)], limit=1, + country = request.env["res.country"].search( + [("code", "=", request.geoip.country_code)], limit=1 ) else: country = order.partner_shipping_id.country_id diff --git a/addons/delivery/models/__init__.py b/addons/delivery/models/__init__.py index 0acd8064567d5..403f07f6ce4dd 100644 --- a/addons/delivery/models/__init__.py +++ b/addons/delivery/models/__init__.py @@ -1,13 +1,15 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import delivery_carrier -from . import delivery_price_rule -from . import delivery_zip_prefix -from . import ir_http -from . import ir_module_module -from . import payment_provider -from . import payment_transaction -from . import product_category -from . import res_partner -from . import sale_order -from . import sale_order_line +from . import ( + delivery_carrier, + delivery_price_rule, + delivery_zip_prefix, + ir_http, + ir_module_module, + payment_provider, + payment_transaction, + product_category, + res_partner, + sale_order, + sale_order_line, +) diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index 70b9cb9af19bc..9ea2f91cfb24a 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -11,11 +11,11 @@ class DeliveryCarrier(models.Model): - _name = 'delivery.carrier' + _name = "delivery.carrier" _description = "Delivery Method" - _order = 'sequence, id' + _order = "sequence, id" - ''' A Shipping Provider + """A Shipping Provider In order to add your own external provider, follow these steps: @@ -29,113 +29,192 @@ class DeliveryCarrier(models.Model): _cancel_shipment __get_default_custom_package_code (they are documented hereunder) - ''' + """ - # -------------------------------- # - # Internals for shipping providers # - # -------------------------------- # - - name = fields.Char('Delivery Method', required=True, translate=True) + name = fields.Char(string="Delivery Method", translate=True, required=True) active = fields.Boolean(default=True) sequence = fields.Integer(help="Determine the display order", default=10) - # This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex') + # This field will be overwritten by internal shipping providers by adding their own type. delivery_type = fields.Selection( - [('base_on_rule', 'Based on Rules'), ('fixed', 'Fixed Price')], - string='Provider', - default='fixed', + string="Provider", + selection=[("base_on_rule", "Based on Rules"), ("fixed", "Fixed Price")], + default="fixed", required=True, ) allow_cash_on_delivery = fields.Boolean( string="Cash on Delivery", help="Allow customers to choose Cash on Delivery as their payment method.", ) - integration_level = fields.Selection([('rate', 'Get Rate'), ('rate_and_ship', 'Get Rate and Create Shipment')], string="Integration Level", default='rate_and_ship', help="Action while validating Delivery Orders") - prod_environment = fields.Boolean("Environment", help="Set to True if your credentials are certified for production.") - debug_logging = fields.Boolean('Debug logging', help="Log requests in order to ease debugging") - company_id = fields.Many2one('res.company', string='Company', related='product_id.company_id', store=True, readonly=False) - product_id = fields.Many2one('product.product', string='Delivery Product', required=True, ondelete='restrict') - tracking_url = fields.Char(string='Tracking Link', help="This option adds a link for the customer in the portal to track their package easily. Use as a placeholder in your URL.") - currency_id = fields.Many2one(related='product_id.currency_id') + integration_level = fields.Selection( + help="Action while validating Delivery Orders.", + selection=[("rate", "Get Rate"), ("rate_and_ship", "Get Rate and Create Shipment")], + default="rate_and_ship", + ) + prod_environment = fields.Boolean( + string="Environment", help="Set to True if your credentials are certified for production." + ) + debug_logging = fields.Boolean( + string="Debug logging", help="Log requests in order to ease debugging" + ) + company_id = fields.Many2one( + comodel_name="res.company", related="product_id.company_id", store=True, readonly=False + ) + product_id = fields.Many2one( + string="Delivery Product", + comodel_name="product.product", + ondelete="restrict", + required=True, + ) + tracking_url = fields.Char( + string="Tracking Link", + help="This option adds a link for the customer in the portal to track their package easily." + " Use as a placeholder in your URL.", + ) + currency_id = fields.Many2one(related="product_id.currency_id") invoice_policy = fields.Selection( - selection=[('estimated', "Estimated cost")], string="Invoicing Policy", - default='estimated', - required=True, help="Estimated Cost: the customer will be invoiced the estimated cost of the shipping.", + selection=[("estimated", "Estimated cost")], + default="estimated", + required=True, ) - country_ids = fields.Many2many('res.country', 'delivery_carrier_country_rel', 'carrier_id', 'country_id', 'Countries') - state_ids = fields.Many2many('res.country.state', 'delivery_carrier_state_rel', 'carrier_id', 'state_id', 'States') + country_ids = fields.Many2many( + string="Countries", + comodel_name="res.country", + relation="delivery_carrier_country_rel", + column1="carrier_id", + column2="country_id", + ) + state_ids = fields.Many2many( + string="States", + comodel_name="res.country.state", + relation="delivery_carrier_state_rel", + column1="carrier_id", + column2="state_id", + ) zip_prefix_ids = fields.Many2many( - 'delivery.zip.prefix', 'delivery_zip_prefix_rel', 'carrier_id', 'zip_prefix_id', 'Zip Prefixes', - help="Prefixes of zip codes that this delivery method applies to. Note that regular expressions can be used to support countries with varying zip code lengths, i.e. '$' can be added to end of prefix to match the exact zip (e.g. '100$' will only match '100' and not '1000')") - - max_weight = fields.Float('Max Weight', help="If the total weight of the order is over this weight, the method won't be available.") - weight_uom_name = fields.Char(string='Weight unit of measure label', compute='_compute_weight_uom_name') - max_volume = fields.Float('Max Volume', help="If the total volume of the order is over this volume, the method won't be available.") - volume_uom_name = fields.Char(string='Volume unit of measure label', compute='_compute_volume_uom_name') - must_have_tag_ids = fields.Many2many(string='Must Have Tags', comodel_name='product.tag', relation='product_tag_delivery_carrier_must_have_rel', - help="The method is available only if at least one product of the order has one of these tags.") - excluded_tag_ids = fields.Many2many(string='Excluded Tags', comodel_name='product.tag', relation='product_tag_delivery_carrier_excluded_rel', - help="The method is NOT available if at least one product of the order has one of these tags.") + string="Zip Prefixes", + help="Prefixes of zip codes that this delivery method applies to. Note that regular" + " expressions can be used to support countries with varying zip code lengths, i.e. '$'" + " can be added to end of prefix to match the exact zip (e.g. '100$' will only match" + " '100' and not '1000')", + comodel_name="delivery.zip.prefix", + relation="delivery_zip_prefix_rel", + column1="carrier_id", + column2="zip_prefix_id", + ) + + max_weight = fields.Float( + help="If the total weight of the order is over this weight, the method won't be available." + ) + weight_uom_name = fields.Char( + string="Weight unit of measure label", compute="_compute_weight_uom_name" + ) + max_volume = fields.Float( + help="If the total volume of the order is over this volume, the method won't be available." + ) + volume_uom_name = fields.Char( + string="Volume unit of measure label", compute="_compute_volume_uom_name" + ) + must_have_tag_ids = fields.Many2many( + string="Must Have Tags", + help="The method is available only if at least one product of the order has one of these" + " tags.", + comodel_name="product.tag", + relation="product_tag_delivery_carrier_must_have_rel", + ) + excluded_tag_ids = fields.Many2many( + string="Excluded Tags", + help="The method is NOT available if at least one product of the order has one of these" + " tags.", + comodel_name="product.tag", + relation="product_tag_delivery_carrier_excluded_rel", + ) carrier_description = fields.Text( - 'Description', translate=True, - help="A description of the delivery method that you want to communicate to your customers on the Sales Order and sales confirmation email." - "E.g. instructions for customers to follow.") + string="Description", + help="A description of the delivery method that you want to communicate to your customers" + " on the Sales Order and sales confirmation email. E.g. instructions for customers to" + " follow.", + translate=True, + ) - margin = fields.Float(help='This percentage will be added to the shipping price.') - fixed_margin = fields.Float(help='This fixed amount will be added to the shipping price.') - free_over = fields.Boolean('Free if order amount is above', help="If the order total amount (shipping excluded) is above or equal to this value, the customer benefits from a free shipping", default=False) + margin = fields.Float(help="This percentage will be added to the shipping price.") + fixed_margin = fields.Float(help="This fixed amount will be added to the shipping price.") + free_over = fields.Boolean( + string="Free if order amount is above", + help="If the order total amount (shipping excluded) is above or equal to this value, the" + " customer benefits from a free shipping.", + ) amount = fields.Float( - string="Amount", + help="Amount of the order to benefit from a free shipping, expressed in the company" + " currency.", default=1000, - help="Amount of the order to benefit from a free shipping, expressed in the company currency", ) can_generate_return = fields.Boolean(compute="_compute_can_generate_return") - return_label_on_delivery = fields.Boolean(string="Generate Return Label", help="The return label is automatically generated at the delivery.") - get_return_label_from_portal = fields.Boolean(string="Return Label Accessible from Customer Portal", help="The return label can be downloaded by the customer from the customer portal.") + return_label_on_delivery = fields.Boolean( + string="Generate Return Label", + help="The return label is automatically generated at the delivery.", + ) + get_return_label_from_portal = fields.Boolean( + string="Return Label Accessible from Customer Portal", + help="The return label can be downloaded by the customer from the customer portal.", + ) supports_shipping_insurance = fields.Boolean(compute="_compute_supports_shipping_insurance") shipping_insurance = fields.Integer( - "Insurance Percentage", - help="Shipping insurance is a service which may reimburse senders whose parcels are lost, stolen, and/or damaged in transit.", - default=0 + string="Insurance Percentage", + help="Shipping insurance is a service which may reimburse senders whose parcels are lost," + " stolen, and/or damaged in transit.", + default=0, ) price_rule_ids = fields.One2many( - 'delivery.price.rule', 'carrier_id', 'Pricing Rules', copy=True + string="Pricing Rules", + comodel_name="delivery.price.rule", + inverse_name="carrier_id", + copy=True, ) _margin_not_under_100_percent = models.Constraint( - 'CHECK (margin >= -1)', - 'Margin cannot be lower than -100%', + "CHECK (margin >= -1)", "Margin cannot be lower than -100%" ) _shipping_insurance_is_percentage = models.Constraint( - 'CHECK(shipping_insurance >= 0 AND shipping_insurance <= 100)', - 'The shipping insurance must be a percentage between 0 and 100.', + "CHECK(shipping_insurance >= 0 AND shipping_insurance <= 100)", + "The shipping insurance must be a percentage between 0 and 100.", ) - @api.constrains('must_have_tag_ids', 'excluded_tag_ids') + @api.constrains("must_have_tag_ids", "excluded_tag_ids") def _check_tags(self): for carrier in self: if carrier.must_have_tag_ids & carrier.excluded_tag_ids: - raise UserError(_("Delivery method %(name)s cannot have the same tag in both Must Have Tags and Excluded Tags."), name=carrier.name) + raise UserError( + _( + "Delivery method %(name)s cannot have the same tag in both Must Have Tags" + " and Excluded Tags.", + name=carrier.name, + ) + ) def _compute_weight_uom_name(self): - self.weight_uom_name = self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() + self.weight_uom_name = self.env[ + "product.template" + ]._get_weight_uom_name_from_ir_config_parameter() def _compute_volume_uom_name(self): - self.volume_uom_name = self.env['product.template']._get_volume_uom_name_from_ir_config_parameter() + self.volume_uom_name = self.env[ + "product.template" + ]._get_volume_uom_name_from_ir_config_parameter() - @api.depends('delivery_type') + @api.depends("delivery_type") def _compute_can_generate_return(self): for carrier in self: carrier.can_generate_return = False - @api.depends('delivery_type') + @api.depends("delivery_type") def _compute_supports_shipping_insurance(self): for carrier in self: carrier.supports_shipping_insurance = False @@ -149,20 +228,20 @@ def toggle_debug(self): c.debug_logging = not c.debug_logging def install_more_provider(self): - exclude_apps = ['delivery_barcode', 'delivery_stock_picking_batch', 'delivery_iot'] + exclude_apps = ["delivery_barcode", "delivery_stock_picking_batch", "delivery_iot"] return { - 'name': _('New Providers'), - 'res_model': 'ir.module.module', - 'view_mode': 'kanban,list', - 'views': [ - (self.env.ref('delivery.delivery_provider_module_kanban').id, 'kanban'), - (self.env.ref('delivery.delivery_provider_module_list').id, 'list'), + "name": _("New Providers"), + "res_model": "ir.module.module", + "view_mode": "kanban,list", + "views": [ + (self.env.ref("delivery.delivery_provider_module_kanban").id, "kanban"), + (self.env.ref("delivery.delivery_provider_module_list").id, "list"), ], - 'domain': [['name', '=like', 'delivery_%'], ['name', 'not in', exclude_apps]], - 'type': 'ir.actions.act_window', - 'help': _('''

+ "domain": [["name", "=like", "delivery_%"], ["name", "not in", exclude_apps]], + "type": "ir.actions.act_window", + "help": _("""

Buy Odoo Enterprise now to get more providers. -

'''), +

"""), } def _is_available_for_order(self, order): @@ -171,8 +250,8 @@ def _is_available_for_order(self, order): if not self._match(order.partner_shipping_id, order): return False - if self.delivery_type == 'base_on_rule': - return self.rate_shipment(order).get('success') + if self.delivery_type == "base_on_rule": + return self.rate_shipment(order).get("success") return True @@ -196,45 +275,44 @@ def _match_address(self, partner): if self.state_ids and partner.state_id not in self.state_ids: return False if self.zip_prefix_ids: - regex = re.compile('|'.join(['^' + zip_prefix for zip_prefix in self.zip_prefix_ids.mapped('name')])) + regex = re.compile( + "|".join(["^" + zip_prefix for zip_prefix in self.zip_prefix_ids.mapped("name")]) + ) if not partner.zip or not re.match(regex, partner.zip.upper()): return False return True def _match_must_have_tags(self, source): self.ensure_one() - if source._name == 'sale.order': + if source._name == "sale.order": products = source.order_line.product_id - elif source._name == 'stock.picking': - products = source.move_ids.with_prefetch().mapped('product_id') + elif source._name == "stock.picking": + products = source.move_ids.with_prefetch().mapped("product_id") else: raise UserError(_("Invalid source document type")) return not self.must_have_tag_ids or any( - tag in products.all_product_tag_ids - for tag in self.must_have_tag_ids + tag in products.all_product_tag_ids for tag in self.must_have_tag_ids ) def _match_excluded_tags(self, source): self.ensure_one() - if source._name == 'sale.order': + if source._name == "sale.order": products = source.order_line.product_id - elif source._name == 'stock.picking': - products = source.move_ids.with_prefetch().mapped('product_id') + elif source._name == "stock.picking": + products = source.move_ids.with_prefetch().mapped("product_id") else: raise UserError(_("Invalid source document type")) return not any(tag in products.all_product_tag_ids for tag in self.excluded_tag_ids) def _match_weight(self, source): self.ensure_one() - if source._name == 'sale.order': + if source._name == "sale.order": total_weight = sum( - line.product_id.weight * line.product_qty - for line in source.order_line + line.product_id.weight * line.product_qty for line in source.order_line ) - elif source._name == 'stock.picking': + elif source._name == "stock.picking": total_weight = sum( - move.product_id.weight * move.product_uom_qty - for move in source.move_ids + move.product_id.weight * move.product_uom_qty for move in source.move_ids ) else: raise UserError(_("Invalid source document type")) @@ -242,36 +320,34 @@ def _match_weight(self, source): def _match_volume(self, source): self.ensure_one() - if source._name == 'sale.order': + if source._name == "sale.order": total_volume = sum( - line.product_id.volume * line.product_qty - for line in source.order_line + line.product_id.volume * line.product_qty for line in source.order_line ) - elif source._name == 'stock.picking': + elif source._name == "stock.picking": total_volume = sum( - move.product_id.volume * move.product_uom_qty - for move in source.move_ids + move.product_id.volume * move.product_uom_qty for move in source.move_ids ) else: raise UserError(_("Invalid source document type")) return not self.max_volume or total_volume <= self.max_volume - @api.onchange('integration_level') + @api.onchange("integration_level") def _onchange_integration_level(self): - if self.integration_level == 'rate': - self.invoice_policy = 'estimated' + if self.integration_level == "rate": + self.invoice_policy = "estimated" - @api.onchange('can_generate_return') + @api.onchange("can_generate_return") def _onchange_can_generate_return(self): if not self.can_generate_return: self.return_label_on_delivery = False - @api.onchange('return_label_on_delivery') + @api.onchange("return_label_on_delivery") def _onchange_return_label_on_delivery(self): if not self.return_label_on_delivery: self.get_return_label_from_portal = False - @api.onchange('country_ids') + @api.onchange("country_ids") def _onchange_country_ids(self): self.state_ids -= self.state_ids.filtered( lambda state: state._origin.id not in self.country_ids.state_ids.ids @@ -281,7 +357,10 @@ def _onchange_country_ids(self): def copy_data(self, default=None): vals_list = super().copy_data(default=default) - return [dict(vals, name=self.env._("%s (copy)", carrier.name)) for carrier, vals in zip(self, vals_list)] + return [ + dict(vals, name=self.env._("%s (copy)", carrier.name)) + for carrier, vals in zip(self, vals_list) + ] def _get_delivery_type(self): """Return the delivery type. @@ -294,9 +373,13 @@ def _get_delivery_type(self): def _apply_margins(self, price, order=False): self.ensure_one() - if self.delivery_type == 'fixed': + if self.delivery_type == "fixed": return float(price) - fixed_margin_in_sale_currency = self._compute_currency(order, self.fixed_margin, 'company_to_pricelist') if order else self.fixed_margin + fixed_margin_in_sale_currency = ( + self._compute_currency(order, self.fixed_margin, "company_to_pricelist") + if order + else self.fixed_margin + ) return float(price) * (1.0 + self.margin) + fixed_margin_in_sale_currency # -------------------------- # @@ -304,7 +387,7 @@ def _apply_margins(self, price, order=False): # -------------------------- # def rate_shipment(self, order): - ''' Compute the price of the order shipment + """Compute the price of the order shipment. :param order: record of sale.order :returns: a dict with structure @@ -315,44 +398,46 @@ def rate_shipment(self, order): 'error_message': a string containing an error message, 'warning_message': a string containing a warning message} :rtype: dict - ''' + """ # TODO maybe the currency code? self.ensure_one() - if hasattr(self, '%s_rate_shipment' % self.delivery_type): - res = getattr(self, '%s_rate_shipment' % self.delivery_type)(order) + if hasattr(self, "%s_rate_shipment" % self.delivery_type): + res = getattr(self, "%s_rate_shipment" % self.delivery_type)(order) # apply fiscal position company = self.company_id or order.company_id or self.env.company - res['price'] = self.product_id._get_tax_included_unit_price( + res["price"] = self.product_id._get_tax_included_unit_price( company, company.currency_id, order.date_order, - 'sale', + "sale", fiscal_position=order.fiscal_position_id, - product_price_unit=res['price'], - product_currency=company.currency_id + product_price_unit=res["price"], + product_currency=company.currency_id, ) # apply margin on computed price - res['price'] = self._apply_margins(res['price'], order) + res["price"] = self._apply_margins(res["price"], order) # save the real price in case a free_over rule overide it to 0 - res['carrier_price'] = res['price'] + res["carrier_price"] = res["price"] # free when order is large enough amount_without_delivery = order._compute_amount_total_without_delivery() if ( - res['success'] + res["success"] and self.free_over - and self.delivery_type != 'base_on_rule' - and self._compute_currency(order, amount_without_delivery, 'pricelist_to_company') >= self.amount + and self.delivery_type != "base_on_rule" + and self._compute_currency(order, amount_without_delivery, "pricelist_to_company") + >= self.amount ): - res['warning_message'] = _('The shipping is free since the order amount exceeds %.2f.', self.amount) - res['price'] = 0.0 + res["warning_message"] = _( + "The shipping is free since the order amount exceeds %.2f.", self.amount + ) + res["price"] = 0.0 return res - else: - return { - 'success': False, - 'price': 0.0, - 'error_message': _('Error: this delivery method is not available.'), - 'warning_message': False, - } + return { + "success": False, + "price": 0.0, + "error_message": _("Error: this delivery method is not available."), + "warning_message": False, + } def log_xml(self, xml_string, func): self.ensure_one() @@ -366,15 +451,17 @@ def log_xml(self, xml_string, func): db_registry = Registry(db_name) with db_registry.cursor() as cr: env = api.Environment(cr, SUPERUSER_ID, {}) - IrLogging = env['ir.logging'] - IrLogging.sudo().create({'name': 'delivery.carrier', - 'type': 'server', - 'dbname': db_name, - 'level': 'DEBUG', - 'message': xml_string, - 'path': self.delivery_type, - 'func': func, - 'line': 1}) + IrLogging = env["ir.logging"] + IrLogging.sudo().create({ + "name": "delivery.carrier", + "type": "server", + "dbname": db_name, + "level": "DEBUG", + "message": xml_string, + "path": self.delivery_type, + "func": func, + "line": 1, + }) except psycopg2.Error: pass @@ -382,9 +469,14 @@ def log_xml(self, xml_string, func): # Fixed price shipping, aka a very simple provider # # ------------------------------------------------ # - fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price') + fixed_price = fields.Float( + string="Fixed Price", + compute="_compute_fixed_price", + inverse="_set_product_fixed_price", + store=True, + ) - @api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price') + @api.depends("product_id.list_price", "product_id.product_tmpl_id.list_price") def _compute_fixed_price(self): for carrier in self: carrier.fixed_price = carrier.product_id.list_price @@ -396,15 +488,16 @@ def _set_product_fixed_price(self): def fixed_rate_shipment(self, order): carrier = self._match_address(order.partner_shipping_id) if not carrier: - return {'success': False, - 'price': 0.0, - 'error_message': _('Error: this delivery method is not available for this address.'), - 'warning_message': False} + return { + "success": False, + "price": 0.0, + "error_message": _( + "Error: this delivery method is not available for this address." + ), + "warning_message": False, + } price = order.pricelist_id._get_product_price(self.product_id, 1.0) - return {'success': True, - 'price': price, - 'error_message': False, - 'warning_message': False} + return {"success": True, "price": price, "error_message": False, "warning_message": False} # ----------------------------------- # # Based on rule delivery type methods # @@ -413,40 +506,52 @@ def fixed_rate_shipment(self, order): def base_on_rule_rate_shipment(self, order): carrier = self._match_address(order.partner_shipping_id) if not carrier: - return {'success': False, - 'price': 0.0, - 'error_message': _('Error: this delivery method is not available for this address.'), - 'warning_message': False} + return { + "success": False, + "price": 0.0, + "error_message": _( + "Error: this delivery method is not available for this address." + ), + "warning_message": False, + } try: price_unit = self._get_price_available(order) except UserError as e: - return {'success': False, - 'price': 0.0, - 'error_message': e.args[0], - 'warning_message': False} + return { + "success": False, + "price": 0.0, + "error_message": e.args[0], + "warning_message": False, + } - price_unit = self._compute_currency(order, price_unit, 'company_to_pricelist') + price_unit = self._compute_currency(order, price_unit, "company_to_pricelist") - return {'success': True, - 'price': price_unit, - 'error_message': False, - 'warning_message': False} + return { + "success": True, + "price": price_unit, + "error_message": False, + "warning_message": False, + } def _get_conversion_currencies(self, order, conversion): - company_currency = (self.company_id or self.env['res.company']._get_main_company()).currency_id + company_currency = ( + self.company_id or self.env["res.company"]._get_main_company() + ).currency_id pricelist_currency = order.currency_id - if conversion == 'company_to_pricelist': + if conversion == "company_to_pricelist": return company_currency, pricelist_currency - elif conversion == 'pricelist_to_company': + if conversion == "pricelist_to_company": return pricelist_currency, company_currency def _compute_currency(self, order, price, conversion): from_currency, to_currency = self._get_conversion_currencies(order, conversion) if from_currency.id == to_currency.id: return price - return from_currency._convert(price, to_currency, order.company_id, order.date_order or fields.Date.today()) + return from_currency._convert( + price, to_currency, order.company_id, order.date_order or fields.Date.today() + ) def _get_price_available(self, order): self.ensure_one() @@ -455,7 +560,7 @@ def _get_price_available(self, order): total = weight = volume = quantity = wv = 0 total_delivery = 0.0 for line in order.order_line: - if line.state == 'cancel': + if line.state == "cancel": continue if line.is_delivery: total_delivery += line.price_total @@ -463,33 +568,38 @@ def _get_price_available(self, order): continue if line.product_id.type == "service": continue - qty = line.product_uom_id._compute_quantity(line.product_uom_qty, line.product_id.uom_id) + qty = line.product_uom_id._compute_quantity( + line.product_uom_qty, line.product_id.uom_id + ) weight += (line.product_id.weight or 0.0) * qty volume += (line.product_id.volume or 0.0) * qty wv += (line.product_id.weight or 0.0) * (line.product_id.volume or 0.0) * qty quantity += qty total = (order.amount_total or 0.0) - total_delivery - total = self._compute_currency(order, total, 'pricelist_to_company') + total = self._compute_currency(order, total, "pricelist_to_company") # weight is either, # 1- weight chosen by user in choose.delivery.carrier wizard passed by context # 2- saved weight to use on sale order # 3- total order line weight as fallback - weight = self.env.context.get('order_weight') or order.shipping_weight or weight + weight = self.env.context.get("order_weight") or order.shipping_weight or weight return self._get_price_from_picking(total, weight, volume, quantity, wv=wv) - def _get_price_dict(self, total, weight, volume, quantity, wv=0.): - '''Hook allowing to retrieve dict to be used in _get_price_from_picking() function. - Hook to be overridden when we need to add some field to product and use it in variable factor from price rules. ''' + def _get_price_dict(self, total, weight, volume, quantity, wv=0.0): + """Format delivery price values. + + Hook to be overridden when we need to add some field to product and use it in variable + factor from price rules. + """ return { - 'price': total, - 'volume': volume, - 'weight': weight, - 'wv': wv or volume * weight, - 'quantity': quantity + "price": total, + "volume": volume, + "weight": weight, + "wv": wv or volume * weight, + "quantity": quantity, } - def _get_price_from_picking(self, total, weight, volume, quantity, wv=0.): + def _get_price_from_picking(self, total, weight, volume, quantity, wv=0.0): price = 0.0 criteria_found = False price_dict = self._get_price_dict(total, weight, volume, quantity, wv=wv) diff --git a/addons/delivery/models/delivery_price_rule.py b/addons/delivery/models/delivery_price_rule.py index 20a078be5eb6d..2e14b05b05ce4 100644 --- a/addons/delivery/models/delivery_price_rule.py +++ b/addons/delivery/models/delivery_price_rule.py @@ -3,25 +3,32 @@ from odoo import api, fields, models from odoo.tools import format_amount - VARIABLE_SELECTION = [ - ('weight', "Weight"), - ('volume', "Volume"), - ('wv', "Weight * Volume"), - ('price', "Price"), - ('quantity', "Quantity"), + ("weight", "Weight"), + ("volume", "Volume"), + ("wv", "Weight * Volume"), + ("price", "Price"), + ("quantity", "Quantity"), ] class DeliveryPriceRule(models.Model): - _name = 'delivery.price.rule' + _name = "delivery.price.rule" _description = "Delivery Price Rules" - _order = 'sequence, list_price, id' + _order = "sequence, list_price, id" - @api.depends('variable', 'operator', 'max_value', 'list_base_price', 'list_price', 'variable_factor', 'currency_id') + @api.depends( + "variable", + "operator", + "max_value", + "list_base_price", + "list_price", + "variable_factor", + "currency_id", + ) def _compute_name(self): for rule in self: - name = 'if %s %s %.02f then' % (rule.variable, rule.operator, rule.max_value) + name = "if %s %s %.02f then" % (rule.variable, rule.operator, rule.max_value) if rule.currency_id: base_price = format_amount(self.env, rule.list_base_price, rule.currency_id) price = format_amount(self.env, rule.list_price, rule.currency_id) @@ -29,25 +36,38 @@ def _compute_name(self): base_price = "%.2f" % rule.list_base_price price = "%.2f" % rule.list_price if rule.list_base_price and not rule.list_price: - name = '%s fixed price %s' % (name, base_price) + name = "%s fixed price %s" % (name, base_price) elif rule.list_price and not rule.list_base_price: - name = '%s %s times %s' % (name, price, rule.variable_factor) + name = "%s %s times %s" % (name, price, rule.variable_factor) else: - name = '%s fixed price %s plus %s times %s' % ( - name, base_price, price, rule.variable_factor + name = "%s fixed price %s plus %s times %s" % ( + name, + base_price, + price, + rule.variable_factor, ) rule.name = name - name = fields.Char(compute='_compute_name') + name = fields.Char(compute="_compute_name") sequence = fields.Integer(required=True, default=10) - carrier_id = fields.Many2one('delivery.carrier', 'Carrier', required=True, index=True, ondelete='cascade') - currency_id = fields.Many2one(related='carrier_id.currency_id') + carrier_id = fields.Many2one( + comodel_name="delivery.carrier", ondelete="cascade", required=True, index=True + ) + currency_id = fields.Many2one(related="carrier_id.currency_id") - variable = fields.Selection(selection=VARIABLE_SELECTION, required=True, default='quantity') - operator = fields.Selection([('==', '='), ('<=', '<='), ('<', '<'), ('>=', '>='), ('>', '>')], required=True, default='<=') - max_value = fields.Float('Maximum Value', required=True) - list_base_price = fields.Float(string='Sale Base Price', digits='Product Price', required=True, default=0.0) - list_price = fields.Float('Sale Price', digits='Product Price', required=True, default=0.0) + variable = fields.Selection(selection=VARIABLE_SELECTION, default="quantity", required=True) + operator = fields.Selection( + selection=[("==", "="), ("<=", "<="), ("<", "<"), (">=", ">="), (">", ">")], + default="<=", + required=True, + ) + max_value = fields.Float(string="Maximum Value", required=True) + list_base_price = fields.Float( + string="Sale Base Price", digits="Product Price", default=0.0, required=True + ) + list_price = fields.Float( + string="Sale Price", digits="Product Price", default=0.0, required=True + ) variable_factor = fields.Selection( - selection=VARIABLE_SELECTION, string="Variable Factor", required=True, default='weight' + selection=VARIABLE_SELECTION, default="weight", required=True ) diff --git a/addons/delivery/models/delivery_zip_prefix.py b/addons/delivery/models/delivery_zip_prefix.py index 94d8c715b5edf..bbff60ea30fff 100644 --- a/addons/delivery/models/delivery_zip_prefix.py +++ b/addons/delivery/models/delivery_zip_prefix.py @@ -4,27 +4,25 @@ class DeliveryZipPrefix(models.Model): - """ Zip prefix that a delivery.carrier will deliver to. """ - _name = 'delivery.zip.prefix' - _description = 'Delivery Zip Prefix' - _order = 'name, id' + """Zip prefix that a delivery.carrier will deliver to.""" - name = fields.Char('Prefix', required=True) + _name = "delivery.zip.prefix" + _description = "Delivery Zip Prefix" + _order = "name, id" + + name = fields.Char(string="Prefix", required=True) @api.model_create_multi def create(self, vals_list): for vals in vals_list: - # we cannot easily convert a list of prefix names into upper to compare with partner zips - # later on, so let's ensure they are always upper - vals['name'] = vals['name'].upper() + # we cannot easily convert a list of prefix names into upper to compare with partner + # zips later on, so let's ensure they are always upper + vals["name"] = vals["name"].upper() return super().create(vals_list) def write(self, vals): - if 'name' in vals: - vals['name'] = vals['name'].upper() + if "name" in vals: + vals["name"] = vals["name"].upper() return super().write(vals) - _name_uniq = models.Constraint( - 'unique (name)', - 'Prefix already exists!', - ) + _name_uniq = models.Constraint("unique (name)", "Prefix already exists!") diff --git a/addons/delivery/models/ir_http.py b/addons/delivery/models/ir_http.py index 0a8db84b0ec00..7c543aeffa17f 100644 --- a/addons/delivery/models/ir_http.py +++ b/addons/delivery/models/ir_http.py @@ -4,9 +4,9 @@ class IrHttp(models.AbstractModel): - _inherit = 'ir.http' + _inherit = "ir.http" @classmethod def _get_translation_frontend_modules_name(cls): mods = super()._get_translation_frontend_modules_name() - return mods + ['delivery'] + return mods + ["delivery"] diff --git a/addons/delivery/models/ir_module_module.py b/addons/delivery/models/ir_module_module.py index 93210aba59a53..9a7ba1f3dac90 100644 --- a/addons/delivery/models/ir_module_module.py +++ b/addons/delivery/models/ir_module_module.py @@ -4,20 +4,20 @@ class IrModuleModule(models.Model): - _name = 'ir.module.module' - _inherit = ['ir.module.module'] + _name = "ir.module.module" + _inherit = ["ir.module.module"] def action_view_delivery_methods(self): self.ensure_one() module_name = self.name # e.g., delivery_dhl - if not module_name.startswith('delivery_'): + if not module_name.startswith("delivery_"): return False - delivery_type = module_name.removeprefix('delivery_') # dhl, fedex, etc. - action = self.env.ref('delivery.action_delivery_carrier_form').read()[0] - if delivery_type == 'mondialrelay': - action['context'] = {'search_default_is_mondialrelay': True} + delivery_type = module_name.removeprefix("delivery_") # dhl, fedex, etc. + action = self.env.ref("delivery.action_delivery_carrier_form").read()[0] + if delivery_type == "mondialrelay": + action["context"] = {"search_default_is_mondialrelay": True} else: - action['context'] = {'search_default_delivery_type': delivery_type} + action["context"] = {"search_default_delivery_type": delivery_type} return action diff --git a/addons/delivery/models/payment_provider.py b/addons/delivery/models/payment_provider.py index 7f85225d0bbd8..61d55efaeb306 100644 --- a/addons/delivery/models/payment_provider.py +++ b/addons/delivery/models/payment_provider.py @@ -7,16 +7,16 @@ class PaymentProvider(models.Model): - _inherit = 'payment.provider' + _inherit = "payment.provider" - custom_mode = fields.Selection(selection_add=[('cash_on_delivery', 'Cash On Delivery')]) + custom_mode = fields.Selection(selection_add=[("cash_on_delivery", "Cash On Delivery")]) # === CRUD METHODS === # def _get_default_payment_method_codes(self): - """ Override of `payment` to return the default payment method codes. """ + """Override of `payment` to return the default payment method codes.""" self.ensure_one() - if self.custom_mode != 'cash_on_delivery': + if self.custom_mode != "cash_on_delivery": return super()._get_default_payment_method_codes() return const.DEFAULT_PAYMENT_METHOD_CODES @@ -24,7 +24,7 @@ def _get_default_payment_method_codes(self): @api.model def _get_compatible_providers(self, *args, sale_order_id=None, report=None, **kwargs): - """ Override of payment to exclude COD providers if the delivery method doesn't match. + """Override of payment to exclude COD providers if the delivery method doesn't match. :param int sale_order_id: The sales order to be paid, if any, as a `sale.order` id. :param dict report: The availability report. @@ -35,11 +35,11 @@ def _get_compatible_providers(self, *args, sale_order_id=None, report=None, **kw *args, sale_order_id=sale_order_id, report=report, **kwargs ) - sale_order = self.env['sale.order'].browse(sale_order_id).exists() + sale_order = self.env["sale.order"].browse(sale_order_id).exists() if not sale_order.carrier_id.allow_cash_on_delivery: unfiltered_providers = compatible_providers compatible_providers = compatible_providers.filtered( - lambda p: p.custom_mode != 'cash_on_delivery' + lambda p: p.custom_mode != "cash_on_delivery" ) payment_utils.add_to_report( report, diff --git a/addons/delivery/models/payment_transaction.py b/addons/delivery/models/payment_transaction.py index 0b26b8e4081b2..9a4b3bb2fe074 100644 --- a/addons/delivery/models/payment_transaction.py +++ b/addons/delivery/models/payment_transaction.py @@ -4,15 +4,15 @@ class PaymentTransaction(models.Model): - _inherit = 'payment.transaction' + _inherit = "payment.transaction" def _post_process(self): - """ Override of `payment` to confirm orders with the cash_on_delivery payment method and - trigger a picking creation. """ + """Override of `payment` to confirm orders with the cash_on_delivery payment method and + trigger a picking creation.""" cod_pending_txs = self.filtered( - lambda tx: tx.provider_id.custom_mode == 'cash_on_delivery' and tx.state == 'pending' + lambda tx: tx.provider_id.custom_mode == "cash_on_delivery" and tx.state == "pending" ) - cod_pending_txs.sale_order_ids.filtered( - lambda so: so.state == 'draft' - ).with_context(send_email=True).action_confirm() + cod_pending_txs.sale_order_ids.filtered(lambda so: so.state == "draft").with_context( + send_email=True + ).action_confirm() super()._post_process() diff --git a/addons/delivery/models/product_category.py b/addons/delivery/models/product_category.py index cff4c7ac3ff3f..147b622645176 100644 --- a/addons/delivery/models/product_category.py +++ b/addons/delivery/models/product_category.py @@ -9,6 +9,13 @@ class ProductCategory(models.Model): @api.ondelete(at_uninstall=False) def _unlink_except_delivery_category(self): - delivery_category = self.env.ref('delivery.product_category_deliveries', raise_if_not_found=False) + delivery_category = self.env.ref( + "delivery.product_category_deliveries", raise_if_not_found=False + ) if delivery_category and delivery_category in self: - raise UserError(_("You cannot delete this product category as it is used on the products linked to delivery methods.")) + raise UserError( + _( + "You cannot delete this product category as it is used on the products linked" + " to delivery methods." + ) + ) diff --git a/addons/delivery/models/res_partner.py b/addons/delivery/models/res_partner.py index dd28de04b2a56..ed69097fb8aea 100644 --- a/addons/delivery/models/res_partner.py +++ b/addons/delivery/models/res_partner.py @@ -5,10 +5,15 @@ class ResPartner(models.Model): - _inherit = 'res.partner' + _inherit = "res.partner" - property_delivery_carrier_id = fields.Many2one('delivery.carrier', company_dependent=True, string="Delivery Method", help="Used in sales orders.") + property_delivery_carrier_id = fields.Many2one( + string="Delivery Method", + help="Used in sales orders.", + comodel_name="delivery.carrier", + company_dependent=True, + ) is_pickup_location = fields.Boolean() # Whether it is a pickup point address. def _get_delivery_address_domain(self): - return super()._get_delivery_address_domain() & Domain('is_pickup_location', '=', False) + return super()._get_delivery_address_domain() & Domain("is_pickup_location", "=", False) diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py index 370b4b9b0e9e9..8c6b6a1d60818 100644 --- a/addons/delivery/models/sale_order.py +++ b/addons/delivery/models/sale_order.py @@ -7,60 +7,83 @@ class SaleOrder(models.Model): - _inherit = 'sale.order' + _inherit = "sale.order" pickup_location_data = fields.Json() - carrier_id = fields.Many2one('delivery.carrier', string="Delivery Method", check_company=True, help="Fill this field if you plan to invoice the shipping based on picking.") + carrier_id = fields.Many2one( + string="Delivery Method", + help="Fill this field if you plan to invoice the shipping based on picking.", + comodel_name="delivery.carrier", + check_company=True, + ) delivery_message = fields.Char(readonly=True, copy=False) - delivery_set = fields.Boolean(compute='_compute_delivery_state') - recompute_delivery_price = fields.Boolean('Delivery cost should be recomputed') - is_all_service = fields.Boolean("Service Product", compute="_compute_is_service_products") - shipping_weight = fields.Float("Shipping Weight", compute="_compute_shipping_weight", store=True, readonly=False) + delivery_set = fields.Boolean(compute="_compute_delivery_state") + recompute_delivery_price = fields.Boolean(string="Delivery cost should be recomputed") + is_all_service = fields.Boolean( + string="Service Product", compute="_compute_is_service_products" + ) + shipping_weight = fields.Float(compute="_compute_shipping_weight", store=True, readonly=False) def _compute_partner_shipping_id(self): - """ Override to reset the delivery address when a pickup location was selected. """ + """Override to reset the delivery address when a pickup location was selected.""" super()._compute_partner_shipping_id() for order in self: if order.partner_shipping_id.is_pickup_location: order.partner_shipping_id = order.partner_id - @api.depends('order_line') + @api.depends("order_line") def _compute_is_service_products(self): for so in self: - so.is_all_service = all(line.product_id.type == 'service' for line in so.order_line.filtered(lambda x: not x.display_type)) + so.is_all_service = all( + line.product_id.type == "service" + for line in so.order_line.filtered(lambda x: not x.display_type) + ) def _compute_amount_total_without_delivery(self): self.ensure_one() - delivery_cost = sum([l.price_total for l in self.order_line if l.is_delivery]) + delivery_cost = sum([ol.price_total for ol in self.order_line if ol.is_delivery]) return self.amount_total - delivery_cost - @api.depends('order_line') + @api.depends("order_line") def _compute_delivery_state(self): for order in self: order.delivery_set = any(line.is_delivery for line in order.order_line) - @api.onchange('order_line', 'partner_id', 'partner_shipping_id') + @api.onchange("order_line", "partner_id", "partner_shipping_id") def onchange_order_line(self): self.ensure_one() - delivery_line = self.order_line.filtered('is_delivery') + delivery_line = self.order_line.filtered("is_delivery") if delivery_line: self.recompute_delivery_price = True def _get_update_prices_lines(self): - """ Exclude delivery lines from price list recomputation based on product instead of carrier """ + """Exclude delivery lines from pricelist recomputation based on product instead of + carrier.""" lines = super()._get_update_prices_lines() return lines.filtered(lambda line: not line.is_delivery) def _remove_delivery_line(self): - """Remove delivery products from the sales orders""" + """Remove delivery products from the sales orders.""" delivery_lines = self.order_line.filtered("is_delivery") if not delivery_lines: return to_delete = delivery_lines.filtered(lambda x: x.qty_invoiced == 0) if not to_delete: raise UserError( - _('You can not update the shipping costs on an order where it was already invoiced!\n\nThe following delivery lines (product, invoiced quantity and price) have already been processed:\n\n') - + '\n'.join(['- %s: %s x %s' % (line.product_id.with_context(display_default_code=False).display_name, line.qty_invoiced, line.price_unit) for line in delivery_lines]) + _( + "You can not update the shipping costs on an order where it was already" + " invoiced!\n\nThe following delivery lines (product, invoiced quantity and" + " price) have already been processed:\n\n" + ) + + "\n".join([ + "- %s: %s x %s" + % ( + line.product_id.with_context(display_default_code=False).display_name, + line.qty_invoiced, + line.price_unit, + ) + for line in delivery_lines + ]) ) to_delete.unlink() @@ -72,7 +95,7 @@ def set_delivery_line(self, carrier, amount): return True def _set_pickup_location(self, pickup_location_data): - """ Set the pickup location on the current order. + """Set the pickup location on the current order. Note: self.ensure_one() @@ -80,7 +103,7 @@ def _set_pickup_location(self, pickup_location_data): :return: None """ self.ensure_one() - use_locations_fname = f'{self.carrier_id.delivery_type}_use_locations' + use_locations_fname = f"{self.carrier_id.delivery_type}_use_locations" if hasattr(self.carrier_id, use_locations_fname): use_location = getattr(self.carrier_id, use_locations_fname) if use_location and pickup_location_data: @@ -90,7 +113,7 @@ def _set_pickup_location(self, pickup_location_data): self.pickup_location_data = pickup_location def _get_pickup_locations(self, zip_code=None, country=None, **kwargs): - """ Return the pickup locations of the delivery method close to a given zip code. + """Return the pickup locations of the delivery method close to a given zip code. Use provided `zip_code` and `country` or the order's delivery address to determine the zip code and the country to use. @@ -105,44 +128,44 @@ def _get_pickup_locations(self, zip_code=None, country=None, **kwargs): self.ensure_one() if zip_code: assert country # country is required if zip_code is provided. - partner_address = self.env['res.partner'].new({ - 'active': False, - 'country_id': country.id, - 'zip': zip_code, + partner_address = self.env["res.partner"].new({ + "active": False, + "country_id": country.id, + "zip": zip_code, }) else: partner_address = self.partner_shipping_id try: - error = {'error': _("No pick-up points are available for this delivery address.")} - function_name = f'_{self.carrier_id.delivery_type}_get_close_locations' + error = {"error": _("No pick-up points are available for this delivery address.")} + function_name = f"_{self.carrier_id.delivery_type}_get_close_locations" if not hasattr(self.carrier_id, function_name): return error pickup_locations = getattr(self.carrier_id, function_name)(partner_address, **kwargs) if not pickup_locations: return error - return {'pickup_locations': pickup_locations} + return {"pickup_locations": pickup_locations} except UserError as e: - return {'error': str(e)} + return {"error": str(e)} def action_open_delivery_wizard(self): - view_id = self.env.ref('delivery.choose_delivery_carrier_view_form').id - if self.env.context.get('carrier_recompute'): - name = _('Update shipping cost') + view_id = self.env.ref("delivery.choose_delivery_carrier_view_form").id + if self.env.context.get("carrier_recompute"): + name = _("Update shipping cost") else: - name = _('Add a delivery method') + name = _("Add a delivery method") return { - 'name': name, - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'choose.delivery.carrier', - 'view_id': view_id, - 'views': [(view_id, 'form')], - 'target': 'new', - 'context': { - 'default_order_id': self.id, - 'default_carrier_id': self.carrier_id, - 'default_total_weight': self._get_estimated_weight() - } + "name": name, + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "choose.delivery.carrier", + "view_id": view_id, + "views": [(view_id, "form")], + "target": "new", + "context": { + "default_order_id": self.id, + "default_carrier_id": self.carrier_id, + "default_total_weight": self._get_estimated_weight(), + }, } def _action_confirm(self): @@ -153,51 +176,59 @@ def _action_confirm(self): continue # Retrieve all the data : name, street, city, state, zip, country. - name = order_location.get('name') or order.partner_shipping_id.name - street = order_location['street'] - city = order_location['city'] - zip_code = order_location['zip_code'] - country_code = order_location['country_code'] - country = order.env['res.country'].search([('code', '=', country_code)]).id - state = order.env['res.country.state'].search([ - ('code', '=', order_location['state']), - ('country_id', '=', country), - ]).id if (order_location.get('state') and country) else None + name = order_location.get("name") or order.partner_shipping_id.name + street = order_location["street"] + city = order_location["city"] + zip_code = order_location["zip_code"] + country_code = order_location["country_code"] + country = order.env["res.country"].search([("code", "=", country_code)]).id + state = None + if order_location.get("state") and country: + state = ( + order.env["res.country.state"] + .search([("code", "=", order_location["state"]), ("country_id", "=", country)]) + .id + ) parent_id = order.partner_shipping_id.id email = order.partner_shipping_id.email phone = order.partner_shipping_id.phone # Check if the current partner has a partner of type 'delivery' with the same address. - existing_partner = order.env['res.partner'].search([ - ('street', '=', street), - ('city', '=', city), - ('state_id', '=', state), - ('country_id', '=', country), - ('parent_id', '=', parent_id), - ('type', '=', 'delivery'), - ], limit=1) - - shipping_partner = existing_partner or order.env['res.partner'].create({ - 'parent_id': parent_id, - 'type': 'delivery', - 'name': name, - 'street': street, - 'city': city, - 'state_id': state, - 'zip': zip_code, - 'country_id': country, - 'email': email, - 'phone': phone, - 'is_pickup_location': True, + existing_partner = order.env["res.partner"].search( + [ + ("street", "=", street), + ("city", "=", city), + ("state_id", "=", state), + ("country_id", "=", country), + ("parent_id", "=", parent_id), + ("type", "=", "delivery"), + ], + limit=1, + ) + + shipping_partner = existing_partner or order.env["res.partner"].create({ + "parent_id": parent_id, + "type": "delivery", + "name": name, + "street": street, + "city": city, + "state_id": state, + "zip": zip_code, + "country_id": country, + "email": email, + "phone": phone, + "is_pickup_location": True, + }) + order.with_context(update_delivery_shipping_partner=True).write({ + "partner_shipping_id": shipping_partner }) - order.with_context(update_delivery_shipping_partner=True).write({'partner_shipping_id': shipping_partner}) return super()._action_confirm() def _prepare_delivery_line_vals(self, carrier, price_unit): context = {} if self.partner_id: # set delivery detail in the customer language - context['lang'] = self.partner_id.lang + context["lang"] = self.partner_id.lang carrier = carrier.with_context(lang=self.partner_id.lang) # Apply fiscal position @@ -209,31 +240,30 @@ def _prepare_delivery_line_vals(self, carrier, price_unit): # Create the sales order line if carrier.product_id.description_sale: - so_description = '%s: %s' % (carrier.name, - carrier.product_id.description_sale) + so_description = "%s: %s" % (carrier.name, carrier.product_id.description_sale) else: so_description = carrier.name values = { - 'order_id': self.id, - 'name': so_description, - 'price_unit': price_unit, - 'product_uom_qty': 1, - 'product_id': carrier.product_id.id, - 'tax_ids': [(6, 0, taxes_ids)], - 'is_delivery': True, + "order_id": self.id, + "name": so_description, + "price_unit": price_unit, + "product_uom_qty": 1, + "product_id": carrier.product_id.id, + "tax_ids": [(6, 0, taxes_ids)], + "is_delivery": True, } - if carrier.free_over and self.currency_id.is_zero(price_unit) : - values['name'] = _('%s\nFree Shipping', values['name']) + if carrier.free_over and self.currency_id.is_zero(price_unit): + values["name"] = _("%s\nFree Shipping", values["name"]) if self.order_line: - values['sequence'] = self.order_line[-1].sequence + 1 + values["sequence"] = self.order_line[-1].sequence + 1 del context return values def _create_delivery_line(self, carrier, price_unit): values = self._prepare_delivery_line_vals(carrier, price_unit) - return self.env['sale.order.line'].sudo().create(values) + return self.env["sale.order.line"].sudo().create(values) - @api.depends('order_line.product_uom_qty', 'order_line.product_uom_id') + @api.depends("order_line.product_uom_qty", "order_line.product_uom_id") def _compute_shipping_weight(self): for order in self: order.shipping_weight = order._get_estimated_weight() @@ -241,12 +271,17 @@ def _compute_shipping_weight(self): def _get_estimated_weight(self): self.ensure_one() weight = 0.0 - for order_line in self.order_line.filtered(lambda l: l.product_id.type == 'consu' and not l.is_delivery and not l.display_type and l.product_uom_qty > 0): + for order_line in self.order_line.filtered( + lambda ol: ol.product_id.type == "consu" + and not ol.is_delivery + and not ol.display_type + and ol.product_uom_qty > 0 + ): weight += order_line.product_qty * order_line.product_id.weight return weight def _update_order_line_info(self, product_id, quantity, **kwargs): - """ Override of `sale` to recompute the delivery prices. + """Override of `sale` to recompute the delivery prices. :param int product_id: The product, as a `product.product` id. :return: The unit price price of the product, based on the pricelist of the sale order and diff --git a/addons/delivery/models/sale_order_line.py b/addons/delivery/models/sale_order_line.py index ccd6b448d048f..aad9a66e19469 100644 --- a/addons/delivery/models/sale_order_line.py +++ b/addons/delivery/models/sale_order_line.py @@ -4,18 +4,18 @@ class SaleOrderLine(models.Model): - _inherit = 'sale.order.line' + _inherit = "sale.order.line" is_delivery = fields.Boolean(string="Is a Delivery", default=False) product_qty = fields.Float( - string='Product Qty', compute='_compute_product_qty', digits='Product Unit' + string="Product Qty", compute="_compute_product_qty", digits="Product Unit" ) - recompute_delivery_price = fields.Boolean(related='order_id.recompute_delivery_price') + recompute_delivery_price = fields.Boolean(related="order_id.recompute_delivery_price") def _can_be_invoiced_alone(self): return super()._can_be_invoiced_alone() and not self.is_delivery - @api.depends('product_id', 'product_uom_id', 'product_uom_qty') + @api.depends("product_id", "product_uom_id", "product_uom_qty") def _compute_product_qty(self): for line in self: if not line.product_id or not line.product_uom_id or not line.product_uom_qty: @@ -26,7 +26,7 @@ def _compute_product_qty(self): ) def unlink(self): - self.filtered('is_delivery').order_id.filtered('carrier_id').carrier_id = False + self.filtered("is_delivery").order_id.filtered("carrier_id").carrier_id = False return super().unlink() def _is_delivery(self): @@ -36,10 +36,9 @@ def _is_delivery(self): def _get_invalid_delivery_weight_lines(self): """Retrieve lines containing physical products with no weight defined.""" return self.filtered( - lambda line: - line.product_qty > 0 - and line.product_id.type not in ('service', 'combo') - and line.product_id.weight == 0, + lambda line: line.product_qty > 0 + and line.product_id.type not in ("service", "combo") + and line.product_id.weight == 0 ) # override to allow deletion of delivery line in a confirmed order @@ -52,11 +51,10 @@ def _check_line_unlink(self): :rtype: recordset sale.order.line :returns: set of lines that cannot be deleted """ - undeletable_lines = super()._check_line_unlink() return undeletable_lines.filtered(lambda line: not line.is_delivery) def _compute_pricelist_item_id(self): - delivery_lines = self.filtered('is_delivery') + delivery_lines = self.filtered("is_delivery") super(SaleOrderLine, self - delivery_lines)._compute_pricelist_item_id() delivery_lines.pricelist_item_id = False diff --git a/addons/delivery/tests/__init__.py b/addons/delivery/tests/__init__.py index 59a0c5f869917..7f36e7d33c491 100644 --- a/addons/delivery/tests/__init__.py +++ b/addons/delivery/tests/__init__.py @@ -1,7 +1,9 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_delivery_cost -from . import test_delivery_availability -from . import test_payment_provider -from . import test_payment_transaction -from . import test_sale_order +from . import ( + test_delivery_availability, + test_delivery_cost, + test_payment_provider, + test_payment_transaction, + test_sale_order, +) diff --git a/addons/delivery/tests/cash_on_delivery_common.py b/addons/delivery/tests/cash_on_delivery_common.py index 0c3013595a37a..2106a151fa158 100644 --- a/addons/delivery/tests/cash_on_delivery_common.py +++ b/addons/delivery/tests/cash_on_delivery_common.py @@ -5,12 +5,11 @@ class CashOnDeliveryCommon(PaymentCustomCommon, DeliveryCommon): - @classmethod def setUpClass(cls): super().setUpClass() - cls.sale_order = cls.env['sale.order'].create({ - 'partner_id': cls.partner.id, - 'state': 'draft', + cls.sale_order = cls.env["sale.order"].create({ + "partner_id": cls.partner.id, + "state": "draft", }) - cls.cod_provider = cls._prepare_provider(code='custom', custom_mode='cash_on_delivery') + cls.cod_provider = cls._prepare_provider(code="custom", custom_mode="cash_on_delivery") diff --git a/addons/delivery/tests/common.py b/addons/delivery/tests/common.py index aee1639fb500c..c2e98575d120d 100644 --- a/addons/delivery/tests/common.py +++ b/addons/delivery/tests/common.py @@ -4,13 +4,12 @@ class DeliveryCommon(TransactionCase): - @classmethod def setUpClass(cls): super().setUpClass() - cls.env['delivery.carrier'].search([]).action_archive() - cls.delivery_categ = cls.env.ref('delivery.product_category_deliveries') + cls.env["delivery.carrier"].search([]).action_archive() + cls.delivery_categ = cls.env.ref("delivery.product_category_deliveries") product = cls._prepare_carrier_product() cls.free_delivery = cls._prepare_carrier(product, fixed_price=0.0) @@ -19,22 +18,22 @@ def setUpClass(cls): @classmethod def _prepare_carrier_product(cls, **values): default_values = { - 'name': "Carrier Product", - 'type': 'service', - 'categ_id': cls.delivery_categ.id, - 'sale_ok': False, - 'purchase_ok': False, - 'invoice_policy': 'order', - 'list_price': 5.0, + "name": "Carrier Product", + "type": "service", + "categ_id": cls.delivery_categ.id, + "sale_ok": False, + "purchase_ok": False, + "invoice_policy": "order", + "list_price": 5.0, } - return cls.env['product.product'].create(dict(default_values, **values)) + return cls.env["product.product"].create(dict(default_values, **values)) @classmethod def _prepare_carrier(cls, product, **values): default_values = { - 'name': "Test Carrier", - 'fixed_price': 5.0, - 'delivery_type': 'fixed', - 'product_id': product.id, + "name": "Test Carrier", + "fixed_price": 5.0, + "delivery_type": "fixed", + "product_id": product.id, } - return cls.env['delivery.carrier'].create(dict(default_values, **values)) + return cls.env["delivery.carrier"].create(dict(default_values, **values)) diff --git a/addons/delivery/tests/test_delivery_availability.py b/addons/delivery/tests/test_delivery_availability.py index 52746817d3f16..ad53aa13987f9 100644 --- a/addons/delivery/tests/test_delivery_availability.py +++ b/addons/delivery/tests/test_delivery_availability.py @@ -7,190 +7,209 @@ from odoo.addons.sale.tests.common import SaleCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestDeliveryAvailability(DeliveryCommon, SaleCommon): - @classmethod def setUpClass(cls): super().setUpClass() - cls.must_have_tag = cls.env['product.tag'].create({ - 'name': 'Must Have', - }) - cls.exclude_tag = cls.env['product.tag'].create({ - 'name': 'Exclude', - }) + cls.must_have_tag = cls.env["product.tag"].create({"name": "Must Have"}) + cls.exclude_tag = cls.env["product.tag"].create({"name": "Exclude"}) cls.non_restricted_carrier = cls._prepare_carrier(cls.carrier.product_id) cls.product_line = cls.sale_order.order_line.filtered( - lambda sol: sol.product_id == cls.product, + lambda sol: sol.product_id == cls.product ) cls.product_line.product_uom_qty = 1.0 def test_00_order_with_heavy_product_simple(self): - self.carrier.write({ - 'max_weight': 10.0, - }) + self.carrier.write({"max_weight": 10.0}) - self.product.write({ - 'weight': 11.0, - }) + self.product.write({"weight": 11.0}) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Product weight exceeds carrier's max weight") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Product weight exceeds carrier's max weight", + ) def test_01_order_with_heavy_product_different_uom(self): - self.carrier.write({ - 'max_weight': 10.0, - }) + self.carrier.write({"max_weight": 10.0}) - self.product.write({ - 'weight': 1.0, - }) + self.product.write({"weight": 1.0}) - self.product_line.write({ - 'product_uom_id': self.uom_dozen.id, - }) + self.product_line.write({"product_uom_id": self.uom_dozen.id}) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Order lines should be converted to the default UoM before checking weight") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Order lines should be converted to the default UoM before checking weight", + ) def test_02_order_with_big_product_simple(self): - self.carrier.write({ - 'max_volume': 10.0, - }) + self.carrier.write({"max_volume": 10.0}) - self.product.write({ - 'volume': 11.0, - }) + self.product.write({"volume": 11.0}) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Product volume exceeds carrier's max volume") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Product volume exceeds carrier's max volume", + ) def test_03_order_with_big_product_different_uom(self): - self.carrier.write({ - 'max_volume': 10.0, - }) + self.carrier.write({"max_volume": 10.0}) - self.product.write({ - 'volume': 1.0, - }) + self.product.write({"volume": 1.0}) - self.product_line.write({ - 'product_uom_id': self.uom_dozen.id, - }) + self.product_line.write({"product_uom_id": self.uom_dozen.id}) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Order lines should be converted to the default UoM before checking volume") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Order lines should be converted to the default UoM before checking volume", + ) def test_04_check_must_have_tag(self): self.carrier.must_have_tag_ids = [ Command.link(self.must_have_tag.id), - Command.link(self.must_have_tag.copy({'name': "Alt Must Have"}).id), + Command.link(self.must_have_tag.copy({"name": "Alt Must Have"}).id), ] - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Delivery method's must have tag is not set on any product in the order") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Delivery method's must have tag is not set on any product in the order", + ) - self.product.write({ - 'product_tag_ids': [self.must_have_tag.id], - }) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + self.product.write({"product_tag_ids": [self.must_have_tag.id]}) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() self.assertIn( self.carrier, choose_delivery_carrier.available_carrier_ids, - "Delivery method should be available if at least one must-have tag is present in the products", + "Delivery method should be available if at least one must-have tag is present in the" + " products", ) def test_05_check_excluded_tag(self): - self.carrier.write({ - 'excluded_tag_ids': [self.exclude_tag.id], - }) + self.carrier.write({"excluded_tag_ids": [self.exclude_tag.id]}) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertTrue(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Delivery method's excluded tag is not set on any product in the order") + self.assertTrue( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Delivery method's excluded tag is not set on any product in the order", + ) - self.product.write({ - 'product_tag_ids': [self.exclude_tag.id], - }) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + self.product.write({"product_tag_ids": [self.exclude_tag.id]}) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Delivery method's excluded tag is set on one product in the order") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Delivery method's excluded tag is set on one product in the order", + ) def test_06_check_tags_complex(self): self.carrier.write({ - 'must_have_tag_ids': [self.must_have_tag.id], - 'excluded_tag_ids': [self.exclude_tag.id], + "must_have_tag_ids": [self.must_have_tag.id], + "excluded_tag_ids": [self.exclude_tag.id], }) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Delivery method's must have tag is not set on any product in the order") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Delivery method's must have tag is not set on any product in the order", + ) - self.product.write({ - 'product_tag_ids': [self.must_have_tag.id], - }) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + self.product.write({"product_tag_ids": [self.must_have_tag.id]}) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertTrue(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Delivery method's must have tag is set on one product in the order") + self.assertTrue( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Delivery method's must have tag is set on one product in the order", + ) - self.product.write({ - 'product_tag_ids': [self.exclude_tag.id, self.must_have_tag.id], - }) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + self.product.write({"product_tag_ids": [self.exclude_tag.id, self.must_have_tag.id]}) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Delivery method's excluded tag is set on one product in the order") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Delivery method's excluded tag is set on one product in the order", + ) - self.product.write({ - 'product_tag_ids': [self.must_have_tag.id], - }) - self.service_product.write({ - 'product_tag_ids': [self.exclude_tag.id], - }) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_order.id, - 'default_carrier_id': self.non_restricted_carrier.id, - })) + self.product.write({"product_tag_ids": [self.must_have_tag.id]}) + self.service_product.write({"product_tag_ids": [self.exclude_tag.id]}) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_order.id, + "default_carrier_id": self.non_restricted_carrier.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() - self.assertFalse(self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, "Delivery method's excluded tag is set on one product in the order") + self.assertFalse( + self.carrier.id in choose_delivery_carrier.available_carrier_ids.ids, + "Delivery method's excluded tag is set on one product in the order", + ) diff --git a/addons/delivery/tests/test_delivery_cost.py b/addons/delivery/tests/test_delivery_cost.py index 08555ec4565d6..db3e5e7c87d5b 100644 --- a/addons/delivery/tests/test_delivery_cost.py +++ b/addons/delivery/tests/test_delivery_cost.py @@ -10,9 +10,8 @@ from odoo.addons.sale.tests.common import SaleCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestDeliveryCost(DeliveryCommon, SaleCommon): - @classmethod def setUpClass(cls): super().setUpClass() @@ -20,224 +19,257 @@ def setUpClass(cls): cls._enable_uom() # the tests hereunder assume all the prices in USD - cls.env.company.country_id = cls.env.ref('base.us').id + cls.env.company.country_id = cls.env.ref("base.us").id cls.product.weight = 1.0 cls.product_delivery_normal = cls._prepare_carrier_product( - name='Normal Delivery Charges', - list_price=10.0, + name="Normal Delivery Charges", list_price=10.0 ) cls.normal_delivery = cls._prepare_carrier( product=cls.product_delivery_normal, - name='Normal Delivery Charges', - delivery_type='fixed', + name="Normal Delivery Charges", + delivery_type="fixed", fixed_price=10.0, ) - cls.partner_4 = cls.env['res.partner'].create({ - 'name': 'Another Customer', - 'child_ids': [ - Command.create({ - 'name': "Another Customer's Address", - }) - ] + cls.partner_4 = cls.env["res.partner"].create({ + "name": "Another Customer", + "child_ids": [Command.create({"name": "Another Customer's Address"})], }) cls.partner_address_13 = cls.partner_4.child_ids - cls.product_uom_hour = cls.env.ref('uom.product_uom_hour') + cls.product_uom_hour = cls.env.ref("uom.product_uom_hour") def test_00_delivery_cost(self): # In order to test Carrier Cost # Create sales order with Normal Delivery Charges - self.sale_normal_delivery_charges = self.env['sale.order'].create({ - 'partner_id': self.partner.id, - 'partner_invoice_id': self.partner.id, - 'partner_shipping_id': self.partner.id, - 'order_line': [ - Command.create({ - 'product_id': self.product.id, - 'price_unit': 750.00, - }) - ], + self.sale_normal_delivery_charges = self.env["sale.order"].create({ + "partner_id": self.partner.id, + "partner_invoice_id": self.partner.id, + "partner_shipping_id": self.partner.id, + "order_line": [Command.create({"product_id": self.product.id, "price_unit": 750.00})], }) # I add delivery cost in Sales order - self.a_sale = self.env['account.account'].create({ - 'code': 'X2020', - 'name': 'Product Sales - (test)', - 'account_type': 'income', - 'tag_ids': [Command.set(self.env.ref('account.account_tag_operating').ids)] + self.a_sale = self.env["account.account"].create({ + "code": "X2020", + "name": "Product Sales - (test)", + "account_type": "income", + "tag_ids": [Command.set(self.env.ref("account.account_tag_operating").ids)], }) - self.product_consultant = self.env['product.product'].create({ - 'sale_ok': True, - 'list_price': 75.0, - 'standard_price': 30.0, - 'uom_id': self.product_uom_hour.id, - 'name': 'Service', - 'type': 'service' + self.product_consultant = self.env["product.product"].create({ + "sale_ok": True, + "list_price": 75.0, + "standard_price": 30.0, + "uom_id": self.product_uom_hour.id, + "name": "Service", + "type": "service", }) # I add delivery cost in Sales order - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.sale_normal_delivery_charges.id, - 'default_carrier_id': self.normal_delivery.id - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.sale_normal_delivery_charges.id, + "default_carrier_id": self.normal_delivery.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.button_confirm() # I check sales order after added delivery cost line = self.sale_normal_delivery_charges.order_line.filtered_domain([ - ('product_id', '=', self.normal_delivery.product_id.id)]) + ("product_id", "=", self.normal_delivery.product_id.id) + ]) self.assertEqual(len(line), 1, "Delivery cost is not Added") - zin = str(delivery_wizard.display_price) + " " + str(delivery_wizard.delivery_price) + ' ' + line.company_id.country_id.code + line.company_id.name - self.assertEqual(float_compare(line.price_subtotal, 10.0, precision_digits=2), 0, - "Delivery cost does not correspond to 10.0. %s %s" % (line.price_subtotal, zin)) + zin = ( + str(delivery_wizard.display_price) + + " " + + str(delivery_wizard.delivery_price) + + " " + + line.company_id.country_id.code + + line.company_id.name + ) + self.assertEqual( + float_compare(line.price_subtotal, 10.0, precision_digits=2), + 0, + "Delivery cost does not correspond to 10.0. %s %s" % (line.price_subtotal, zin), + ) # I confirm the sales order self.sale_normal_delivery_charges.action_confirm() # Create one more sales order with Free Delivery Charges - self.delivery_sale_order_cost = self.env['sale.order'].create({ - 'partner_id': self.partner_4.id, - 'partner_invoice_id': self.partner_address_13.id, - 'partner_shipping_id': self.partner_address_13.id, - 'order_line': [ + self.delivery_sale_order_cost = self.env["sale.order"].create({ + "partner_id": self.partner_4.id, + "partner_invoice_id": self.partner_address_13.id, + "partner_shipping_id": self.partner_address_13.id, + "order_line": [ Command.create({ - 'product_id': self.product_consultant.id, - 'product_uom_qty': 24, - 'price_unit': 75.00, + "product_id": self.product_consultant.id, + "product_uom_qty": 24, + "price_unit": 75.00, }), Command.create({ - 'product_id': self.product.id, - 'product_uom_qty': 30, - 'price_unit': 38.25, - }) + "product_id": self.product.id, + "product_uom_qty": 30, + "price_unit": 38.25, + }), ], }) # I add free delivery cost in Sales order - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': self.delivery_sale_order_cost.id, - 'default_carrier_id': self.free_delivery.id - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": self.delivery_sale_order_cost.id, + "default_carrier_id": self.free_delivery.id, + }) + ) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.button_confirm() # I check sales order after adding delivery cost line = self.delivery_sale_order_cost.order_line.filtered_domain([ - ('product_id', '=', self.free_delivery.product_id.id)]) + ("product_id", "=", self.free_delivery.product_id.id) + ]) self.assertEqual(len(line), 1, "Delivery cost is not Added") - self.assertEqual(float_compare(line.price_subtotal, 0, precision_digits=2), 0, - "Delivery cost is not correspond.") + self.assertEqual( + float_compare(line.price_subtotal, 0, precision_digits=2), + 0, + "Delivery cost is not correspond.", + ) # I set default delivery policy - self.env['res.config.settings'].create({}).execute() + self.env["res.config.settings"].create({}).execute() def test_01_delivery_cost_from_pricelist(self): - """ This test aims to validate the use of a pricelist to compute the delivery cost in the case the associated - product of the delivery method is defined in the pricelist """ - + """This test aims to validate the use of a pricelist to compute the delivery cost in the + case the associated product of the delivery method is defined in the pricelist.""" # Create pricelist with a custom price for the standard delivery method - my_pricelist = self.env['product.pricelist'].create({ - 'name': 'shipping_cost_change', - 'item_ids': [Command.create({ - 'compute_price': 'fixed', - 'fixed_price': 5, - 'applied_on': '0_product_variant', - 'product_id': self.normal_delivery.product_id.id, - })], + my_pricelist = self.env["product.pricelist"].create({ + "name": "shipping_cost_change", + "item_ids": [ + Command.create({ + "compute_price": "fixed", + "fixed_price": 5, + "applied_on": "0_product_variant", + "product_id": self.normal_delivery.product_id.id, + }) + ], }) # Create sales order with Normal Delivery Charges - sale_pricelist_based_delivery_charges = self.env['sale.order'].create({ - 'partner_id': self.partner.id, - 'pricelist_id': my_pricelist.id, - 'order_line': [Command.create({ - 'product_id': self.product.id, - 'product_uom_qty': 1, - 'price_unit': 750.00, - })], + sale_pricelist_based_delivery_charges = self.env["sale.order"].create({ + "partner_id": self.partner.id, + "pricelist_id": my_pricelist.id, + "order_line": [ + Command.create({ + "product_id": self.product.id, + "product_uom_qty": 1, + "price_unit": 750.00, + }) + ], }) # Add of delivery cost in Sales order - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': sale_pricelist_based_delivery_charges.id, - 'default_carrier_id': self.normal_delivery.id - })) - self.assertEqual(delivery_wizard.delivery_price, 5.0, "Delivery cost does not correspond to 5.0 in wizard") + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": sale_pricelist_based_delivery_charges.id, + "default_carrier_id": self.normal_delivery.id, + }) + ) + self.assertEqual( + delivery_wizard.delivery_price, + 5.0, + "Delivery cost does not correspond to 5.0 in wizard", + ) delivery_wizard.save().button_confirm() line = sale_pricelist_based_delivery_charges.order_line.filtered_domain([ - ('product_id', '=', self.normal_delivery.product_id.id)]) + ("product_id", "=", self.normal_delivery.product_id.id) + ]) self.assertEqual(len(line), 1, "Delivery cost hasn't been added to SO") self.assertEqual(line.price_subtotal, 5.0, "Delivery cost does not correspond to 5.0") def test_02_delivery_cost_from_different_currency(self): - """ This test aims to validate the use of a pricelist using a different currency to compute the delivery cost in - the case the associated product of the delivery method is defined in the pricelist """ - + """This test aims to validate the use of a pricelist using a different currency to compute + the delivery cost in the case the associated product of the delivery method is defined in + the pricelist.""" # Create pricelist with a custom price for the standard delivery method - my_pricelist = self.env['product.pricelist'].create({ - 'name': 'shipping_cost_change', - 'item_ids': [Command.create({ - 'compute_price': 'fixed', - 'fixed_price': 5, - 'applied_on': '0_product_variant', - 'product_id': self.normal_delivery.product_id.id, - })], - 'currency_id': self.env.ref('base.EUR').id, + my_pricelist = self.env["product.pricelist"].create({ + "name": "shipping_cost_change", + "item_ids": [ + Command.create({ + "compute_price": "fixed", + "fixed_price": 5, + "applied_on": "0_product_variant", + "product_id": self.normal_delivery.product_id.id, + }) + ], + "currency_id": self.env.ref("base.EUR").id, }) # Create sales order with Normal Delivery Charges - sale_pricelist_based_delivery_charges = self.env['sale.order'].create({ - 'partner_id': self.partner.id, - 'pricelist_id': my_pricelist.id, - 'order_line': [Command.create({ - 'product_id': self.product.id, - 'product_uom_qty': 1, - 'price_unit': 750.00, - })], + sale_pricelist_based_delivery_charges = self.env["sale.order"].create({ + "partner_id": self.partner.id, + "pricelist_id": my_pricelist.id, + "order_line": [ + Command.create({ + "product_id": self.product.id, + "product_uom_qty": 1, + "price_unit": 750.00, + }) + ], }) # Add of delivery cost in Sales order - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ - 'default_order_id': sale_pricelist_based_delivery_charges.id, - 'default_carrier_id': self.normal_delivery.id - })) - self.assertEqual(delivery_wizard.delivery_price, 5.0, "Delivery cost does not correspond to 5.0 in wizard") + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context({ + "default_order_id": sale_pricelist_based_delivery_charges.id, + "default_carrier_id": self.normal_delivery.id, + }) + ) + self.assertEqual( + delivery_wizard.delivery_price, + 5.0, + "Delivery cost does not correspond to 5.0 in wizard", + ) delivery_wizard.save().button_confirm() line = sale_pricelist_based_delivery_charges.order_line.filtered_domain([ - ('product_id', '=', self.normal_delivery.product_id.id)]) + ("product_id", "=", self.normal_delivery.product_id.id) + ]) self.assertEqual(len(line), 1, "Delivery cost hasn't been added to SO") self.assertEqual(line.price_subtotal, 5.0, "Delivery cost does not correspond to 5.0") def test_01_taxes_on_delivery_cost(self): # Creating taxes and fiscal position - self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('product.group_product_pricelist').id)]}) - - fiscal_position = self.env['account.fiscal.position'].create({ - 'name': 'fiscal_pos_a', + self.env.ref("base.group_user").write({ + "implied_ids": [(4, self.env.ref("product.group_product_pricelist").id)] }) - tax_price_include, tax_price_exclude = self.env['account.tax'].create([{ - 'name': '10% inc', - 'type_tax_use': 'sale', - 'amount_type': 'percent', - 'amount': 10, - 'price_include_override': 'tax_included', - 'include_base_amount': True, - }, { - 'name': '15% exc', - 'type_tax_use': 'sale', - 'amount_type': 'percent', - 'amount': 15, - 'fiscal_position_ids': [Command.link(fiscal_position.id)], - }]) + + fiscal_position = self.env["account.fiscal.position"].create({"name": "fiscal_pos_a"}) + tax_price_include, tax_price_exclude = self.env["account.tax"].create([ + { + "name": "10% inc", + "type_tax_use": "sale", + "amount_type": "percent", + "amount": 10, + "price_include_override": "tax_included", + "include_base_amount": True, + }, + { + "name": "15% exc", + "type_tax_use": "sale", + "amount_type": "percent", + "amount": 15, + "fiscal_position_ids": [Command.link(fiscal_position.id)], + }, + ]) tax_price_exclude.original_tax_ids = tax_price_include # Setting tax on delivery product @@ -245,8 +277,8 @@ def test_01_taxes_on_delivery_cost(self): # Create sales order # Required to see `pricelist_id` in the view - self.env.user.group_ids += self.env.ref('product.group_product_pricelist') - order_form = Form(self.env['sale.order'].with_context(tracking_disable=True)) + self.env.user.group_ids += self.env.ref("product.group_product_pricelist") + order_form = Form(self.env["sale.order"].with_context(tracking_disable=True)) order_form.partner_id = self.partner order_form.fiscal_position_id = fiscal_position @@ -256,132 +288,138 @@ def test_01_taxes_on_delivery_cost(self): line.product_uom_qty = 1.0 sale_order = order_form.save() - self.assertRecordValues(sale_order.order_line, [{'price_subtotal': 9.09, 'price_total': 10.45}]) + self.assertRecordValues( + sale_order.order_line, [{"price_subtotal": 9.09, "price_total": 10.45}] + ) - # Now trying to add the delivery line using the delivery wizard, the results should be the same as before - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context(default_order_id=sale_order.id, - default_carrier_id=self.normal_delivery.id)) + # Now trying to add the delivery line using the delivery wizard, the results should be the + # same as before + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context( + default_order_id=sale_order.id, default_carrier_id=self.normal_delivery.id + ) + ) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.button_confirm() line = sale_order.order_line.filtered_domain([ - ('product_id', '=', self.normal_delivery.product_id.id), - ('is_delivery', '=', True), + ("product_id", "=", self.normal_delivery.product_id.id), + ("is_delivery", "=", True), ]) - self.assertRecordValues(line, [{'price_subtotal': 9.09, 'price_total': 10.45}]) + self.assertRecordValues(line, [{"price_subtotal": 9.09, "price_total": 10.45}]) def test_estimated_weight(self): """ Test that negative qty SO lines are not included in the estimated weight calculation of delivery method (since it's used when calculating their rates). """ - sale_order = self.env['sale.order'].create({ - 'partner_id': self.partner.id, - 'order_line': [ - Command.create({ - 'product_id': self.product.id, - 'product_uom_qty': 1, - }), - Command.create({ - 'product_id': self.product.id, - 'product_uom_qty': -1, - }), + sale_order = self.env["sale.order"].create({ + "partner_id": self.partner.id, + "order_line": [ + Command.create({"product_id": self.product.id, "product_uom_qty": 1}), + Command.create({"product_id": self.product.id, "product_uom_qty": -1}), ], }) shipping_weight = sale_order._get_estimated_weight() - self.assertEqual(shipping_weight, self.product.weight, "Only positive quantity products' weights should be included in estimated weight") + self.assertEqual( + shipping_weight, + self.product.weight, + "Only positive quantity products' weights should be included in estimated weight", + ) def test_get_invalid_delivery_weight_lines(self): """Ensure we can retrieve lines that contain physical products without a weight value.""" order = self.empty_order weightless_product = self._create_product(weight=0.0, list_price=50.0) - combos = self.env['product.combo'].create([{ - 'name': "Combo A", - 'combo_item_ids': [Command.create({'product_id': self.product.id})], - }, { - 'name': "Combo B", - 'combo_item_ids': [Command.create({'product_id': weightless_product.id})], + combos = self.env["product.combo"].create([ + { + "name": "Combo A", + "combo_item_ids": [Command.create({"product_id": self.product.id})], + }, + { + "name": "Combo B", + "combo_item_ids": [Command.create({"product_id": weightless_product.id})], }, ]) - combo_product = self._create_product(type='combo', combo_ids=combos.ids) - combo_line = self.env['sale.order.line'].create({ - 'order_id': order.id, - 'product_id': combo_product.id, + combo_product = self._create_product(type="combo", combo_ids=combos.ids) + combo_line = self.env["sale.order.line"].create({ + "order_id": order.id, + "product_id": combo_product.id, }) order.order_line = [ - *[Command.create({ - 'product_id': product.id, - 'combo_item_id': combo.combo_item_ids.id, - 'linked_line_id': combo_line.id, - }) for product, combo in zip(self.product + weightless_product, combos)], - Command.create({'product_id': weightless_product.id, 'product_uom_qty': 0}), - Command.create({'product_id': self.service_product.id}), - Command.create({'display_type': 'line_section', 'name': "Misc."}), - Command.create({'is_downpayment': True, 'price_unit': 5.0}), + *[ + Command.create({ + "product_id": product.id, + "combo_item_id": combo.combo_item_ids.id, + "linked_line_id": combo_line.id, + }) + for product, combo in zip(self.product + weightless_product, combos) + ], + Command.create({"product_id": weightless_product.id, "product_uom_qty": 0}), + Command.create({"product_id": self.service_product.id}), + Command.create({"display_type": "line_section", "name": "Misc."}), + Command.create({"is_downpayment": True, "price_unit": 5.0}), ] error_lines = order.order_line._get_invalid_delivery_weight_lines() self.assertIn( - weightless_product, error_lines.product_id, + weightless_product, + error_lines.product_id, "The weightless product should be part of the erroneous lines", ) self.assertEqual(len(error_lines), 1, "Only 1 line should have an invalid weight") self.assertTrue(error_lines.combo_item_id, "The erroneous line should be part of a combo") def test_fixed_price_margins(self): - """ - margins should be ignored for fixed price carriers - """ - sale_order = self.env['sale.order'].create({ - 'partner_id': self.partner.id, - 'name': 'SO - fixed del', - 'order_line': [ - (0, 0, { - 'product_id': self.product.id, - 'product_uom_qty': 1, - }), - ] + """Margins should be ignored for fixed price carriers.""" + sale_order = self.env["sale.order"].create({ + "partner_id": self.partner.id, + "name": "SO - fixed del", + "order_line": [(0, 0, {"product_id": self.product.id, "product_uom_qty": 1})], }) self.normal_delivery.fixed_margin = 100 self.normal_delivery.margin = 4.2 - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context(default_order_id=sale_order.id, - default_carrier_id=self.normal_delivery.id)) + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context( + default_order_id=sale_order.id, default_carrier_id=self.normal_delivery.id + ) + ) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.button_confirm() - line = sale_order.order_line.filtered('is_delivery') + line = sale_order.order_line.filtered("is_delivery") self.assertEqual(line.price_unit, self.normal_delivery.fixed_price) def test_price_with_weight_volume_variable(self): - """ Test that the price is correctly computed when the variable is weight*volume. """ + """Test that the price is correctly computed when the variable is weight*volume.""" qty = 3 list_price = 2 volume = 2.5 weight = 1.5 - sale_order = self.env['sale.order'].create({ - 'partner_id': self.partner_4.id, - 'order_line': [ - (0, 0, { - 'product_id': self.env['product.product'].create({ - 'name': 'wv', - 'weight': weight, - 'volume': volume, - }).id, - 'product_uom_qty': qty, - }), + sale_order = self.env["sale.order"].create({ + "partner_id": self.partner_4.id, + "order_line": [ + Command.create({ + "product_id": self.env["product.product"] + .create({"name": "wv", "weight": weight, "volume": volume}) + .id, + "product_uom_qty": qty, + }) ], }) - delivery = self.env['delivery.carrier'].create({ - 'name': 'Delivery Charges', - 'delivery_type': 'base_on_rule', - 'product_id': self.product_delivery_normal.id, - 'price_rule_ids': [(0, 0, { - 'variable': 'price', - 'operator': '>=', - 'max_value': 0, - 'list_price': list_price, - 'variable_factor': 'wv', - })] + delivery = self.env["delivery.carrier"].create({ + "name": "Delivery Charges", + "delivery_type": "base_on_rule", + "product_id": self.product_delivery_normal.id, + "price_rule_ids": [ + Command.create({ + "variable": "price", + "operator": ">=", + "max_value": 0, + "list_price": list_price, + "variable_factor": "wv", + }) + ], }) self.assertEqual( delivery._get_price_available(sale_order), @@ -390,198 +428,201 @@ def test_price_with_weight_volume_variable(self): ) def test_delivery_product_taxes_on_branch(self): - """ Check taxes populated on delivery line on branch company. - Taxes from the branch company should be taken with a fallback on parent company. + """Check taxes populated on delivery line on branch company. + Taxes from the branch company should be taken with a fallback on parent company. """ company = self.env.company - branch = self.env['res.company'].create({ - 'name': 'Branch', - 'country_id': company.country_id.id, - 'parent_id': company.id, + branch = self.env["res.company"].create({ + "name": "Branch", + "country_id": company.country_id.id, + "parent_id": company.id, }) # create taxes for the parent company and its branch - tax_groups = self.env['account.tax.group'].create([{ - 'name': 'Tax Group A', - 'company_id': company.id, - }, { - 'name': 'Tax Group B', - 'company_id': branch.id, - }]) - tax_a = self.env['account.tax'].create({ - 'name': 'Tax A', - 'type_tax_use': 'sale', - 'amount_type': 'percent', - 'amount': 10, - 'tax_group_id': tax_groups[0].id, - 'company_id': company.id, - }) - tax_b = self.env['account.tax'].create({ - 'name': 'Tax B', - 'type_tax_use': 'sale', - 'amount_type': 'percent', - 'amount': 20, - 'tax_group_id': tax_groups[1].id, - 'company_id': branch.id, + tax_groups = self.env["account.tax.group"].create([ + {"name": "Tax Group A", "company_id": company.id}, + {"name": "Tax Group B", "company_id": branch.id}, + ]) + tax_a = self.env["account.tax"].create({ + "name": "Tax A", + "type_tax_use": "sale", + "amount_type": "percent", + "amount": 10, + "tax_group_id": tax_groups[0].id, + "company_id": company.id, + }) + tax_b = self.env["account.tax"].create({ + "name": "Tax B", + "type_tax_use": "sale", + "amount_type": "percent", + "amount": 20, + "tax_group_id": tax_groups[1].id, + "company_id": branch.id, }) # create delivery product with taxes from both branch and parent company - delivery_product = self.env['product.product'].create({ - 'name': 'Delivery Product', - 'taxes_id': [Command.set((tax_a + tax_b).ids)], + delivery_product = self.env["product.product"].create({ + "name": "Delivery Product", + "taxes_id": [Command.set((tax_a + tax_b).ids)], }) # create delivery - delivery = self.env['delivery.carrier'].create({ - 'name': 'Delivery Charges', - 'delivery_type': 'fixed', - 'product_id': delivery_product.id, - 'company_id': branch.id, + delivery = self.env["delivery.carrier"].create({ + "name": "Delivery Charges", + "delivery_type": "fixed", + "product_id": delivery_product.id, + "company_id": branch.id, }) # create a SO from Branch - sale_order = self.env['sale.order'].create({ - 'partner_id': self.partner_4.id, - 'company_id': branch.id, - 'order_line': [Command.create({ - 'product_id': self.product.id, - 'product_uom_qty': 1, - })], + sale_order = self.env["sale.order"].create({ + "partner_id": self.partner_4.id, + "company_id": branch.id, + "order_line": [Command.create({"product_id": self.product.id, "product_uom_qty": 1})], }) # add delivery - wizard = self.env['choose.delivery.carrier'].create({ - 'order_id': sale_order.id, - 'carrier_id': delivery.id, - 'company_id': branch.id, + wizard = self.env["choose.delivery.carrier"].create({ + "order_id": sale_order.id, + "carrier_id": delivery.id, + "company_id": branch.id, }) wizard.button_confirm() - delivery_line = sale_order.order_line.filtered(lambda l: l.is_delivery) + delivery_line = sale_order.order_line.filtered(lambda line: line.is_delivery) # delivery line should have taxes from the branch company - self.assertRecordValues(delivery_line, [{'product_id': delivery_product.id, 'tax_ids': tax_b.ids}]) + self.assertRecordValues( + delivery_line, [{"product_id": delivery_product.id, "tax_ids": tax_b.ids}] + ) # update delivery product by setting only the tax from parent company - delivery_product.write({'taxes_id': [Command.set((tax_a).ids)]}) + delivery_product.write({"taxes_id": [Command.set((tax_a).ids)]}) # update delivery - wizard = self.env['choose.delivery.carrier'].create({ - 'order_id': sale_order.id, - 'carrier_id': delivery.id, - 'company_id': branch.id, + wizard = self.env["choose.delivery.carrier"].create({ + "order_id": sale_order.id, + "carrier_id": delivery.id, + "company_id": branch.id, }) wizard.button_confirm() - delivery_line = sale_order.order_line.filtered(lambda l: l.is_delivery) + delivery_line = sale_order.order_line.filtered(lambda line: line.is_delivery) - # delivery line should have taxes from the parent company as there is no tax from the branch company - self.assertRecordValues(delivery_line, [{'product_id': delivery_product.id, 'tax_ids': tax_a.ids}]) + # delivery line should have taxes from the parent company as there is no tax from the branch + # company + self.assertRecordValues( + delivery_line, [{"product_id": delivery_product.id, "tax_ids": tax_a.ids}] + ) def test_update_weight_in_shipping_when_change_quantity(self): - product_test = self.env['product.product'].create({ - 'name': 'Test product', - 'weight': 1, - }) - sale_order = self.env['sale.order'].create({ - 'partner_id': self.partner.id, - 'order_line': [ + product_test = self.env["product.product"].create({"name": "Test product", "weight": 1}) + sale_order = self.env["sale.order"].create({ + "partner_id": self.partner.id, + "order_line": [Command.create({"product_id": product_test.id, "product_uom_qty": 10})], + }) + delivery = self.env["delivery.carrier"].create({ + "name": "Delivery Charges", + "delivery_type": "base_on_rule", + "product_id": product_test.id, + "price_rule_ids": [ Command.create({ - 'product_id': product_test.id, - 'product_uom_qty': 10, + "variable": "weight", + "operator": "<=", + "max_value": 30, + "list_base_price": 5, + "variable_factor": "weight", }), - ], - }) - delivery = self.env['delivery.carrier'].create({ - 'name': 'Delivery Charges', - 'delivery_type': 'base_on_rule', - 'product_id': product_test.id, - 'price_rule_ids': [ Command.create({ - 'variable': 'weight', - 'operator': '<=', - 'max_value': 30, - 'list_base_price': 5, - 'variable_factor': 'weight', + "variable": "weight", + "operator": ">=", + "max_value": 60, + "list_base_price": 10, + "variable_factor": "weight", }), - Command.create({ - 'variable': 'weight', - 'operator': '>=', - 'max_value': 60, - 'list_base_price': 10, - 'variable_factor': 'weight', - }) - ] + ], }) del_form = sale_order.action_open_delivery_wizard() - choose_delivery_carrier = self.env[del_form['res_model']].with_context(del_form['context']).create({ - 'carrier_id': delivery.id, - 'order_id': sale_order.id - }) + choose_delivery_carrier = ( + self.env[del_form["res_model"]] + .with_context(del_form["context"]) + .create({"carrier_id": delivery.id, "order_id": sale_order.id}) + ) choose_delivery_carrier.button_confirm() self.assertEqual(choose_delivery_carrier.total_weight, 10) - sale_order.order_line.write({ - 'product_uom_qty': 100, - }) + sale_order.order_line.write({"product_uom_qty": 100}) updated_del_form = sale_order.action_open_delivery_wizard() - self.assertEqual(updated_del_form['context']['default_total_weight'], 100) + self.assertEqual(updated_del_form["context"]["default_total_weight"], 100) def test_base_on_rule_currency_is_converted(self): """ For based on rules delivery method without a company, check that the price - is converted from the main's company's currency to the current company's on SOs + is converted from the main's company's currency to the current company's on SOs. """ - # Create a company that uses a different currency - currency_bells = self.env['res.currency'].create({ - 'name': 'Bell', - 'symbol': 'C', - }) + currency_bells = self.env["res.currency"].create({"name": "Bell", "symbol": "C"}) - nook_inc = self.env['res.company'].create({ - 'name': 'Nook inc.', - 'currency_id': currency_bells.id, + nook_inc = self.env["res.company"].create({ + "name": "Nook inc.", + "currency_id": currency_bells.id, }) - with freeze_time('2000-01-01'): # Make sure the rate is in the past - self.env['res.currency.rate'].with_company(nook_inc).create({ - 'currency_id': currency_bells.id, - 'company_rate': 0.5, - 'inverse_company_rate': 2, + with freeze_time("2000-01-01"): # Make sure the rate is in the past + self.env["res.currency.rate"].with_company(nook_inc).create({ + "currency_id": currency_bells.id, + "company_rate": 0.5, + "inverse_company_rate": 2, }) # Company less delivery method - product_delivery_rule = self.env['product.product'].with_company(nook_inc).create({ - 'name': 'rule delivery charges', - 'type': 'service', - 'list_price': 10.0, - 'categ_id': self.env.ref('delivery.product_category_deliveries').id, - }) + product_delivery_rule = ( + self.env["product.product"] + .with_company(nook_inc) + .create({ + "name": "rule delivery charges", + "type": "service", + "list_price": 10.0, + "categ_id": self.env.ref("delivery.product_category_deliveries").id, + }) + ) - delivery = self.env['delivery.carrier'].with_company(nook_inc).create({ - 'name': 'Rule Delivery', - 'delivery_type': 'base_on_rule', - 'product_id': product_delivery_rule.id, - 'price_rule_ids': [(0, 0, { - 'variable': 'price', - 'operator': '>=', - 'max_value': 0, - 'variable_factor': 'weight', - 'list_base_price': 15, - })], - 'fixed_margin': 10, - }) + delivery = ( + self.env["delivery.carrier"] + .with_company(nook_inc) + .create({ + "name": "Rule Delivery", + "delivery_type": "base_on_rule", + "product_id": product_delivery_rule.id, + "price_rule_ids": [ + Command.create({ + "variable": "price", + "operator": ">=", + "max_value": 0, + "variable_factor": "weight", + "list_base_price": 15, + }) + ], + "fixed_margin": 10, + }) + ) # Create sale using the delivery method - so = self.env['sale.order'].with_company(nook_inc).create({ - 'partner_id': self.partner_4.id, - 'partner_invoice_id': self.partner_4.id, - 'partner_shipping_id': self.partner_4.id, - 'order_line': [(0, 0, { - 'name': 'PC Assamble + 2GB RAM', - 'product_id': self.product.id, - 'product_uom_qty': 1, - 'price_unit': 750.00, - })], - }) + so = ( + self.env["sale.order"] + .with_company(nook_inc) + .create({ + "partner_id": self.partner_4.id, + "partner_invoice_id": self.partner_4.id, + "partner_shipping_id": self.partner_4.id, + "order_line": [ + Command.create({ + "name": "PC Assamble + 2GB RAM", + "product_id": self.product.id, + "product_uom_qty": 1, + "price_unit": 750.00, + }) + ], + }) + ) - delivery_wizard = Form(self.env['choose.delivery.carrier'].with_company(nook_inc).with_context({ - 'default_order_id': so.id, - 'default_carrier_id': delivery.id, - })) + delivery_wizard = Form( + self.env["choose.delivery.carrier"] + .with_company(nook_inc) + .with_context({"default_order_id": so.id, "default_carrier_id": delivery.id}) + ) choose_delivery_carrier = delivery_wizard.save() choose_delivery_carrier.button_confirm() diff --git a/addons/delivery/tests/test_payment_provider.py b/addons/delivery/tests/test_payment_provider.py index 9d7b6917917f8..34f9e340dfff3 100644 --- a/addons/delivery/tests/test_payment_provider.py +++ b/addons/delivery/tests/test_payment_provider.py @@ -5,27 +5,40 @@ from odoo.addons.delivery.tests.cash_on_delivery_common import CashOnDeliveryCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestCODPaymentProvider(CashOnDeliveryCommon): - def test_cod_provider_available_when_dm_cod_enabled(self): order = self.sale_order self.free_delivery.allow_cash_on_delivery = True order.carrier_id = self.free_delivery - compatible_providers = self.env['payment.provider'].sudo()._get_compatible_providers( - self.company.id, self.partner.id, self.amount, sale_order_id=order.id + compatible_providers = ( + self.env["payment.provider"] + .sudo() + ._get_compatible_providers( + self.company.id, self.partner.id, self.amount, sale_order_id=order.id + ) + ) + self.assertTrue( + any( + p.code == "custom" and p.custom_mode == "cash_on_delivery" + for p in compatible_providers + ) ) - self.assertTrue(any( - p.code == 'custom' and p.custom_mode == 'cash_on_delivery' for p in compatible_providers - )) def test_cod_provider_unavailable_when_dm_cod_disabled(self): order = self.sale_order self.free_delivery.allow_cash_on_delivery = False order.carrier_id = self.free_delivery - compatible_providers = self.env['payment.provider'].sudo()._get_compatible_providers( - self.company.id, self.partner.id, self.amount, sale_order_id=order.id + compatible_providers = ( + self.env["payment.provider"] + .sudo() + ._get_compatible_providers( + self.company.id, self.partner.id, self.amount, sale_order_id=order.id + ) + ) + self.assertFalse( + any( + p.code == "custom" and p.custom_mode == "cash_on_delivery" + for p in compatible_providers + ) ) - self.assertTrue(not any( - p.code == 'custom' and p.custom_mode == 'cash_on_delivery' for p in compatible_providers - )) diff --git a/addons/delivery/tests/test_payment_transaction.py b/addons/delivery/tests/test_payment_transaction.py index 25933e4bf1f58..ef24d8592767a 100644 --- a/addons/delivery/tests/test_payment_transaction.py +++ b/addons/delivery/tests/test_payment_transaction.py @@ -6,21 +6,20 @@ from odoo.addons.delivery.tests.cash_on_delivery_common import CashOnDeliveryCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestCODPaymentTransaction(CashOnDeliveryCommon): - def test_choosing_cod_payment_confirms_order(self): order = self.sale_order self.free_delivery.allow_cash_on_delivery = True order.carrier_id = self.free_delivery tx = self._create_transaction( - flow='direct', + flow="direct", sale_order_ids=[order.id], - state='pending', + state="pending", provider_id=self.cod_provider.id, payment_method_id=self.cod_provider.payment_method_ids.id, ) - with mute_logger('odoo.addons.sale.models.payment_transaction'): + with mute_logger("odoo.addons.sale.models.payment_transaction"): tx._post_process() - self.assertEqual(order.state, 'sale') + self.assertEqual(order.state, "sale") diff --git a/addons/delivery/tests/test_sale_order.py b/addons/delivery/tests/test_sale_order.py index 6117883fad919..4f0705dc2bef5 100644 --- a/addons/delivery/tests/test_sale_order.py +++ b/addons/delivery/tests/test_sale_order.py @@ -3,10 +3,9 @@ from odoo.addons.sale.tests.common import SaleCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestSaleOrder(SaleCommon): - def test_avoid_setting_pickup_location_as_default_delivery_address(self): - self._create_partner(type='delivery', parent_id=self.partner.id, is_pickup_location=True) - so = self.env['sale.order'].create({'partner_id': self.partner.id}) + self._create_partner(type="delivery", parent_id=self.partner.id, is_pickup_location=True) + so = self.env["sale.order"].create({"partner_id": self.partner.id}) self.assertFalse(so.partner_shipping_id.is_pickup_location) diff --git a/addons/delivery/wizard/choose_delivery_carrier.py b/addons/delivery/wizard/choose_delivery_carrier.py index a49ef08c8707b..b467143706326 100644 --- a/addons/delivery/wizard/choose_delivery_carrier.py +++ b/addons/delivery/wizard/choose_delivery_carrier.py @@ -5,92 +5,113 @@ class ChooseDeliveryCarrier(models.TransientModel): - _name = 'choose.delivery.carrier' - _description = 'Delivery Method Selection Wizard' + _name = "choose.delivery.carrier" + _description = "Delivery Method Selection Wizard" def _get_default_weight_uom(self): - return self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() + return self.env["product.template"]._get_weight_uom_name_from_ir_config_parameter() - order_id = fields.Many2one('sale.order', required=True, ondelete="cascade") - partner_id = fields.Many2one('res.partner', related='order_id.partner_id', required=True) + order_id = fields.Many2one(comodel_name="sale.order", ondelete="cascade", required=True) + partner_id = fields.Many2one( + comodel_name="res.partner", related="order_id.partner_id", required=True + ) carrier_id = fields.Many2one( - 'delivery.carrier', string="Delivery Method", - required=True, + comodel_name="delivery.carrier", domain="[('id', 'in', available_carrier_ids)]", + required=True, ) - delivery_type = fields.Selection(related='carrier_id.delivery_type') + delivery_type = fields.Selection(related="carrier_id.delivery_type") delivery_price = fields.Float() - display_price = fields.Float(string='Cost', readonly=True) - currency_id = fields.Many2one('res.currency', related='order_id.currency_id') - company_id = fields.Many2one('res.company', related='order_id.company_id') - available_carrier_ids = fields.Many2many("delivery.carrier", compute='_compute_available_carrier', string="Available Carriers") - invoicing_message = fields.Text(compute='_compute_invoicing_message') + display_price = fields.Float(string="Cost", readonly=True) + currency_id = fields.Many2one(comodel_name="res.currency", related="order_id.currency_id") + company_id = fields.Many2one(comodel_name="res.company", related="order_id.company_id") + available_carrier_ids = fields.Many2many( + string="Available Carriers", + comodel_name="delivery.carrier", + compute="_compute_available_carrier", + ) + invoicing_message = fields.Text(compute="_compute_invoicing_message") delivery_message = fields.Text(readonly=True) - total_weight = fields.Float(string='Total Order Weight', related='order_id.shipping_weight', readonly=False) - weight_uom_name = fields.Char(readonly=True, default=_get_default_weight_uom) + total_weight = fields.Float( + string="Total Order Weight", related="order_id.shipping_weight", readonly=False + ) + weight_uom_name = fields.Char(default=_get_default_weight_uom, readonly=True) - @api.onchange('carrier_id', 'total_weight') + @api.onchange("carrier_id", "total_weight") def _onchange_carrier_id(self): self.delivery_message = False - if self.delivery_type in ('fixed', 'base_on_rule'): + if self.delivery_type in ("fixed", "base_on_rule"): vals = self._get_delivery_rate() - if vals.get('error_message'): - return {'error': vals['error_message']} + if vals.get("error_message"): + return {"error": vals["error_message"]} else: self.display_price = 0 self.delivery_price = 0 - @api.onchange('order_id') + @api.onchange("order_id") def _onchange_order_id(self): - # fixed and base_on_rule delivery price will computed on each carrier change so no need to recompute here - if self.carrier_id and self.order_id.delivery_set and self.delivery_type not in ('fixed', 'base_on_rule'): + # Fixed and base_on_rule delivery price will compute on each carrier change so no need to + # recompute here + if ( + self.carrier_id + and self.order_id.delivery_set + and self.delivery_type not in ("fixed", "base_on_rule") + ): vals = self._get_delivery_rate() - if vals.get('error_message'): + if vals.get("error_message"): warning = { - 'title': _("%(carrier)s Error", carrier=self.carrier_id.name), - 'message': vals['error_message'], - 'type': 'notification', + "title": _("%(carrier)s Error", carrier=self.carrier_id.name), + "message": vals["error_message"], + "type": "notification", } - return {'warning': warning} + return {"warning": warning} - @api.depends('carrier_id') + @api.depends("carrier_id") def _compute_invoicing_message(self): self.ensure_one() self.invoicing_message = "" - @api.depends('partner_id') + @api.depends("partner_id") def _compute_available_carrier(self): for rec in self: - carriers = self.env['delivery.carrier'].search(self.env['delivery.carrier']._check_company_domain(rec.order_id.company_id)) - rec.available_carrier_ids = carriers.available_carriers(rec.order_id.partner_shipping_id, rec.order_id) if rec.partner_id else carriers + carriers = self.env["delivery.carrier"].search( + self.env["delivery.carrier"]._check_company_domain(rec.order_id.company_id) + ) + rec.available_carrier_ids = ( + carriers.available_carriers(rec.order_id.partner_shipping_id, rec.order_id) + if rec.partner_id + else carriers + ) def _get_delivery_rate(self): - vals = self.carrier_id.with_context(order_weight=self.total_weight).rate_shipment(self.order_id) - if vals.get('success'): - self.delivery_message = vals.get('warning_message', False) - self.delivery_price = vals['price'] - self.display_price = vals['carrier_price'] - return {'no_rate': vals.get('no_rate', False)} - return {'error_message': vals['error_message']} + vals = self.carrier_id.with_context(order_weight=self.total_weight).rate_shipment( + self.order_id + ) + if vals.get("success"): + self.delivery_message = vals.get("warning_message", False) + self.delivery_price = vals["price"] + self.display_price = vals["carrier_price"] + return {"no_rate": vals.get("no_rate", False)} + return {"error_message": vals["error_message"]} def update_price(self): vals = self._get_delivery_rate() - if vals.get('error_message'): - raise UserError(vals.get('error_message')) + if vals.get("error_message"): + raise UserError(vals.get("error_message")) return { - 'name': _('Add a delivery method'), - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'choose.delivery.carrier', - 'res_id': self.id, - 'target': 'new', - 'context': vals, + "name": _("Add a delivery method"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "choose.delivery.carrier", + "res_id": self.id, + "target": "new", + "context": vals, } def button_confirm(self): self.order_id.set_delivery_line(self.carrier_id, self.delivery_price) self.order_id.write({ - 'recompute_delivery_price': False, - 'delivery_message': self.delivery_message, + "recompute_delivery_price": False, + "delivery_message": self.delivery_message, }) diff --git a/addons/website_sale_collect/controllers/__init__.py b/addons/website_sale_collect/controllers/__init__.py index 1389142f87fff..6df092ba618ae 100644 --- a/addons/website_sale_collect/controllers/__init__.py +++ b/addons/website_sale_collect/controllers/__init__.py @@ -1,5 +1,3 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import delivery -from . import main -from . import payment +from . import delivery, main, payment diff --git a/addons/website_sale_collect/controllers/delivery.py b/addons/website_sale_collect/controllers/delivery.py index f4c06aa5f6ea7..39384856be7e0 100644 --- a/addons/website_sale_collect/controllers/delivery.py +++ b/addons/website_sale_collect/controllers/delivery.py @@ -6,27 +6,26 @@ class InStoreDelivery(Delivery): - @route() def website_sale_get_pickup_locations(self, zip_code=None, **kwargs): - """ Override of `website_sale` to set the pickup in store delivery method on the order in + """Override of `website_sale` to set the pickup in store delivery method on the order in order to retrieve pickup locations when called from the product page. If there is no order create a temporary one to display pickup locations. """ - if kwargs.get('product_id'): # Called from the product page. + if kwargs.get("product_id"): # Called from the product page. order_sudo = request.cart in_store_dm = request.website.sudo().in_store_dm_id if not order_sudo: # Pickup location requested without a cart creation. # Create a temporary order to fetch pickup locations. - temp_order = request.env['sale.order'].new({'carrier_id': in_store_dm.id}) + temp_order = request.env["sale.order"].new({"carrier_id": in_store_dm.id}) return temp_order.sudo()._get_pickup_locations(zip_code, **kwargs) # Skip super - elif order_sudo.carrier_id.delivery_type != 'in_store': + if order_sudo.carrier_id.delivery_type != "in_store": order_sudo.set_delivery_line(in_store_dm, in_store_dm.product_id.list_price) return super().website_sale_get_pickup_locations(zip_code, **kwargs) - @route('/shop/set_click_and_collect_location', type='jsonrpc', auth='public', website=True) + @route("/shop/set_click_and_collect_location", type="jsonrpc", auth="public", website=True) def shop_set_click_and_collect_location(self, pickup_location_data): - """ Set the pickup location and the in-store delivery method on the current order or created + """Set the pickup location and the in-store delivery method on the current order or created one. This route is called from location selector on /product and is distinct from @@ -37,14 +36,14 @@ def shop_set_click_and_collect_location(self, pickup_location_data): :return: None """ order_sudo = request.cart or request.website._create_cart() - if order_sudo.carrier_id.delivery_type != 'in_store': + if order_sudo.carrier_id.delivery_type != "in_store": in_store_dm = request.website.sudo().in_store_dm_id order_sudo.set_delivery_line(in_store_dm, in_store_dm.product_id.list_price) order_sudo._set_pickup_location(pickup_location_data) def _get_additional_delivery_context(self): - """ Override of `website_sale` to include the default pickup location data for in-store - delivery methods with a single warehouse. """ + """Override of `website_sale` to include the default pickup location data for in-store + delivery methods with a single warehouse.""" res = super()._get_additional_delivery_context() order_sudo = request.cart if request.website.sudo().in_store_dm_id: @@ -56,6 +55,6 @@ def _get_delivery_methods_express_checkout(cls, order_sudo): """Override to exclude `in_store` delivery methods from exress checkout delivery options.""" dm_rate_mapping = super()._get_delivery_methods_express_checkout(order_sudo) for dm in list(dm_rate_mapping): - if dm.delivery_type == 'in_store': + if dm.delivery_type == "in_store": del dm_rate_mapping[dm] return dm_rate_mapping diff --git a/addons/website_sale_collect/controllers/main.py b/addons/website_sale_collect/controllers/main.py index c150e09e61fd6..784272c1e8b2a 100644 --- a/addons/website_sale_collect/controllers/main.py +++ b/addons/website_sale_collect/controllers/main.py @@ -7,62 +7,60 @@ class WebsiteSaleCollect(WebsiteSale): - def _prepare_product_values(self, product, category, **kwargs): - """ Override of `website_sale` to configure the Click & Collect Availability widget. """ + """Override of `website_sale` to configure the Click & Collect Availability widget.""" res = super()._prepare_product_values(product, category, **kwargs) if in_store_dm_sudo := request.website.sudo().in_store_dm_id: order_sudo = request.cart selected_location_data = {} single_location = len(in_store_dm_sudo.warehouse_ids) == 1 if ( - order_sudo.carrier_id.delivery_type == 'in_store' + order_sudo.carrier_id.delivery_type == "in_store" and order_sudo.pickup_location_data ): selected_location_data = order_sudo.pickup_location_data elif single_location: - selected_location_data = ( - in_store_dm_sudo.warehouse_ids[0]._prepare_pickup_location_data() - ) + default_wh = in_store_dm_sudo.warehouse_ids[0] + selected_location_data = default_wh._prepare_pickup_location_data() res.update({ - 'selected_location_data': selected_location_data, - 'show_select_store_button': not single_location, - 'zip_code': ( # Define the zip code. + "selected_location_data": selected_location_data, + "show_select_store_button": not single_location, + "zip_code": ( # Define the zip code. order_sudo.partner_shipping_id.zip - or selected_location_data.get('zip_code') + or selected_location_data.get("zip_code") or request.geoip.postal.code - or '' # String expected for the widget. + or "" # String expected for the widget. ), }) return res def _prepare_checkout_page_values(self, order_sudo, **query_params): - """ Override of `website_sale` to include the unavailable products for the selected pickup - location and set the pickup location when there is only one warehouse available. """ + """Override of `website_sale` to include the unavailable products for the selected pickup + location and set the pickup location when there is only one warehouse available.""" res = super()._prepare_checkout_page_values(order_sudo, **query_params) if order_sudo.only_services: return res res.update(order_sudo._prepare_in_store_default_location_data()) - if order_sudo.carrier_id.delivery_type == 'in_store' and order_sudo.pickup_location_data: - res['insufficient_stock_data'] = order_sudo._get_insufficient_stock_data( - order_sudo.pickup_location_data.get('id') + if order_sudo.carrier_id.delivery_type == "in_store" and order_sudo.pickup_location_data: + res["insufficient_stock_data"] = order_sudo._get_insufficient_stock_data( + order_sudo.pickup_location_data.get("id") ) return res def _get_shop_payment_errors(self, order): - """ Override of `website_sale` to includes errors if no pickup location is selected or some - products are unavailable. """ + """Override of `website_sale` to includes errors if no pickup location is selected or some + products are unavailable.""" errors = super()._get_shop_payment_errors(order) - if order._has_deliverable_products() and order.carrier_id.delivery_type == 'in_store': + if order._has_deliverable_products() and order.carrier_id.delivery_type == "in_store": if not order.pickup_location_data: errors.append(( _("Sorry, we are unable to ship your order."), _("Please choose a store to collect your order."), )) else: - selected_wh_id = order.pickup_location_data['id'] + selected_wh_id = order.pickup_location_data["id"] if not order._is_in_stock(selected_wh_id): errors.append(( _("Sorry, we are unable to ship your order."), diff --git a/addons/website_sale_collect/controllers/payment.py b/addons/website_sale_collect/controllers/payment.py index d96016ad62e0f..41507719dbf72 100644 --- a/addons/website_sale_collect/controllers/payment.py +++ b/addons/website_sale_collect/controllers/payment.py @@ -7,9 +7,8 @@ class OnSitePaymentPortal(PaymentPortal): - def _validate_transaction_for_order(self, transaction, sale_order): - """ Override of `website_sale` to ensure the on-site payment provider is not used without + """Override of `website_sale` to ensure the on-site payment provider is not used without the in-store pickup delivery method. This also sets the warehouse of the selected pickup location on the sales order. @@ -25,9 +24,9 @@ def _validate_transaction_for_order(self, transaction, sale_order): # This should never be triggered unless the user intentionally forges a request. provider = transaction.provider_id if ( - sale_order.carrier_id.delivery_type != 'in_store' - and provider.code == 'custom' - and provider.custom_mode == 'on_site' + sale_order.carrier_id.delivery_type != "in_store" + and provider.code == "custom" + and provider.custom_mode == "on_site" ): raise ValidationError( _("You can only pay on site when selecting the pick up in store delivery method.") diff --git a/addons/website_sale_collect/models/__init__.py b/addons/website_sale_collect/models/__init__.py index 10490be85ef96..6aef2a86f0244 100644 --- a/addons/website_sale_collect/models/__init__.py +++ b/addons/website_sale_collect/models/__init__.py @@ -1,10 +1,12 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import delivery_carrier -from . import payment_provider -from . import payment_transaction -from . import product_template -from . import res_config_settings -from . import sale_order -from . import stock_warehouse -from . import website +from . import ( + delivery_carrier, + payment_provider, + payment_transaction, + product_template, + res_config_settings, + sale_order, + stock_warehouse, + website, +) diff --git a/addons/website_sale_collect/models/delivery_carrier.py b/addons/website_sale_collect/models/delivery_carrier.py index 4cd8d08e241ff..9a5b2dfe093fa 100644 --- a/addons/website_sale_collect/models/delivery_carrier.py +++ b/addons/website_sale_collect/models/delivery_carrier.py @@ -9,29 +9,33 @@ class DeliveryCarrier(models.Model): - _inherit = 'delivery.carrier' + _inherit = "delivery.carrier" delivery_type = fields.Selection( - selection_add=[('in_store', "Pick up in store")], ondelete={'in_store': 'set default'} + selection_add=[("in_store", "Pick up in store")], ondelete={"in_store": "set default"} ) - warehouse_ids = fields.Many2many(string="Stores", comodel_name='stock.warehouse') + warehouse_ids = fields.Many2many(string="Stores", comodel_name="stock.warehouse") - @api.constrains('delivery_type', 'is_published', 'warehouse_ids') + @api.constrains("delivery_type", "is_published", "warehouse_ids") def _check_in_store_dm_has_warehouses_when_published(self): - if any(self.filtered( - lambda dm: dm.delivery_type == 'in_store' - and dm.is_published - and not dm.warehouse_ids - )): + if any( + self.filtered( + lambda dm: dm.delivery_type == "in_store" + and dm.is_published + and not dm.warehouse_ids + ) + ): raise ValidationError( _("The delivery method must have at least one warehouse to be published.") ) - @api.constrains('delivery_type', 'company_id', 'warehouse_ids') + @api.constrains("delivery_type", "company_id", "warehouse_ids") def _check_warehouses_have_same_company(self): for dm in self: - if dm.delivery_type == 'in_store' and dm.company_id and any( - wh.company_id and dm.company_id != wh.company_id for wh in dm.warehouse_ids + if ( + dm.delivery_type == "in_store" + and dm.company_id + and any(wh.company_id and dm.company_id != wh.company_id for wh in dm.warehouse_ids) ): raise ValidationError( _("The delivery method and a warehouse must share the same company") @@ -40,31 +44,29 @@ def _check_warehouses_have_same_company(self): @api.model_create_multi def create(self, vals_list): for vals in vals_list: - if vals.get('delivery_type') == 'in_store': - vals['integration_level'] = 'rate' - vals['allow_cash_on_delivery'] = False + if vals.get("delivery_type") == "in_store": + vals["integration_level"] = "rate" + vals["allow_cash_on_delivery"] = False # Set the default warehouses and publish if one is found. - if 'company_id' in vals: - company_id = vals.get('company_id') + if "company_id" in vals: + company_id = vals.get("company_id") else: company_id = ( - self.env['product.product'].browse(vals.get('product_id')).company_id.id + self.env["product.product"].browse(vals.get("product_id")).company_id.id or self.env.company.id ) - warehouses = self.env['stock.warehouse'].search( - [('company_id', 'in', company_id)] - ) + warehouses = self.env["stock.warehouse"].search([("company_id", "in", company_id)]) vals.update({ - 'warehouse_ids': [Command.set(warehouses.ids)], - 'is_published': bool(warehouses), + "warehouse_ids": [Command.set(warehouses.ids)], + "is_published": bool(warehouses), }) return super().create(vals_list) def write(self, vals): - if vals.get('delivery_type') == 'in_store': - vals['integration_level'] = 'rate' - vals['allow_cash_on_delivery'] = False + if vals.get("delivery_type") == "in_store": + vals["integration_level"] = "rate" + vals["allow_cash_on_delivery"] = False return super().write(vals) # === BUSINESS METHODS ===# @@ -82,9 +84,9 @@ def _in_store_get_close_locations(self, partner_address, product_id=None, uom_id try: product_id = product_id and int(product_id) except ValueError: - product = self.env['product.product'] + product = self.env["product.product"] else: - product = self.env['product.product'].browse(product_id) + product = self.env["product.product"].browse(product_id) partner_address.geo_localize() # Calculate coordinates. @@ -97,27 +99,27 @@ def _in_store_get_close_locations(self, partner_address, product_id=None, uom_id # Prepare the stock data based on either the product or the order. if product: # Called from the product page. - uom = self.env['uom.uom'].browse(uom_id) + uom = self.env["uom.uom"].browse(uom_id) cart_qty = order_sudo._get_cart_qty(product.id) in_store_stock_data = utils.format_product_stock_values( product, wh_id=wh.id, uom=uom, cart_qty=cart_qty ) else: # Called from the checkout page. - in_store_stock_data = {'in_stock': order_sudo._is_in_stock(wh.id)} + in_store_stock_data = {"in_stock": order_sudo._is_in_stock(wh.id)} # Calculate the distance between the partner address and the warehouse location. pickup_location_values.update({ - 'additional_data': {'in_store_stock_data': in_store_stock_data}, - 'distance': utils.calculate_partner_distance(partner_address, wh.partner_id), + "additional_data": {"in_store_stock_data": in_store_stock_data}, + "distance": utils.calculate_partner_distance(partner_address, wh.partner_id), }) pickup_locations.append(pickup_location_values) - return sorted(pickup_locations, key=lambda k: k['distance']) + return sorted(pickup_locations, key=lambda k: k["distance"]) def in_store_rate_shipment(self, *_args): return { - 'success': True, - 'price': self.product_id.list_price, - 'error_message': False, - 'warning_message': False, + "success": True, + "price": self.product_id.list_price, + "error_message": False, + "warning_message": False, } diff --git a/addons/website_sale_collect/models/payment_provider.py b/addons/website_sale_collect/models/payment_provider.py index e047f1fdd7b41..835b4a0c88a9d 100644 --- a/addons/website_sale_collect/models/payment_provider.py +++ b/addons/website_sale_collect/models/payment_provider.py @@ -7,16 +7,16 @@ class PaymentProvider(models.Model): - _inherit = 'payment.provider' + _inherit = "payment.provider" - custom_mode = fields.Selection(selection_add=[('on_site', "Pay on site")]) + custom_mode = fields.Selection(selection_add=[("on_site", "Pay on site")]) # === CRUD METHODS === # def _get_default_payment_method_codes(self): - """ Override of `payment` to return the default payment method codes. """ + """Override of `payment` to return the default payment method codes.""" self.ensure_one() - if self.custom_mode != 'on_site': + if self.custom_mode != "on_site": return super()._get_default_payment_method_codes() return const.DEFAULT_PAYMENT_METHOD_CODES @@ -26,7 +26,7 @@ def _get_default_payment_method_codes(self): def _get_compatible_providers( self, company_id, *args, sale_order_id=None, website_id=None, report=None, **kwargs ): - """ Override of payment to exclude on-site payment providers if the delivery method is not + """Override of payment to exclude on-site payment providers if the delivery method is not pick up in store. :param int company_id: The company to which providers must belong, as a `res.company` id @@ -44,16 +44,16 @@ def _get_compatible_providers( report=report, **kwargs, ) - order = self.env['sale.order'].browse(sale_order_id).exists() + order = self.env["sale.order"].browse(sale_order_id).exists() # Show on-site payment providers only if in-store delivery methods exist and the order # contains physical products. - if order.carrier_id.delivery_type != 'in_store' or not any( - product.type == 'consu' for product in order.order_line.product_id + if order.carrier_id.delivery_type != "in_store" or not any( + product.type == "consu" for product in order.order_line.product_id ): unfiltered_providers = compatible_providers compatible_providers = compatible_providers.filtered( - lambda p: p.code != 'custom' or p.custom_mode != 'on_site' + lambda p: p.code != "custom" or p.custom_mode != "on_site" ) payment_utils.add_to_report( report, diff --git a/addons/website_sale_collect/models/payment_transaction.py b/addons/website_sale_collect/models/payment_transaction.py index d78d97675b05b..a531c86c3c40b 100644 --- a/addons/website_sale_collect/models/payment_transaction.py +++ b/addons/website_sale_collect/models/payment_transaction.py @@ -4,15 +4,15 @@ class PaymentTransaction(models.Model): - _inherit = 'payment.transaction' + _inherit = "payment.transaction" def _post_process(self): - """ Override of `payment` to confirm orders with the on_site payment method and trigger - a picking creation. """ + """Override of `payment` to confirm orders with the on_site payment method and trigger + a picking creation.""" on_site_pending_txs = self.filtered( - lambda tx: tx.provider_id.custom_mode == 'on_site' and tx.state == 'pending' + lambda tx: tx.provider_id.custom_mode == "on_site" and tx.state == "pending" ) - on_site_pending_txs.sale_order_ids.filtered( - lambda so: so.state == 'draft' - ).with_context(send_email=True).action_confirm() + on_site_pending_txs.sale_order_ids.filtered(lambda so: so.state == "draft").with_context( + send_email=True + ).action_confirm() super()._post_process() diff --git a/addons/website_sale_collect/models/product_template.py b/addons/website_sale_collect/models/product_template.py index 83a602ba11bbb..14e442b8178e0 100644 --- a/addons/website_sale_collect/models/product_template.py +++ b/addons/website_sale_collect/models/product_template.py @@ -7,11 +7,11 @@ class ProductTemplate(models.Model): - _inherit = 'product.template' + _inherit = "product.template" def _get_additionnal_combination_info(self, product_or_template, quantity, uom, date, website): """Override of `website_sale` to add information on whether Click & Collect is enabled and - on the stock of the product. """ + on the stock of the product.""" res = super()._get_additionnal_combination_info( product_or_template, quantity, uom, date, website ) @@ -24,36 +24,39 @@ def _get_additionnal_combination_info(self, product_or_template, quantity, uom, order_sudo = request.cart cart_qty = order_sudo._get_cart_qty(product_sudo.id) # Enable the Click & Collect Availability widget. - res['show_click_and_collect_availability'] = True - res['uom_id'] = uom.id + res["show_click_and_collect_availability"] = True + res["uom_id"] = uom.id # Prepare the delivery stock data. - available_delivery_methods_sudo = self.env['delivery.carrier'].sudo().search([ - '|', ('website_id', '=', website.id), ('website_id', '=', False), - ('website_published', '=', True), - ('delivery_type', '!=', 'in_store'), + DeliveryCarrier = self.env["delivery.carrier"].sudo() + available_delivery_methods_sudo = DeliveryCarrier.search([ + "|", + ("website_id", "=", website.id), + ("website_id", "=", False), + ("website_published", "=", True), + ("delivery_type", "!=", "in_store"), ]) if available_delivery_methods_sudo: - res['delivery_stock_data'] = utils.format_product_stock_values( + res["delivery_stock_data"] = utils.format_product_stock_values( product_sudo, uom=uom, cart_qty=cart_qty ) else: - res['delivery_stock_data'] = {} + res["delivery_stock_data"] = {} # Prepare the in-store stock data. if ( order_sudo - and order_sudo.carrier_id.delivery_type == 'in_store' + and order_sudo.carrier_id.delivery_type == "in_store" and order_sudo.pickup_location_data ): # Get stock values for the product variant in the selected store. - res['in_store_stock_data'] = utils.format_product_stock_values( + res["in_store_stock_data"] = utils.format_product_stock_values( product_sudo, uom=uom, - wh_id=order_sudo.pickup_location_data['id'], + wh_id=order_sudo.pickup_location_data["id"], cart_qty=cart_qty, ) else: - res['in_store_stock_data'] = utils.format_product_stock_values( + res["in_store_stock_data"] = utils.format_product_stock_values( product_sudo, uom=uom, free_qty=website.sudo()._get_max_in_store_product_available_qty(product_sudo), diff --git a/addons/website_sale_collect/models/res_config_settings.py b/addons/website_sale_collect/models/res_config_settings.py index abb2a23660024..f55f691b9bce6 100644 --- a/addons/website_sale_collect/models/res_config_settings.py +++ b/addons/website_sale_collect/models/res_config_settings.py @@ -4,23 +4,23 @@ class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' + _inherit = "res.config.settings" def action_view_in_store_delivery_methods(self): - """ Return an action to browse pickup delivery methods in list view, or in form view if - there is only one. """ - in_store_dms = self.env['delivery.carrier'].search([('delivery_type', '=', 'in_store')]) + """Return an action to browse pickup delivery methods in list view, or in form view if + there is only one.""" + in_store_dms = self.env["delivery.carrier"].search([("delivery_type", "=", "in_store")]) if len(in_store_dms) == 1: return { - 'type': 'ir.actions.act_window', - 'res_model': 'delivery.carrier', - 'view_mode': 'form', - 'res_id': in_store_dms.id, + "type": "ir.actions.act_window", + "res_model": "delivery.carrier", + "view_mode": "form", + "res_id": in_store_dms.id, } return { - 'type': 'ir.actions.act_window', - 'name': _("Delivery Methods"), - 'res_model': 'delivery.carrier', - 'view_mode': 'list,form', - 'context': '{"search_default_delivery_type": "in_store"}', + "type": "ir.actions.act_window", + "name": _("Delivery Methods"), + "res_model": "delivery.carrier", + "view_mode": "list,form", + "context": '{"search_default_delivery_type": "in_store"}', } diff --git a/addons/website_sale_collect/models/sale_order.py b/addons/website_sale_collect/models/sale_order.py index 5dac4b6fbba92..45f182832d96a 100644 --- a/addons/website_sale_collect/models/sale_order.py +++ b/addons/website_sale_collect/models/sale_order.py @@ -8,27 +8,25 @@ class SaleOrder(models.Model): - _inherit = 'sale.order' + _inherit = "sale.order" def _compute_warehouse_id(self): """Override of `website_sale_stock` to avoid recomputations for in_store orders when the warehouse was set by the pickup_location_data.""" in_store_orders_with_pickup_data = self.filtered( - lambda so: ( - so.carrier_id.delivery_type == 'in_store' and so.pickup_location_data - ) + lambda so: (so.carrier_id.delivery_type == "in_store" and so.pickup_location_data) ) super(SaleOrder, self - in_store_orders_with_pickup_data)._compute_warehouse_id() for order in in_store_orders_with_pickup_data: - order.warehouse_id = order.pickup_location_data['id'] + order.warehouse_id = order.pickup_location_data["id"] def _compute_fiscal_position_id(self): """Override of `sale` to set the fiscal position matching the selected pickup location for pickup in-store orders.""" in_store_orders = self.filtered( - lambda so: so.carrier_id.delivery_type == 'in_store' and so.pickup_location_data + lambda so: so.carrier_id.delivery_type == "in_store" and so.pickup_location_data ) - AccountFiscalPosition = self.env['account.fiscal.position'].sudo() + AccountFiscalPosition = self.env["account.fiscal.position"].sudo() for order in in_store_orders: order.fiscal_position_id = AccountFiscalPosition._get_fiscal_position( order.partner_id, delivery=order.warehouse_id.partner_id @@ -37,11 +35,11 @@ def _compute_fiscal_position_id(self): def _set_delivery_method(self, delivery_method, rate=None): """Override of `website_sale` to recompute warehouse and fiscal position when a new - delivery method is not in-store anymore. """ + delivery method is not in-store anymore.""" self.ensure_one() was_in_store_order = ( - self.carrier_id.delivery_type == 'in_store' - and delivery_method.delivery_type != 'in_store' + self.carrier_id.delivery_type == "in_store" + and delivery_method.delivery_type != "in_store" ) super()._set_delivery_method(delivery_method, rate=rate) if was_in_store_order: @@ -54,12 +52,12 @@ def _set_pickup_location(self, pickup_location_data): taxes. """ super()._set_pickup_location(pickup_location_data) - if self.carrier_id.delivery_type != 'in_store': + if self.carrier_id.delivery_type != "in_store": return self.pickup_location_data = json.loads(pickup_location_data) if self.pickup_location_data: - self.warehouse_id = self.pickup_location_data['id'] + self.warehouse_id = self.pickup_location_data["id"] self._compute_fiscal_position_id() else: self._compute_warehouse_id() @@ -74,10 +72,10 @@ def _get_pickup_locations(self, zip_code=None, country=None, **kwargs): if zip_code and not country: country_code = None if self.pickup_location_data: - country_code = self.pickup_location_data['country_code'] + country_code = self.pickup_location_data["country_code"] elif request.geoip.country_code: country_code = request.geoip.country_code - country = self.env['res.country'].search([('code', '=', country_code)], limit=1) + country = self.env["res.country"].search([("code", "=", country_code)], limit=1) if not country: zip_code = None # Reset the zip code to skip the `assert` in the `super` call. return super()._get_pickup_locations(zip_code=zip_code, country=country, **kwargs) @@ -85,45 +83,45 @@ def _get_pickup_locations(self, zip_code=None, country=None, **kwargs): def _get_shop_warehouse_id(self): """Override of `website_sale_stock` to consider the chosen warehouse.""" self.ensure_one() - if self.carrier_id.delivery_type == 'in_store': + if self.carrier_id.delivery_type == "in_store": return self.warehouse_id.id return super()._get_shop_warehouse_id() def _check_cart_is_ready_to_be_paid(self): """Override of `website_sale` to check if all products are in stock in the selected - warehouse. """ + warehouse.""" if ( self._has_deliverable_products() - and self.carrier_id.delivery_type == 'in_store' + and self.carrier_id.delivery_type == "in_store" and not self._is_in_stock(self.warehouse_id.id) ): - raise ValidationError(self.env._( - "Some products are not available in the selected store." - )) + raise ValidationError( + self.env._("Some products are not available in the selected store.") + ) return super()._check_cart_is_ready_to_be_paid() # === TOOLING ===# def _prepare_in_store_default_location_data(self): """Prepare the default pickup location values for each in-store delivery method available - for the order. """ + for the order.""" default_pickup_locations = {} for dm in self._get_delivery_methods(): if ( - dm.delivery_type == 'in_store' + dm.delivery_type == "in_store" and dm.id != self.carrier_id.id and len(dm.warehouse_ids) == 1 ): pickup_location_data = dm.warehouse_ids[0]._prepare_pickup_location_data() if pickup_location_data: default_pickup_locations[dm.id] = { - 'pickup_location_data': pickup_location_data, - 'insufficient_stock_data': self._get_insufficient_stock_data( - pickup_location_data['id'] + "pickup_location_data": pickup_location_data, + "insufficient_stock_data": self._get_insufficient_stock_data( + pickup_location_data["id"] ), } - return {'default_pickup_locations': default_pickup_locations} + return {"default_pickup_locations": default_pickup_locations} def _is_in_stock(self, wh_id): """Check whether all storable products of the cart are in stock in the given warehouse. @@ -145,33 +143,36 @@ def _get_insufficient_stock_data(self, wh_id): :rtype: dict """ insufficient_stock_data = {} - for product, ols in self.order_line.grouped('product_id').items(): + for product, ols in self.order_line.grouped("product_id").items(): if not product.is_storable or product.allow_out_of_stock_order: continue free_qty = product.with_context(warehouse_id=wh_id).free_qty for ol in ols: - free_qty_in_uom = max(int(product.uom_id._compute_quantity( - free_qty, ol.product_uom_id, rounding_method='DOWN' - )), 0) # Round down as only integer quantities can be sold. + free_qty_in_uom = product.uom_id._compute_quantity( + free_qty, ol.product_uom_id, rounding_method="DOWN" + ) + # Round down as only integer quantities can be sold. + free_qty_in_uom = max(int(free_qty_in_uom), 0) line_qty_in_uom = ol.product_uom_qty if line_qty_in_uom > free_qty_in_uom: # Not enough stock. # Set a warning on the order line. insufficient_stock_data[ol] = free_qty_in_uom ol.shop_warning = self.env._( "%(available_qty)s/%(line_qty)s available at this location", - available_qty=free_qty_in_uom, line_qty=int(line_qty_in_uom), + available_qty=free_qty_in_uom, + line_qty=int(line_qty_in_uom), ) free_qty -= ol.product_uom_id._compute_quantity(line_qty_in_uom, product.uom_id) return insufficient_stock_data def _verify_updated_quantity(self, order_line, product_id, new_qty, uom_id, **kwargs): """Override of `website_sale_stock` to skip the verification when click and collect - is activated. The quantity is verified later. """ - product = self.env['product.product'].browse(product_id) + is activated. The quantity is verified later.""" + product = self.env["product.product"].browse(product_id) if ( product.is_storable and not product.allow_out_of_stock_order and self.website_id.in_store_dm_id ): - return new_qty, '' + return new_qty, "" return super()._verify_updated_quantity(order_line, product_id, new_qty, uom_id, **kwargs) diff --git a/addons/website_sale_collect/models/stock_warehouse.py b/addons/website_sale_collect/models/stock_warehouse.py index 84bdbb4efb15d..6338af85f365a 100644 --- a/addons/website_sale_collect/models/stock_warehouse.py +++ b/addons/website_sale_collect/models/stock_warehouse.py @@ -5,10 +5,10 @@ class StockWarehouse(models.Model): - _inherit = 'stock.warehouse' + _inherit = "stock.warehouse" opening_hours = fields.Many2one( - string="Opening Hours", comodel_name='resource.calendar', check_company=True + string="Opening Hours", comodel_name="resource.calendar", check_company=True ) def _prepare_pickup_location_data(self): @@ -20,15 +20,15 @@ def _prepare_pickup_location_data(self): # Format the pickup location values of the warehouse. try: pickup_location_values = { - 'id': self.id, - 'name': wh_location['name'].title(), - 'street': wh_location['street'].title(), - 'city': wh_location.city.title(), - 'state': wh_location.state_id.code or '', - 'zip_code': wh_location.zip or '', - 'country_code': wh_location.country_code, - 'latitude': wh_location.partner_latitude, - 'longitude': wh_location.partner_longitude, + "id": self.id, + "name": wh_location["name"].title(), + "street": wh_location["street"].title(), + "city": wh_location.city.title(), + "state": wh_location.state_id.code or "", + "zip_code": wh_location.zip or "", + "country_code": wh_location.country_code, + "latitude": wh_location.partner_latitude, + "longitude": wh_location.partner_longitude, } except AttributeError: return {} @@ -37,11 +37,11 @@ def _prepare_pickup_location_data(self): if self.opening_hours: opening_hours_dict = {str(i): [] for i in range(7)} for att in self.opening_hours.attendance_ids: - if att.day_period in ('morning', 'afternoon'): + if att.day_period in ("morning", "afternoon"): opening_hours_dict[att.dayofweek].append( - f'{format_duration(att.hour_from)} - {format_duration(att.hour_to)}' + f"{format_duration(att.hour_from)} - {format_duration(att.hour_to)}" ) - pickup_location_values['opening_hours'] = opening_hours_dict + pickup_location_values["opening_hours"] = opening_hours_dict else: - pickup_location_values['opening_hours'] = {} + pickup_location_values["opening_hours"] = {} return pickup_location_values diff --git a/addons/website_sale_collect/models/website.py b/addons/website_sale_collect/models/website.py index b4e300fbb51ee..df824a36bd910 100644 --- a/addons/website_sale_collect/models/website.py +++ b/addons/website_sale_collect/models/website.py @@ -4,29 +4,34 @@ class Website(models.Model): - _inherit = 'website' + _inherit = "website" in_store_dm_id = fields.Many2one( string="In-store Delivery Method", - comodel_name='delivery.carrier', - compute='_compute_in_store_dm_id', + comodel_name="delivery.carrier", + compute="_compute_in_store_dm_id", ) def _compute_in_store_dm_id(self): - in_store_delivery_methods = self.env['delivery.carrier'].search( - [('delivery_type', '=', 'in_store'), ('is_published', '=', True)] - ) + in_store_delivery_methods = self.env["delivery.carrier"].search([ + ("delivery_type", "=", "in_store"), + ("is_published", "=", True), + ]) for website in self: website.in_store_dm_id = in_store_delivery_methods.filtered_domain([ - '|', ('website_id', '=', False), ('website_id', '=', website.id), - '|', ('company_id', '=', False), ('company_id', '=', website.company_id.id), + "|", + ("website_id", "=", False), + ("website_id", "=", website.id), + "|", + ("company_id", "=", False), + ("company_id", "=", website.company_id.id), ])[:1] def _get_product_available_qty(self, product, **kwargs): - """ Override of `website_sale_stock` to include free quantities of the product in warehouses - of in-store delivery method and return maximum possible for one order. Needed only if a - warehouse is set on website, otherwise free quantity is already calculated from all - warehouses.""" + """Override of `website_sale_stock` to include free quantities of the product in warehouses + of in-store delivery method and return maximum possible for one order. Needed only if a + warehouse is set on website, otherwise free quantity is already calculated from all + warehouses.""" free_qty = super()._get_product_available_qty(product, **kwargs) if self.warehouse_id and self.sudo().in_store_dm_id: # If warehouse is set on website. # Check free quantities in the in-store warehouses. @@ -34,8 +39,11 @@ def _get_product_available_qty(self, product, **kwargs): return free_qty def _get_max_in_store_product_available_qty(self, product): - """ Return maximum amount of product available to deliver with in store delivery method. """ - return max([ - product.with_context(warehouse_id=wh.id).free_qty - for wh in self.sudo().in_store_dm_id.warehouse_ids - ], default=0) + """Return maximum amount of product available to deliver with in store delivery method.""" + return max( + [ + product.with_context(warehouse_id=wh.id).free_qty + for wh in self.sudo().in_store_dm_id.warehouse_ids + ], + default=0, + ) diff --git a/addons/website_sale_collect/tests/__init__.py b/addons/website_sale_collect/tests/__init__.py index b4ee93e2bf6e5..8eb80449aa41f 100644 --- a/addons/website_sale_collect/tests/__init__.py +++ b/addons/website_sale_collect/tests/__init__.py @@ -1,11 +1,13 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_delivery_carrier -from . import test_click_and_collect_express_checkout -from . import test_click_and_collect_flow -from . import test_in_store_delivery -from . import test_payment_provider -from . import test_payment_transaction -from . import test_product_template -from . import test_sale_order -from . import test_website +from . import ( + test_click_and_collect_express_checkout, + test_click_and_collect_flow, + test_delivery_carrier, + test_in_store_delivery, + test_payment_provider, + test_payment_transaction, + test_product_template, + test_sale_order, + test_website, +) diff --git a/addons/website_sale_collect/tests/common.py b/addons/website_sale_collect/tests/common.py index 57fbf28c9ec4d..d10a5b4a80268 100644 --- a/addons/website_sale_collect/tests/common.py +++ b/addons/website_sale_collect/tests/common.py @@ -7,7 +7,6 @@ class ClickAndCollectCommon(PaymentCustomCommon, WebsiteSaleStockCommon): - @classmethod def setUpClass(cls): super().setUpClass() @@ -17,11 +16,11 @@ def setUpClass(cls): # Create the in-store delivery method. cls.dm_product = cls._prepare_carrier_product(list_price=0.0) - cls.provider = cls._prepare_provider(code='custom', custom_mode='on_site') + cls.provider = cls._prepare_provider(code="custom", custom_mode="on_site") cls.in_store_dm = cls._prepare_carrier( cls.dm_product, fixed_price=0.0, - delivery_type='in_store', + delivery_type="in_store", warehouse_ids=[Command.set([cls.warehouse.id])], name="Example in-store delivery", is_published=True, @@ -29,12 +28,11 @@ def setUpClass(cls): def _create_in_store_delivery_order(self, **values): default_values = { - 'partner_id': self.partner.id, - 'website_id': self.website.id, - 'order_line': [Command.create({ - 'product_id': self.storable_product.id, - 'product_uom_qty': 5.0, - })], - 'carrier_id': self.in_store_dm.id, + "partner_id": self.partner.id, + "website_id": self.website.id, + "order_line": [ + Command.create({"product_id": self.storable_product.id, "product_uom_qty": 5.0}) + ], + "carrier_id": self.in_store_dm.id, } - return self.env['sale.order'].create(dict(default_values, **values)) + return self.env["sale.order"].create(dict(default_values, **values)) diff --git a/addons/website_sale_collect/tests/test_click_and_collect_express_checkout.py b/addons/website_sale_collect/tests/test_click_and_collect_express_checkout.py index eb73e71e05870..46d0052da10a1 100644 --- a/addons/website_sale_collect/tests/test_click_and_collect_express_checkout.py +++ b/addons/website_sale_collect/tests/test_click_and_collect_express_checkout.py @@ -6,9 +6,8 @@ from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestClickAndCollectExpressCheckout(ClickAndCollectCommon): - def test_exclude_in_store_delivery_methods(self): express_delivery_methods = InStoreDelivery._get_delivery_methods_express_checkout(self.cart) diff --git a/addons/website_sale_collect/tests/test_click_and_collect_flow.py b/addons/website_sale_collect/tests/test_click_and_collect_flow.py index 69ed71a973368..7beb556bbc111 100644 --- a/addons/website_sale_collect/tests/test_click_and_collect_flow.py +++ b/addons/website_sale_collect/tests/test_click_and_collect_flow.py @@ -1,45 +1,41 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests import tagged - from odoo.tests.common import HttpCase + from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestClickAndCollectFlow(HttpCase, ClickAndCollectCommon): - @classmethod def setUpClass(cls): super().setUpClass() cls.storable_product.name = "Test CAC Product" - cls.provider.write({ - 'state': 'enabled', - 'is_published': True, - }) - cls.in_store_dm.warehouse_ids[0].partner_id = cls.env['res.partner'].create({ + cls.provider.write({"state": "enabled", "is_published": True}) + cls.in_store_dm.warehouse_ids[0].partner_id = cls.env["res.partner"].create({ **cls.dummy_partner_address_values, - 'name': "Shop 1", - 'partner_latitude': 1.0, - 'partner_longitude': 2.0, + "name": "Shop 1", + "partner_latitude": 1.0, + "partner_longitude": 2.0, }) def test_buy_with_click_and_collect_as_public_user(self): """ Test the basic flow of buying with click and collect as a public user with more than - one delivery method available + one delivery method available. """ - self.start_tour('/', 'website_sale_collect_widget') + self.start_tour("/", "website_sale_collect_widget") def test_default_location_is_set_for_pick_up_in_store(self): """ Verify that when `Pick Up In Store` is the only active delivery method with the only wh, the checkout flow automatically sets the default store location. """ - self.env['delivery.carrier'].search([]).active = False + self.env["delivery.carrier"].search([]).active = False self.in_store_dm.active = True self.in_store_dm.is_published = True - self.start_tour('/', 'website_sale_collect_buy_product_default_location_pick_up_in_store') + self.start_tour("/", "website_sale_collect_buy_product_default_location_pick_up_in_store") def test_cash_on_delivery_resets_on_in_store_type(self): """ @@ -47,11 +43,11 @@ def test_cash_on_delivery_resets_on_in_store_type(self): to the 'in_store' delivery type, the 'allow_cash_on_delivery' field is automatically reset to False. """ - carrier = self.env['delivery.carrier'].create({ - 'name': 'Test Carrier', - 'allow_cash_on_delivery': True, - 'delivery_type': 'fixed', - 'product_id': self.storable_product.id, + carrier = self.env["delivery.carrier"].create({ + "name": "Test Carrier", + "allow_cash_on_delivery": True, + "delivery_type": "fixed", + "product_id": self.storable_product.id, }) - carrier.delivery_type = 'in_store' + carrier.delivery_type = "in_store" self.assertEqual(carrier.allow_cash_on_delivery, False) diff --git a/addons/website_sale_collect/tests/test_delivery_carrier.py b/addons/website_sale_collect/tests/test_delivery_carrier.py index f87e2523de13b..2bdb8db9abf6e 100644 --- a/addons/website_sale_collect/tests/test_delivery_carrier.py +++ b/addons/website_sale_collect/tests/test_delivery_carrier.py @@ -4,16 +4,15 @@ from odoo import Command from odoo.exceptions import ValidationError -from odoo.tests import Form, tagged +from odoo.tests import tagged from odoo.addons.website_sale.tests.common import MockRequest from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestDeliveryCarrier(ClickAndCollectCommon, WebsiteSaleStockCommon): - def test_prevent_publishing_when_no_warehouse(self): self.in_store_dm.is_published = False self.in_store_dm.warehouse_ids = [Command.clear()] @@ -22,82 +21,88 @@ def test_prevent_publishing_when_no_warehouse(self): def test_same_company_for_delivery_method_and_warehouse(self): self.in_store_dm.company_id = self.company_id - self.companyA = self.env['res.company'].create({'name': 'Company A'}) + self.companyA = self.env["res.company"].create({"name": "Company A"}) self.warehouse_2 = self._create_warehouse(company_id=self.companyA.id) with self.assertRaises(ValidationError): self.in_store_dm.warehouse_ids = [Command.set([self.warehouse_2.id])] def test_creating_in_store_delivery_method_sets_integration_level_to_rate(self): - new_in_store_carrier = self.env['delivery.carrier'].create({ - 'name': "Test Carrier", - 'delivery_type': 'in_store', - 'product_id': self.dm_product.id, + new_in_store_carrier = self.env["delivery.carrier"].create({ + "name": "Test Carrier", + "delivery_type": "in_store", + "product_id": self.dm_product.id, }) - self.assertEqual(new_in_store_carrier.integration_level, 'rate') + self.assertEqual(new_in_store_carrier.integration_level, "rate") def test_in_store_get_close_locations_returned_data(self): so = self._create_in_store_delivery_order() # Create a partner for a warehouse. - wh_address_partner = self.env['res.partner'].create({ + wh_address_partner = self.env["res.partner"].create({ **self.dummy_partner_address_values, - 'name': "Shop 1", - 'partner_latitude': 1.0, - 'partner_longitude': 2.0, + "name": "Shop 1", + "partner_latitude": 1.0, + "partner_longitude": 2.0, }) self.warehouse.partner_id = wh_address_partner.id - self.warehouse.opening_hours = self.env['resource.calendar'].create({ - 'name': 'Opening hours', - 'attendance_ids': [ + self.warehouse.opening_hours = self.env["resource.calendar"].create({ + "name": "Opening hours", + "attendance_ids": [ Command.create({ - 'name': 'Monday Morning', - 'dayofweek': '0', - 'hour_from': 8, - 'hour_to': 12, - 'day_period': 'morning', + "name": "Monday Morning", + "dayofweek": "0", + "hour_from": 8, + "hour_to": 12, + "day_period": "morning", }), Command.create({ - 'name': 'Monday Lunch', - 'dayofweek': '0', - 'hour_from': 12, - 'hour_to': 13, - 'day_period': 'lunch', + "name": "Monday Lunch", + "dayofweek": "0", + "hour_from": 12, + "hour_to": 13, + "day_period": "lunch", }), Command.create({ - 'name': 'Monday Afternoon', - 'dayofweek': '0', - 'hour_from': 13, - 'hour_to': 17, - 'day_period': 'afternoon', + "name": "Monday Afternoon", + "dayofweek": "0", + "hour_from": 13, + "hour_to": 17, + "day_period": "afternoon", }), ], }) - with patch( - 'odoo.addons.base_geolocalize.models.res_partner.ResPartner.geo_localize', - return_value=True - ), MockRequest(self.env, website=self.website, sale_order_id=so.id): + with ( + patch( + "odoo.addons.base_geolocalize.models.res_partner.ResPartner.geo_localize", + return_value=True, + ), + MockRequest(self.env, website=self.website, sale_order_id=so.id), + ): locations = self.in_store_dm._in_store_get_close_locations(wh_address_partner) self.assertEqual( - locations, [{ - 'id': self.warehouse.id, - 'name': wh_address_partner['name'].title(), - 'street': wh_address_partner['street'].title(), - 'city': wh_address_partner.city.title(), - 'zip_code': wh_address_partner.zip, - 'state': wh_address_partner.state_id.code, - 'country_code': wh_address_partner.country_code, - 'latitude': wh_address_partner.partner_latitude, - 'longitude': wh_address_partner.partner_longitude, - 'additional_data': {'in_store_stock_data': {'in_stock': True}}, - 'opening_hours': { - '0': ['08:00 - 12:00', '13:00 - 17:00'], - '1': [], - '2': [], - '3': [], - '4': [], - '5': [], - '6': [], - }, - 'distance': 0.0, - }] + locations, + [ + { + "id": self.warehouse.id, + "name": wh_address_partner["name"].title(), + "street": wh_address_partner["street"].title(), + "city": wh_address_partner.city.title(), + "zip_code": wh_address_partner.zip, + "state": wh_address_partner.state_id.code, + "country_code": wh_address_partner.country_code, + "latitude": wh_address_partner.partner_latitude, + "longitude": wh_address_partner.partner_longitude, + "additional_data": {"in_store_stock_data": {"in_stock": True}}, + "opening_hours": { + "0": ["08:00 - 12:00", "13:00 - 17:00"], + "1": [], + "2": [], + "3": [], + "4": [], + "5": [], + "6": [], + }, + "distance": 0.0, + } + ], ) diff --git a/addons/website_sale_collect/tests/test_in_store_delivery.py b/addons/website_sale_collect/tests/test_in_store_delivery.py index b5df9e682c38d..0a249b65fc047 100644 --- a/addons/website_sale_collect/tests/test_in_store_delivery.py +++ b/addons/website_sale_collect/tests/test_in_store_delivery.py @@ -1,6 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from unittest.mock import patch + from odoo.tests import tagged from odoo.addons.payment.tests.http_common import PaymentHttpCommon @@ -8,19 +9,19 @@ from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestInStoreDeliveryController(PaymentHttpCommon, ClickAndCollectCommon): def setUp(self): super().setUp() self.InStoreController = InStoreDelivery() def test_order_not_created_on_fetching_pickup_location_with_empty_cart(self): - count_so_before = self.env['sale.order'].search_count([]) - url = self._build_url('/website_sale/get_pickup_locations') + count_so_before = self.env["sale.order"].search_count([]) + url = self._build_url("/website_sale/get_pickup_locations") with patch( - 'odoo.addons.website_sale_collect.models.sale_order.SaleOrder._get_pickup_locations', - return_value={} + "odoo.addons.website_sale_collect.models.sale_order.SaleOrder._get_pickup_locations", + return_value={}, ): - self.make_jsonrpc_request(url, {'product_id': 1}) - count_so_after = self.env['sale.order'].search_count([]) + self.make_jsonrpc_request(url, {"product_id": 1}) + count_so_after = self.env["sale.order"].search_count([]) self.assertEqual(count_so_after, count_so_before) diff --git a/addons/website_sale_collect/tests/test_payment_provider.py b/addons/website_sale_collect/tests/test_payment_provider.py index 45628dd7d8495..e6989b991c4e6 100644 --- a/addons/website_sale_collect/tests/test_payment_provider.py +++ b/addons/website_sale_collect/tests/test_payment_provider.py @@ -5,23 +5,24 @@ from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestOnSitePaymentProvider(HttpCase, ClickAndCollectCommon): - def test_on_site_provider_available_when_in_store_delivery_is_chosen(self): order = self._create_in_store_delivery_order() - compatible_providers = self.env['payment.provider'].sudo()._get_compatible_providers( + PaymentProvider = self.env["payment.provider"].sudo() + compatible_providers = PaymentProvider._get_compatible_providers( self.company.id, self.partner.id, self.amount, sale_order_id=order.id ) - self.assertTrue(any( - p.code == 'custom' and p.custom_mode == 'on_site' for p in compatible_providers - )) + self.assertTrue( + any(p.code == "custom" and p.custom_mode == "on_site" for p in compatible_providers) + ) def test_on_site_provider_unavailable_when_no_in_store_delivery(self): order = self._create_in_store_delivery_order(carrier_id=self.free_delivery.id) - compatible_providers = self.env['payment.provider'].sudo()._get_compatible_providers( + PaymentProvider = self.env["payment.provider"].sudo() + compatible_providers = PaymentProvider._get_compatible_providers( self.company.id, self.partner.id, self.amount, sale_order_id=order.id ) - self.assertFalse(any( - p.code == 'custom' and p.custom_mode == 'on_site' for p in compatible_providers - )) + self.assertFalse( + any(p.code == "custom" and p.custom_mode == "on_site" for p in compatible_providers) + ) diff --git a/addons/website_sale_collect/tests/test_payment_transaction.py b/addons/website_sale_collect/tests/test_payment_transaction.py index 14255156f04d4..a2c6e4da6be09 100644 --- a/addons/website_sale_collect/tests/test_payment_transaction.py +++ b/addons/website_sale_collect/tests/test_payment_transaction.py @@ -6,18 +6,17 @@ from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestOnSitePaymentTransaction(HttpCase, ClickAndCollectCommon): - def test_choosing_on_site_payment_confirms_order(self): - order = self._create_so(carrier_id=self.carrier.id, state='draft') + order = self._create_so(carrier_id=self.carrier.id, state="draft") tx = self._create_transaction( - flow='direct', + flow="direct", sale_order_ids=[order.id], - state='pending', + state="pending", payment_method_id=self.provider.payment_method_ids.id, ) - with mute_logger('odoo.addons.sale.models.payment_transaction'): + with mute_logger("odoo.addons.sale.models.payment_transaction"): tx._post_process() - self.assertEqual(order.state, 'sale') + self.assertEqual(order.state, "sale") diff --git a/addons/website_sale_collect/tests/test_product_template.py b/addons/website_sale_collect/tests/test_product_template.py index a7360e830cce3..4227bf2961176 100644 --- a/addons/website_sale_collect/tests/test_product_template.py +++ b/addons/website_sale_collect/tests/test_product_template.py @@ -8,19 +8,18 @@ from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestProductTemplate(ClickAndCollectCommon): - def test_out_of_stock_product_available_when_allow_continue_selling(self): product = self._create_product(allow_out_of_stock_order=True) self.free_delivery.is_published = True with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id): - comb_info = self.env['product.template']._get_additionnal_combination_info( + comb_info = self.env["product.template"]._get_additionnal_combination_info( product, quantity=1, date=datetime(2000, 1, 1), uom=self.uom_unit, website=self.website, ) - self.assertTrue(comb_info['delivery_stock_data']['in_stock']) - self.assertTrue(comb_info['in_store_stock_data']['in_stock']) + self.assertTrue(comb_info["delivery_stock_data"]["in_stock"]) + self.assertTrue(comb_info["in_store_stock_data"]["in_stock"]) diff --git a/addons/website_sale_collect/tests/test_sale_order.py b/addons/website_sale_collect/tests/test_sale_order.py index 83808bb2d144c..79e36795157f7 100644 --- a/addons/website_sale_collect/tests/test_sale_order.py +++ b/addons/website_sale_collect/tests/test_sale_order.py @@ -1,15 +1,14 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.fields import Command from odoo.exceptions import ValidationError +from odoo.fields import Command from odoo.tests import tagged from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestSaleOrder(ClickAndCollectCommon): - @classmethod def setUpClass(cls): super().setUpClass() @@ -24,54 +23,54 @@ def test_warehouse_is_updated_when_changing_delivery_line(self): def test_setting_pickup_location_assigns_warehouse(self): so = self._create_in_store_delivery_order() - so._set_pickup_location('{"id":' + str(self.warehouse.id) + '}') + so._set_pickup_location('{"id":' + str(self.warehouse.id) + "}") self.assertEqual(so.warehouse_id, self.warehouse) def test_warehouse_is_not_reset_on_public_user_checkout(self): warehouse_2 = self._create_warehouse() so = self._create_in_store_delivery_order(partner_id=self.public_user.id) - so._set_pickup_location('{"id":' + str(warehouse_2.id) + '}') + so._set_pickup_location('{"id":' + str(warehouse_2.id) + "}") # change the partner_id as would happen in a checkout so.partner_id = self.partner.id self.assertEqual(so.warehouse_id, warehouse_2) def test_warehouse_is_computed_based_on_pickup_location(self): warehouse_2 = self._create_warehouse() - so = self._create_in_store_delivery_order(pickup_location_data={'id': warehouse_2.id}) + so = self._create_in_store_delivery_order(pickup_location_data={"id": warehouse_2.id}) self.assertEqual(so.warehouse_id, warehouse_2) def test_fiscal_position_id_is_computed_from_pickup_location_partner(self): - fp_be = self.env['account.fiscal.position'].create({ - 'name': "Test BE fiscal position", - 'country_id': self.country_be.id, - 'auto_apply': True, + fp_be = self.env["account.fiscal.position"].create({ + "name": "Test BE fiscal position", + "country_id": self.country_be.id, + "auto_apply": True, }) self.default_partner.country_id = self.country_us self.warehouse.partner_id.country_id = self.country_be so = self._create_in_store_delivery_order( partner_shipping_id=self.default_partner.id, - pickup_location_data={'id': self.warehouse.id}, + pickup_location_data={"id": self.warehouse.id}, ) self.assertEqual(so.fiscal_position_id, fp_be) def test_setting_pickup_location_assigns_correct_fiscal_position(self): - fp_be = self.env['account.fiscal.position'].create({ - 'name': "Test BE fiscal position", - 'country_id': self.country_be.id, - 'auto_apply': True, + fp_be = self.env["account.fiscal.position"].create({ + "name": "Test BE fiscal position", + "country_id": self.country_be.id, + "auto_apply": True, }) so = self._create_in_store_delivery_order() self.default_partner.country_id = self.country_be warehouse = self._create_warehouse() warehouse.partner_id = self.default_partner - so._set_pickup_location('{"id":' + str(warehouse.id) + '}') + so._set_pickup_location('{"id":' + str(warehouse.id) + "}") self.assertEqual(so.fiscal_position_id, fp_be) def test_selecting_not_in_store_dm_resets_fiscal_position(self): - fp_us = self.env['account.fiscal.position'].create({ - 'name': "Test US fiscal position", - 'country_id': self.country_us.id, - 'auto_apply': True, + fp_us = self.env["account.fiscal.position"].create({ + "name": "Test US fiscal position", + "country_id": self.country_us.id, + "auto_apply": True, }) so = self._create_in_store_delivery_order() so.fiscal_position_id = fp_us @@ -87,10 +86,9 @@ def test_free_qty_calculated_from_order_wh_if_dm_is_in_store(self): self.assertEqual(free_qty, 10) def test_prevent_buying_out_of_stock_products(self): - cart = self._create_in_store_delivery_order(order_line=[Command.create({ - 'product_id': self.product_2.id, - 'product_uom_qty': 5.0, - })]) + cart = self._create_in_store_delivery_order( + order_line=[Command.create({"product_id": self.product_2.id, "product_uom_qty": 5.0})] + ) cart.warehouse_id = self.warehouse with self.assertRaises(ValidationError): cart._check_cart_is_ready_to_be_paid() @@ -98,10 +96,7 @@ def test_prevent_buying_out_of_stock_products(self): def test_product_in_stock_is_available(self): cart = self._create_in_store_delivery_order( order_line=[ - Command.create({ - 'product_id': self.storable_product.id, - 'product_uom_qty': 5.0, - }) + Command.create({"product_id": self.storable_product.id, "product_uom_qty": 5.0}) ] ) insufficient_stock_data = cart._get_insufficient_stock_data(self.warehouse.id) @@ -110,14 +105,7 @@ def test_product_in_stock_is_available(self): def test_product_out_of_stock_continue_selling_is_available(self): self.product_2.allow_out_of_stock_order = True cart = self._create_in_store_delivery_order( - order_line=[ - Command.create( - { - 'product_id': self.product_2.id, - 'product_uom_qty': 5.0, - } - ) - ] + order_line=[Command.create({"product_id": self.product_2.id, "product_uom_qty": 5.0})] ) insufficient_stock_data = cart._get_insufficient_stock_data(self.warehouse.id) self.assertFalse(insufficient_stock_data) @@ -125,10 +113,7 @@ def test_product_out_of_stock_continue_selling_is_available(self): def test_product_insufficient_stock_is_unavailable(self): cart = self._create_in_store_delivery_order( order_line=[ - Command.create({ - 'product_id': self.storable_product.id, - 'product_uom_qty': 15.0, - }) + Command.create({"product_id": self.storable_product.id, "product_uom_qty": 15.0}) ] ) insufficient_stock_data = cart._get_insufficient_stock_data(self.warehouse.id) @@ -137,30 +122,26 @@ def test_product_insufficient_stock_is_unavailable(self): def test_insufficient_stock_with_mixed_uom_order_lines(self): """Test that the insufficient stock is correctly computed when the order lines use different UoMs.""" - pack_of_6_id = self.ref('uom.product_uom_pack_6') + pack_of_6_id = self.ref("uom.product_uom_pack_6") # 1 pack of 6 + 5 units = 11 units in the cart cart = self._create_in_store_delivery_order( order_line=[ - Command.create( - { - 'product_id': self.storable_product.id, - 'product_uom_qty': 1.0, - 'product_uom_id': pack_of_6_id, - } - ), - Command.create( - { - 'product_id': self.storable_product.id, - 'product_uom_qty': 5.0, - 'product_uom_id': self.storable_product.uom_id.id, - } - ), + Command.create({ + "product_id": self.storable_product.id, + "product_uom_qty": 1.0, + "product_uom_id": pack_of_6_id, + }), + Command.create({ + "product_id": self.storable_product.id, + "product_uom_qty": 5.0, + "product_uom_id": self.storable_product.uom_id.id, + }), ] ) # 10 units available, 11 requested, so 1 unit short insufficient_stock_data = cart._get_insufficient_stock_data(self.warehouse.id) ol_unit = cart.order_line.filtered( - lambda l: l.product_uom_id == self.storable_product.uom_id + lambda line: line.product_uom_id == self.storable_product.uom_id ) # only 4 units are available for the second order line instead of 5 self.assertEqual(insufficient_stock_data[ol_unit], 4) @@ -168,32 +149,29 @@ def test_insufficient_stock_with_mixed_uom_order_lines(self): def test_product_in_stock_with_mixed_uom_order_lines_is_available(self): """Test that if there is enough stock for all order lines the insufficient stock is empty.""" - pack_of_6_id = self.ref('uom.product_uom_pack_6') + pack_of_6_id = self.ref("uom.product_uom_pack_6") # 1 pack of 6 + 4 units = 10 units in the cart - cart = self._create_in_store_delivery_order(order_line=[ - Command.create({ - 'product_id': self.storable_product.id, - 'product_uom_qty': 4.0, - 'product_uom_id': self.storable_product.uom_id.id, - }), - Command.create({ - 'product_id': self.storable_product.id, - 'product_uom_qty': 1.0, - 'product_uom_id': pack_of_6_id, - }), - ]) + cart = self._create_in_store_delivery_order( + order_line=[ + Command.create({ + "product_id": self.storable_product.id, + "product_uom_qty": 4.0, + "product_uom_id": self.storable_product.uom_id.id, + }), + Command.create({ + "product_id": self.storable_product.id, + "product_uom_qty": 1.0, + "product_uom_id": pack_of_6_id, + }), + ] + ) # 10 units available, 10 requested insufficient_stock_data = cart._get_insufficient_stock_data(self.warehouse.id) self.assertFalse(insufficient_stock_data) def test_out_of_stock_product_is_unavailable(self): cart = self._create_in_store_delivery_order( - order_line=[ - Command.create({ - 'product_id': self.product_2.id, - 'product_uom_qty': 5.0, - }), - ] + order_line=[Command.create({"product_id": self.product_2.id, "product_uom_qty": 5.0})] ) insufficient_stock_data = cart._get_insufficient_stock_data(self.warehouse.id) self.assertIn(cart.order_line, insufficient_stock_data) @@ -202,10 +180,7 @@ def test_product_in_different_warehouse_is_unavailable(self): self.warehouse_2 = self._create_warehouse() cart = self._create_in_store_delivery_order( order_line=[ - Command.create({ - 'product_id': self.storable_product.id, - 'product_uom_qty': 5.0, - }) + Command.create({"product_id": self.storable_product.id, "product_uom_qty": 5.0}) ] ) insufficient_stock_data = cart._get_insufficient_stock_data(self.warehouse_2.id) diff --git a/addons/website_sale_collect/tests/test_website.py b/addons/website_sale_collect/tests/test_website.py index 07df92096f695..f0d77c7ac9499 100644 --- a/addons/website_sale_collect/tests/test_website.py +++ b/addons/website_sale_collect/tests/test_website.py @@ -6,9 +6,8 @@ from odoo.addons.website_sale_collect.tests.common import ClickAndCollectCommon -@tagged('post_install', '-at_install') +@tagged("post_install", "-at_install") class TestWebsite(ClickAndCollectCommon): - @classmethod def setUpClass(cls): super().setUpClass() diff --git a/addons/website_sale_collect/views/delivery_carrier_views.xml b/addons/website_sale_collect/views/delivery_carrier_views.xml index 8e0e3a7b3228f..f2615329e4d35 100644 --- a/addons/website_sale_collect/views/delivery_carrier_views.xml +++ b/addons/website_sale_collect/views/delivery_carrier_views.xml @@ -1,6 +1,6 @@ - + In-store Delivery Carrier Form