Very often businesses that have a portfolio of projects within different industries need the option of categorizing the projects. However the core version of Odoo does not support such functionality, even though in some cases can be used the Tags option, however in that case it is difficult to group the project by category since the Tag field is many2many​.

The “Project Type” module makes this functionality possible. It is a very simple module that introduces the Type​ field in the Project and Task models. Each Project or Task can belong only to one Project Type. Once the projects are categorized they can be grouped using the Group By: Type​ option.

 Group By Project Type

The different project types can be set up in the Project > Configuration > Project Types​. Each Project Type can have multiple Subtypes. The available fields for each Project Type are:

  • Name
  • Code
  • Parent Type
  • Description
  • Can be applied for Projects
  • Can be applied for Tasks

The last two listed options are used to configure the Project Type where to be shown in Projects and/or Tasks. In the tab "Subtypes" can be found all the related Subtypes. Additionally, can be created new directly from there.

 Project Type Form View

The Project Manager group has full access rights to the Project Types, while every user in the system can see them in the projects and tasks.

One possible improvement for the module can be when a certain type is selected in a project, on creation of a related task the same Type to be selected as the default value, with option to be edited if needed. Because from a business perspective, it can be expected that the project's related tasks would have the same Project Type. To provide more flexibility this option can be customizable per Project Type.

The module is maintained by Odoo Community Association (OCA) and the latest available version is 17.0.1.0.0.

Functional
Modules Review

In multi-company mode, there are cases when one field would need to store different values for each company.

An example of such fields are some of the accounting settings per product, such as Income Account and Expense Account.

In order for a field to be able to store different values per company it is required in the field definition to be set the attribute company_dependent​ to True​.

Part of the definition of the standard_price​ field which is another example in Odoo of such a field is shown below:

standard_price = fields.Float(
    string='Cost',
company_dependent=True, )

While from a user perspective, this functionality depends only on one parameter, the technical side of it is a bit more complex. These fields do not store their values in the related model’s table. Instead, they are using the ir.property​ table for that. When the value is set in the field, the backend will try to find an existing ir.property​ record that matches the model’s record, field and company. If there is no one it will create a new record with the following information:

  • name​: The name of the field
  • fields_id​: The ID of the field that holds the value
  • res_id​: The ID of the related model’s record
  • company_id​: The ID of the company
  • value_​: The value will be stored in one of the available value fields, depending on the type of the field. For example, the standard_price​ field's value will be stored in the value_float​ column.

Company-dependent fields are searchable and when a value is requested for them the system queries the ir.property​  table.

The list of all company-dependent values can be found at Settings > Technical > Parameters > Company Properties​, developer mode needs to be activated in order to see the Technical menu.

Technical
Tutorials

In certain business scenarios, it is required to maintain audit logs on actions affecting the data in the system such as create​, read​, update​, and delete​ (CRUD).

Although the native version of Odoo does not support such functionality yet, there is a module maintained by OCA that covers these types of operations.

In this review, we will go through the options that the “Audit Log” module offers.

The latest available version of the module at the moment of writing is 17.0.1.0.0. Once the addon is installed in the system the related sub-menus can be found in the “Technical” menu.

There are five available sub-menus:

  • Rules
  • Logs
  • Log Lines
  • User Sessions
  • HTTPS Requests

Only the content in the first sub-menu, “Rules”, is customizable. While the rest of them are read-only list views that contain the different types of logs. The type of logs that each of them contains is closely related to their names.

For the module to start logging data, it is required to create a rule. Each rule is defined per model, which means that every model in the system will need a separate rule in order to log data. Except for the model name that needs to be selected, a rule has the following additional fields:

  • Name: The name of the rule, for example, if the logging is for sale.order​ model the name can be "Sales Order"
  • Type: Two available options “Full Log” which compares the data before and after the operation and logs both states, in this case, data from computed fields is logged as well, but because of the complexity of the operation, it can be a bit slower. On the other side the second option “Fast Log” logs only the new values for the changes made during the create​ and write​ operations, since this is much simpler it is also faster.
  • Capture Record: If selected, when the record is deleted the full info of the record will be added to the log, otherwise a simple log without any details for the record will be added. This option is possible only if the type “Full Log” is selected.
  • Action: This field is visible in debug mode, but even it is possible to edit it, on “Subscribe” action will be overridden by the “View Logs” action created for the rule.
  • Users to Exclude: Can be specified multiple users that need to be excluded from the logs
  • Fields to Exclude: Can be specified list of fields that need to be excluded from the logs
  • Log Reads: If selected, every time a user sees the model’s data a log will be created, by default is not selected
  • Log Writes: If selected, every time a user updates a record related to the model a log will be created
  • Log Deletes: If selected, when a user deletes a record related to the model a log will be created
  • Log Creates: If selected, when a user creates a new record related to the model a log will be created

The last three options are by default selected on the creation of a new rule.

After creating the rule, the next step is subscribing to it by using the "Subscribe" button. Once that is done, the system will start logging data according to the rule configuration.
The rule is editable only in the “Draft” state so in case if anything needs to be changed needs to be moved back to “Draft” using the “Unsubscribe” button.

Audit Log Rule Form View

From a security perspective, the module has two groups:

  • Auditlog User
  • Auditlog Manager

The first one allows users to see the logs from the record using the action “View Log”, which can be found in the record’s action menu. The users with this group have only read access to the logs.

The “Auditlog Manager” group adds full access rights to the related audit log models, however, a user can have access to the Audit log menus only if they have the “Administration Settings” group.

The logs are by default kept in the system for 6 months, but it is possible to change that in the "Auto-vacuum audit logs” scheduled action that can be found in Settings > Technical > Automation > Scheduled Actions​.

 Audit Log Auto-vacuum

The number 180​​ in the method model.autovacum(180)​​ is number of days that the logs need to be kept, by updating this number the period that the logs are kept in the system will be changed. 
Also, as a second parameter to this method, it is possible to add the number of logs that can be deleted per run, for each of the related models where the logs are stored. This parameter can be helpful in cases when there are too many logs that need to be removed at once. Because of this, Odoo can fail the task and not remove any of them. As a result of that the system can accumulate a large amount of logs, which can increase the size of the database and also slow down the system.
Therefore our recommendation is to ensure that the scheduled action is successfully running and it is set to remove a limited number of logs per run in shorter intervals. Finally should be tested manually if the action is working as expected once the settings are completed.
The "Auto-vacuum audit logs” is not enabled by default, therefore once the module is installed it needs to be enabled.

This module is a great addition to systems that require such functionality. Also, it is regularly maintained by the Odoo Community Association (OCA) which can assure that from a technical perspective is well build module with appropriate quality assurance checks. 

Functional
Modules Review


In some cases, there is a need to override the create​ method in Odoo and add additional logic to it. 

When overriding this method, the custom logic is triggered on each iteration of creating a new object.

 This means whenever a new object is created the extra code will be evaluated and applied.

In case when the additional logic is closely related to the object's data, this approach can be all right, since the codebase looks simpler and more clear.

However, in situations where some of the custom logic is repeating, especially when using database data, the logic will be executed n-times for n-objects, without any need for that.

In such cases should be used the decorator model_create_multi​. Adding this decorator to the create​ method allows the repeating code to be executed only once and applied to each object using a for​ loop.

It is important to mention that when using the model_create_multi​ decorator, the method receives a list of values while returns a list of newly created objects.

A very simple case to understand the need for this decorator is shown below:


@api.model_create_multi
def create(self, vals_list):
    round_precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
    for vals in vals_list:
		    vals["quantity"] = float_round(vals["quantity"], precision_digits=round_precision)
    return super().create(vals_list)
        

Without using the decorator the logic related to getting the value for round_precision​ will be executed every time a new object is created, which means for a batch of n-objects will be executed n-times.

But in our example, it will be executed only once and applied to all object values using the for​ loop.

Technical
Tips

When using many2many_tags​ widget, it is possible to define a background color for each of the tag values. For that to work the following steps need to be done:

  • The related model needs to have an Integer type field where the color index will be stored, usually named color​.
  • The color​ field needs to be added in the view using the widget color_picker
  • In the definition of the relational field in the XML view, when using the widget many2many_tags​, it needs to be set the option color_field​ with value the name of the color field previously defined.​
  • The last step is optional, by default when a user selects a value, can edit the background color of the value's tag, however, it is possible to make the color not editable at this stage, by using the option no_edit_color

Let’s review the steps above using as an example the relational many2many​ field tag_ids in the sale.order​ model related to the relational model crm.tag​​.

The model crm.tag​ there is a field color​ defined in the model definition as the following:

color = fields.Integer("Color")

The definition of the field in the XML view, using the widget color_picker​ looks like the following:

<field name="color" required="True" widget="color_picker"/>

Having this definition of the field makes it possible for every sales tag value defined in the system to be selected a color which later will be used as a background color for the tag.

Once the field is set up in the relational model the next step is when defining the related field tag_ids​ in the XML views to use the widget many2many_tags​ and add as an option the color_field​.

<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>

This will add a background color for the selected tag values in the sales order. Also users will be able to change the color of the tags during the selection process. A possible issue with this change is that the color will be applied to all sales orders where the same value is used. In case the admin wants to keep the colors same as initially set, it is possible to disable the edit of the color at the moment of selection by using the option no_edit_color​ set to True​.

<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_edit_color': true}"/>



Technical
Tutorials


In Odoo it is really convenient to change the price of multiple products using the pricelist option. In this post we will go through the steps that are needed to make that happen.

At first would be need to enable the option “Advanced Price Rules” in the Sales configuration page, which can be found in Sales >> Configuration >> Settings

Once that is done, open the pricelist page from Sales >> Products >> Pricelists​ and select an existing pricelist or create a new one.

The pricelist can be specific per company, country group or website. In our case we will assume that the pricelist is global for all products without any restriction.

In the “Price Rules” tab click on the button “Add a line” which will open a wizard as the one on the image:


Select the Computation​ method “DIscount” and in the Discount​ field set the value by which the price should be changed.

The important thing when setting this field is to take into consideration that if the price need to increase the value should be negative or in case of decrease it should be positive.

For example if the price need to increase for all products by 10% than the value in this field should be -10. In case the price for all products need to decrease by 10% the value should be 10.

For more complex pricing rules need to be used the option “Formula”, which still have the field Discount​ but additionally can be setup few more variables such as:

  • Base On​:  The price field based on which the calculation will be done (Sales Price or Cost Price)
  • Extra Fee​: Additional fixed fee added on the price calculated with the discount
  • Rounding Method​: Decimal number representing the minimum non-zero value at the desired precision.
  • Margins​: Minimum margin over the base price

An image of the wizard with “Formula” options is shown in the image below:

 

The created pricelist need to be used in the Order for the new prices to apply.

Functional
Tutorials


When using a date field in the email templates it is important to use it with formatting function for the value to be represented in the format set in the language settings.

The function for formatting a date is format_date​ and by default is using the format specified in the language settings. However, it is possible to specify a custom one (using LDML format), if needed.

An example of adding a date in an email template is shown in the example below:

<p>Date Created: <t t-out="format_date(object.date_order) or ''"></t></p>

In case it is needed to display datetime​ or time​ values the functions available for them are: format_datetime​ and format_time​.


Functional Technical
Tips

In everyday business cases, there are a lot of situations where a particular field needs to have different attributes or attribute values based on different access groups.

This type of case is possible to handle in Odoo, however, there are two different solutions based on which version of Odoo is used.

In Odoo 15 and older versions, this can be achieved by adding a more generic definition of the field in the main view, while the one specific for certain groups is added in a new inherited view, which is limited only to the specific access group.

For example, a very simple case can be if the field phone​ in the Leads view by default needs to be readonly​, but only for group Sales Administrator is editable:

<record id="crm_lead_view_form" model="https://ir.ui.view">
<field name="name">https://crm.lead.form</field>
<field name="model">crm.lead</field>
<field name="arch" type="xml">
<form>
​​...
<field name='phone' readonly="True"/>
</form>
</field>
</record>

<record id="crm_lead_view_phone_form" model="https://ir.ui.view">
<field name="model">crm.lead</field>
<field name="inherit_id" ref="crm.crm_lead_view_form"/>
<field name="priority">99</field>
<field name="groups_id" eval="[(6, 0, [ref('sales_team.group_sale_manager')])]"/>
<field name="arch" type="xml">
<form>
<field name='phone' position="attributes">
<attribute name="readonly">False</attribute>
</field>
</form>
</field>
</record>

Starting from Odoo 16 this type of inherited view with an attached group to it is no longer supported, and will show a server error if used. The new approach now is by defining the same field twice in the view with different access group rules.

The same example with the phone​ field in Odoo 16 would look like the following:

<record id="crm_lead_view_form" model="ir.ui.view">
    <field name="name">crm.lead.form</field>
    <field name="model">crm.lead</field>
    <field name="arch" type="xml">
        <form>
            ...
            <field name='phone' groups="!sales_team.group_sale_manager" readonly="True"/>
            <field name='phone' groups="sales_team.group_sale_manager" readonly="False"/>
        </form>
    </field>
</record>

To avoid any confusion, this example does not intend to show the best solution on how to make a field editable only for certain access groups, but it is just used to demonstrate how to define the same field with different attributes for different groups.

Technical
Tips

The module Partner Stock Risk extends the Account Financial Risk module functionality by adding an extra financial risk check on validation of inventory transfers with "Customer" as a destination location.

Once the module is installed, the extra check is enabled without any additional settings. In order to disable the check the module needs to be uninstalled.

The stock financial risk validation is based only on the General Limits, which means at least one General Limit financial risk needs to be enabled for the check to work.

Specific Limits are not taken into consideration for this check, which is expected, knowing that there is no specific option for Stock in the financial risk Specific Limits section.

The module is a great addition to business processes where additional financial risk validation is needed before dispatching the products. For example, if the invoice is generated after the delivery this module will help in preventing products from being shipped in the first place for customers with financial risk issues.

The module is maintained by the Odoo Community Association (OCA).

Functional
Modules Review