Automating Competitive Price Tracking in Odoo

Simplify Competitor Price Tracking

Keeping product prices competitive becomes increasingly difficult when competitors update their prices daily, and teams rely on manual exports or spreadsheets to keep up. As product catalogues grow, these processes quickly break down.

To solve this, we did a Tweakers integration in Odoo 16 that automatically downloads and syncs product prices directly from the Tweakers CSV feed, using product EANs and SKUs as matching keys.

This integration keeps your product prices accurate and competitive, without manual uploads or external scripts.

Why Use Tweakers for Market Pricing?

Tweakers.net is one of Europe’s most trusted tech marketplaces, providing live price comparisons from hundreds of online retailers.

For resellers of electronics, IT equipment, or hardware components, monitoring Tweakers’ market prices is crucial to staying competitive.

What This Integration Solves?

This integration addresses common pricing challenges in Odoo by:

  • Removing the need for manual CSV uploads and price updates
  • Keeping competitor pricing data synchronised automatically
  • Supporting multiple SKUs and EANs for accurate product matching
  • Enabling quick comparison between internal sale prices and market rates

Technical Architecture Overview

At a high level, the integration follows a simple and reliable flow:

  • Store Tweakers API credentials in Odoo Settings
  • Download the Tweakers CSV feed once per day
  • Parse the file to extract prices by EANs and SKUs
  • Update a dedicated Tweakers price field (tweakers_price) automatically
  • Allow teams to compare Odoo’s sale price with Tweaker’s market price
  • Automate everything with an Odoo cron job

Step 1: Add Tweakers’ Configuration to Odoo Settings

The first step is making the integration configurable.


from odoo import models, fields, api
class ResConfigSettings(models.TransientModel):
    _inherit = 'res.config.settings'
    enable_tweakers = fields.Boolean("Tweakers Integration")
    tweakers_url = fields.Char(string='API URL')
    tweakers_pass = fields.Char(string='API Key')
    def set_values(self):
        super(ResConfigSettings, self).set_values()
        params = self.env['ir.config_parameter'].sudo()
        params.set_param('enable_tweakers', self.enable_tweakers)
        params.set_param('tweakers_url', self.tweakers_url)
        params.set_param('tweakers_pass', self.tweakers_pass)
    @api.model
    def get_values(self):
        res = super(ResConfigSettings, self).get_values()
        params = self.env['ir.config_parameter'].sudo()
        res.update(
            enable_tweakers=params.get_param('enable_tweakers'),
            tweakers_url=params.get_param('tweakers_url'),
            tweakers_pass=params.get_param('tweakers_pass'),
        )
        return res
  
Settings Views

<odoo>
  <data>
    <record model="ir.ui.view" id="res_config_settings_view_form">
      <field name="name">res.config.settings.view.form.inherit.tweakers</field>
      <field name="model">res.config.settings</field>
      <field name="priority" eval="95"/>
      <field name="inherit_id" ref="sale.res_config_settings_view_form"/>
      <field name="arch" type="xml">
        <xpath expr="//div[@id='variant_options']" position="before">
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane">
              <field name="enable_tweakers"/>
            </div>
            <div class="o_setting_right_pane">
              <label for="enable_tweakers"/>
              <div class="text-muted"> Export Product info From Tweakers API </div>
              <div class="content-group" attrs="{'invisible': [('enable_tweakers', '=', False)]}">
                <div class="mt16 row">
                  <label for="tweakers_url" class="col-6 o_light_label"/>
                  <field name="tweakers_url"/>
                  <label for="tweakers_pass" class="col-6 o_light_label"/>
                  <field name="tweakers_pass" password="True"/>
                </div>
              </div>
            </div>
          </div>
        </xpath>
      </field>
    </record>
  </data>
</odoo>
  

This provides an easy interface to enable or disable the integration, set the Tweaker's feed URL, and configure your API key.

Step 2: Download and Store the Tweakers CSV Feed

Tweakers provides a CSV file containing all product prices, SKUs, and EANs. Instead of processing it in memory, the file is stored in the module’s static directory.


def create_product_list_from_tweakers(self):
    """
        Download the latest product list from the Tweakers API
        and save it into the module's static CSV folder.
        """
    params = self.env['ir.config_parameter'].sudo()
    if not params.get_param('enable_tweakers'):
        return False
    url = params.get_param('tweakers_url')
    api_key = params.get_param('tweakers_pass')
    csv_folder = os.path.join(get_module_path('module_name'), 'static/src/csv')
    csv_file_path = os.path.join(csv_folder, 'productlist.csv')
    os.makedirs(csv_folder, exist_ok=True)
    if os.path.exists(csv_file_path):
        os.remove(csv_file_path)
    response = requests.get(url, auth=(api_key, ""))
    if response.status_code == 200:
        with open(csv_file_path, "wb") as f:
            f.write(response.content)
            _logger.info("Tweakers CSV downloaded successfully!")
            return True
    else:
        _logger.warning(f"Failed to fetch CSV. Status code: {response.status_code}")
return False
  

Step 3: Parse EANs and SKUs from the CSV

Next, we convert the CSV into a price dictionary so that each SKU or EAN maps to its Tweaker's market price.


def build_price_dict_from_csv(self, csv_path):
   """
        Build a dictionary of all SKUs and EANs in the Tweakers CSV.
        """
price_dict = {}
with open(csv_path, newline="", encoding="utf-8") as csvfile:
    reader = csv.DictReader(csvfile, delimiter=";")
    for row in reader:
        price_str = row.get("minprijs")
        if not price_str or price_str.strip() == "":
            continue
        price = float(price_str)
        if row.get("sku_codes (separated with a ,)"):
            sku_list = [s.strip() for s in row["sku_codes (separated with a ,)"].split(",") if s.strip()]
            for sku in sku_list:
                price_dict[sku] = price
        if row.get("ean_codes (separated with a ,)"):
            ean_list = [e.strip() for e in row["ean_codes (separated with a ,)"].split(",") if e.strip()]
            for ean in ean_list:
                price_dict[ean] = price
return price_dict
  

​This ensures multiple identifiers per product are supported — ideal for suppliers with variants.

Step 4: Schedule Daily Price Synchronisation

​The final step is automating everything using an Odoo cron job. Each run downloads the latest feed, parses pricing data, and updates products automatically.


def cron_fetch_tweakers_prices(self):
    """
        Scheduled job (cron) to update product prices from Tweakers.
        ""
    if self.create_product_list_from_tweakers():
        csv_path = os.path.join(get_module_path('module_name'), 'static/src/csv/productlist.csv')
        price_dict = self.build_price_dict_from_csv(csv_path)
        products = self.search([("ean", "!=", False)])
        for product in products:
            low_price = None
            ean_list = [product.ean]
            if product.additional_ean:
                ean_list.append(product.additional_ean)
            for ean in ean_list:
                low_price = price_dict.get(ean)
                if low_price:
                    break
            if not low_price and product.part_number:
                low_price = price_dict.get(product.part_number)
            if low_price:
                ex_vat_price = low_price / (1 + 21 / 100)
                product.tweakers_price = ex_vat_price
                _logger.info(f"[Tweakers] Updated {product.name} with price - {ex_vat_price}")
    return True
  
XML Cron Definition

<record id="ir_cron_fetch_tweakers_prices" model="ir.cron">
    <field name="name">Fetch Product Price (Tweakers)</field>
    <field name="active">True</field>
    <field name="user_id" ref="base.user_root"/>
    <field name="interval_number">1</field>
    <field name="interval_type">days</field>
    <field name="numbercall">-1</field>
    <field name="model_id" ref="product.model_product_template"/>
    <field name="state">code</field>
    <field name="code">model.cron_fetch_tweakers_prices()</field>
</record>
  

Business Impact

​After deploying this integration:

  • Product prices are synchronised daily without manual effort
  • Pricing teams can instantly compare internal prices with market data
  • Errors caused by outdated CSV imports are eliminated
  • Repricing decisions become faster and more accurate

Most importantly, pricing intelligence becomes part of the ERP instead of an external process.

Key Takeaways

​This Tweakers integration turns Odoo 16 into a market-aware pricing system. By combining structured competitor data with automated workflows, businesses can move from reactive price updates to proactive pricing strategy.

Automation of competitor pricing is essential for any team aiming to maintain competitive margins and stay relevant in fast-moving markets.

Found this article useful?

Explore more development guides and solutions from our team.

Check out more posts
Sign in to leave a comment
Smarter Odoo Fields with Compute and Inverse Methods