Odoo FTP Product Sync for Vendor Inventory Updates

Supplier Product and Inventory Sync in Odoo

Suppliers frequently provide product catalogues, pricing updates, and inventory availability through FTP or SFTP servers. For businesses managing multiple vendors, manually downloading files and updating products in Odoo can quickly become time-consuming, error-prone, and difficult to scale.

The challenge becomes even more complex when each supplier uses different file formats, column names, and product identifiers. Building a separate import process for every supplier is rarely sustainable.

In this article, we'll explore how to build a dynamic FTP synchronisation system in Odoo that automatically imports CSV/XLSX files, maps columns to Odoo fields, updates product and vendor information, synchronises inventory levels, and runs on a fully automated schedule.

This implementation supports:

  • FTP connection validation
  • Dynamic column mapping
  • CSV and XLSX file processing
  • Product identification using configurable fields
  • Vendor stock synchronisation
  • Automated inventory updates
  • Scheduled cron synchronisation
  • Mismatch logging
  • Secure FTP credential handling

Business Requirement

Many suppliers share product inventory files through FTP/SFTP servers.

Example vendor feed:

Vendor SKU Product Name Stock Price
ABC001 Wireless Mouse 50 15.99

The challenge:

  • Every supplier uses different column names
  • Different file formats
  • Different product identifiers
  • Different inventory structures

Instead of hardcoding imports for every vendor, we create a dynamic mapping-based FTP engine.

Solution Overview

Before diving into the implementation details, let's look at how the synchronisation workflow operates.

The solution is designed to be vendor-agnostic, meaning the same synchronisation engine can support multiple suppliers regardless of their file format or column structure.

The workflow consists of:

  • Connecting to the supplier's FTP or SFTP server
  • Downloading the latest inventory or product feed
  • Detecting and processing CSV or Excel files
  • Mapping supplier columns to Odoo fields dynamically
  • Identifying products using configurable search keys
  • Updating supplier-specific information
  • Synchronising inventory quantities
  • Recording mismatches and validation errors
  • Running automatically through scheduled actions

FTP Configuration Model

The main model stores:

  • FTP credentials
  • File configuration
  • Vendor information
  • Import settings
  • Inventory mappings
python
class FTPSyncing(models.Model):
   _name = 'ftp.syncing'
   name = fields.Char('Name')
   partner_id = fields.Many2one(
       'res.partner',
       string="Supplier"
   )
   ftp_url = fields.Char('URL')
   ftp_port = fields.Char(
       'Port',
       default='21'
   )
   ftp_username = fields.Char('Username')
   ftp_password = fields.Char('Password')
   ftp_file_name = fields.Char(
       string="File Name"
   )
   directory_name = fields.Char(
       string="Directory Name"
   )
  

This allows each supplier to maintain separate FTP configurations.

Securing FTP Password Fields

In the form view:

<field name="ftp_password"
password="True"/>

This masks the password value in the Odoo UI.

Without this attribute, credentials remain visible to users.

Testing FTP Connections

Before running imports, the system validates FTP connectivity.

python
def action_check_ftp_connection(self):
   f = ftplib.FTP()
   f.connect(
       self.ftp_url,
       int(self.ftp_port),
       10
   )
   f.login(
       self.ftp_username,
       self.ftp_password
   )
  

This helps users:

  • Verify credentials
  • Validate server access
  • Detect connection issues early

Building a Dynamic Column Mapping System

One of the most powerful features is dynamic column mapping.

Each supplier can use different file structures:

Supplier A:

SKU: ABC
Qty: 10

Supplier B:

VendorCode: ABC
Stock: 10

Instead of hardcoding field names, we map columns dynamically.

Column Mapping Model

python
class FTPSyncingColumnMapping(models.Model):
   _name = 'ftp.syncing.column.mapping'
   excel_column_name = fields.Char(
       string="Excel Column Header"
   )
   odoo_field_id = fields.Many2one(
       'ir.model.fields',
       string="Odoo Field"
   )
   is_search_key = fields.Boolean(
       string="Use for Record Search"
   )
  

This allows:

  • Mapping CSV columns to Odoo fields
  • Configurable product search keys
  • Reusable synchronisation logic

Product Identification Logic

The system dynamically searches products using:

  • Internal Reference
  • Barcode
  • Vendor Product Code
  • Product Name

Example:

python
product_id = product_obj.search([
   (field_name, '=', excel_value)
], limit=1)
  

It also supports vendor-based matching through product.supplierinfo.

Handling Vendor Product Codes

Vendor product codes are stored in product.supplierinfo.

Example:

python
supplier_id = self.env[
   'product.supplierinfo'
].search([
   ('product_code', '=', excel_value),
   ('partner_id', '=', ftp_record.partner_id.id)
], limit=1)
  

This is extremely useful when suppliers use their own SKU formats.

Supporting CSV File Imports

The system downloads and processes CSV files automatically.

python
reader = csv.DictReader(
   open(file.name)
)
for data in reader:
   self._process_ftp_row_data(
       data,
       ftp_record,
       mismatch_log_id
   )
  

Supporting XLSX File Imports

The system also supports Excel files using openpyxl.

workbook = load_workbook(file.name)

sheet = workbook.active

Rows are converted dynamically into dictionaries:

data = dict(zip(headers, row))

This allows one import engine to support:

  • CSV
  • XLSX
  • XLS

Preparing Dynamic Mapping Values

The import engine automatically prepares values for:

  • product.template
  • product.supplierinfo
python
if model_name == 'product.template':
   product_vals[field_name] = excel_value
elif model_name == 'product.supplierinfo':
   supplier_vals[field_name] = excel_value
  

This makes the synchronisation engine highly reusable.

Updating Vendor Information

Vendor records are automatically updated or created.

python
supplier_vals.update({
   'partner_id': ftp_record.partner_id.id,
   'product_id': product_id.product_variant_ids[0].id,
   'include_in_vendor_stock': True
})
  

If the supplier already exists:

supplier_id.write(supplier_vals)

Otherwise:

self.env['product.supplierinfo'].create(
supplier_vals
)

Updating Inventory Automatically

The system updates stock quantities using stock.quant.

python
quant_id = self.env[
   'stock.quant'
].search([
   ('product_id', '=', product_id.product_variant_ids[0].id),
   ('location_id', '=', location_id.id)
], limit=1)
  

If no quant exists:

quant_id = self.env[
'stock.quant'
].create(vals)

Then the inventory is applied:

quant_id.action_apply_inventory()

Why Use stock.quant?

Using stock quant ensures:

  • Proper inventory adjustment
  • Accurate stock valuation
  • Inventory traceability
  • Compatibility with Odoo inventory workflows

Mismatch Logging System

The system logs:

  • Missing products
  • Invalid mappings
  • Import failures
  • Inventory updates

Example:

self.create_mismatch_log_line(
"Product not found : %s" % excel_value,
mismatch_log_id
)

This helps administrators monitor import quality.

Automatic Scheduled Synchronisation

Synchronisation runs automatically using cron jobs.

python
cron_obj.create({
   'name': name_on_cron,
   'model_id': self.env.ref(
       'numla_ftp_syncing.model_ftp_syncing'
   ).id,
   'state': 'code',
   'code': 'model.create_cron_ftp_syncing({0})'.format(self.id),
})
  

This allows:

  • Hourly imports
  • Daily synchronization
  • Fully automated workflows

Processing Workflow

The complete workflow:

  1. Connect to the FTP server
  2. Download file
  3. Detect file type
  4. Parse CSV/XLSX
  5. Identify products
  6. Update vendor information
  7. Update product fields
  8. Update inventory
  9. Log mismatches
  10. Complete synchronization

Handling Unsupported File Types

The system validates supported formats:

if file_name.lower().endswith('.csv'):
elif file_name.lower().endswith('.xlsx')

Otherwise:

_logger.error(
_("Unsupported file format")
)

Preventing Invalid Search Key Configurations

Only one search key is allowed per FTP configuration.

@api.constrains(
'is_search_key',
'ftp_syncing_id'
)

This avoids ambiguous product searches.

Real-World Use Cases

Vendor Inventory Sync

Automatically update supplier stock every hour.

Marketplace Feed Imports

Import product catalogues from external marketplaces.

Automated Price Updates

Synchronise supplier pricing automatically.

Multi-Vendor Product Management

Handle multiple suppliers with different file formats.

Best Practices

1. Always Validate FTP Connections

Use connection tests before enabling cron jobs.

2. Use Dynamic Column Mapping

Avoid hardcoded import logic.

3. Add Detailed Logs

Logs simplify debugging and monitoring.

4. Support Multiple File Formats

CSV and XLSX support increases compatibility.

5. Secure FTP Credentials

Always use masked password fields.

Final Thoughts

This FTP synchronisation engine creates a flexible and scalable integration framework inside Odoo.

By combining:

  • Dynamic field mapping
  • Automated inventory updates
  • Vendor synchronization
  • Cron automation
  • FTP connectivity

You can build enterprise-grade supplier integration workflows with minimal manual effort.

This approach is highly reusable and can support multiple vendors without requiring separate import logic for each supplier.

Found this article useful?

Explore more development guides and solutions from our team.

Check out more posts
Sign in to leave a comment
Multilingual Product Descriptions in Odoo for ChannelEngine