Compare commits
30 Commits
master-ser
...
saas-16.2-
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c6105bcf24 |
[IMP] create JavaScript howtos
The JavaScript cheatsheet is outdated, we therefore remove it and
replace it by multiple howtos:
- Create a view from scratch
- Extending an existing view
- Create a field from scratch
- Extend an existing field
- Create a client action
There is other subjects to introduce as the web framework is big. Other
future contributions will cover them.
X-original-commit:
|
||
|
|
c91ea3d251 |
[ADD] accounting: enablebanking
task-3232701
closes odoo/documentation#4001
X-original-commit:
|
||
|
|
9878b58a04 |
[MOV] accounting: structure of bank sync docs
X-original-commit:
|
||
|
|
fdea740edc |
[ADD] field service: default warehouse
task-2948598
closes odoo/documentation#3998
X-original-commit:
|
||
|
|
a8deaf9594 |
[IMP] tutorials: add warning about mutable global variables
The whole concept of multi-tenancy is not really approached within the
tutorial.
This commit adds a warning about never using mutable global variables
within odoo to seed the idea in the reader's mind.
task-3059110
closes odoo/documentation#3990
X-original-commit:
|
||
|
|
4914c45d88 |
[IMP] Website: review website translation page
task-3255779
closes odoo/documentation#3977
X-original-commit:
|
||
|
|
6757b2691b |
[IMP]: add manual valuation section
Add images and manual valuation
add ref tag and retitle doc to sentence case
Remove trailing whitespaces
update explanation
closes odoo/documentation#3983
X-original-commit:
|
||
|
|
11b2c5250b |
[IMP] Adyen: additional minimum requirements for users
Adding requirements for users to use Adyen. Forward to master.
closes odoo/documentation#3962
Taskid: 3159712
X-original-commit:
|
||
|
|
d6acaf70c1 |
[IMP] ePoS: vulgarise the SSL ePos issue
SSL/HTTPS topic is complicated for most of
Odoo customers as it is quite technical.
This PR should help them guide them to better
understand the issue and how to fix it themselves.
Support can't be provided to each device, browsers and OS.
But we did add some guides regarding the more
"popular" ones and some "keyword" to search
online for the others.
closes odoo/documentation#3924
X-original-commit:
|
||
|
|
ec40c817da |
[ADD] attendances: hr and attendances categories + hardware page
Task ID: 3251124
closes odoo/documentation#3959
X-original-commit:
|
||
|
|
69248346db |
[IMP] pos: update fiscal positions page
Task ID: 2862506
closes odoo/documentation#3927
X-original-commit:
|
||
|
|
c69cb37be3 |
[IMP] mail plugins: add instructions to gmail plugin
closes odoo/documentation#3946
X-original-commit:
|
||
|
|
19288088f9 |
[IMP] sales: menuselection fix
Fixed a menuselection error and deleted instances of second-person pov
Closes task 3116083
closes odoo/documentation#3941
X-original-commit:
|
||
|
|
24f4348a46 |
[IMP] General: Oauth seemore additions
closes odoo/documentation#3936
X-original-commit:
|
||
|
|
dc2a988173 |
[IMP] supported_version: release saas-16.2
closes odoo/documentation#3908
X-original-commit:
|
||
|
|
9fb4c01b19 |
[FIX] inventory: fix BoM typo
closes odoo/documentation#3912
X-original-commit:
|
||
|
|
7effc12f02 |
[IMP] coding_guidelines: fix python code indents
A few python code blocks on the coding guidelines are indented twice
(8 spaces instead of 4), which is not correct.
closes odoo/documentation#3905
X-original-commit:
|
||
|
|
0d6466fbbf |
[IMP] accounting: bank transactions
task-3204835
closes odoo/documentation#3896
X-original-commit:
|
||
|
|
9651908b73 |
[IMP] helpdesk: updated ticketing channels setup
closes odoo/documentation#3879
X-original-commit:
|
||
|
|
5c89a56634 |
[IMP] Support: Update What can I expect
closes odoo/documentation#3885
X-original-commit:
|
||
|
|
7c4efb6fa9 |
[IMP] pos: and update kitchen printing
Task ID: 3235139
closes odoo/documentation#3874
X-original-commit:
|
||
|
|
7c8b8269eb |
[IMP] helpdesk: updated teams and stages setup content
closes odoo/documentation#3869
X-original-commit:
|
||
|
|
ccd282023a |
[IMP] inventory: fix sendcloud doc
closes odoo/documentation#3870
X-original-commit:
|
||
|
|
19b4797d37 |
[FIX] legal: fix some broken links (pdfs, translations)
1) PDF files are generated and stored at the root of the CURRENT_BRANCH
directory. The links to those files are generated at different levels of
the doctree, which makes it impossible to use a relative path.
For example the same "Enterprise Agreement" doc in EN is published on:
- /16.0/legal/terms/enterprise.html
- /16.0/fr/legal/terms/enterprise.html
As a workaround, use absolute links for the PDFs. They won't work
locally for now. Can be improved later, as long as we don't break
those links located in various depths of the troctree.
2) The legal constracts aren't translated in all availables languages
(yet), so those links are 404s now. Introduced a conf.py variable
`legal_translations` with the list of languages where translated
contracts are indeed available, and falling back to the EN version
otherwise. Some languages don't have *all* the contracts translated, so
some 404 may remain temporarily.
Forward-port of
|
||
|
|
6def5713d3 |
[ADD] eCommerce: customer interaction
Adding a page on customer interaction and adding redirects. Forward to
master.
closes odoo/documentation#3861
Taskid: 3224716
X-original-commit:
|
||
|
|
cc997535b2 |
[IMP] pos: take the half-up out of the last note
Task ID: 3184227
closes odoo/documentation#3856
X-original-commit:
|
||
|
|
cb06fb7687 |
[ADD] developer: add _get_available_tokens
Introduced with this commit:
|
||
|
|
a18fe9a25b |
[FIX] dev/tuto: grammar mistake in 02_setup
task-3238089
closes odoo/documentation#3848
X-original-commit:
|
||
|
|
3f0a7cd34e |
[IMP] supported_versions: flag saas-16.1 as supported
closes odoo/documentation#3840
X-original-commit:
|
||
|
|
aeed9418b4 | [REL] saas-16.2 |
2
Makefile
|
|
@@ -26,7 +26,7 @@ SOURCE_DIR = content
|
|||
|
||||
HTML_BUILD_DIR = $(BUILD_DIR)/html
|
||||
ifdef VERSIONS
|
||||
HTML_BUILD_DIR := $(HTML_BUILD_DIR)/master
|
||||
HTML_BUILD_DIR := $(HTML_BUILD_DIR)/saas-16.2
|
||||
endif
|
||||
ifneq ($(CURRENT_LANG),en)
|
||||
HTML_BUILD_DIR := $(HTML_BUILD_DIR)/$(CURRENT_LANG)
|
||||
|
|
|
|||
2
conf.py
|
|
@@ -22,7 +22,7 @@ copyright = 'Odoo S.A.'
|
|||
# `version` is the version info for the project being documented, acts as replacement for |version|,
|
||||
# also used in various other places throughout the built documents.
|
||||
# `release` is the full version, including alpha/beta/rc tags. Acts as replacement for |release|.
|
||||
version = release = 'master'
|
||||
version = release = 'saas-16.2'
|
||||
|
||||
# `current_branch` is the technical name of the current branch.
|
||||
# E.g., saas-15.4 -> saas-15.4; 12.0 -> 12.0, master -> master (*).
|
||||
|
|
|
|||
|
|
@@ -8,6 +8,7 @@ Bank and cash
|
|||
:titlesonly:
|
||||
|
||||
bank/setup
|
||||
bank/feeds
|
||||
bank/bank_synchronization
|
||||
bank/transactions
|
||||
bank/reconciliation
|
||||
bank/interbank
|
||||
|
|
|
|||
|
|
@@ -1,6 +1,8 @@
|
|||
======================================
|
||||
Bank synchronization: automatic import
|
||||
======================================
|
||||
:show-content:
|
||||
|
||||
====================
|
||||
Bank synchronization
|
||||
====================
|
||||
|
||||
Odoo can synchronize directly with your bank institution to get all bank statements imported
|
||||
automatically into your database.
|
||||
|
|
@@ -15,9 +17,12 @@ To connect to the banks, Odoo uses multiple web-services:
|
|||
|
||||
- **Plaid**: United States of America and Canada
|
||||
- **Yodlee**: Worldwide
|
||||
- **Salt Edge**: Europe (:doc:`more information <saltedge>`)
|
||||
- **Ponto**: Europe (:doc:`more information <ponto>`)
|
||||
- **EnableBanking**: Scandinavian countries
|
||||
- :doc:`Salt Edge <bank_synchronization/saltedge>`: Europe
|
||||
- :doc:`Ponto <bank_synchronization/ponto>`: Europe
|
||||
- :doc:`Enable Banking <bank_synchronization/enablebanking>`: Scandinavian countries
|
||||
|
||||
.. seealso::
|
||||
:doc:`transactions`
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
|
@@ -196,7 +201,9 @@ You can update your credentials by going to :menuselection:`Accounting Dashboard
|
|||
Configuration --> Accounting: Online Synchronization`,open the connection you want to update your
|
||||
credentials and click on the :guilabel:`Update Credentials` button.
|
||||
|
||||
.. seealso::
|
||||
* :doc:`transactions`
|
||||
* :doc:`ponto`
|
||||
* :doc:`saltedge`
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
bank_synchronization/saltedge
|
||||
bank_synchronization/ponto
|
||||
bank_synchronization/enablebanking
|
||||
|
|
@@ -0,0 +1,36 @@
|
|||
==============
|
||||
Enable Banking
|
||||
==============
|
||||
|
||||
**Enable Banking** is a third-party provider aggregating banking information from bank accounts all
|
||||
in one place. It offers non-intrusive connectivity to ASPSPs' official APIs across Europe without
|
||||
storing data.
|
||||
|
||||
.. image:: enablebanking/enablebanking.png
|
||||
:align: center
|
||||
:alt: Enable Banking logo
|
||||
|
||||
**Odoo** synchronizes directly with banks to get access to all bank transactions and automatically
|
||||
import them into your database.
|
||||
|
||||
.. seealso::
|
||||
- :doc:`../bank_synchronization`
|
||||
- `Enable Banking website <https://enablebanking.com/>`_
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Link bank accounts with Odoo
|
||||
----------------------------
|
||||
|
||||
#. Start synchronization by clicking on :menuselection:`Accounting --> Configuration -->
|
||||
Add a Bank Account`;
|
||||
#. Select your bank;
|
||||
#. Make sure you give your consent to share your account information with Odoo by clicking
|
||||
:guilabel:`Continue authentication`;
|
||||
|
||||
.. image:: enablebanking/enablebankingauth.png
|
||||
:align: center
|
||||
:alt: Enable Banking authentication page
|
||||
|
||||
#. Finally, you are redirected to your bank's login page.
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
|
@@ -1,6 +1,6 @@
|
|||
======================================
|
||||
Ponto as bank synchronization provider
|
||||
======================================
|
||||
=====
|
||||
Ponto
|
||||
=====
|
||||
|
||||
**Ponto** is a service that allows companies and professionals to aggregate their accounts in one
|
||||
place and directly see all their transactions within one app. It is a third-party solution that is
|
||||
|
|
@@ -16,9 +16,9 @@ into your database.
|
|||
Ponto is a paid third-party provider that can handle the synchronization between your bank accounts
|
||||
and Odoo. `Its pricing is 4€/month per account/integration <https://myponto.com/en#pricing>`_.
|
||||
|
||||
.. note::
|
||||
You can find more information about bank synchronization :doc:`on this page
|
||||
<bank_synchronization>`.
|
||||
.. seealso::
|
||||
- :doc:`../bank_synchronization`
|
||||
- :doc:`../transactions`
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
|
@@ -115,10 +115,3 @@ and we advise you to contact Ponto directly.
|
|||
.. important::
|
||||
Using an institution in beta is beneficial for Ponto, it allows them to have real
|
||||
feedback on the connection with the institution.
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
* :doc:`bank_synchronization`
|
||||
* :doc:`saltedge`
|
||||
* :doc:`transactions`
|
||||
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
|
@@ -1,6 +1,6 @@
|
|||
==========================================
|
||||
Salt Edge as bank synchronization provider
|
||||
==========================================
|
||||
=========
|
||||
Salt Edge
|
||||
=========
|
||||
|
||||
**Salt Edge** is a third-party provider that aggregates banking information
|
||||
from your bank accounts. It supports ~5000 institutions in more than 50
|
||||
|
|
@@ -8,12 +8,15 @@ countries.
|
|||
|
||||
.. image:: saltedge/saltedge-logo.png
|
||||
:align: center
|
||||
:width: 50%
|
||||
:alt: Salt Edge Logo
|
||||
|
||||
Odoo can synchronize directly with your bank to get all bank statements imported
|
||||
automatically into your database.
|
||||
|
||||
Salt Edge is a free third-party provider.
|
||||
.. seealso::
|
||||
- :doc:`../bank_synchronization`
|
||||
- :doc:`../transactions`
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
|
@@ -103,9 +106,3 @@ In case you already have a connection with the same credentials present on your
|
|||
and this synchronization was created with Odoo, you will normally be able to find it by going to
|
||||
:menuselection:`Accounting --> Configuration --> Online Synchronization`. Please make sure to do an
|
||||
*Update Credentials* to reactivate the connection.
|
||||
|
||||
.. seealso::
|
||||
|
||||
* :doc:`bank_synchronization`
|
||||
* :doc:`ponto`
|
||||
* :doc:`transactions`
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
|
@@ -1,13 +0,0 @@
|
|||
:nosearch:
|
||||
|
||||
==========
|
||||
Bank feeds
|
||||
==========
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
|
||||
feeds/transactions
|
||||
feeds/bank_synchronization
|
||||
feeds/ponto
|
||||
feeds/saltedge
|
||||
|
|
@@ -91,7 +91,7 @@ Manage and reconcile bank statements
|
|||
------------------------------------
|
||||
|
||||
The next step is to book the bank statements to finalize the transaction by creating,
|
||||
:doc:`importing <feeds/transactions>`, or :doc:`synchronizing <feeds/bank_synchronization>` your
|
||||
:doc:`importing <transactions>`, or :doc:`synchronizing <bank_synchronization>` your
|
||||
:guilabel:`Transactions lines`. Fill in the :guilabel:`Ending balance` and click on the
|
||||
:guilabel:`Reconcile` button.
|
||||
|
||||
|
|
|
|||
|
|
@@ -125,5 +125,5 @@ line*.
|
|||
.. seealso::
|
||||
|
||||
- :doc:`use_cases`
|
||||
- :doc:`../feeds/bank_synchronization`
|
||||
- :doc:`../bank_synchronization`
|
||||
- :doc:`../../receivables/customer_invoices/cash_discounts`
|
||||
|
|
|
|||
|
|
@@ -102,4 +102,4 @@ right and validate all related payments :
|
|||
:align: center
|
||||
|
||||
.. seealso::
|
||||
* :doc:`../feeds/bank_synchronization`
|
||||
:doc:`../bank_synchronization`
|
||||
|
|
|
|||
|
|
@@ -4,7 +4,7 @@ Bank and cash accounts
|
|||
|
||||
You can manage as many bank or cash accounts as needed on your database. Configuring them well
|
||||
allows you to have all your banking data up-to-date and ready for :doc:`reconciliation
|
||||
<../../bank/reconciliation/use_cases>` with your journal entries.
|
||||
<../reconciliation/use_cases>` with your journal entries.
|
||||
|
||||
In Odoo Accounting, each bank account has a dedicated journal set to post all entries in a dedicated
|
||||
account. Both the journal and the account are automatically created and configured whenever you add
|
||||
|
|
@@ -33,7 +33,7 @@ To connect your bank account to your database, go to :menuselection:`Accounting
|
|||
follow the instructions.
|
||||
|
||||
.. seealso::
|
||||
:doc:`../../bank/feeds/bank_synchronization`
|
||||
:doc:`../bank_synchronization`
|
||||
|
||||
Create a bank account
|
||||
---------------------
|
||||
|
|
@@ -83,7 +83,7 @@ You can edit the accounting information and bank account number according to you
|
|||
|
||||
.. seealso::
|
||||
- :doc:`../../others/multi_currency`
|
||||
- :doc:`../../bank/feeds/transactions`
|
||||
- :doc:`../transactions`
|
||||
- :doc:`../../bank/setup/outstanding_accounts`
|
||||
|
||||
Suspense account
|
||||
|
|
@@ -132,5 +132,5 @@ Bank feeds
|
|||
with your database.
|
||||
|
||||
.. seealso::
|
||||
- :doc:`../../bank/feeds/bank_synchronization`
|
||||
- :doc:`../../bank/feeds/transactions`
|
||||
- :doc:`../bank_synchronization`
|
||||
- :doc:`../transactions`
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
|
@@ -55,7 +55,7 @@ Connect your bank account to your database and have your bank statements synced
|
|||
so, find your bank in the list, click on *Connect*, and follow the instructions on-screen.
|
||||
|
||||
.. note::
|
||||
:doc:`Click here <../../bank/feeds/bank_synchronization>` for more information about this feature.
|
||||
:doc:`Click here <../../bank/bank_synchronization>` for more information about this feature.
|
||||
|
||||
If your Bank Institution can’t be synchronized automatically, or if you prefer not to sync it with
|
||||
your database, you may also configure your bank account manually by clicking on *Create it*, and
|
||||
|
|
@@ -188,6 +188,6 @@ Send yourself a sample invoice by email to make sure everything is correctly con
|
|||
.. seealso::
|
||||
* :doc:`../../bank/setup/bank_accounts`
|
||||
* :doc:`chart_of_accounts`
|
||||
* :doc:`../../bank/feeds/bank_synchronization`
|
||||
* :doc:`../../bank/bank_synchronization`
|
||||
* :doc:`../../../fiscal_localizations`
|
||||
* `Odoo Tutorials: Accounting Basics <https://www.odoo.com/r/lsZ>`_
|
||||
|
|
|
|||
|
|
@@ -11,4 +11,3 @@ Valuation Methods
|
|||
reporting/inventory_valuation_config
|
||||
reporting/using_inventory_valuation
|
||||
reporting/integrating_landed_costs
|
||||
/applications/finance/accounting/others/inventory/avg_price_valuation
|
||||
|
|
|
|||
|
|
@@ -1,7 +1,9 @@
|
|||
=================================
|
||||
Inventory Valuation Configuration
|
||||
Inventory valuation configuration
|
||||
=================================
|
||||
|
||||
.. _inventory/inventory_valuation_config:
|
||||
|
||||
All of a company's stock on-hand contributes to the valuation of its inventory. That value should
|
||||
be reflected in the company's accounting records to accurately show the value of the company and
|
||||
all of its assets.
|
||||
|
|
@@ -27,32 +29,32 @@ between locations in a company's inventory.
|
|||
need to be periodically checked to ensure accuracy, and adjustments may be needed on an ongoing
|
||||
basis depending on the needs and priorities of the business.
|
||||
|
||||
Types of Accounting
|
||||
Types of accounting
|
||||
-------------------
|
||||
|
||||
Accounting entries will depend on the accounting mode: Continental or Anglo-Saxon.
|
||||
Accounting entries will depend on the accounting mode: *Continental* or *Anglo-Saxon*.
|
||||
|
||||
.. tip::
|
||||
Verify the accounting mode by activating the :ref:`developer-mode`
|
||||
and navigating to :menuselection:`Accounting --> Configuration --> Settings`.
|
||||
Verify the accounting mode by activating the :ref:`developer-mode` and navigating to
|
||||
:menuselection:`Accounting --> Configuration --> Settings`.
|
||||
|
||||
In Anglo-Saxon accounting, the costs of goods sold (COGS) are reported when products are sold or
|
||||
In *Anglo-Saxon* accounting, the costs of goods sold (COGS) are reported when products are sold or
|
||||
delivered. This means that the cost of a good is only recorded as an expense when a customer is
|
||||
invoiced for a product. Interim Stock Accounts are used for the input and output accounts, and are
|
||||
both Asset Accounts in the Balance Sheet.
|
||||
invoiced for a product. *Interim Stock Accounts* are used for the input and output accounts, and are
|
||||
both *Asset Accounts* in the balance sheet.
|
||||
|
||||
In Continental accounting, the cost of a good is reported as soon as a product is received into
|
||||
stock. Additionally, a *single* Expense account is used for both input and output accounts in
|
||||
the Balance Sheet.
|
||||
In *Continental* accounting, the cost of a good is reported as soon as a product is received into
|
||||
stock. Additionally, a single *Expense* account is used for both input and output accounts in
|
||||
the balance sheet.
|
||||
|
||||
Costing Methods
|
||||
Costing methods
|
||||
---------------
|
||||
|
||||
Below are the three costing methods that can be used in Odoo for inventory valuation.
|
||||
|
||||
- **Standard Price**: is the default costing method in Odoo. The cost of the product is manually
|
||||
defined on the product form, and this cost is used to compute the valuation. Even if the purchase
|
||||
price on a Purchase Order differs, the valuation will still use the cost defined on the product
|
||||
price on a purchase order differs, the valuation will still use the cost defined on the product
|
||||
form.
|
||||
- **Average Cost (AVCO)**: calculates the valuation of a product based on the average cost of that
|
||||
product, divided by the total number of available stock on-hand. With this costing method,
|
||||
|
|
@@ -81,10 +83,11 @@ valuation method should apply.
|
|||
Under the :guilabel:`Inventory Valuation` heading are two labels: :guilabel:`Costing Method` and
|
||||
:guilabel:`Inventory Valuation`. Pick the desired :guilabel:`Costing Method` using the drop-down
|
||||
menu (e.g. :guilabel:`Standard`, :guilabel:`Average Cost (AVCO)`, or :guilabel:`First In First Out
|
||||
(FIFO)` and switch the :guilabel:`Inventory Valuation` to :guilabel:`Automated`.
|
||||
(FIFO)`) and switch the :guilabel:`Inventory Valuation` to :guilabel:`Automated`.
|
||||
|
||||
.. seealso::
|
||||
:doc:`Using the inventory valuation <using_inventory_valuation>`
|
||||
:doc:`Using the inventory valuation
|
||||
</applications/inventory_and_mrp/inventory/management/reporting/using_inventory_valuation>`
|
||||
|
||||
.. note::
|
||||
When choosing :guilabel:`Average Cost (AVCO)` as the :guilabel:`Costing Method`, the numerical
|
||||
|
|
|
|||
|
|
@@ -1,77 +1,130 @@
|
|||
=============================
|
||||
Using the inventory valuation
|
||||
=============================
|
||||
=========================
|
||||
Using inventory valuation
|
||||
=========================
|
||||
|
||||
Inventory valuation refers to how you value your stock. It’s a very
|
||||
important aspect of a business as the inventory can be the biggest asset
|
||||
of a company.
|
||||
*Inventory valuation* is a quintessential accounting procedure that calculates the value of on-hand
|
||||
stock. Once determined, the inventory valuation amount is then incorporated into a company's overall
|
||||
value.
|
||||
|
||||
In this documentation, we will explain how the inventory valuation works
|
||||
in Odoo.
|
||||
In Odoo, this process can be conducted manually— by warehouse employees physically counting the
|
||||
products— or automatically through the database.
|
||||
|
||||
Inventory valuation: the basics
|
||||
===============================
|
||||
.. seealso::
|
||||
:ref:`Inventory valuation configuration <inventory/inventory_valuation_config>`
|
||||
|
||||
Understand the basics of inventory valuation
|
||||
============================================
|
||||
|
||||
In order to understand how moving products in and out of stock affects the company's overall value,
|
||||
consider the following product and stock moves scenario below.
|
||||
|
||||
Receive a product
|
||||
-----------------
|
||||
|
||||
Each time a product enters or leaves your stock, the value of your
|
||||
inventory is impacted. The way it is impacted depends on the
|
||||
configuration of your product (more info here).
|
||||
For example, consider an physical product, a simple *table*, which is categorized as `Office
|
||||
Furniture` in the product form's :guilabel:`Product Category` field.
|
||||
|
||||
Let’s take an example with a product - a table - configured with a
|
||||
*FIFO costing method* and an automated inventory valuation.
|
||||
|
||||
I purchase 10 tables at a cost of $10.
|
||||
|
||||
.. image:: using_inventory_valuation/use_inventory_valuation_01.png
|
||||
:align: center
|
||||
|
||||
When I’ll confirm the receipt of the products, the value of my inventory
|
||||
will be impacted. If I want to know what this impact is, I can click on
|
||||
the valuation stat button.
|
||||
Navigate to the the product category itself by going to :menuselection:`Inventory app -->
|
||||
Configuration --> Product Categories`, and on the form, set the :guilabel:`Costing Method` as `First
|
||||
In First Out (FIFO)` and the :guilabel:`Inventory Valuation` field as `Automated`.
|
||||
|
||||
.. tip::
|
||||
The consignment feature allows you to set owners on your stock (discover
|
||||
more about the consignment feature). When you receive products that are
|
||||
owned by another company, they are not taken into account in your
|
||||
inventory valuation.
|
||||
Alternatively access the :guilabel:`Product Categories` dashboard by clicking on the
|
||||
:guilabel:`internal link` arrow on the individual product's form, by hovering over the
|
||||
:guilabel:`Product Category` field.
|
||||
|
||||
.. image:: using_inventory_valuation/use_inventory_valuation_02.png
|
||||
:align: center
|
||||
Next, assume 10 tables are purchased at a price of $10.00, each. The :abbr:`PO (Purchase Order)` for
|
||||
those tables will show the subtotal of the purchase as $100, plus any additional costs or taxes.
|
||||
|
||||
.. note::
|
||||
You need access rights on the accounting module to see that button.
|
||||
.. image:: using_inventory_valuation/purchase-order.png
|
||||
:align: center
|
||||
:alt: Purchase order with 10 tables products valued at $10 each.
|
||||
|
||||
In this case, I can see that the 10 tables entered the stock for a total
|
||||
value of $100.
|
||||
After selecting :guilabel:`Validate` on the :abbr:`PO (Purchase Order)`, use the
|
||||
:guilabel:`Valuation` smart button to view how the value of inventory was impacted.
|
||||
|
||||
.. image:: using_inventory_valuation/use_inventory_valuation_03.png
|
||||
:align: center
|
||||
.. image:: using_inventory_valuation/valuation-smart-button.png
|
||||
:align: center
|
||||
:alt: Valuation smart button on a receipt.
|
||||
|
||||
I can also easily access the accounting entry that has been generated
|
||||
(in case of automated inventory valuation).
|
||||
.. important::
|
||||
:ref:`Developer mode <developer-mode>` must be turned on to see the *Valuation* smart button.
|
||||
|
||||
.. image:: using_inventory_valuation/use_inventory_valuation_04.png
|
||||
:align: center
|
||||
.. tip::
|
||||
The :doc:`consignment </applications/inventory_and_mrp/inventory/management/misc/owned_stock>`
|
||||
feature allows ownership to items in stock. Thus, products owned by other companies are not
|
||||
accounted for in the host company's inventory valuation.
|
||||
|
||||
The :guilabel:`Stock Valuation` dashboard then displays valuation of all products in the shipment,
|
||||
along with their quantities and valuation. In the example of 10 tables being purchased, the
|
||||
:guilabel:`Total Value` column of the dashboard would display a calculated valuation of $100.
|
||||
|
||||
.. image:: using_inventory_valuation/stock-valuation-product.png
|
||||
:align: center
|
||||
:alt: Stock valuation page depicting the products within a shipment.
|
||||
|
||||
In Odoo, automatic inventory valuation records are also recorded in the *Accounting* app. To access
|
||||
these accounting entries, navigate to :menuselection:`Accounting --> Journal Entries`, and look for
|
||||
inventory valuation entries with the `STJ` prefix in the :guilabel:`Journal` and :guilabel:`Number`
|
||||
columns, respectively.
|
||||
|
||||
.. image:: using_inventory_valuation/inventory-valuation-entry.png
|
||||
:align: center
|
||||
:alt: Accounting entry for the inventory valuation of 10 tables.
|
||||
|
||||
Deliver a product
|
||||
-----------------
|
||||
|
||||
In the same logic, when a table will be delivered, the stock valuation
|
||||
will be impacted and you will have access to a similar information.
|
||||
In the same logic, when a table is shipped to a customer and leaves the warehouse, the stock
|
||||
valuation decreases. The :guilabel:`Valuation` smart button on the :abbr:`DO (Delivery Order)`,
|
||||
likewise, displays the stock valuation record as it does on a :abbr:`PO (Purchase Order)`
|
||||
|
||||
.. image:: using_inventory_valuation/use_inventory_valuation_05.png
|
||||
:align: center
|
||||
.. image:: using_inventory_valuation/decreased-stock-valuation.png
|
||||
:align: center
|
||||
:alt: Decreased stock valuation after a product is shipped.
|
||||
|
||||
The inventory valuation report
|
||||
------------------------------
|
||||
|
||||
The summary of this is accessible via the inventory valuation report
|
||||
(accessible from :menuselection:`Inventory --> Reporting --> Inventory Valuation`). It gives
|
||||
you, product per product, the value of your stock. By clicking on the
|
||||
button *Inventory At Date*, you can have the same information for a
|
||||
past date.
|
||||
To view the current value of all products in the warehouse, go to :menuselection:`Inventory app -->
|
||||
Reporting --> Inventory Valuation`. The records in the table are organized by product, and selecting
|
||||
a product's drop-down menu displays detailed records with the :guilabel:`Date`,
|
||||
:guilabel:`Quantity`, and :guilabel:`Total Value` of the inventory.
|
||||
|
||||
.. image:: using_inventory_valuation/use_inventory_valuation_06.png
|
||||
:align: center
|
||||
.. image:: using_inventory_valuation/inventory-valuation-products.png
|
||||
:align: center
|
||||
:alt: Inventory valuation report showing multiple products.
|
||||
|
||||
.. note::
|
||||
The :guilabel:`Inventory At Date` button, located in the top-left corner of the :guilabel:`Stock
|
||||
Valuation` dashboard, shows the inventory valuation of products available during a prior
|
||||
specified date.
|
||||
|
||||
Update product unit price
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For any company: lead times, supply chain failures, and other risk factors can contribute to
|
||||
invisible costs. Although Odoo attempts to accurately represent the stock value, *manual valuation*
|
||||
serves as an additional tool to update the unit price of products.
|
||||
|
||||
.. important::
|
||||
Manual valuation is intended for products that can be purchased and received for a cost greater
|
||||
than 0, or have product categories set with :guilabel:`Costing Method` set as either `Average
|
||||
Cost (AVCO)` or `First In First Out (FIFO)`.
|
||||
|
||||
.. image:: using_inventory_valuation/add-manual-valuation.png
|
||||
:align: center
|
||||
:alt: Add manual valuation of stock value to a product.
|
||||
|
||||
Create manual valuation entries on the :guilabel:`Stock Valuation` dashboard by first navigating to
|
||||
:menuselection:`Inventory app --> Reporting --> Inventory Valuation`, and then click the little gray
|
||||
drop-down icon to the left of a product's name, to then reveal stock valuation line items below as
|
||||
well as a teal :guilabel:`+ (plus)` button on the right.
|
||||
|
||||
Click the teal :guilabel:`+ (plus)` button to open up the :guilabel:`Product Revaluation` form,
|
||||
where updates to the calculation of inventory valuation can be made, by increasing or decreasing the
|
||||
unit price of each product.
|
||||
|
||||
.. image:: using_inventory_valuation/product-revaluation.png
|
||||
:align: center
|
||||
:alt: Product revaluation form adding a value of $1.00 with the reason being inflation.
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
|
@@ -89,6 +89,8 @@ Area*. Then, once the quality check is done, move them to their *Storage Locatio
|
|||
orders and their products are grouped in one transfer, whereas the packing operation respects the
|
||||
grouping per customer order.
|
||||
|
||||
.. _use-routes/routes-rules:
|
||||
|
||||
Use routes and rules
|
||||
====================
|
||||
|
||||
|
|
|
|||
|
|
@@ -15,3 +15,4 @@ Field Service
|
|||
|
||||
fsm/sales
|
||||
fsm/helpdesk
|
||||
fsm/default_warehouse
|
||||
|
|
|
|||
42
content/applications/services/fsm/default_warehouse.rst
Normal file
|
|
@@ -0,0 +1,42 @@
|
|||
======================
|
||||
User default warehouse
|
||||
======================
|
||||
|
||||
Setting up a **default warehouse** can be useful for field technicians who keep a supply in their
|
||||
van or those who always resupply from the same warehouse.
|
||||
|
||||
Products in sales orders created during field interventions are always pulled from the same
|
||||
warehouse, keeping the inventory accurate with the default warehouse feature.
|
||||
|
||||
.. seealso::
|
||||
:doc:`../../inventory_and_mrp/inventory`
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
To set up a user default warehouse, the :doc:`storage locations
|
||||
<../../inventory_and_mrp/inventory/management/warehouses/warehouses_locations>` and :ref:`multi-step
|
||||
routes <use-routes/routes-rules>` features need to be activated. It is also necessary to have more
|
||||
than one warehouse in your database.
|
||||
|
||||
.. seealso::
|
||||
- :doc:`../../inventory_and_mrp/inventory/management/warehouses/warehouses_locations`
|
||||
- :doc:`../../inventory_and_mrp/inventory/routes/concepts/use_routes`
|
||||
|
||||
User account
|
||||
------------
|
||||
|
||||
To set up a default warehouse for a specific user, go to :menuselection:`Settings --> Users -->
|
||||
Manage users`, open a user, then go to the :guilabel:`Preferences` tab, scroll down to
|
||||
:guilabel:`Inventory`, and select the default warehouse from the drop-down menu.
|
||||
|
||||
.. image:: default_warehouse/user-default.png
|
||||
:alt: Selection of a default warehouse on a user profile.
|
||||
|
||||
Default warehouse in field service tasks
|
||||
========================================
|
||||
|
||||
Once a default warehouse has been configured for a user, the materials used for a sales order linked
|
||||
to a Field Service task are pulled from that specific warehouse. In the sales order, go to the
|
||||
:guilabel:`Other Info` tab, then scroll down to :guilabel:`Delivery`. The default warehouse is
|
||||
applied correctly.
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@@ -1,39 +1,95 @@
|
|||
===========================
|
||||
How to translate my website
|
||||
===========================
|
||||
===================
|
||||
Website translation
|
||||
===================
|
||||
|
||||
Overview
|
||||
========
|
||||
The contents of your website pages (i.e., text strings) can be translated into different languages
|
||||
directly on your website.
|
||||
|
||||
In addition to creating great modern websites, Odoo gives you the
|
||||
possibility to translate it in different languages.
|
||||
Your website is displayed in the language that matches the visitor's browser's language, unless that
|
||||
particular language has not been installed. In this case, the website is displayed in the
|
||||
:ref:`default language <translate/default-language>`. The visitor can still select another language
|
||||
in the language menu.
|
||||
|
||||
Process
|
||||
=======
|
||||
Installing languages
|
||||
====================
|
||||
|
||||
Once your website is created, you have the opportunity to translate it
|
||||
in as many different languages as you want.
|
||||
To translate your website, you first have to add the required languages:
|
||||
|
||||
You can only translate your website manually, follow the next step.
|
||||
#. Go to your website.
|
||||
#. Scroll to the bottom of the page to the **language menu**.
|
||||
#. Click the language and select :guilabel:`Add a language`.
|
||||
|
||||
Now go to your website. On the bottom right corner of the page, click on
|
||||
**Add a language**.
|
||||
.. image:: translate/website-add-language.png
|
||||
:alt: Add a language to your website.
|
||||
|
||||
.. image:: translate/translate_website01.png
|
||||
:align: center
|
||||
#. Click the :guilabel:`Languages` field and select the required language from the drop-down list.
|
||||
Repeat this step for each additional language.
|
||||
#. Click the :guilabel:`Add` button.
|
||||
|
||||
Choose the language in which you want to translate your website and then
|
||||
click on **Load.**
|
||||
.. tip::
|
||||
You can also edit your website's languages from the backend, in the :guilabel:`Settings`. Go to
|
||||
:menuselection:`Website –> Configuration –> Settings` and add/remove the required languages in
|
||||
the :guilabel:`Languages` field, in the :guilabel:`Website info` section.
|
||||
|
||||
.. image:: translate/translate_website02.png
|
||||
:align: center
|
||||
.. _translate/default-language:
|
||||
|
||||
You will see that Now, next to English there is also French, which means
|
||||
that the page for the translation has been created. You can also see
|
||||
that some of the text has been translated automatically.
|
||||
Default language
|
||||
----------------
|
||||
|
||||
.. image:: translate/translate_website03.png
|
||||
:align: center
|
||||
If the language of the visitor's browser is not installed on your website, the content is displayed
|
||||
in the default language.
|
||||
|
||||
To translate the content of the website, click on **Translate** (here
|
||||
**Traduire** since we want to translate the website in French).
|
||||
To define a default language, go to :menuselection:`Website –> Configuration –> Settings`, and
|
||||
select a language in the :guilabel:`Default` field.
|
||||
|
||||
.. note::
|
||||
This field is visible only if multiple languages are already configured for your website.
|
||||
|
||||
Translating the contents
|
||||
========================
|
||||
|
||||
Once the languages have been added, you can translate the contents of your website. To do so, go to
|
||||
your website, select the language from the language menu and click the :guilabel:`Translate` button
|
||||
on the right part of the task bar to activate the **translation mode**.
|
||||
|
||||
.. image:: translate/translate-button.png
|
||||
:alt: Translate button
|
||||
|
||||
As a result:
|
||||
|
||||
- Text strings that have already been translated are highlighted in green;
|
||||
- Text strings that need to be translated are highlighted in yellow.
|
||||
|
||||
.. image:: translate/website-translation-yellow.png
|
||||
:alt: Text to be translated highlighted in yellow
|
||||
|
||||
You can then replace the original text with the translation by clicking the block, editing its
|
||||
contents and saving.
|
||||
|
||||
.. tip::
|
||||
- Once the languages have been installed, you can also translate some items (e.g.,
|
||||
the product's name and description) from the backend (e.g., in the product template). To do so,
|
||||
click the language code (e.g., :guilabel:`EN`) next to the text you want to translate (e.g.,
|
||||
the product name) and add the translation.
|
||||
|
||||
.. image:: translate/product-translation.png
|
||||
:alt: Translate product-related items.
|
||||
|
||||
- You can also :doc:`export/import translations <../../../../developer/howtos/translations>`
|
||||
to translate multiple items (e.g., product names and descriptions) in one go.
|
||||
|
||||
Language selector menu
|
||||
======================
|
||||
|
||||
To add a language selector menu:
|
||||
|
||||
#. Go to your website and click :guilabel:`Edit`.
|
||||
#. Select the block where you want to add the language selector menu (e.g., the header).
|
||||
#. Select the :guilabel:`Customize` tab.
|
||||
#. In the :guilabel:`Navbar` section, set the :guilabel:`Language selector` field to either
|
||||
:guilabel:`Dropdown` or :guilabel:`Inline`.
|
||||
|
||||
.. image:: translate/language-selector.png
|
||||
:alt: Add a language selector menu.
|
||||
|
||||
#. Click :guilabel:`Save`.
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
|
@@ -9,6 +9,9 @@ How-to guides
|
|||
:titlesonly:
|
||||
|
||||
howtos/scss_tips
|
||||
howtos/javascript_field
|
||||
howtos/javascript_view
|
||||
howtos/javascript_client_action
|
||||
howtos/web_services
|
||||
howtos/company
|
||||
howtos/accounting_localization
|
||||
|
|
@@ -23,6 +26,21 @@ How-to guides
|
|||
|
||||
Follow this guide to keep the technical debt of your CSS code under control.
|
||||
|
||||
.. card:: Customize a field
|
||||
:target: howtos/javascript_field
|
||||
|
||||
Learn how to customize field components in the Odoo JavaScript web framework.
|
||||
|
||||
.. card:: Customize a view type
|
||||
:target: howtos/javascript_view
|
||||
|
||||
Learn how to customize view types in the Odoo JavaScript web framework.
|
||||
|
||||
.. card:: Create a client action
|
||||
:target: howtos/javascript_client_action
|
||||
|
||||
Learn how to create client actions in the Odoo JavaScript web framework.
|
||||
|
||||
.. card:: Web services
|
||||
:target: howtos/web_services
|
||||
|
||||
|
|
|
|||
46
content/developer/howtos/javascript_client_action.rst
Normal file
|
|
@@ -0,0 +1,46 @@
|
|||
|
||||
======================
|
||||
Create a client action
|
||||
======================
|
||||
|
||||
A client action triggers an action that is entirely implemented in the client side.
|
||||
One of the benefits of using a client action is the ability to create highly customized interfaces
|
||||
with ease. A client action is typically defined by an OWL component; we can also use the web
|
||||
framework and use services, core components, hooks,...
|
||||
|
||||
#. Create the :ref:`client action <reference/actions/client>`, don't forget to
|
||||
make it accessible.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record model="ir.actions.client" id="my_client_action">
|
||||
<field name="name">My Client Action</field>
|
||||
<field name="tag">my_module.MyClientAction</field>
|
||||
</record>
|
||||
|
||||
#. Create a component that represents the client action.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`my_client_action.js`
|
||||
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
class MyClientAction extends Component {}
|
||||
MyClientAction.template = "my_module.clientaction";
|
||||
|
||||
// remember the tag name we put in the first step
|
||||
registry.category("actions").add("my_module.MyClientAction", MyClientAction);
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`my_client_action.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="awesome_tshirt.clientaction" owl="1">
|
||||
Hello world
|
||||
</t>
|
||||
</templates>
|
||||
107
content/developer/howtos/javascript_field.rst
Normal file
|
|
@@ -0,0 +1,107 @@
|
|||
|
||||
=================
|
||||
Customize a field
|
||||
=================
|
||||
|
||||
Subclass an existing field component
|
||||
====================================
|
||||
|
||||
Let's take an example where we want to extends the `BooleanField` to create a boolean field
|
||||
displaying "Late!" in red whenever the checkbox is checked.
|
||||
|
||||
#. Create a new widget component extending the desired field component.
|
||||
|
||||
.. code-block:: javascript
|
||||
:caption: :file:`late_order_boolean_field.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { BooleanField } from "@web/views/fields/boolean/boolean_field";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
|
||||
class LateOrderBooleanField extends BooleanField {}
|
||||
LateOrderBooleanField.template = "my_module.LateOrderBooleanField";
|
||||
|
||||
#. Create the field template.
|
||||
|
||||
The component uses a new template with the name `my_module.LateOrderBooleanField`. Create it by
|
||||
inheriting the current template of the `BooleanField`.
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`late_order_boolean_field.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="my_module.LateOrderBooleanField" t-inherit="web.BooleanField" owl="1">
|
||||
<xpath expr="//CheckBox" position="after">
|
||||
<span t-if="props.value" class="text-danger"> Late! </span>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Register the component to the fields registry.
|
||||
|
||||
.. code-block::
|
||||
:caption: :file:`late_order_boolean_field.js`
|
||||
|
||||
registry.category("fields").add("late_boolean", LateOrderBooleanField);
|
||||
|
||||
#. Add the widget in the view arch as an attribute of the field.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="somefield" widget="late_boolean"/>
|
||||
|
||||
Create a new field component
|
||||
============================
|
||||
|
||||
Assume that we want to create a field that displays a simple text in red.
|
||||
|
||||
#. Create a new Owl component representing our new field
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`my_text_field.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export class MyTextField extends Component {
|
||||
|
||||
/**
|
||||
* @param {boolean} newValue
|
||||
*/
|
||||
onChange(newValue) {
|
||||
this.props.update(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
MyTextField.template = xml`
|
||||
<input t-att-id="props.id" class="text-danger" t-att-value="props.value" onChange.bind="onChange" />
|
||||
`;
|
||||
MyTextField.props = {
|
||||
...standardFieldProps,
|
||||
};
|
||||
MyTextField.supportedTypes = ["char"];
|
||||
|
||||
The imported `standardFieldProps` contains the standard props passed by the `View` such as
|
||||
the `update` function to update the value, the `type` of the field in the model, the
|
||||
`readonly` boolean, and others.
|
||||
|
||||
#. In the same file, register the component to the fields registry.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`my_text_field.js`
|
||||
|
||||
registry.category("fields").add("my_text_field", MyTextField);
|
||||
|
||||
This maps the widget name in the arch to its actual component.
|
||||
|
||||
#. Add the widget in the view arch as an attribute of the field.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="somefield" widget="my_text_field"/>
|
||||
262
content/developer/howtos/javascript_view.rst
Normal file
|
|
@@ -0,0 +1,262 @@
|
|||
=====================
|
||||
Customize a view type
|
||||
=====================
|
||||
|
||||
Subclass an existing view
|
||||
=========================
|
||||
|
||||
Assume we need to create a custom version of a generic view. For example, a kanban view with some
|
||||
extra ribbon-like widget on top (to display some specific custom information). In that case, this
|
||||
can be done in a few steps:
|
||||
|
||||
#. Extend the kanban controller/renderer/model and register it in the view registry.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`custom_kanban_controller.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { KanbanController } from "@web/views/kanban/kanban_controller";
|
||||
import { kanbanView } from "@web/views/kanban/kanban_view";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
// the controller usually contains the Layout and the renderer.
|
||||
class CustomKanbanController extends KanbanController {
|
||||
// Your logic here, override or insert new methods...
|
||||
// if you override setup(), don't forget to call super.setup()
|
||||
}
|
||||
|
||||
CustomKanbanController.template = "my_module.CustomKanbanView";
|
||||
|
||||
export const customKanbanView = {
|
||||
...kanbanView, // contains the default Renderer/Controller/Model
|
||||
Controller: CustomKanbanController,
|
||||
};
|
||||
|
||||
// Register it to the views registry
|
||||
registry.category("views").add("custom_kanban", customeKanbanView);
|
||||
|
||||
In our custom kanban, we defined a new template. We can either inherit the kanban controller
|
||||
template and add our template pieces or we can define a completely new template.
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`custom_kanban_controller.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates>
|
||||
<t t-name="my_module.CustomKanbanView" t-inherit="web.KanbanView" owl="1">
|
||||
<xpath expr="//Layout" position="before">
|
||||
<div>
|
||||
Hello world !
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Use the view with the `js_class` attribute in arch.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<kanban js_class="custom_kanban">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<!--Your comment-->
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
|
||||
The possibilities for extending views are endless. While we have only extended the controller
|
||||
here, you can also extend the renderer to add new buttons, modify how records are presented, or
|
||||
customize the dropdown, as well as extend other components such as the model and `buttonTemplate`.
|
||||
|
||||
Create a new view from scratch
|
||||
==============================
|
||||
|
||||
Creating a new view is an advanced topic. This guide highlight only the essential steps.
|
||||
|
||||
#. Create the controller.
|
||||
|
||||
The primary role of a controller is to facilitate the coordination between various components
|
||||
of a view, such as the Renderer, Model, and Layout.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_controller.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component, onWillStart, useState} from "@odoo/owl";
|
||||
|
||||
export class BeautifulController extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
|
||||
// The controller create the model and make it reactive so whenever this.model is
|
||||
// accessed and edited then it'll cause a rerendering
|
||||
this.model = useState(
|
||||
new this.props.Model(
|
||||
this.orm,
|
||||
this.props.resModel,
|
||||
this.props.fields,
|
||||
this.props.archInfo,
|
||||
this.props.domain
|
||||
)
|
||||
);
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.model.load();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BeautifulController.template = "my_module.View";
|
||||
BeautifulController.components = { Layout };
|
||||
|
||||
The template of the Controller displays the control panel with Layout and also the
|
||||
renderer.
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`beautiful_controller.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="my_module.View" owl="1">
|
||||
<Layout display="props.display" className="'h-100 overflow-auto'">
|
||||
<t t-component="props.Renderer" records="model.records" propsYouWant="'Hello world'"/>
|
||||
</Layout>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Create the renderer.
|
||||
|
||||
The primary function of a renderer is to generate a visual representation of data by rendering
|
||||
the view that includes records.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_renderer.js`
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
export class BeautifulRenderer extends Component {}
|
||||
|
||||
BeautifulRenderer.template = "my_module.Renderer";
|
||||
|
||||
.. code-block:: xml
|
||||
:caption: :file:`beautiful_renderer.xml`
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="my_module.Renderer" owl="1">
|
||||
<t t-esc="props.propsYouWant"/>
|
||||
<t t-foreach="props.records" t-as="record" t-key="record.id">
|
||||
// Show records
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
#. Create the model.
|
||||
|
||||
The role of the model is to retrieve and manage all the necessary data in the view.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_model.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
|
||||
export class BeautifulModel {
|
||||
constructor(orm, resModel, fields, archInfo, domain) {
|
||||
this.orm = orm;
|
||||
this.resModel = resModel;
|
||||
// We can access arch information parsed by the beautiful arch parser
|
||||
const { fieldFromTheArch } = archInfo;
|
||||
this.fieldFromTheArch = fieldFromTheArch;
|
||||
this.fields = fields;
|
||||
this.domain = domain;
|
||||
this.keepLast = new KeepLast();
|
||||
}
|
||||
|
||||
async load() {
|
||||
// The keeplast protect against concurrency call
|
||||
const { length, records } = await this.keepLast.add(
|
||||
this.orm.webSearchRead(this.resModel, this.domain, [this.fieldsFromTheArch], {})
|
||||
);
|
||||
this.records = records;
|
||||
this.recordsLength = length;
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
For advanced cases, instead of creating a model from scratch, it is also possible to use
|
||||
`RelationalModel`, which is used by other views.
|
||||
|
||||
#. Create the arch parser.
|
||||
|
||||
The role of the arch parser is to parse the arch view so the view has access to the information.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_arch_parser.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { XMLParser } from "@web/core/utils/xml";
|
||||
|
||||
export class BeautifulArchParser extends XMLParser {
|
||||
parse(arch) {
|
||||
const xmlDoc = this.parseXML(arch);
|
||||
const fieldFromTheArch = xmlDoc.getAttribute("fieldFromTheArch");
|
||||
return {
|
||||
fieldFromTheArch,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#. Create the view and combine all the pieces together, then register the view in the views
|
||||
registry.
|
||||
|
||||
.. code-block:: js
|
||||
:caption: :file:`beautiful_view.js`
|
||||
|
||||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { BeautifulController } from "./beautiful_controller";
|
||||
import { BeautifulArchParser } from "./beautiful_arch_parser";
|
||||
import { BeautifylModel } from "./beautiful_model";
|
||||
import { BeautifulRenderer } from "./beautiful_renderer";
|
||||
|
||||
export const beautifulView = {
|
||||
type: "beautiful",
|
||||
display_name: "Beautiful",
|
||||
icon: "fa fa-picture-o", // the icon that will be displayed in the Layout panel
|
||||
multiRecord: true,
|
||||
Controller: BeautifulController,
|
||||
ArchParser: BeautifulArchParser,
|
||||
Model: BeautifulModel,
|
||||
Renderer: BeautifulRenderer,
|
||||
|
||||
props(genericProps, view) {
|
||||
const { ArchParser } = view;
|
||||
const { arch } = genericProps;
|
||||
const archInfo = new ArchParser().parse(arch);
|
||||
|
||||
return {
|
||||
...genericProps,
|
||||
Model: view.Model,
|
||||
Renderer: view.Renderer,
|
||||
archInfo,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("views").add("beautifulView", beautifulView);
|
||||
|
||||
#. Use the view in an arch.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
...
|
||||
<beautiful fieldFromTheArch="res.partner"/>
|
||||
...
|
||||
|
|
@@ -47,16 +47,16 @@ The test runner will simply run any test case, as described in the official
|
|||
`unittest documentation`_, but Odoo provides a number of utilities and helpers
|
||||
related to testing Odoo content (modules, mainly):
|
||||
|
||||
.. autoclass:: odoo.tests.TransactionCase
|
||||
.. autoclass:: odoo.tests.common.TransactionCase
|
||||
:members: browse_ref, ref
|
||||
|
||||
.. autoclass:: odoo.tests.SingleTransactionCase
|
||||
.. autoclass:: odoo.tests.common.SingleTransactionCase
|
||||
:members: browse_ref, ref
|
||||
|
||||
.. autoclass:: odoo.tests.HttpCase
|
||||
.. autoclass:: odoo.tests.common.HttpCase
|
||||
:members: browse_ref, ref, url_open, browser_js
|
||||
|
||||
.. autofunction:: odoo.tests.tagged
|
||||
.. autofunction:: odoo.tests.common.tagged
|
||||
|
||||
By default, tests are run once right after the corresponding module has been
|
||||
installed. Test cases can also be configured to run after all modules have
|
||||
|
|
@@ -72,10 +72,10 @@ been installed, and not run right after the module installation::
|
|||
Page = self.env['website.page']
|
||||
|
||||
The most common situation is to use
|
||||
:class:`~odoo.tests.TransactionCase` and test a property of a model
|
||||
:class:`~odoo.tests.common.TransactionCase` and test a property of a model
|
||||
in each method::
|
||||
|
||||
class TestModelA(TransactionCase):
|
||||
class TestModelA(common.TransactionCase):
|
||||
def test_some_action(self):
|
||||
record = self.env['model.a'].create({'field': 'value'})
|
||||
record.some_action()
|
||||
|
|
@@ -89,13 +89,13 @@ in each method::
|
|||
|
||||
Test methods must start with ``test_``
|
||||
|
||||
.. autoclass:: odoo.tests.Form
|
||||
.. autoclass:: odoo.tests.common.Form
|
||||
:members:
|
||||
|
||||
.. autoclass:: odoo.tests.M2MProxy
|
||||
.. autoclass:: odoo.tests.common.M2MProxy
|
||||
:members: add, remove, clear
|
||||
|
||||
.. autoclass:: odoo.tests.O2MProxy
|
||||
.. autoclass:: odoo.tests.common.O2MProxy
|
||||
:members: new, edit, remove
|
||||
|
||||
Running tests
|
||||
|
|
@@ -115,9 +115,9 @@ Test selection
|
|||
In Odoo, Python tests can be tagged to facilitate the test selection when
|
||||
running tests.
|
||||
|
||||
Subclasses of :class:`odoo.tests.BaseCase` (usually through
|
||||
:class:`~odoo.tests.TransactionCase` or
|
||||
:class:`~odoo.tests.HttpCase`) are automatically tagged with
|
||||
Subclasses of :class:`odoo.tests.common.BaseCase` (usually through
|
||||
:class:`~odoo.tests.common.TransactionCase` or
|
||||
:class:`~odoo.tests.common.HttpCase`) are automatically tagged with
|
||||
``standard`` and ``at_install`` by default.
|
||||
|
||||
Invocation
|
||||
|
|
@@ -132,12 +132,12 @@ This option defaults to ``+standard`` meaning tests tagged ``standard``
|
|||
(explicitly or implicitly) will be run by default when starting Odoo
|
||||
with :option:`--test-enable <odoo-bin --test-enable>`.
|
||||
|
||||
When writing tests, the :func:`~odoo.tests.tagged` decorator can be
|
||||
When writing tests, the :func:`~odoo.tests.common.tagged` decorator can be
|
||||
used on **test classes** to add or remove tags.
|
||||
|
||||
The decorator's arguments are tag names, as strings.
|
||||
|
||||
.. danger:: :func:`~odoo.tests.tagged` is a class decorator, it has no
|
||||
.. danger:: :func:`~odoo.tests.common.tagged` is a class decorator, it has no
|
||||
effect on functions or methods
|
||||
|
||||
Tags can be prefixed with the minus (``-``) sign, to *remove* them instead of
|
||||
|
|
@@ -180,7 +180,7 @@ ones:
|
|||
$ odoo-bin --test-tags 'standard,-slow'
|
||||
|
||||
When you write a test that does not inherit from the
|
||||
:class:`~odoo.tests.BaseCase`, this test will not have the default tags,
|
||||
:class:`~odoo.tests.common.BaseCase`, this test will not have the default tags,
|
||||
you have to add them explicitly to have the test included in the default test
|
||||
suite. This is a common issue when using a simple ``unittest.TestCase`` as
|
||||
they're not going to get run:
|
||||
|
|
@@ -230,7 +230,7 @@ Special tags
|
|||
~~~~~~~~~~~~
|
||||
|
||||
- ``standard``: All Odoo tests that inherit from
|
||||
:class:`~odoo.tests.BaseCase` are implicitly tagged standard.
|
||||
:class:`~odoo.tests.common.BaseCase` are implicitly tagged standard.
|
||||
:option:`--test-tags <odoo-bin --test-tags>` also defaults to ``standard``.
|
||||
|
||||
That means untagged test will be executed by default when tests are enabled.
|
||||
|
|
@@ -715,7 +715,7 @@ Python
|
|||
~~~~~~
|
||||
|
||||
To start a tour from a python test, make the class inherit from
|
||||
:class:`~odoo.tests.HTTPCase`, and call `start_tour`:
|
||||
:class:`~odoo.tests.common.HTTPCase`, and call `start_tour`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@@ -858,7 +858,7 @@ Query counts
|
|||
|
||||
One of the ways to test performance is to measure database queries. Manually, this can be tested with the
|
||||
`--log-sql` CLI parameter. If you want to establish the maximum number of queries for an operation,
|
||||
you can use the :meth:`~odoo.tests.BaseCase.assertQueryCount` method, integrated in Odoo test classes.
|
||||
you can use the :meth:`~odoo.tests.common.BaseCase.assertQueryCount` method, integrated in Odoo test classes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
|||
|
|
@@ -1728,6 +1728,12 @@ Possible children of the view element are:
|
|||
``name`` (required)
|
||||
the name of the field to fetch
|
||||
|
||||
``allow_group_range_value`` (optional)
|
||||
whether a ``date`` or ``datetime`` field allows a value computed from a
|
||||
group range (which consists of the first and last dates of the group).
|
||||
Enables the 'quick create' and 'drag and drop' features when the kanban
|
||||
view is grouped by that field. Default: false.
|
||||
|
||||
.. include:: views/header_buttons.rst
|
||||
|
||||
.. note::
|
||||
|
|
|
|||
|
|
@@ -15,7 +15,6 @@ JavaScript framework
|
|||
frontend/services
|
||||
frontend/hooks
|
||||
frontend/patching_code
|
||||
frontend/javascript_cheatsheet
|
||||
frontend/javascript_reference
|
||||
frontend/mobile
|
||||
frontend/qweb
|
||||
|
|
|
|||
|
|
@@ -1,546 +0,0 @@
|
|||
|
||||
.. _reference/jscs:
|
||||
|
||||
=====================
|
||||
Javascript Cheatsheet
|
||||
=====================
|
||||
|
||||
There are many ways to solve a problem in JavaScript, and in Odoo. However, the
|
||||
Odoo framework was designed to be extensible (this is a pretty big constraint),
|
||||
and some common problems have a nice standard solution. The standard solution
|
||||
has probably the advantage of being easy to understand for an odoo developer,
|
||||
and will probably keep working when Odoo is modified.
|
||||
|
||||
This document tries to explain the way one could solve some of these issues.
|
||||
Note that this is not a reference. This is just a random collection of recipes,
|
||||
or explanations on how to proceed in some cases.
|
||||
|
||||
|
||||
First of all, remember that the first rule of customizing odoo with JS is:
|
||||
*try to do it in python*. This may seem strange, but the python framework is
|
||||
quite extensible, and many behaviours can be done simply with a touch of xml or
|
||||
python. This has usually a lower cost of maintenance than working with JS:
|
||||
|
||||
- the JS framework tends to change more, so JS code needs to be more frequently
|
||||
updated
|
||||
- it is often more difficult to implement a customized behaviour if it needs to
|
||||
communicate with the server and properly integrate with the javascript framework.
|
||||
There are many small details taken care by the framework that customized code
|
||||
needs to replicate. For example, responsiveness, or updating the url, or
|
||||
displaying data without flickering.
|
||||
|
||||
|
||||
.. note:: This document does not really explain any concepts. This is more a
|
||||
cookbook. For more details, please consult the javascript reference
|
||||
page (see :doc:`javascript_reference`)
|
||||
|
||||
Creating a new field widget
|
||||
===========================
|
||||
|
||||
This is probably a really common usecase: we want to display some information in
|
||||
a form view in a really specific (maybe business dependent) way. For example,
|
||||
assume that we want to change the text color depending on some business condition.
|
||||
|
||||
This can be done in three steps: creating a new widget, registering it in the
|
||||
field registry, then adding the widget to the field in the form view
|
||||
|
||||
- creating a new widget:
|
||||
This can be done by extending a widget:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var FieldChar = require('web.basic_fields').FieldChar;
|
||||
|
||||
var CustomFieldChar = FieldChar.extend({
|
||||
_renderReadonly: function () {
|
||||
// implement some custom logic here
|
||||
},
|
||||
});
|
||||
|
||||
- registering it in the field registry:
|
||||
The web client needs to know the mapping between a widget name and its
|
||||
actual class. This is done by a registry:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
|
||||
fieldRegistry.add('my-custom-field', CustomFieldChar);
|
||||
|
||||
- adding the widget in the form view
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="somefield" widget="my-custom-field"/>
|
||||
|
||||
Note that only the form, list and kanban views use this field widgets registry.
|
||||
These views are tightly integrated, because the list and kanban views can
|
||||
appear inside a form view).
|
||||
|
||||
Modifying an existing field widget
|
||||
==================================
|
||||
|
||||
Another use case is that we want to modify an existing field widget. For
|
||||
example, the voip addon in odoo need to modify the FieldPhone widget to add the
|
||||
possibility to easily call the given number on voip. This is done by *including*
|
||||
the FieldPhone widget, so there is no need to change any existing form view.
|
||||
|
||||
Field Widgets (instances of (subclass of) AbstractField) are like every other
|
||||
widgets, so they can be monkey patched. This looks like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var basic_fields = require('web.basic_fields');
|
||||
var Phone = basic_fields.FieldPhone;
|
||||
|
||||
Phone.include({
|
||||
events: _.extend({}, Phone.prototype.events, {
|
||||
'click': '_onClick',
|
||||
}),
|
||||
|
||||
_onClick: function (e) {
|
||||
if (this.mode === 'readonly') {
|
||||
e.preventDefault();
|
||||
var phoneNumber = this.value;
|
||||
// call the number on voip...
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Note that there is no need to add the widget to the registry, since it is already
|
||||
registered.
|
||||
|
||||
Modifying a main widget from the interface
|
||||
==========================================
|
||||
|
||||
Another common usecase is the need to customize some elements from the user
|
||||
interface. For example, adding a message in the home menu. The usual process
|
||||
in this case is again to *include* the widget. This is the only way to do it,
|
||||
since there are no registries for those widgets.
|
||||
|
||||
This is usually done with code looking like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var HomeMenu = require('web_enterprise.HomeMenu');
|
||||
|
||||
HomeMenu.include({
|
||||
render: function () {
|
||||
this._super();
|
||||
// do something else here...
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
Creating a new view (from scratch)
|
||||
==================================
|
||||
|
||||
Creating a new view is a more advanced topic. This cheatsheet will only
|
||||
highlight the steps that will probably need to be done (in no particular order):
|
||||
|
||||
- adding a new view type to the field ``type`` of ``ir.ui.view``::
|
||||
|
||||
class View(models.Model):
|
||||
_inherit = 'ir.ui.view'
|
||||
|
||||
type = fields.Selection(selection_add=[('map', "Map")])
|
||||
|
||||
- adding the new view type to the field ``view_mode`` of ``ir.actions.act_window.view``::
|
||||
|
||||
class ActWindowView(models.Model):
|
||||
_inherit = 'ir.actions.act_window.view'
|
||||
|
||||
view_mode = fields.Selection(selection_add=[('map', "Map")])
|
||||
|
||||
|
||||
- creating the four main pieces which makes a view (in JavaScript):
|
||||
we need a view (a subclass of ``AbstractView``, this is the factory), a
|
||||
renderer (from ``AbstractRenderer``), a controller (from ``AbstractController``)
|
||||
and a model (from ``AbstractModel``). I suggest starting by simply
|
||||
extending the superclasses:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var AbstractController = require('web.AbstractController');
|
||||
var AbstractModel = require('web.AbstractModel');
|
||||
var AbstractRenderer = require('web.AbstractRenderer');
|
||||
var AbstractView = require('web.AbstractView');
|
||||
|
||||
var MapController = AbstractController.extend({});
|
||||
var MapRenderer = AbstractRenderer.extend({});
|
||||
var MapModel = AbstractModel.extend({});
|
||||
|
||||
var MapView = AbstractView.extend({
|
||||
config: {
|
||||
Model: MapModel,
|
||||
Controller: MapController,
|
||||
Renderer: MapRenderer,
|
||||
},
|
||||
});
|
||||
|
||||
- adding the view to the registry:
|
||||
As usual, the mapping between a view type and the actual class needs to be
|
||||
updated:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
viewRegistry.add('map', MapView);
|
||||
|
||||
- implementing the four main classes:
|
||||
The ``View`` class needs to parse the ``arch`` field and setup the other
|
||||
three classes. The ``Renderer`` is in charge of representing the data in
|
||||
the user interface, the ``Model`` is supposed to talk to the server, to
|
||||
load data and process it. And the ``Controller`` is there to coordinate,
|
||||
to talk to the web client, ...
|
||||
|
||||
- creating some views in the database:
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="customer_map_view" model="ir.ui.view">
|
||||
<field name="name">customer.map.view</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="arch" type="xml">
|
||||
<map latitude="partner_latitude" longitude="partner_longitude">
|
||||
<field name="name"/>
|
||||
</map>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
Customizing an existing view
|
||||
============================
|
||||
|
||||
Assume we need to create a custom version of a generic view. For example, a
|
||||
kanban view with some extra *ribbon-like* widget on top (to display some
|
||||
specific custom information). In that case, this can be done with 3 steps:
|
||||
extend the kanban view (which also probably mean extending controllers/renderers
|
||||
and/or models), then registering the view in the view registry, and finally,
|
||||
using the view in the kanban arch (a specific example is the helpdesk dashboard).
|
||||
|
||||
- extending a view:
|
||||
Here is what it could look like:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var HelpdeskDashboardRenderer = KanbanRenderer.extend({
|
||||
...
|
||||
});
|
||||
|
||||
var HelpdeskDashboardModel = KanbanModel.extend({
|
||||
...
|
||||
});
|
||||
|
||||
var HelpdeskDashboardController = KanbanController.extend({
|
||||
...
|
||||
});
|
||||
|
||||
var HelpdeskDashboardView = KanbanView.extend({
|
||||
config: _.extend({}, KanbanView.prototype.config, {
|
||||
Model: HelpdeskDashboardModel,
|
||||
Renderer: HelpdeskDashboardRenderer,
|
||||
Controller: HelpdeskDashboardController,
|
||||
}),
|
||||
});
|
||||
|
||||
- adding it to the view registry:
|
||||
as usual, we need to inform the web client of the mapping between the name
|
||||
of the views and the actual class.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var viewRegistry = require('web.view_registry');
|
||||
viewRegistry.add('helpdesk_dashboard', HelpdeskDashboardView);
|
||||
|
||||
- using it in an actual view:
|
||||
we now need to inform the web client that a specific ``ir.ui.view`` needs to
|
||||
use our new class. Note that this is a web client specific concern. From
|
||||
the point of view of the server, we still have a kanban view. The proper
|
||||
way to do this is by using a special attribute ``js_class`` (which will be
|
||||
renamed someday into ``widget``, because this is really not a good name) on
|
||||
the root node of the arch:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<record id="helpdesk_team_view_kanban" model="ir.ui.view" >
|
||||
...
|
||||
<field name="arch" type="xml">
|
||||
<kanban js_class="helpdesk_dashboard">
|
||||
...
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
.. note::
|
||||
|
||||
Note: you can change the way the view interprets the arch structure. However,
|
||||
from the server point of view, this is still a view of the same base type,
|
||||
subjected to the same rules (rng validation, for example). So, your views still
|
||||
need to have a valid arch field.
|
||||
|
||||
Promises and asynchronous code
|
||||
==============================
|
||||
|
||||
For a very good and complete introduction to promises, please read this excellent article https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/async%20%26%20performance/ch3.md
|
||||
|
||||
Creating new Promises
|
||||
---------------------
|
||||
|
||||
- turn a constant into a promise
|
||||
There are 2 static functions on Promise that create a resolved or rejected promise based on a constant:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var p = Promise.resolve({blabla: '1'}); // creates a resolved promise
|
||||
p.then(function (result) {
|
||||
console.log(result); // --> {blabla: '1'};
|
||||
});
|
||||
|
||||
|
||||
var p2 = Promise.reject({error: 'error message'}); // creates a rejected promise
|
||||
p2.catch(function (reason) {
|
||||
console.log(reason); // --> {error: 'error message');
|
||||
});
|
||||
|
||||
|
||||
.. note:: Note that even if the promises are created already resolved or rejected, the `then` or `catch` handlers will still be called asynchronously.
|
||||
|
||||
|
||||
- based on an already asynchronous code
|
||||
Suppose that in a function you must do a rpc, and when it is completed set the result on this.
|
||||
The `this._rpc` is a function that returns a `Promise`.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function callRpc() {
|
||||
var self = this;
|
||||
return this._rpc(...).then(function (result) {
|
||||
self.myValueFromRpc = result;
|
||||
});
|
||||
}
|
||||
|
||||
- for callback based function
|
||||
Suppose that you were using a function `this.close` that takes as parameter a callback that is called when the closing is finished.
|
||||
Now suppose that you are doing that in a method that must send a promise that is resolved when the closing is finished.
|
||||
|
||||
.. code-block:: javascript
|
||||
:linenos:
|
||||
|
||||
function waitForClose() {
|
||||
var self = this;
|
||||
return new Promise (function(resolve, reject) {
|
||||
self.close(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
* line 2: we save the `this` into a variable so that in an inner function, we can access the scope of our component
|
||||
* line 3: we create and return a new promise. The constructor of a promise takes a function as parameter. This function itself has 2 parameters that we called here `resolve` and `reject`
|
||||
- `resolve` is a function that, when called, puts the promise in the resolved state.
|
||||
- `reject` is a function that, when called, puts the promise in the rejected state. We do not use reject here and it can be omitted.
|
||||
* line 4: we are calling the function close on our object. It takes a function as parameter (the callback) and it happens that resolve is already a function, so we can pass it directly. To be clearer, we could have written:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
return new Promise (function (resolve) {
|
||||
self.close(function () {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
- creating a promise generator (calling one promise after the other *in sequence* and waiting for the last one)
|
||||
Suppose that you need to loop over an array, do an operation *in sequence* and resolve a promise when the last operation is done.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function doStuffOnArray(arr) {
|
||||
var done = Promise.resolve();
|
||||
arr.forEach(function (item) {
|
||||
done = done.then(function () {
|
||||
return item.doSomethingAsynchronous();
|
||||
});
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
This way, the promise you return is effectively the last promise.
|
||||
- creating a promise, then resolving it outside the scope of its definition (anti-pattern)
|
||||
.. note:: we do not recommend using this, but sometimes it is useful. Think carefully for alternatives first...
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
...
|
||||
var resolver, rejecter;
|
||||
var prom = new Promise(function (resolve, reject){
|
||||
resolver = resolve;
|
||||
rejecter = reject;
|
||||
});
|
||||
...
|
||||
|
||||
resolver("done"); // will resolve the promise prom with the result "done"
|
||||
rejecter("error"); // will reject the promise prom with the reason "error"
|
||||
|
||||
Waiting for Promises
|
||||
--------------------
|
||||
|
||||
- waiting for a number of Promises
|
||||
if you have multiple promises that all need to be waited, you can convert them into a single promise that will be resolved when all the promises are resolved using Promise.all(arrayOfPromises).
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var prom1 = doSomethingThatReturnsAPromise();
|
||||
var prom2 = Promise.resolve(true);
|
||||
var constant = true;
|
||||
|
||||
var all = Promise.all([prom1, prom2, constant]); // all is a promise
|
||||
// results is an array, the individual results correspond to the index of their
|
||||
// promise as called in Promise.all()
|
||||
all.then(function (results) {
|
||||
var prom1Result = results[0];
|
||||
var prom2Result = results[1];
|
||||
var constantResult = results[2];
|
||||
});
|
||||
return all;
|
||||
|
||||
|
||||
- waiting for a part of a promise chain, but not another part
|
||||
If you have an asynchronous process that you want to wait to do something, but you also want to return to the caller before that something is done.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function returnAsSoonAsAsyncProcessIsDone() {
|
||||
var prom = AsyncProcess();
|
||||
prom.then(function (resultOfAsyncProcess) {
|
||||
return doSomething();
|
||||
});
|
||||
/* returns prom which will only wait for AsyncProcess(),
|
||||
and when it will be resolved, the result will be the one of AsyncProcess */
|
||||
return prom;
|
||||
}
|
||||
|
||||
Error handling
|
||||
--------------
|
||||
|
||||
- in general in promises
|
||||
The general idea is that a promise should not be rejected for control flow, but should only be rejected for errors.
|
||||
When that is the case, you would have multiple resolutions of your promise with, for instance status codes that you would have to check in the `then` handlers and a single `catch` handler at the end of the promise chain.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function a() {
|
||||
x.y(); // <-- this is an error: x is undefined
|
||||
return Promise.resolve(1);
|
||||
}
|
||||
function b() {
|
||||
return Promise.reject(2);
|
||||
}
|
||||
|
||||
a().catch(console.log); // will log the error in a
|
||||
a().then(b).catch(console.log); // will log the error in a, the then is not executed
|
||||
b().catch(console.log); // will log the rejected reason of b (2)
|
||||
Promise.resolve(1)
|
||||
.then(b) // the then is executed, it executes b
|
||||
.then(...) // this then is not executed
|
||||
.catch(console.log); // will log the rejected reason of b (2)
|
||||
|
||||
|
||||
|
||||
- in Odoo specifically
|
||||
In Odoo, it happens that we use promise rejection for control flow, like in mutexes and other concurrency primitives defined in module `web.concurrency`
|
||||
We also want to execute the catch for *business* reasons, but not when there is a coding error in the definition of the promise or of the handlers.
|
||||
For this, we have introduced the concept of `guardedCatch`. It is called like `catch` but not when the rejected reason is an error
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
function blabla() {
|
||||
if (someCondition) {
|
||||
return Promise.reject("someCondition is truthy");
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
var promise = blabla();
|
||||
promise.then(function (result) { console.log("everything went fine"); })
|
||||
// this will be called if blabla returns a rejected promise, but not if it has an error
|
||||
promise.guardedCatch(function (reason) { console.log(reason); });
|
||||
|
||||
// ...
|
||||
|
||||
var anotherPromise =
|
||||
blabla().then(function () { console.log("everything went fine"); })
|
||||
// this will be called if blabla returns a rejected promise,
|
||||
// but not if it has an error
|
||||
.guardedCatch(console.log);
|
||||
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var promiseWithError = Promise.resolve().then(function () {
|
||||
x.y(); // <-- this is an error: x is undefined
|
||||
});
|
||||
promiseWithError.guardedCatch(function (reason) {console.log(reason);}); // will not be called
|
||||
promiseWithError.catch(function (reason) {console.log(reason);}); // will be called
|
||||
|
||||
|
||||
|
||||
Testing asynchronous code
|
||||
-------------------------
|
||||
|
||||
- using promises in tests
|
||||
In the tests code, we support the latest version of Javascript, including primitives like `async` and `await`. This makes using and waiting for promises very easy.
|
||||
Most helper methods also return a promise (either by being marked `async` or by returning a promise directly.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var testUtils = require('web.test_utils');
|
||||
QUnit.test("My test", async function (assert) {
|
||||
// making the function async has 2 advantages:
|
||||
// 1) it always returns a promise so you don't need to define `var done = assert.async()`
|
||||
// 2) it allows you to use the `await`
|
||||
assert.expect(1);
|
||||
|
||||
var form = await testUtils.createView({ ... });
|
||||
await testUtils.form.clickEdit(form);
|
||||
await testUtils.form.click('jquery selector');
|
||||
assert.containsOnce('jquery selector');
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test("My test - no async - no done", function (assert) {
|
||||
// this function is not async, but it returns a promise.
|
||||
// QUnit will wait for for this promise to be resolved.
|
||||
assert.expect(1);
|
||||
|
||||
return testUtils.createView({ ... }).then(function (form) {
|
||||
return testUtils.form.clickEdit(form).then(function () {
|
||||
return testUtils.form.click('jquery selector').then(function () {
|
||||
assert.containsOnce('jquery selector');
|
||||
form.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
QUnit.test("My test - no async", function (assert) {
|
||||
// this function is not async and does not return a promise.
|
||||
// we have to use the done function to signal QUnit that the test is async and will be finished inside an async callback
|
||||
assert.expect(1);
|
||||
var done = assert.async();
|
||||
|
||||
testUtils.createView({ ... }).then(function (form) {
|
||||
testUtils.form.clickEdit(form).then(function () {
|
||||
testUtils.form.click('jquery selector').then(function () {
|
||||
assert.containsOnce('jquery selector');
|
||||
form.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
as you can see, the nicer form is to use `async/await` as it is clearer and shorter to write.
|
||||
|
|
@@ -13,6 +13,14 @@ tools to facilitate database interactions.
|
|||
Before moving forward in the exercise, make sure the ``estate`` module is installed, i.e. it
|
||||
must appear as 'Installed' in the Apps list.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use mutable global variables.
|
||||
|
||||
A single Odoo instance can run several databases in parallel within the same python process.
|
||||
Distinct modules might be installed on each of these databases, therefore we cannot rely on
|
||||
global variables that would be updated depending on installed modules.
|
||||
|
||||
Object-Relational Mapping
|
||||
=========================
|
||||
|
||||
|
|
|
|||
|
|
@@ -8,6 +8,9 @@ administration/upgrade/online_hosting.rst administration/upgrade/odoo_online.rst
|
|||
|
||||
# applications/accounting
|
||||
|
||||
applications/finance/accounting/bank/feeds/bank_statements.rst content/applications/finance/accounting/bank/bank_statements.rst # feeds/* --> /*
|
||||
applications/finance/accounting/bank/feeds/ponto.rst applications/finance/accounting/bank/bank_synchronization/ponto.rst # feeds/* --> bank_synchronization/*
|
||||
applications/finance/accounting/bank/feeds/saltedge.rst applications/finance/accounting/bank/bank_synchronization/saltedge.rst # feeds/* --> bank_synchronization/*
|
||||
applications/finance/accounting/bank/misc/interbank.rst applications/finance/accounting/bank/interbank.rst # misc/interbank --> interbank
|
||||
applications/finance/accounting/fiscal_localizations.rst applications/finance/fiscal_localizations.rst # /accounting/ --> /
|
||||
applications/finance/accounting/fiscal_localizations/localizations/argentina.rst applications/finance/fiscal_localizations/argentina.rst # /accounting/fiscal_localizations/localizations/* --> /fiscal_localizations/*
|
||||
|
|
|
|||
|
|
@@ -46,4 +46,4 @@ developer/howtos/discover_js_framework/07_testing.rst developer/tutorials/discov
|
|||
# developer/reference/frontend
|
||||
|
||||
developer/reference/frontend/icons_library.rst contributing/development/ui/icons.rst # Odoo UI icons -> UI Icons
|
||||
|
||||
developer/reference/frontend/javascript_cheatsheet.rst developer/howtos/javascript_create_field.rst # refactor JavaScript cheatsheet into howtos
|
||||
|
|
|
|||