14 KiB
معماری و عملیات ایمپورت Excel کالا و خدمت
هدف
این سند رفتار فنی endpoint زیر را پس از بهینهسازی ثبت میکند:
POST /api/v1/products/business/{business_id}/import/excel
هدف، حذف query و commit ردیفبهردیف، حفظ قرارداد فعلی API و جلوگیری از بازسازی چندبارهٔ سند تراز افتتاحیه است. محدودیت فعلی فایل همچنان ۱۵ مگابایت و ۵۰۰۰ ردیف داده است.
مشکل پیشین
در پیادهسازی قبلی، یک ردیف فایل رسمی ممکن بود همان کالا را تا پنج مرتبه از دیتابیس پیدا کند. وجود ستونهای «تعداد اولیه» در header نیز حتی با سلول خالی، lookup اضافه ایجاد میکرد. اجرای واقعی هر کالا را جداگانه commit، serialize و cache-invalidate میکرد.
در ردیفهای دارای تعداد اولیه، سرویس تککالا سند افتتاحیه را میخواند، کل خطوط
آن را پیمایش میکرد و تمام خطوط را دوباره مینوشت. برای N ردیف، حجم کار
تقریباً برابر 1 + 2 + ... + N بود.
جریان جدید
خواندن workbook
↓
ساخت index مراجع و کالاهای منطبق
↓
اعتبارسنجی و تولید plan بدون write ردیفبهردیف
↓
flush کالاهای معتبر در یک Unit of Work
↓
ادغام همهٔ تغییرات تعداد اولیه در حافظه
↓
یک بازنویسی سند افتتاحیه
↓
همگامسازی اختلافی رسید/حواله انبار به تفکیک انبار
↓
یک commit و سپس یک cache invalidation
lookup کالا
کلیدهای غیرخالی فایل ابتدا جمعآوری و با queryهای IN حداکثر ۵۰۰تایی
بارگذاری میشوند. نتیجه براساس کد یا نام normalizeشده در حافظه نگهداری میشود.
dry-run، تصمیم insert/update و اجرای واقعی همگی از همین index استفاده میکنند.
تطبیق براساس کد همچنان گزینهٔ پیشنهادی است. در حالت تطبیق با نام، چند کالای موجود با نام یکسان خطای ابهام تولید میکنند. تکرار کلید تطبیق داخل خود فایل نیز پیش از write بهعنوان خطای ردیف گزارش میشود.
مراجع
دستهبندیها، ویژگیها، انبارها، انواع مالیات، واحدهای مالیاتی و ارزهای فعال
یکبار بارگذاری و index میشوند. ایجاد خودکار دسته یا ویژگی با flush انجام
میشود و commit آن به پایان Unit of Work موکول میگردد.
تعداد اولیه
صرف وجود ستونهای تعداد اولیه باعث پردازش نمیشود. فقط ردیفی که مقدار تعداد یا بهای تمامشده دارد وارد plan افتتاحیه میشود.
در اجرای واقعی:
- امکان ویرایش تراز افتتاحیه پیش از write بررسی میشود.
- سند موجود با قفل ردیفی خوانده میشود.
- خطوط کالا براساس
(product_id, warehouse_id)در map قرار میگیرند. - همهٔ تغییرات فایل روی map اعمال میشوند.
- خطوط اشخاص، بانک، صندوق، تنخواه و سایر حسابها حفظ میشوند.
- سند یکبار متوازن و یکبار نوشته میشود.
پیچیدگی ادغام خطوط O(existing_lines + imported_changes) است.
رفتار تراکنشی
- dry-run هیچ write، commit یا cache invalidation ندارد.
- خطاهای validation پیش از apply به تفکیک ردیف در پاسخ گزارش میشوند.
- ردیفهای معتبر در یک transaction اعمال میشوند.
- ایجاد کالا و ثبت تعداد اولیه در همان transaction هستند.
- خطای دیتابیس یا خطای غیرمنتظره در apply باعث rollback کل apply میشود؛ بنابراین کالای ساختهشده بدون تعداد اولیه باقی نمیماند.
- conflict همزمان یا نقض constraint با خطای
IMPORT_WRITE_CONFLICTو HTTP 409 برگردانده میشود. - cache محصولات و کاتالوگ عمومی فقط پس از commit موفق و فقط یکبار پاک میشود.
این رفتار عمداً atomicتر از پیادهسازی قدیمی است که commitهای جزئی متعدد داشت.
قرارداد API
فیلدهای موجود پاسخ حفظ شدهاند:
summaryerrorsreference_summarypreviewدر dry-runwarehouse_syncبرای خلاصه اسناد فیزیکی افتتاحیه
فیلد افزودهشدهٔ performance شامل مقادیر زیر برحسب میلیثانیه است:
parse_msvalidation_msapply_mstotal_ms
این دادهها secret، محتوای سلولها یا اطلاعات اتصال را ثبت نمیکنند.
تغییرات سرویسهای مشترک
سرویسهای ایجاد و ویرایش کالا گزینههای زیر را دارند و مقدار پیشفرض آنها رفتار endpointهای تککالا را حفظ میکند:
auto_commit=Trueserialize_result=Truedefer_cache_invalidation=Falseprevalidated_code_uniqueness=Falseprevalidated_attribute_ids=False
repositoryهای دسته، ویژگی و سند نیز auto_commit=True دارند. فقط importer این
گزینهها را برای Unit of Work گروهی غیرفعال میکند.
فایلهای اصلی
adapters/api/v1/products.py: orchestration، plan و پاسخapp/services/product_excel_import_normalize.py: index تطبیق و ارزapp/services/product_excel_import_opening_balance.py: validation ردیفapp/services/product_opening_balance_service.py: ادغام و apply گروهیapp/services/opening_balance_warehouse_sync_service.py: sync اختلافی موجودی فیزیکیapp/services/product_service.py: write بدون commit/serialization اجباریapp/services/opening_balance_service.py: upsert قابل استفاده در transaction بیرونیadapters/db/repositories/document_repository.py: write با commit اختیاری
کلاینت
دیالوگ Flutter برای این درخواست timeout دریافت پنجدقیقهای دارد. timeout بلندتر راهحل کارایی نیست؛ فقط از قطع زودهنگام درخواست معتبر در شبکههای کند جلوگیری میکند. UI همچنان باید از اجرای دوبارهٔ درخواست هنگام loading جلوگیری کند.
endpoint بهصورت sync در threadpool اجرا میشود تا parse اکسل و SQLAlchemy
همگام، event loop درخواستهای دیگر را مسدود نکنند. این رفتار فقط برای همین
endpoint با گزینهٔ offload_sync روی گارد دسترسی فعال شده است.
صفحهٔ تراز افتتاحیه خط سیستمی «بستن اختلاف تراز افتتاحیه» را از ردیفهای قابل ویرایش مخفی میکند. برای جلوگیری از نمایش اشتباه بستانکار صفر پس از ایمپورت، محاسبهٔ جمع UI در صورت فعالبودن auto-balance و انتخاب حساب حقوق صاحبان سهام، اثر همان خط مخفی را روی سمت مقابل بازسازی میکند. اگر auto-balance خاموش باشد یا حساب مقابل انتخاب نشده باشد، اختلاف خام همچنان نمایش داده میشود.
validation و تست
تستهای لازم:
tests/test_product_excel_import.py
tests/test_product_excel_import_opening_balance.py
tests/test_product_opening_balance.py
اجرای تست در محیط توسعه فقط از wrapper ایزوله مجاز است:
/home/mohammad/projects/hesabix/dev-env/toolkit/scripts/run-tests-isolated.sh -- \
tests/test_product_excel_import.py \
tests/test_product_excel_import_opening_balance.py \
tests/test_product_opening_balance.py -q
wrapper روی worktree کثیف عمداً اجرا نمیشود. برای validation نهایی باید تغییرات
در یک branch/worktree تمیز و مطابق رویهٔ Git پروژه قرار گیرند؛ تست مستقیم با
.env متصل به hesabix_dev ممنوع است.
نتیجهٔ اعتبارسنجی ۱۴۰۵/۰۷/۰۹ (۲۰۲۶-۱۰-۰۱)
- compilation فایلهای Python تغییرکرده موفق بود.
- بررسی محدود Ruff برای
F821،F822،F823وE902موفق بود. git diff --checkبرای فایلهای این تغییر موفق بود.- snapshot تمیز و مستقل برای اجرای wrapper ساخته شد و مرحلههای
--checkو--dry-runآن موفق بودند. - سه فایل تست هدف با wrapper ایزوله اجرا شدند:
28 passedو176 warningsدر25.31s. دیتابیس تصادفیhesabix_test_*پس از اجرا حذف شد و دیتابیس توسعه استفاده یا تغییر داده نشد. - این سه فایل DB-free هستند. migration chain فعلی repository نمیتواند یک
PostgreSQL کاملاً خالی را بسازد، زیرا revision
20250226_000002_add_bale_messenger_supportوجود جدولusersرا فرض میکند. بنابراین برای این اجرای unit-test فقط مرحلهٔalembic upgrade headدر snapshot تست bypass شد. این نتیجه پوشش integration دیتابیس یا endpoint HTTP محسوب نمیشود و مشکل baseline migration باید جداگانه اصلاح شود.
microbenchmark تابع ادغام گروهی روی همان محیط، با هفت تکرار و گزارش median:
| خطوط موجود | تغییرات ورودی | median | خطوط خروجی |
|---|---|---|---|
| ۱۰۰۰ | ۱۰۰۰ | ۱۵٫۰۸۲ ms | ۱۰۰۰ |
| ۵۰۰۰ | ۵۰۰۰ | ۸۶٫۵۶۱ ms | ۵۰۰۰ |
| ۱۰۰۰۰ | ۱۰۰۰۰ | ۱۸۸٫۹۳۱ ms | ۱۰۰۰۰ |
این اعداد فقط هزینهٔ _merge_product_opening_balance_changes را اندازه میگیرند
و benchmark انتهابهانتهای HTTP/Excel/PostgreSQL نیستند. رشد مشاهدهشده با
پیچیدگی خطی طراحی جدید سازگار است، اما معیار «پنج برابر سریعتر برای ۱۰۰۰ ردیف»
فقط پس از اجرای benchmark انتهابهانتها در محیط staging قابل تأیید است.
معیار پذیرش عملیاتی
- فایل بدون تعداد اولیه هیچ فراخوانی سرویس سند افتتاحیه نداشته باشد.
- lookup کالا متناسب با تعداد chunkها رشد کند، نه تعداد ردیفها.
- اجرای واقعی یک commit نهایی داشته باشد.
- سند افتتاحیه برای کل فایل یک بار apply شود.
- برای هر انبار و جهت حرکت حداکثر یک سند گروهی ساخته شود.
- تکرار فایل یکسان هیچ حرکت انبار تازهای نسازد.
- موجودی افتتاحیه در محاسبات ترکیبی دو بار شمرده نشود.
- cache invalidation برای کل import یک بار انجام شود.
- نتیجهٔ dry-run و real import برای insert/update/skip یکسان باشد.
- زمان ۱۰۰۰ ردیف روی یک محیط ثابت حداقل پنج برابر بهتر از baseline قدیمی باشد.
پایش و عیبیابی
در log، summary و زمان مراحل را با business_id و تعداد ردیف ثبت کنید؛ نام فایل،
مقادیر سلولها، token و اطلاعات اتصال نباید log شوند. برای تشخیص کندی، ابتدا
performance را بررسی کنید:
parse_msبالا: اندازه/پیچیدگی workbook یا openpyxlvalidation_msبالا: تعداد مراجع، نامهای مبهم یا validationهای دامنهapply_msبالا: constraint، barcode، sync موجودی یا سند افتتاحیه
rollback
این تغییر migration دیتابیس ندارد. rollback کد با بازگرداندن orchestration قدیمی ممکن است، اما بهدلیل خطر partial commit توصیه نمیشود. اگر rollback عملیاتی لازم شد، endpoint ایمپورت موقتاً غیرفعال شود و سپس نسخهٔ قبلی deploy گردد؛ هیچ سند یا کالایی برای rollback نباید بهصورت دستی حذف شود. قبل از هر اصلاح داده، backup و audit نتیجهٔ import بررسی شود.
محدودیتهای آگاهانه
- تولید کد خودکار همچنان برای هر کالای بدون کد نیازمند تخصیص یکتاست.
- validation بارکد عمومی و sync تغییر کنترل موجودی عمداً حذف نشدهاند؛ اینها قواعد دامنهاند و در صورت نیاز باید در فاز جداگانه batch شوند.
- endpoint هنوز نتیجه را در همان درخواست HTTP برمیگرداند. اگر پس از benchmark فایل ۵۰۰۰ ردیفی طولانی بماند، مرحلهٔ بعد انتقال apply به job پسزمینه با progress و idempotency است؛ افزایش بیشتر timeout جایگزین آن نیست.