From a54c304487429139c08f384d7b2b620b50cf6f83 Mon Sep 17 00:00:00 2001 From: "Valentin Vallaeys (vava)" Date: Mon, 20 Feb 2023 13:34:03 +0000 Subject: [PATCH 1/7] [REF] (stock_)delivery: split delivery from stock Extract the Inventory logic from the `delivery` module into a new `stock_delivery` module. This will allow to integrate the basic delivery features into the website_sale app in a one-app free database. task-3074497 Part-of: odoo/odoo#110686 --- addons/delivery/__manifest__.py | 23 +- addons/delivery/data/delivery_demo.xml | 83 ------ addons/delivery/models/__init__.py | 5 - addons/delivery/models/delivery_carrier.py | 193 +------------ addons/delivery/models/sale_order.py | 14 +- addons/delivery/security/ir.model.access.csv | 9 +- addons/delivery/tests/__init__.py | 3 - addons/delivery/views/delivery_view.xml | 132 +-------- addons/delivery/wizard/__init__.py | 2 - .../wizard/choose_delivery_carrier.py | 5 +- addons/stock_delivery/__init__.py | 4 + addons/stock_delivery/__manifest__.py | 33 +++ addons/stock_delivery/data/delivery_demo.xml | 88 ++++++ addons/stock_delivery/models/__init__.py | 9 + .../stock_delivery/models/delivery_carrier.py | 254 ++++++++++++++++++ .../models/delivery_request_objects.py | 0 .../models/product_template.py | 0 addons/stock_delivery/models/sale_order.py | 32 +++ .../models/stock_move.py | 0 .../models/stock_package_type.py | 0 .../models/stock_picking.py | 2 +- .../models/stock_quant_package.py | 0 .../security/ir.model.access.csv | 7 + addons/stock_delivery/tests/__init__.py | 5 + .../tests/test_carrier_propagation.py | 0 .../tests/test_delivery_stock_move.py | 0 .../tests/test_packing_delivery.py | 0 .../views/delivery_portal_template.xml | 2 +- addons/stock_delivery/views/delivery_view.xml | 143 ++++++++++ .../views/product_template_view.xml | 0 .../views/report_deliveryslip.xml | 0 .../views/report_package_barcode.xml | 0 .../views/report_shipping.xml | 0 .../views/stock_move_line_views.xml | 0 .../views/stock_package_type_views.xml | 0 .../views/stock_picking_type_views.xml | 0 .../views/stock_rule_views.xml | 0 addons/stock_delivery/wizard/__init__.py | 5 + .../wizard/choose_delivery_carrier.py | 13 + .../wizard/choose_delivery_package.py | 0 .../wizard/choose_delivery_package_views.xml | 0 .../wizard/stock_return_picking.py | 0 42 files changed, 616 insertions(+), 450 deletions(-) create mode 100644 addons/stock_delivery/__init__.py create mode 100644 addons/stock_delivery/__manifest__.py create mode 100644 addons/stock_delivery/data/delivery_demo.xml create mode 100644 addons/stock_delivery/models/__init__.py create mode 100644 addons/stock_delivery/models/delivery_carrier.py rename addons/{delivery => stock_delivery}/models/delivery_request_objects.py (100%) rename addons/{delivery => stock_delivery}/models/product_template.py (100%) create mode 100644 addons/stock_delivery/models/sale_order.py rename addons/{delivery => stock_delivery}/models/stock_move.py (100%) rename addons/{delivery => stock_delivery}/models/stock_package_type.py (100%) rename addons/{delivery => stock_delivery}/models/stock_picking.py (99%) rename addons/{delivery => stock_delivery}/models/stock_quant_package.py (100%) create mode 100644 addons/stock_delivery/security/ir.model.access.csv create mode 100644 addons/stock_delivery/tests/__init__.py rename addons/{delivery => stock_delivery}/tests/test_carrier_propagation.py (100%) rename addons/{delivery => stock_delivery}/tests/test_delivery_stock_move.py (100%) rename addons/{delivery => stock_delivery}/tests/test_packing_delivery.py (100%) rename addons/{delivery => stock_delivery}/views/delivery_portal_template.xml (98%) create mode 100644 addons/stock_delivery/views/delivery_view.xml rename addons/{delivery => stock_delivery}/views/product_template_view.xml (100%) rename addons/{delivery => stock_delivery}/views/report_deliveryslip.xml (100%) rename addons/{delivery => stock_delivery}/views/report_package_barcode.xml (100%) rename addons/{delivery => stock_delivery}/views/report_shipping.xml (100%) rename addons/{delivery => stock_delivery}/views/stock_move_line_views.xml (100%) rename addons/{delivery => stock_delivery}/views/stock_package_type_views.xml (100%) rename addons/{delivery => stock_delivery}/views/stock_picking_type_views.xml (100%) rename addons/{delivery => stock_delivery}/views/stock_rule_views.xml (100%) create mode 100644 addons/stock_delivery/wizard/__init__.py create mode 100644 addons/stock_delivery/wizard/choose_delivery_carrier.py rename addons/{delivery => stock_delivery}/wizard/choose_delivery_package.py (100%) rename addons/{delivery => stock_delivery}/wizard/choose_delivery_package_views.xml (100%) rename addons/{delivery => stock_delivery}/wizard/stock_return_picking.py (100%) diff --git a/addons/delivery/__manifest__.py b/addons/delivery/__manifest__.py index ae142082a46..7ee5795749e 100644 --- a/addons/delivery/__manifest__.py +++ b/addons/delivery/__manifest__.py @@ -4,33 +4,22 @@ { 'name': 'Delivery Costs', 'version': '1.0', - 'category': 'Inventory/Delivery', + 'category': 'Sales/Delivery', 'description': """ -Allows you to add delivery methods in sale orders and picking. -============================================================== - -You can define your own carrier for prices. When creating -invoices from picking, the system is able to add and compute the shipping line. +Allows you to add delivery methods in sale orders. +================================================== +You can define your own carrier for prices. +The system is able to add and compute the shipping line. """, - 'depends': ['sale_stock', 'sale_management'], + 'depends': ['sale'], 'data': [ 'security/ir.model.access.csv', 'security/delivery_carrier_security.xml', - 'views/product_template_view.xml', 'views/delivery_view.xml', 'views/partner_view.xml', - 'views/delivery_portal_template.xml', 'data/delivery_data.xml', - 'views/report_shipping.xml', - 'views/report_deliveryslip.xml', - 'views/report_package_barcode.xml', 'views/res_config_settings_views.xml', - 'wizard/choose_delivery_package_views.xml', 'wizard/choose_delivery_carrier_views.xml', - 'views/stock_package_type_views.xml', - 'views/stock_picking_type_views.xml', - 'views/stock_rule_views.xml', - 'views/stock_move_line_views.xml', 'report/ir_actions_report_templates.xml', ], 'demo': ['data/delivery_demo.xml'], diff --git a/addons/delivery/data/delivery_demo.xml b/addons/delivery/data/delivery_demo.xml index 7b3f9b3985f..61a742f2405 100644 --- a/addons/delivery/data/delivery_demo.xml +++ b/addons/delivery/data/delivery_demo.xml @@ -71,88 +71,5 @@ - - - - - outgoing shipment - - - - - - - - - - - - outgoing shipment - - - - - - - - - - - - outgoing shipment - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/delivery/models/__init__.py b/addons/delivery/models/__init__.py index 0a096042e15..dea7dd944c0 100644 --- a/addons/delivery/models/__init__.py +++ b/addons/delivery/models/__init__.py @@ -3,11 +3,6 @@ from . import delivery_carrier from . import delivery_price_rule from . import delivery_zip_prefix -from . import product_template from . import res_partner from . import sale_order from . import sale_order_line -from . import stock_move -from . import stock_package_type -from . import stock_picking -from . import stock_quant_package diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py index 6305e301582..08008877066 100644 --- a/addons/delivery/models/delivery_carrier.py +++ b/addons/delivery/models/delivery_carrier.py @@ -4,12 +4,9 @@ import psycopg2 import re from odoo import _, api, fields, models, registry, SUPERUSER_ID -from odoo.exceptions import UserError, ValidationError -from odoo.tools import float_round +from odoo.exceptions import UserError from odoo.tools.safe_eval import safe_eval -from .delivery_request_objects import DeliveryCommodity, DeliveryPackage - class DeliveryCarrier(models.Model): _name = 'delivery.carrier' @@ -52,11 +49,13 @@ class DeliveryCarrier(models.Model): 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') - invoice_policy = fields.Selection([ - ('estimated', 'Estimated cost'), - ('real', 'Real cost') - ], string='Invoicing Policy', default='estimated', required=True, - help="Estimated Cost: the customer will be invoiced the estimated cost of the shipping.\nReal Cost: the customer will be invoiced the real cost of the shipping, the cost of the shipping will be updated on the SO after the delivery.") + 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.", + ) 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') @@ -209,48 +208,6 @@ class DeliveryCarrier(models.Model): res['price'] = 0.0 return res - def send_shipping(self, pickings): - ''' Send the package to the service provider - - :param pickings: A recordset of pickings - :return list: A list of dictionaries (one per picking) containing of the form:: - { 'exact_price': price, - 'tracking_number': number } - # TODO missing labels per package - # TODO missing currency - # TODO missing success, error, warnings - ''' - self.ensure_one() - if hasattr(self, '%s_send_shipping' % self.delivery_type): - return getattr(self, '%s_send_shipping' % self.delivery_type)(pickings) - - def get_return_label(self,pickings, tracking_number=None, origin_date=None): - self.ensure_one() - if self.can_generate_return: - return getattr(self, '%s_get_return_label' % self.delivery_type)(pickings, tracking_number, origin_date) - - def get_return_label_prefix(self): - return 'ReturnLabel-%s' % self.delivery_type - - def get_tracking_link(self, picking): - ''' Ask the tracking link to the service provider - - :param picking: record of stock.picking - :return str: an URL containing the tracking link or False - ''' - self.ensure_one() - if hasattr(self, '%s_get_tracking_link' % self.delivery_type): - return getattr(self, '%s_get_tracking_link' % self.delivery_type)(picking) - - def cancel_shipment(self, pickings): - ''' Cancel a shipment - - :param pickings: A recordset of pickings - ''' - self.ensure_one() - if hasattr(self, '%s_cancel_shipment' % self.delivery_type): - return getattr(self, '%s_cancel_shipment' % self.delivery_type)(pickings) - def log_xml(self, xml_string, func): self.ensure_one() @@ -275,16 +232,6 @@ class DeliveryCarrier(models.Model): except psycopg2.Error: pass - def _get_default_custom_package_code(self): - """ Some delivery carriers require a prefix to be sent in order to use custom - packages (ie not official ones). This optional method will return it as a string. - """ - self.ensure_one() - if hasattr(self, '_%s_get_default_custom_package_code' % self.delivery_type): - return getattr(self, '_%s_get_default_custom_package_code' % self.delivery_type)() - else: - return False - # ------------------------------------------------ # # Fixed price shipping, aka a very simple provider # # ------------------------------------------------ # @@ -313,19 +260,6 @@ class DeliveryCarrier(models.Model): 'error_message': False, 'warning_message': False} - def fixed_send_shipping(self, pickings): - res = [] - for p in pickings: - res = res + [{'exact_price': p.carrier_id.fixed_price, - 'tracking_number': False}] - return res - - def fixed_get_tracking_link(self, picking): - return False - - def fixed_cancel_shipment(self, pickings): - raise NotImplementedError() - # ----------------------------------- # # Based on rule delivery type methods # # ----------------------------------- # @@ -355,7 +289,7 @@ class DeliveryCarrier(models.Model): def _get_conversion_currencies(self, order, conversion): if conversion == 'company_to_pricelist': - from_currency, to_currency = order.company_id.currency_id, order.pricelist_id.currency_id + from_currency, to_currency = order.company_id.currency_id, order.currency_id elif conversion == 'pricelist_to_company': from_currency, to_currency = order.currency_id, order.company_id.currency_id @@ -423,112 +357,3 @@ class DeliveryCarrier(models.Model): raise UserError(_("Not available for current order")) return price - - def base_on_rule_send_shipping(self, pickings): - res = [] - for p in pickings: - carrier = self._match_address(p.partner_id) - if not carrier: - raise ValidationError(_('There is no matching delivery rule.')) - res = res + [{'exact_price': p.carrier_id._get_price_available(p.sale_id) if p.sale_id else 0.0, # TODO cleanme - 'tracking_number': False}] - return res - - def base_on_rule_get_tracking_link(self, picking): - return False - - def base_on_rule_cancel_shipment(self, pickings): - raise NotImplementedError() - - # -------------------------------- # - # get default packages/commodities # - # -------------------------------- # - - def _get_packages_from_order(self, order, default_package_type): - packages = [] - - total_cost = 0 - for line in order.order_line.filtered(lambda line: not line.is_delivery and not line.display_type): - total_cost += self._product_price_to_company_currency(line.product_qty, line.product_id, order.company_id) - - total_weight = order._get_estimated_weight() + default_package_type.base_weight - if total_weight == 0.0: - weight_uom_name = self.env['product.template']._get_weight_uom_name_from_ir_config_parameter() - raise UserError(_("The package cannot be created because the total weight of the products in the picking is 0.0 %s") % (weight_uom_name)) - # If max weight == 0 => division by 0. If this happens, we want to have - # more in the max weight than in the total weight, so that it only - # creates ONE package with everything. - max_weight = default_package_type.max_weight or total_weight + 1 - total_full_packages = int(total_weight / max_weight) - last_package_weight = total_weight % max_weight - - package_weights = [max_weight] * total_full_packages + ([last_package_weight] if last_package_weight else []) - partial_cost = total_cost / len(package_weights) # separate the cost uniformly - order_commodities = self._get_commodities_from_order(order) - - # Split the commodities value uniformly as well - for commodity in order_commodities: - commodity.monetary_value /= len(package_weights) - commodity.qty = max(1, commodity.qty // len(package_weights)) - - for weight in package_weights: - packages.append(DeliveryPackage(order_commodities, weight, default_package_type, total_cost=partial_cost, currency=order.company_id.currency_id, order=order)) - return packages - - def _get_packages_from_picking(self, picking, default_package_type): - packages = [] - - if picking.is_return_picking: - commodities = self._get_commodities_from_stock_move_lines(picking.move_line_ids) - weight = picking._get_estimated_weight() + default_package_type.base_weight - packages.append(DeliveryPackage(commodities, weight, default_package_type, currency=picking.company_id.currency_id, picking=picking)) - return packages - - # Create all packages. - for package in picking.package_ids: - move_lines = picking.move_line_ids.filtered(lambda ml: ml.result_package_id == package) - commodities = self._get_commodities_from_stock_move_lines(move_lines) - package_total_cost = 0.0 - for quant in package.quant_ids: - package_total_cost += self._product_price_to_company_currency(quant.quantity, quant.product_id, picking.company_id) - packages.append(DeliveryPackage(commodities, package.shipping_weight or package.weight, package.package_type_id, name=package.name, total_cost=package_total_cost, currency=picking.company_id.currency_id, picking=picking)) - - # Create one package: either everything is in pack or nothing is. - if picking.weight_bulk: - commodities = self._get_commodities_from_stock_move_lines(picking.move_line_ids) - package_total_cost = 0.0 - for move_line in picking.move_line_ids: - package_total_cost += self._product_price_to_company_currency(move_line.qty_done, move_line.product_id, picking.company_id) - packages.append(DeliveryPackage(commodities, picking.weight_bulk, default_package_type, name='Bulk Content', total_cost=package_total_cost, currency=picking.company_id.currency_id, picking=picking)) - elif not packages: - raise UserError(_("The package cannot be created because the total weight of the products in the picking is 0.0 %s") % (picking.weight_uom_name)) - - return packages - - def _get_commodities_from_order(self, order): - commodities = [] - - for line in order.order_line.filtered(lambda line: not line.is_delivery and not line.display_type): - unit_quantity = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id) - rounded_qty = max(1, float_round(unit_quantity, precision_digits=0)) - country_of_origin = line.product_id.country_of_origin.code or order.warehouse_id.partner_id.country_id.code - commodities.append(DeliveryCommodity(line.product_id, amount=rounded_qty, monetary_value=line.price_reduce_taxinc, country_of_origin=country_of_origin)) - - return commodities - - def _get_commodities_from_stock_move_lines(self, move_lines): - commodities = [] - - for line in move_lines.filtered(lambda line: line.product_id.type in ['product', 'consu']): - if line.state == 'done': - unit_quantity = line.product_uom_id._compute_quantity(line.qty_done, line.product_id.uom_id) - else: - unit_quantity = line.product_uom_id._compute_quantity(line.product_uom_qty, line.product_id.uom_id) - rounded_qty = max(1, float_round(unit_quantity, precision_digits=0)) - country_of_origin = line.product_id.country_of_origin.code or line.picking_id.picking_type_id.warehouse_id.partner_id.country_id.code - commodities.append(DeliveryCommodity(line.product_id, amount=rounded_qty, monetary_value=line.sale_price, country_of_origin=country_of_origin)) - - return commodities - - def _product_price_to_company_currency(self, quantity, product, company): - return company.currency_id._convert(quantity * product.standard_price, product.currency_id, company, fields.Date.today()) diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py index 0a8d3909756..b3588f05dcd 100644 --- a/addons/delivery/models/sale_order.py +++ b/addons/delivery/models/sale_order.py @@ -112,17 +112,13 @@ class SaleOrder(models.Model): values = { 'order_id': self.id, 'name': so_description, + 'price_unit': price_unit, 'product_uom_qty': 1, 'product_uom': carrier.product_id.uom_id.id, 'product_id': carrier.product_id.id, 'tax_id': [(6, 0, taxes_ids)], 'is_delivery': True, } - if carrier.invoice_policy == 'real': - values['price_unit'] = 0 - values['name'] += _(' (Estimated Cost: %s )', self._format_currency_amount(price_unit)) - else: - values['price_unit'] = price_unit if carrier.free_over and self.currency_id.is_zero(price_unit) : values['name'] += '\n' + _('Free Shipping') if self.order_line: @@ -131,14 +127,6 @@ class SaleOrder(models.Model): del context return sol - def _format_currency_amount(self, amount): - pre = post = u'' - if self.currency_id.position == 'before': - pre = u'{symbol}\N{NO-BREAK SPACE}'.format(symbol=self.currency_id.symbol or '') - else: - post = u'\N{NO-BREAK SPACE}{symbol}'.format(symbol=self.currency_id.symbol or '') - return u' {pre}{0}{post}'.format(amount, pre=pre, post=post) - @api.depends('order_line.is_delivery', 'order_line.is_downpayment') def _compute_invoice_status(self): super()._compute_invoice_status() diff --git a/addons/delivery/security/ir.model.access.csv b/addons/delivery/security/ir.model.access.csv index 5eb1fcc9e13..49d024378cb 100644 --- a/addons/delivery/security/ir.model.access.csv +++ b/addons/delivery/security/ir.model.access.csv @@ -6,9 +6,6 @@ access_delivery_price_rule,delivery.price.rule,model_delivery_price_rule,sales_t access_delivery_carrier_manager,delivery.carrier,model_delivery_carrier,sales_team.group_sale_manager,1,1,1,1 access_delivery_price_rule_manager,delivery.price.rule,model_delivery_price_rule,sales_team.group_sale_manager,1,1,1,1 access_delivery_carrier_partner_manager,delivery.carrier partner_manager,model_delivery_carrier,base.group_partner_manager,1,0,0,0 -access_delivery_carrier_stock_user,delivery.carrier stock_user,model_delivery_carrier,stock.group_stock_user,1,0,0,0 -access_delivery_carrier_stock_manager,delivery.carrier,model_delivery_carrier,stock.group_stock_manager,1,1,1,1 -access_delivery_zip_prefix_stock_manager,delivery.zip.prefix,model_delivery_zip_prefix,stock.group_stock_manager,1,1,1,1 -access_delivery_price_rule_stock_manager,delivery.price.rule,model_delivery_price_rule,stock.group_stock_manager,1,1,1,1 -access_choose_delivery_package,access.choose.delivery.package,model_choose_delivery_package,stock.group_stock_user,1,1,1,0 -access_choose_delivery_carrier,access.choose.delivery.carrier,model_choose_delivery_carrier,stock.group_stock_user,1,1,1,0 +access_delivery_zip_prefix_sale_manager,delivery.zip.prefix,model_delivery_zip_prefix,base.group_partner_manager,1,1,1,1 +access_delivery_price_rule_sale_manager,delivery.price.rule,model_delivery_price_rule,sales_team.group_sale_manager,1,1,1,1 +access_choose_delivery_carrier,delivery.choose.delivery.carrier,model_choose_delivery_carrier,sales_team.group_sale_salesman,1,1,1,0 diff --git a/addons/delivery/tests/__init__.py b/addons/delivery/tests/__init__.py index 6a4394ff854..b2c1ec5743e 100644 --- a/addons/delivery/tests/__init__.py +++ b/addons/delivery/tests/__init__.py @@ -1,6 +1,3 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_carrier_propagation from . import test_delivery_cost -from . import test_delivery_stock_move -from . import test_packing_delivery diff --git a/addons/delivery/views/delivery_view.xml b/addons/delivery/views/delivery_view.xml index 460dad1da48..745c98ad829 100644 --- a/addons/delivery/views/delivery_view.xml +++ b/addons/delivery/views/delivery_view.xml @@ -179,9 +179,7 @@ - - delivery.price.rule.form @@ -221,94 +219,10 @@ - - delivery.stock.picking_withcarrier.form.view - stock.picking - - - - - - - - - - -
-
- -