Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e974f3c
[18.0][ADD] zort_connector
TheerayutEncoder Aug 15, 2025
3861bb1
[IMP] auto update SO status
TheerayutEncoder Aug 15, 2025
aaa65f9
[IMP] pre-commit run -a
TheerayutEncoder Aug 15, 2025
ae0157b
[IMP] auto done picking and create draft inv
TheerayutEncoder Aug 17, 2025
f02deaa
[IMP] classify customer and keep API response in zort info
TheerayutEncoder Aug 17, 2025
e75147d
[IMP] Create/Update product on Zort
TheerayutEncoder Aug 17, 2025
19c309a
[IMP] Update qty to zort in picking process
TheerayutEncoder Aug 19, 2025
43768d2
[FIX] bug and error handling when update qty to zort
TheerayutEncoder Aug 21, 2025
e453a01
[IMP] cancel SO if zort was voided
TheerayutEncoder Aug 21, 2025
20d4c90
[UPD] README.rst and refactor
TheerayutEncoder Aug 21, 2025
433f152
[IMP] Add return flow
TheerayutEncoder Aug 22, 2025
4b133e2
[UPD] .pre-commit-config.yaml
TheerayutEncoder Aug 22, 2025
4ac2004
[FIX] pre-commit run -a
TheerayutEncoder Aug 22, 2025
17ef655
[ADD] Zort's default warehouse
TheerayutEncoder Aug 23, 2025
9f6a787
[FIX] get return_order_data
TheerayutEncoder Aug 23, 2025
407e611
[IMP] create credit note from return picking
TheerayutEncoder Aug 23, 2025
57497c8
[FIX] bug and refactor on sale order
TheerayutEncoder Aug 23, 2025
f51474f
[UPD] update readme
TheerayutEncoder Aug 23, 2025
6b351c0
[REV] reverse pre-commit
TheerayutEncoder Aug 23, 2025
b02325d
[IMP] refactor:process_sales_order_from_zort
TheerayutEncoder Aug 23, 2025
2d129db
[FIX] check existing zort order ids
TheerayutEncoder Sep 2, 2025
c6af2ad
[IMP] set timeout on system parameter
TheerayutEncoder Sep 2, 2025
398f7f6
[UPD] zort_connector: update readme
TheerayutEncoder Oct 4, 2025
85610ce
[ADD] e-commerce channel on sales configuration
TheerayutEncoder Oct 6, 2025
ceb3fc6
[ADD] hook method for modify SKU before search
TheerayutEncoder Oct 6, 2025
fc560f9
[ADD] fetch image from zort
TheerayutEncoder Oct 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions zort_connector/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
==============
zort_connector
==============

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:9a7fb265c67b3fca2819f2ebc2ffadda7683e67b56116044131ca90655c85d64
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-ecosoft--odoo%2Fecosoft--addons-lightgray.png?logo=github
:target: https://github.com/ecosoft-odoo/ecosoft-addons/tree/18.0/zort_connector
:alt: ecosoft-odoo/ecosoft-addons

|badge1| |badge2| |badge3|

Zort Connector for Odoo

Integrates Odoo with Zort e-commerce platform for bidirectional synchronization of orders, products, inventory, and returns.

**5 Main Zort API Endpoints Used:**

1. **Add Product API** - Create products in Zort from Odoo
2. **Update Product API** - Update product information in Zort
3. **Update Stock API** - Sync inventory quantities to Zort
4. **Get Orders API** - Import orders from Zort to Odoo
5. **Get Return Orders API** - Process return orders from Zort

**Key Features**

* **Order Management**: Automatic import every 10 minutes with status mapping (Pending→Draft, Waiting→Confirmed, Success→Delivered+Invoiced, Voided→Cancelled)
* **Product Sync**: Create/update products in Zort with SKU matching
* **Stock Sync**: Real-time inventory updates on picking validation
* **Returns Processing**: Automatic return picking creation and credit notes
* **Multi-platform Support**: Lazada, Shopee, Magento integration
* **Robust Logging**: Complete API request/response tracking

**Requirements**

* Odoo 18.0+
* Zort API credentials (Key, Secret, Store Name)
* Valid SKUs (default_code) on products

**Quick Setup**

1. Enable Zort Connector in Settings
2. Configure API credentials
3. Mark products "Sync with Zort"
4. Scheduled actions handle automatic synchronization

**Table of contents**

.. contents::
:local:

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

Bugs are tracked on `GitHub Issues <https://github.com/ecosoft-odoo/ecosoft-addons/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/ecosoft-odoo/ecosoft-addons/issues/new?body=module:%20zort_connector%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* Ecosoft

Contributors
~~~~~~~~~~~~

- Theerayut A. <[email protected]>

Maintainers
~~~~~~~~~~~

.. |[email protected]| image:: https://github.com/[email protected]?size=40px
:target: https://github.com/[email protected]
:alt: [email protected]

Current maintainer:

|[email protected]|

This module is part of the `ecosoft-odoo/ecosoft-addons <https://github.com/ecosoft-odoo/ecosoft-addons/tree/18.0/zort_connector>`_ project on GitHub.

You are welcome to contribute.
3 changes: 3 additions & 0 deletions zort_connector/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import zort_api
from . import controllers
from . import models
27 changes: 27 additions & 0 deletions zort_connector/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2025 Ecosoft Co., Ltd (https://ecosoft.co.th)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "zort_connector",
"summary": "Connects Odoo with Zort",
"version": "18.0.1.0.0",
"license": "AGPL-3",
"author": "Ecosoft, Odoo Community Association (OCA)",
"maintainers": ["[email protected]"],
"website": "https://github.com/ecosoft-odoo/ecosoft-addons",
"depends": ["stock", "sale_management"],
"data": [
"security/ir.model.access.csv",
"data/ir_actions_server_data.xml",
"data/ir_config_parameter_data.xml",
"data/ir_cron_data.xml",
"data/partner_data.xml",
"data/product_data.xml",
"views/res_config_settings_view.xml",
"views/sale_order_view.xml",
"views/product_template_view.xml",
"views/res_partner_view.xml",
"views/stock_picking_view.xml",
"views/zort_ecommerce_channel_views.xml",
],
}
1 change: 1 addition & 0 deletions zort_connector/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import controllers
40 changes: 40 additions & 0 deletions zort_connector/controllers/controllers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from odoo import http
from odoo.http import Response, request


class ZortConnector(http.Controller):
@http.route("/zort_connector/zort_connector", auth="public")
def index(self, **kw):
return "Hello, world"

@http.route(
"/zort_connector/view_zort_order_json/<int:sale_order_id>",
type="http",
auth="user",
)
def view_zort_order_json(self, sale_order_id, **kwargs):
sale_order = request.env["sale.order"].sudo().browse(sale_order_id)
if not sale_order.exists():
return Response("Sale Order not found", status=404)
import json

return Response(
json.dumps(sale_order.zort_order_data, indent=2, ensure_ascii=False),
mimetype="application/json",
)

@http.route(
"/zort_connector/view_zort_return_order_json/<int:picking_id>",
type="http",
auth="user",
)
def view_zort_return_order_json(self, picking_id, **kwargs):
picking = request.env["stock.picking"].sudo().browse(picking_id)
if not picking.exists():
return Response("Picking not found", status=404)
import json

return Response(
json.dumps(picking.zort_return_data, indent=2, ensure_ascii=False),
mimetype="application/json",
)
15 changes: 15 additions & 0 deletions zort_connector/data/ir_actions_server_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="action_update_qty_to_zort" model="ir.actions.server">
<field name="name">Update Quantity to Zort</field>
<field name="model_id" ref="stock.model_stock_picking" />
<field name="binding_model_id" ref="stock.model_stock_picking" />
<field name="binding_type">action</field>
<field name="state">code</field>
<field name="code">
# If update qty in stock.picking fail this allow user to manually trigger the update
if record.state == 'done' and not record.updated_qty_to_zort:
record.action_sync_qty_to_zort()
</field>
</record>
</odoo>
7 changes: 7 additions & 0 deletions zort_connector/data/ir_config_parameter_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="zort_connector_limit_timeout" model="ir.config_parameter">
<field name="key">zort_connector.limit_timeout</field>
<field name="value">10</field>
</record>
</odoo>
20 changes: 20 additions & 0 deletions zort_connector/data/ir_cron_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<!-- need more implementation, this is just an example -->
<record id="ir_cron_auto_sync_zort_order" model="ir.cron">
<field name="name">Auto Sync Zort Order</field>
<field name="model_id" ref="model_sale_order" />
<field name="state">code</field>
<field name="code">model.process_sales_order_from_zort()</field>
<field name="interval_number">10</field>
<field name="interval_type">minutes</field>
</record>
<record id="ir_cron_auto_sync_zort_return_order" model="ir.cron">
<field name="name">Auto Sync Zort Return Order</field>
<field name="model_id" ref="model_stock_picking" />
<field name="state">code</field>
<field name="code">model.action_create_return_picking()</field>
<field name="interval_number">10</field>
<field name="interval_type">minutes</field>
</record>
</odoo>
27 changes: 27 additions & 0 deletions zort_connector/data/partner_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="marketplace_customer_1" model="res.partner">
<field name="name">Marketplace Customer</field>
<field name="is_company" eval="True" />
<field name="company_type">company</field>
<field name="customer_platform_code" eval="'marketplace'" />
</record>
<record id="marketplace_customer_2" model="res.partner">
<field name="name">Shoppee Customer</field>
<field name="is_company" eval="True" />
<field name="company_type">company</field>
<field name="customer_platform_code" eval="'shopee'" />
</record>
<record id="marketplace_customer_3" model="res.partner">
<field name="name">Lazada Customer</field>
<field name="is_company" eval="True" />
<field name="company_type">company</field>
<field name="customer_platform_code" eval="'lazada'" />
</record>
<record id="marketplace_customer_4" model="res.partner">
<field name="name">Tiktok Customer</field>
<field name="is_company" eval="True" />
<field name="company_type">company</field>
<field name="customer_platform_code" eval="'tiktok'" />
</record>
</odoo>
21 changes: 21 additions & 0 deletions zort_connector/data/product_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="product_shipping_fee" model="product.template">
<field name="name">Shipping Fee</field>
<field name="type">service</field>
<field name="purchase_ok" eval="False" />
<field name="taxes_id" eval="False" />
<field name="list_price">0.0</field>
<field name="standard_price">0.0</field>
<field name="default_code">shipping_fee</field>
</record>
<record id="product_discount" model="product.template">
<field name="name">Discount</field>
<field name="type">service</field>
<field name="purchase_ok" eval="False" />
<field name="taxes_id" eval="False" />
<field name="list_price">0.0</field>
<field name="standard_price">0.0</field>
<field name="default_code">zort_discount</field>
</record>
</odoo>
6 changes: 6 additions & 0 deletions zort_connector/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from . import ecommerce_channel
from . import res_config_settings
from . import sale_order
from . import product_template
from . import res_partner
from . import stock_picking
73 changes: 73 additions & 0 deletions zort_connector/models/ecommerce_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright 2025 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).


from odoo import _, fields, models


class ZortEcommerceChannel(models.Model):
"""
Represents a Zort eCommerce channel configuration.

Features:
- Store channel settings (e.g., dummy customer, auto-create customer).
- Example: Lazada
name = "Lazada"
code = "lazada"
use_dummy_customer = True
use_customer_in_odoo = False
auto_create_customer = False

Note:
Sale channel, check from zort api on field `saleschannel`
API: https://open-api.zortout.com/v4/Order/GetOrders
"""

_name = "zort.ecommerce.channel"
_inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Zort eCommerce Channel"
_order = "sequence, name"
_rec_name = "name"

name = fields.Char(required=True, tracking=True)
sequence = fields.Integer(default=10)
active = fields.Boolean(default=True)
code = fields.Char(
required=True,
tracking=True,
help="Check from Zort API 'saleschannel'",
)
description = fields.Text()
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Platform Customer",
help="Platform customer to use if no match found",
)
use_customer_in_odoo = fields.Boolean(
help="Use customer in Odoo if match found else use platform customer",
default=True,
)
auto_create_customer = fields.Boolean(
help="Auto create customer if no match found",
default=False,
)

_sql_constraints = [
(
"code_uniq",
"unique(code)",
_("The code of the eCommerce channel must be unique!"),
)
]

def open_ecommerce_config_form(self):
"""Open eCommerce configuration form"""
self.ensure_one()
return {
"type": "ir.actions.act_window",
"name": _("E-commerce Channel"),
"res_model": "zort.ecommerce.channel",
"view_mode": "form",
"res_id": self.id,
"target": "current",
}
Loading