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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ addon | version | maintainers | summary
[spreadsheet_dashboard_purchase_oca](spreadsheet_dashboard_purchase_oca/) | 18.0.1.0.0 | | Spreadsheet dashboard for vendors
[spreadsheet_dashboard_purchase_stock_oca](spreadsheet_dashboard_purchase_stock_oca/) | 18.0.1.0.0 | | Spreadsheet dashboard for purchases
[spreadsheet_oca](spreadsheet_oca/) | 18.0.1.3.0 | | Allow to edit spreadsheets
[spreadsheet_quotation](spreadsheet_quotation/) | 18.0.1.0.0 | | Allow add a calculator and sync items with a qoutation

[//]: # (end addons)

Expand Down
113 changes: 113 additions & 0 deletions spreadsheet_quotation/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

===============================
Spreadsheet Quotation Calculator
===============================

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

.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |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%2Fspreadsheet-lightgray.png?logo=github
:target: https://github.com/OCA/spreadsheet/tree/18.0/spreadsheet_quotation
:alt: OCA/spreadsheet
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/spreadsheet-18-0/spreadsheet-18-0-spreadsheet_quotation
: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/spreadsheet&target_branch=18.0
:alt: Try me on Runboat

This module allows linking spreadsheet calculators to quotation templates
in Odoo. When a sale order is created from a template that has a
calculator, a copy of the spreadsheet is automatically assigned to the
order with a pre-configured global filter so the ``ODOO.LIST`` formulas
display only that order's lines.

The spreadsheet calculator is built on top of ``spreadsheet_oca`` and
uses ``ODOO.LIST`` formulas to display sale order line data such as
product, quantity, and unit price. A Field Sync side panel lets users map
spreadsheet columns to sale order line fields and push calculated values
back to the quotation.

**Table of contents**

.. contents::
:local:

Installation
============

This module requires:

- ``spreadsheet_oca`` from the OCA spreadsheet repository
- ``sale_management`` from Odoo core addons

Usage
=====

Setting up a quotation calculator
---------------------------------

1. Go to **Sales > Configuration > Quotation Templates**.
2. Open or create a quotation template.
3. Click **Create Calculator** next to the quotation calculator field.
4. Set a name and the initial number of rows in the wizard.
5. Customize the spreadsheet and save it.

Using the calculator on a sale order
------------------------------------

1. Create a new quotation and select a template with a calculator.
2. Use the **Calculator** smart button to open the spreadsheet.
3. Edit values in the spreadsheet.
4. Use **Field Sync** to map columns to sale order line fields.
5. Save the spreadsheet to sync values back to the sale order.

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

Bugs are tracked on `GitHub Issues <https://github.com/OCA/spreadsheet/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 detailed and welcomed
`feedback <https://github.com/OCA/spreadsheet/issues/new?body=module:%20spreadsheet_quotation%0Aversion:%2018.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
-------

* Odoo Community Association (OCA)
* Cloud Lotus

Contributors
------------

* OCA Contributors

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.

This module is part of the `OCA/spreadsheet <https://github.com/OCA/spreadsheet/tree/18.0/spreadsheet_quotation>`_ project on GitHub.

You are welcome to contribute. To learn how please visit
https://odoo-community.org/page/Contribute.
5 changes: 5 additions & 0 deletions spreadsheet_quotation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2026 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import models
from . import wizards
30 changes: 30 additions & 0 deletions spreadsheet_quotation/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2026 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Spreadsheet Quotation Calculator",
"summary": (
"Use spreadsheets as quotation calculators linked " "to sale order templates"
),
"version": "18.0.1.0.0",
"license": "AGPL-3",
"author": "Odoo Community Association (OCA), Cloud Lotus",
"website": "https://github.com/OCA/spreadsheet",
"depends": ["spreadsheet_oca", "sale_management"],
"data": [
"security/ir.model.access.csv",
"wizards/spreadsheet_quotation_create.xml",
"views/sale_order_template_views.xml",
"views/sale_order_views.xml",
],
"assets": {
"spreadsheet.o_spreadsheet": [
"spreadsheet_quotation/static/src/quotation_spreadsheet/field_sync_plugin.esm.js",
"spreadsheet_quotation/static/src/quotation_spreadsheet/field_sync_side_panel.esm.js",
"spreadsheet_quotation/static/src/quotation_spreadsheet/field_sync_side_panel.xml",
"spreadsheet_quotation/static/src/quotation_spreadsheet/quotation_spreadsheet.xml",
],
},
"installable": True,
"development_status": "Alpha",
}
5 changes: 5 additions & 0 deletions spreadsheet_quotation/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2026 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import sale_order_template
from . import sale_order
79 changes: 79 additions & 0 deletions spreadsheet_quotation/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2026 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import json

from odoo import _, api, fields, models


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

spreadsheet_id = fields.Many2one(
"spreadsheet.spreadsheet",
string="Quotation Calculator",
copy=False,
)
has_spreadsheet = fields.Boolean(
compute="_compute_has_spreadsheet",
)

@api.depends("spreadsheet_id")
def _compute_has_spreadsheet(self):
for order in self:
order.has_spreadsheet = bool(order.spreadsheet_id)

@api.onchange("sale_order_template_id")
def _onchange_sale_order_template_id_spreadsheet(self):
if self.sale_order_template_id and self.sale_order_template_id.spreadsheet_id:
spreadsheet = self._copy_template_spreadsheet(
self.sale_order_template_id.spreadsheet_id,
)
self.spreadsheet_id = spreadsheet
elif not self.sale_order_template_id:
self.spreadsheet_id = False

def _copy_template_spreadsheet(self, template_spreadsheet):
"""Create a copy of the template spreadsheet for this sale order."""
return template_spreadsheet.copy(
{"name": _("Calculator - %s", self.name or _("New"))}
)

def _update_spreadsheet_filter(self):
"""Update the global filter default value in the spreadsheet JSON
so the list is filtered to this sale order's id."""
self.ensure_one()
if not self.spreadsheet_id:
return
data = self.spreadsheet_id.spreadsheet_raw
if not data:
return
if isinstance(data, str):
data = json.loads(data)

for gf in data.get("globalFilters", []):
if gf.get("type") == "relation" and gf.get("modelName") == "sale.order":
gf["defaultValue"] = [self.id]
break

self.spreadsheet_id.spreadsheet_raw = data

def write(self, vals):
res = super().write(vals)
if "spreadsheet_id" in vals:
for order in self.filtered("spreadsheet_id"):
order._update_spreadsheet_filter()
return res

@api.model_create_multi
def create(self, vals_list):
orders = super().create(vals_list)
for order in orders.filtered("spreadsheet_id"):
order._update_spreadsheet_filter()
return orders

def action_open_spreadsheet_calculator(self):
self.ensure_one()
if self.spreadsheet_id:
return self.spreadsheet_id.open_spreadsheet()
return False
35 changes: 35 additions & 0 deletions spreadsheet_quotation/models/sale_order_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2026 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


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

spreadsheet_id = fields.Many2one(
"spreadsheet.spreadsheet",
string="Quotation Calculator",
copy=True,
help="Spreadsheet used as pricing calculator for quotations "
"created from this template.",
)

def action_create_spreadsheet_calculator(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"name": "Create Quotation Calculator",
"res_model": "spreadsheet.quotation.create",
"view_mode": "form",
"target": "new",
"context": {
"default_sale_order_template_id": self.id,
},
}

def action_open_spreadsheet_calculator(self):
self.ensure_one()
if self.spreadsheet_id:
return self.spreadsheet_id.open_spreadsheet()
return self.action_create_spreadsheet_calculator()
3 changes: 3 additions & 0 deletions spreadsheet_quotation/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
1 change: 1 addition & 0 deletions spreadsheet_quotation/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* OCA Contributors
15 changes: 15 additions & 0 deletions spreadsheet_quotation/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
This module allows linking spreadsheet calculators to quotation templates
in Odoo. When a sale order is created from a template that has a
calculator, a copy of the spreadsheet is automatically assigned to the
order with a pre-configured global filter so the ODOO.LIST formulas
display only that order's lines.

The spreadsheet calculator is built on top of ``spreadsheet_oca`` and
uses ODOO.LIST formulas to display sale order line data (product,
quantity, unit price, etc.). Users can add custom formulas, calculations,
and charts to build complex pricing logic.

A **Field Sync** side panel lets users map spreadsheet columns to
sale order line fields. This column-based approach is more intuitive
than cell-by-cell mapping and makes it easy to push calculated values
back to the sale order.
42 changes: 42 additions & 0 deletions spreadsheet_quotation/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Setting up a quotation calculator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1. Go to **Sales > Configuration > Quotation Templates**.
2. Open or create a quotation template.
3. Click **Create Calculator** next to the "Quotation Calculator" field.
4. A wizard will open; set a name and number of initial rows, then click
**Create Calculator**.
5. The spreadsheet editor opens with a pre-configured list of
``sale.order.line`` fields.
6. Customize the spreadsheet: add formulas, charts, or extra columns as
needed.
7. Save the spreadsheet.

Using the calculator on a sale order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1. Create a new quotation and select the template that has a calculator.
2. A **Calculator** smart button appears on the sale order form.
3. Click it to open the spreadsheet filtered to the current order's
lines.
4. Edit values in the spreadsheet.
5. Use **File > Field Sync** to map columns to sale order line fields.
6. Save the spreadsheet from the editor.

Syncing values back to the order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

After configuring field sync mappings in the spreadsheet, the mapped
column values can be pushed to the corresponding sale order line fields
when saving the spreadsheet or from the sale order form.

Step-by-step guide
~~~~~~~~~~~~~~~~~~

[1. Add quotation templates in settings](image1.png)
[2. Create a new template & create a calculator](image2.png)
[3. Map Sync fields > columns to fields](image3.png)
[4. Save template > create a quotation from template and open calculator](image4.png)
[5. add new lines to the calculator, save and sync](image5.png)
[6. Add new products if any](image6.png)
[7. Quotation updated!!!](image7.png)
Binary file added spreadsheet_quotation/readme/image1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spreadsheet_quotation/readme/image2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spreadsheet_quotation/readme/image3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spreadsheet_quotation/readme/image4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spreadsheet_quotation/readme/image5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spreadsheet_quotation/readme/image6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spreadsheet_quotation/readme/image7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions spreadsheet_quotation/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_spreadsheet_quotation_create,access_spreadsheet_quotation_create,model_spreadsheet_quotation_create,sales_team.group_sale_salesman,1,1,1,1
Loading
Loading