Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions sale_margin_delivery_cost/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

========================================
Sales Margin after other estimated costs
========================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:93b580f0184d40b8bb5153159fc5efcdadb8c31eba514f1ffa31554189e0ad4a
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmargin--analysis-lightgray.png?logo=github
:target: https://github.com/OCA/margin-analysis/tree/19.0/sale_margin_delivery_cost
:alt: OCA/margin-analysis
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/margin-analysis-19-0/margin-analysis-19-0-sale_margin_delivery_cost
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/margin-analysis&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Track expected shipping costs, allowing more accurate margin
calculations.

Costs could include packaging, documentation or customs expenses. These
may be consumables or services, not directly included in sales order
lines nor accounted with the invoice. But we may still ant the Sales
Order margin to consider these costs, especially if business margins are
tight.

Also allows for carrier shipping costs to be estimated separately,
instead of being automatically added as a sales order line, to charge
the customer.

This is useful in the case the customer is charge with a shipping fee
that can be very different from the actual shipping cost, or when no
shipping fee is charged at all.

In this case the expected shipping costs, computed by the UPDATE
SHIPPING COSTS button, can be stored as Other Costs and considered for
the sales order margin, instead being added as a sales order line and
charged to the customer.

**Table of contents**

.. contents::
:local:

Configuration
=============

On Shipping Methods there is a field to configure them to be tracked
under Other Costs instead of Sales Order Lines.

Navigate to *Sales > Configuration > Sales Orders > Shipping Methods*:

On a *Shipping Method*, set the *Charging Policy* field:

- "As SO Line" is the standard behaviour
- "As Other Cost" add the cost as *Other Costs* sale order line.

Usage
=====

In the Sales Order, add other sales costs in the "Other Costs" field,
after the sales order lines.

Using the DUPDATE SHIPPING COSTS button will update the estimated cost
in the Other Costs field instead of a sales order line, if the selected
Shipping Method is configured that way.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/margin-analysis/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/margin-analysis/issues/new?body=module:%20sale_margin_delivery_cost%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Open Source Integrators

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-dreispt| image:: https://github.com/dreispt.png?size=40px
:target: https://github.com/dreispt
:alt: dreispt

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-dreispt|

This module is part of the `OCA/margin-analysis <https://github.com/OCA/margin-analysis/tree/19.0/sale_margin_delivery_cost>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions sale_margin_delivery_cost/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
22 changes: 22 additions & 0 deletions sale_margin_delivery_cost/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (C) 2021 Open Source Integrators (https://www.opensourceintegrators.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Sales Margin after other estimated costs",
"summary": (
"See sales margins after other expected costs, such as shipping and delivery"
),
"version": "19.0.1.0.0",
"category": "Sales",
"website": "https://github.com/OCA/margin-analysis",
"author": "Open Source Integrators, Odoo Community Association (OCA)",
"license": "AGPL-3",
"maintainers": ["dreispt"],
"development_status": "Beta",
"depends": ["sale_margin", "delivery"],
"data": [
"security/ir.model.access.csv",
"views/delivery_carrier.xml",
"views/sale_order.xml",
],
"installable": True,
}
129 changes: 129 additions & 0 deletions sale_margin_delivery_cost/i18n/sale_margin_delivery_cost.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_margin_delivery_cost
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: sale_margin_delivery_cost
#: model:ir.model.fields.selection,name:sale_margin_delivery_cost.selection__delivery_carrier__charge_policy__other
msgid "As Other Cost"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields.selection,name:sale_margin_delivery_cost.selection__delivery_carrier__charge_policy__sale
msgid "As SO Line"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_delivery_carrier__charge_policy
msgid "Charging Policy"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__company_id
msgid "Company"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__price_unit
msgid "Cost"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__create_uid
msgid "Created by"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__create_date
msgid "Created on"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__recompute_delivery_price
msgid "Delivery cost should be recomputed"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__name
msgid "Description"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_delivery_carrier__display_name
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order__display_name
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__display_name
msgid "Display Name"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,help:sale_margin_delivery_cost.field_delivery_carrier__charge_policy
msgid ""
"How shipping costs are added to the sales order: charged in a sales order "
"line, estimated on an other costs line."
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_delivery_carrier__id
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order__id
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__id
msgid "ID"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__is_delivery
msgid "Is a Delivery"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_delivery_carrier____last_update
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order____last_update
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost____last_update
msgid "Last Modified on"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__write_uid
msgid "Last Updated by"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__write_date
msgid "Last Updated on"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order__other_cost_ids
#: model_terms:ir.ui.view,arch_db:sale_margin_delivery_cost.view_order_form_inherit
msgid "Other Costs"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__product_id
msgid "Product"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model,name:sale_margin_delivery_cost.model_sale_order
#: model:ir.model.fields,field_description:sale_margin_delivery_cost.field_sale_order_other_cost__order_id
msgid "Sales Order"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model,name:sale_margin_delivery_cost.model_sale_order_other_cost
msgid "Sales Order Other Cost"
msgstr ""

#. module: sale_margin_delivery_cost
#: model:ir.model,name:sale_margin_delivery_cost.model_delivery_carrier
msgid "Shipping Methods"
msgstr ""
3 changes: 3 additions & 0 deletions sale_margin_delivery_cost/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import delivery_carrier
from . import sale_order_other_cost
from . import sale_order
21 changes: 21 additions & 0 deletions sale_margin_delivery_cost/models/delivery_carrier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (C) 2021 Open Source Integrators (https://www.opensourceintegrators.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


from odoo import fields, models


class DeliveryCarrier(models.Model):
_inherit = "delivery.carrier"

charge_policy = fields.Selection(
[
("sale", "As SO Line"),
("other", "As Other Cost"),
],
string="Charging Policy",
default="sale",
required=True,
help="How shipping costs are added to the sales order:"
" charged in a sales order line, estimated on an other costs line.",
)
62 changes: 62 additions & 0 deletions sale_margin_delivery_cost/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright (C) 2021 Open Source Integrators (https://www.opensourceintegrators.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


from odoo import api, fields, models


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

other_cost_ids = fields.One2many(
"sale.order.other.cost",
"order_id",
string="Other Costs",
copy=True,
)

@api.depends("order_line.margin", "amount_untaxed", "other_cost_ids.price_unit")
def _compute_margin(self):
super()._compute_margin()
# Subtract other costs from the Sales Order margin
for order in self.filtered("other_cost_ids"):
other_costs = sum(order.mapped("other_cost_ids.price_unit"))
order.margin -= other_costs
order.margin_percent = (
order.amount_untaxed and order.margin / order.amount_untaxed

@Reyes4711-S73 Reyes4711-S73 Aug 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
order.amount_untaxed and order.margin / order.amount_untaxed
order.amount_untaxed and order.margin / (order.amount_untaxed or 1.0)

I think it is possible an amount_untaxed with zero value that generates a recomputation of margin making a ZeroDivisionError

)
return True

def set_delivery_line(self, carrier, amount):
# Using the UPDATE SHIPPING COST button sets an Other Costs line with the cost,
# instead of a Sales Order line.
# This is because we are not charging this cost to the customer.
# We use it to have a better Sales Order margin calculation.

# For "sale" charging policy, use standard logic - shipping as an SO line.
# For "other" charging policy, set the delivery cost in the Other Costs field.
if carrier.charge_policy == "sale":
super().set_delivery_line(carrier, amount)
else:
for order in self:
order.carrier_id = carrier.id
carrier_with_partner_lang = carrier.with_context(
lang=self.partner_id.lang
)
if carrier_with_partner_lang.product_id.description_sale:
so_description = (
f"{carrier_with_partner_lang.name}: "
f"{carrier_with_partner_lang.product_id.description_sale}"
)
else:
so_description = carrier_with_partner_lang.name
values = {
"order_id": self.id,
"name": so_description,
"product_id": carrier.product_id.id,
"is_delivery": True,
"price_unit": amount,
}
order.other_cost_ids.filtered("is_delivery").unlink()
order.other_cost_ids.create(values)
return True
Loading
Loading