Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions milad/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ODOO_URL=http://localhost:8069
ODOO_DB=exam_db
ODOO_USERNAME=admin
ODOO_PASSWORD=admin

APP_DB_HOST=localhost
APP_DB_PORT=5433
APP_DB_NAME=sync_backend
APP_DB_USER=sync_user
APP_DB_PASSWORD=sync_password
11 changes: 11 additions & 0 deletions milad/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.venv/
__pycache__/
*.pyc
*.egg-info/
.env
.pytest_cache/
.coverage
htmlcov/
*.log
.vscode/
.idea/
13 changes: 13 additions & 0 deletions milad/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.12-slim

WORKDIR /app

COPY pyproject.toml ./
COPY src ./src

RUN pip install --no-cache-dir -e .

COPY migrations ./migrations
COPY alembic.ini ./alembic.ini

CMD ["sh", "-c", "alembic upgrade head && python -m backend.cli"]
27 changes: 27 additions & 0 deletions milad/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Odoo → PostgreSQL Sync Backend

This is my solution for the technical exam. It pulls contacts, products, and sale orders out of Odoo and stores them in my own PostgreSQL database, without creating duplicates if you run it more than once.

## What's in here

- An Odoo instance running in Docker (I set this up myself, no server was given to me for the exam)
- A small script to create some test data in Odoo (contacts, products, sale orders)
- A Python backend that connects to Odoo, pulls the data, and saves it to Postgres
- Tests for the important parts
- Docs: `USER.md` (how to run everything) and `TECHNICAL.md` (how it's built and why)

## Quick start

```powershell
docker compose up -d odoo-db odoo app-db
```

Then open `http://localhost:8069`, log in / create the database, install the Sales app, and run the seed script. Full steps are in `USER.md`.

## Why it's built this way

I split the code into layers: one part just talks to Odoo, one part maps Odoo's data into my own format, one part handles saving to the database, and one part ties it all together and keeps track of what happened (what got created, what got updated, what failed). This way if I ever needed to swap Odoo for a different system, or Postgres for something else, I wouldn't have to rewrite everything — just the one piece that changed.

## Status

Still learning parts of this as I go — some of the design decisions I made because they're generally good practice, and I'll be able to explain them properly once I've had time to sit with the whole project and understand it end to end.
116 changes: 116 additions & 0 deletions milad/Technical Documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# یادداشت‌های فنی

این فایل بیشتر برای توضیح ساختار پروژه و چند تصمیمی نوشته شده که شاید از روی کد مشخص نباشن.

## روند Sync

```text
Odoo (XML-RPC)
Adapter
Mapper
Sync Service
Repository
PostgreSQL
```

هر لایه فقط مسئول کار خودش است:

<ul dir="rtl">
<li><b>Adapter</b>: فقط با Odoo ارتباط برقرار می‌کند و داده‌ی خام برمی‌گرداند.</li>
<li><b>Mapper</b>: داده‌ی Odoo را به مدل‌های داخلی تبدیل می‌کند.</li>
<li><b>Sync Service</b>: ترتیب کارها رو مدیریت می‌کنه، خطاها رو هندل می‌کنه.</li>
<li><b>Repository</b>: فقط عملیات مربوط به دیتابیس را انجام می‌دهد.</li>
</ul>

به همین دلیل منطق Sync وابسته به Odoo یا PostgreSQL نیست و هر بخش را می‌توان جداگانه تغییر داد.

## جلوگیری از رکوردهای تکراری

هر جدول یک ستون یکتای `odoo_id` دارد.

هنگام Sync ابتدا رکورد با همان `odoo_id` جستجو می‌شود. اگر وجود داشته باشد، اطلاعاتش به‌روزرسانی می‌شود و در غیر این صورت یک رکورد جدید ایجاد می‌شود.

به همین خاطر اجرای چندباره‌ی Sync همیشه نتیجه‌ی یکسانی دارد و داده‌ی تکراری ایجاد نمی‌شود.

## مدیریت خطا

هر رکورد به‌صورت مستقل پردازش می‌شود.

اگر پردازش یک رکورد با خطا روبه‌رو شود، خطا داخل `sync_logs` ثبت می‌شود و Sync ادامه پیدا می‌کند.

اما اگر خطایی در سطح زیرساخت رخ بدهد، مثل قطع شدن اتصال دیتابیس، اجرای Sync متوقف می‌شود و تراکنش Rollback خواهد شد.

## Retry

در صورت بروز خطاهای ارتباطی با Odoo، عملیات دریافت اطلاعات تا سه بار دوباره امتحان می‌شود.

خطاهای مربوط به اعتبارسنجی یا تبدیل داده Retry نمی‌شوند، چون اجرای دوباره نتیجه‌ی متفاوتی نخواهد داشت.

## Batch Processing

اطلاعات به‌صورت Batchهای ۱۰۰تایی از Odoo دریافت می‌شوند تا مصرف حافظه ثابت بماند و پروژه برای حجم داده‌ی بیشتر هم قابل استفاده باشد.

## Incremental Sync

در حال حاضر امکان Sync افزایشی هم در نظر گرفته شده است. با استفاده از زمان آخرین اجرای موفق، فقط رکوردهایی که بعد از آن تغییر کرده‌اند قابل دریافت هستند. اگر اجرای موفقی وجود نداشته باشد، یک Sync کامل انجام می‌شود.

## Logging

لاگ‌گیری پروژه در دو سطح انجام می‌شود:

**سطح اول: لاگ متنی روی ترمینال (Standard Output)**

با استفاده از ماژول استاندارد `logging` پایتون، هر پیام (`info`, `warning`, `error`) به همراه زمان، سطح و نام ماژول روی خروجی استاندارد چاپ می‌شود. این همان چیزی است که هنگام اجرای `python -m backend.cli` روی ترمینال دیده می‌شود، مثل:

```text
2026-07-24 10:33:44 INFO backend.cli: Sync finished: fetched=99 created=0 updated=99 errors=0
```

**سطح دوم: لاگ ساختاریافته در دیتابیس**

هر بار که پردازش یک رکورد با خطا مواجه شود، علاوه بر چاپ در ترمینال، یک رکورد در جدول `sync_logs` هم ذخیره می‌شود. این رکورد شامل موارد زیر است:

* `sync_run_id` — به کدام اجرای Sync (از جدول `sync_runs`) مربوط است
* `level` — سطح لاگ (مثلاً `error`)
* `message` — متن کامل خطا
* `record_odoo_id` — شناسه‌ی رکورد مشکل‌دار در Odoo (در صورت وجود)
* `created_at` — زمان ثبت لاگ

به این ترتیب لاگ‌های ترمینال گذرا هستند اما لاگ‌های داخل دیتابیس دائمی‌اند و بعداً هم قابل بررسی‌اند.

**اطلاعات هر اجرای Sync در جدول `sync_runs`**

جدول `sync_runs` هم یک رکورد خلاصه از هر اجرا نگه می‌دارد، شامل:

* زمان شروع و پایان اجرا (`started_at`, `finished_at`)
* تعداد رکوردهای دریافت‌شده (`fetched_count`)
* تعداد رکوردهای جدید ایجاد‌شده (`created_count`)
* تعداد رکوردهای به‌روزرسانی‌شده (`updated_count`)
* تعداد خطاها (`error_count`)

این جدول همیشه پر می‌شود، حتی اگر خود اجرای Sync با خطای کلی متوقف شود، چون بخش ثبت آمار در بلوک `finally` قرار دارد و مستقل از موفقیت یا شکست کل فرآیند اجرا می‌شود.

می‌توان این اطلاعات را مستقیم با کوئری روی دیتابیس هم دید، مثلاً:

```sql
SELECT id, operation, started_at, fetched_count, created_count, updated_count, error_count
FROM sync_runs
ORDER BY id DESC
LIMIT 5;
```

## ساختار دیتابیس

* `contacts`
* `products`
* `sale_orders`
* `sale_order_lines`
* `sync_runs`
* `sync_logs`

تغییرات ساختار دیتابیس با **Alembic** مدیریت می‌شود.
94 changes: 94 additions & 0 deletions milad/User Documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# odoo-sync

این پروژه اطلاعات مخاطبین، محصولات و سفارش‌های فروش را از **Odoo** دریافت می‌کند و داخل **PostgreSQL** ذخیره می‌کند.

فرآیند **Sync** به‌صورت **Idempotent** پیاده‌سازی شده؛ یعنی فرقی نمی‌کند یک بار اجرا شود یا چندین بار، رکورد تکراری ایجاد نمی‌شود و فقط اطلاعات موجود به‌روزرسانی خواهند شد.

## پیش‌نیازها

* Docker Desktop
* Python 3.12 یا بالاتر

## راه‌اندازی

ابتدا **Odoo**، دیتابیس آن و دیتابیس برنامه را اجرا کنید:

```powershell
docker compose up -d odoo-db odoo app-db
```

چند ثانیه صبر کنید تا سرویس‌ها بالا بیایند، سپس وضعیت آن‌ها را بررسی کنید:

```powershell
docker compose ps
```

### فقط برای اولین اجرا

مرورگر را باز کنید و وارد آدرس زیر شوید:

```text
http://localhost:8069
```

اگه لازم بود دیتابیس جدیدی بسازید، از تنظیمات زیر استفاده کنید:

* Database: `exam_db`
* Email: `admin`
* Password: `admin`
* Demo Data: `Skip`

اگر صفحه ورود نمایش داده شد، یعنی دیتابیس از قبل ساخته شده است با نام کاربری و رمز `admin` وارد شوید.

بعد از ورود، ماژول **Sales** را نصب کنید. این پروژه برای دریافت مخاطبین، محصولات و سفارش‌های فروش به این ماژول نیاز دارد.


### ایجاد داده‌های نمونه

```powershell
python scripts\seed_odoo.py
```

این اسکریپت چند مخاطب، محصول و سفارش فروش نمونه ایجاد می‌کند و در صورت اجرای دوباره هم مشکلی ایجاد نمی‌کند.

### ساخت جداول دیتابیس

```powershell
alembic upgrade head
```

## اجرای Sync

```powershell
python -m backend.cli
```

خروجی نمونه:

```text
Sync finished: fetched=99 created=99 updated=0 errors=0
```

اگر دوباره همین دستور را اجرا کنید، مقدار `created` دیگر افزایش پیدا نمی‌کند و به‌جای آن مقدار `updated` بیشتر میشه.

## اجرای پروژه با Docker

```powershell
docker compose up --build
```

این دستور Image برنامه را می‌سازد، منتظر آماده شدن **Odoo** و **PostgreSQL** می‌ماند، Migrationها را اجرا می‌کند و در نهایت فرآیند **Sync** را شروع می‌کند.

## اجرای تست‌ها

```powershell
docker compose up -d test-db
pytest -v
```

برای مشاهده **Coverage**:

```powershell
pytest --cov=backend --cov-report=term-missing
```

Loading