# GuaranteeApp — گزارش پیاده‌سازی اپ دسکتاپ (WPF)

> این فایل را سازندهٔ **اپ دسکتاپ ویندوز** نوشته است تا نفر بعدی (بک‌اند/وب/ربات/استقرار) بداند دقیقاً چه چیزی ساخته شده، چه قراردادی با سرور دارد و چه چیزهایی باز است.
> مراجع اصلی: `00-Overview`، `01-Database`، `02-Backend-API`، `03-Desktop-App`، `04-BarTender-Printing`، `08-BuildPlan-Acceptance`.

---

## ۱. وضعیت در یک نگاه

| مورد | وضعیت |
|---|---|
| فاز ۵ (داده: مدل/آپشن/Part Number/گارانتی/سریال/دسته/تاریخچه/SQLite/سینک) | ✅ کامل |
| فاز ۶ (چاپ: LabelCsvWriter، BarTenderLauncher، Mutex، بک‌آپ محلی، پروفایل‌ها، دیالوگ‌ها، Coordinator، چاپ مجدد زیرمجموعه) | ✅ کد کامل؛ تست فیزیکی فاز ۰ باقی است |
| فاز ۷ (آپلود پشتیبان قالب + RestoreService + RestoreDialog) | ✅ کد کامل؛ تست با سرور واقعی باقی است |
| فاز ۸ (دستورالعمل با تصویر، بستهٔ نصب/ACL) | طبق `08 §۱` عمداً انجام نشده (مرحلهٔ بعدی) |
| `dotnet build` (Debug + Release) | ✅ صفر خطا، **صفر هشدار** |
| Unit testها (xUnit) | ✅ **۲۹/۲۹ پاس** |
| تست دود `--smoketest` | ✅ پاس (exit 0) |
| اجرای واقعی UI ویندوز | ✅ بالا می‌آید، بدون کرش |

---

## ۲. ساختار Solution

```
GuaranteeApp.slnx                     ← Solution (فرمت جدید slnx دات‌نت ۱۰)
GuaranteeApp/                         ← اپ WPF — net10.0-windows
├── GlobalUsings.cs                   ← نکته: ImplicitUsings خاموش است؛ usings سراسری این‌جاست
├── appsettings.json                  ← تنظیمات 03 §۷ (کنار exe کپی می‌شود)
├── App.xaml / App.xaml.cs            ← نقطهٔ ورود + هندل --smoketest
├── MainWindow.xaml(.cs)              ← ناوبری ۴ صفحه + نوار «N رکورد سینک‌نشده» + سینک
├── Models/                           ← ۹ موجودیت طبق 01 §۳ (PascalCase)
│   OptionFamily, DeviceOption, ProductModel, ProductModelOption,
│   ProductionBatch, Product, PrintLog, LabelProfile, ModelLabelPreference
├── Data/
│   ├── AppDbContext.cs               ← SQLite + UNIQUE/FK/CheckConstraint (warranty 1..120 و…)
│   ├── DbFactory.cs                  ← ساخت DbContext به‌ازای هر عملیات
│   ├── DatabaseInitializer.cs        ← Migrate + Seed (لنز/نوع اتصال)
│   ├── DesignTimeDbContextFactory.cs ← برای dotnet-ef
│   └── Migrations/                   ← «InitialCreate» — مایگریشن اولیهٔ کامل و نسخه‌دار
├── Services/
│   PartNumberService, SerialNumberService, WarrantyTextService, QrCodeService,
│   SettingsService, ExportService, CloudSyncService, RestoreService,
│   LabelProfileService, LabelCsvWriter, BarTenderLauncher, TemplateBackupService,
│   LabelPrintCoordinator, PrintFlowService, AppServices (DI سبک), SmokeTest,
│   HashUtil/CsvUtil (هش SHA-256 و Escape CSV)
├── ViewModels/                       ← MVVM (RelayCommand/ObservableObject دست‌ساز)
│   MainViewModel, ProductManagementViewModel, ProductionViewModel,
│   HistoryViewModel, SettingsViewModel, PersianDigits
└── Views/
    ProductManagementView, ProductionView, HistoryView, SettingsView,
    LabelTemplateSetupDialog, LabelPrintDialog, RestoreDialog
GuaranteeApp.Tests/                   ← xUnit — net10.0-windows
└── PartNumberServiceTests, SerialNumberServiceTests, WarrantyTextServiceTests(+QR),
    LabelCsvWriterTests, TestHelpers
```

**وابستگی‌های خارجی:** فقط `Microsoft.EntityFrameworkCore.Sqlite 10.0.12` (+ Design) و xUnit. بدون SDK چاپ/ZXing/طراح لیبل/فریم‌ورک MVVM — مطابق `00 §۲`.

---

## ۳. مسیرها و فایل‌های سیستمی

| مسیر | توضیح |
|---|---|
| `C:\ProgramData\GuaranteeApp\Data\guarantee.db` | دیتابیس SQLite (پیش‌فرض؛ بدون دسترسی → `%LocalAppData%\GuaranteeApp\Data`) |
| `C:\ProgramData\GuaranteeApp\LabelData\current.csv` | CSV لیبل — **اتمیک**: نوشتن `current.csv.tmp` سپس `File.Move(overwrite)`؛ UTF-8 با BOM |
| `C:\ProgramData\GuaranteeApp\Templates\` | قالب‌های `.btw` + `_Backups\<فایل>\yyyy-MM-dd_HHmm.btw` (۱۰ نسخهٔ آخر، فقط هنگام تغییر هش) |
| `<پوشهٔ exe>\Exports\` | خروجی CSV تاریخچه (جدا از LabelData — 03 §۷) |
| `<پوشهٔ exe>\appsettings.json` | تنظیمات؛ اگر نوشتن نشد → `%LocalAppData%\GuaranteeApp\appsettings.json` (اولویت بالاتر) |
| `restore_state.json` (کنار db) | وضعیت بازیابی نیمه‌کاره (فاز + afterId) برای «ادامه از نقطهٔ قطع» |

**کلیدهای `appsettings.json`** دقیقاً طبق `03 §۷`. ستون‌های `current.csv` دقیقاً طبق `04 §۳`: `SerialNo,PartNo,QR,Model,Batch,Warranty` (همیشه همهٔ ستون‌ها، مرتب صعودی، Escape کامل، حذف کاراکتر کنترلی، سقف طول).

---

## ۴. قرارداد دقیق با سرور (⚠️ برای سازندهٔ PHP — فاز ۲)

آدرس‌ها نسبت به `ApiBaseUrl` (انتهای آن `/`)، همه با هدر **`X-Api-Key`**. پوشهٔ JSON: `{success, data|error:{code,message}}` حتی خطای منطقی با HTTP 200.

### ۴.۱ Sync (اپ → سرور) — ترتیب: options → models → products → templates

| Endpoint | نکات |
|---|---|
| `POST api/sync/options.php` | یک خانواده در هر درخواست: `familyLocalUuid, familyNumber, familyName, options:[{localUuid, valueIndex, name, isLocked}]`. پاسخ باید `data.syncedOptionUuids` داشته باشد. |
| `POST api/sync/models.php` | `localUuid, name, baseCode, partNumberCode, optionLocalUuids:[…], isLocked, warrantyMonths, warrantyLabelText` |
| `POST api/sync/products.php` | حداکثر **۲۰۰** سریال: `batch:{localUuid, productModelLocalUuid, batchCode, quantity}` + `products:[{serialNumber, productionDate:"yyyy-MM-dd", label1PrintedAt:"yyyy-MM-ddTHH:mm:ss"|null, label2PrintedAt}]`. پاسخ: `insertedCount/updatedCount/matchedOrphanEvents/rejected:[{serialNumber, code}]` — اپ rejected را با کد در UI نشان می‌دهد و فقط بقیه را `Synced=true` می‌کند. |
| `POST api/sync/templates.php` | `labelType, profileName, fileName, contentHash (sha256 hex), printWarranty, contentBase64` — سقف ۴MB سمت اپ چک می‌شود؛ `data.updated` اختیاری. |

### ۴.۲ Restore (سرور → اپ)

`GET api/restore/{options,models,batches,products,templates}.php` دقیقاً با فیلدهای `02 §۶` (صفحه‌بندی `afterId&limit=500`، `nextAfterId=null` = پایان).
- بعد از هر صفحه، `afterId` در `restore_state.json` ذخیره می‌شود → قطع اینترنت/لغو = ادامه از همان نقطه. وجود state = مجاز به ادامه حتی با DB نیمه‌پر.
- قالب‌ها: هش SHA-256 با `contentHash` سنجیده می‌شود؛ ناسازگار = هشدار و رد. `LastBackupHash = contentHash` می‌شود تا آپلود مجدد اتفاق نیفتد.
- سریال‌ها: `PartNumberCode` از مدلِ دسته برداشته می‌شود. همهٔ رکوردها `Synced=true` و `localUuid` سرور حفظ می‌شود.

### ۴.۳ کدهای خطا که اپ خاص هندل می‌کند

`AUTH_INVALID_KEY` (پیام فارسی مخصوص)، `MODEL_LOCKED_CONFLICT`، `SERIAL_ALREADY_EXISTS` (ردیفی)، `RATE_LIMITED`، `VALIDATION_ERROR`، `INTERNAL_ERROR`، خطای شبکه (پیام «اتصال برقرار نشد؛ داده محلی ذخیره شد»). «تست اتصال» تنظیمات = `GET api/restore/options.php`.

---

## ۵. رفتارهای کلیدی پیاده‌سازی‌شده (چک‌لیست `08` دسکتاپ)

- **Part Number:** کد دوریقمی، مرتب صعودی شمارهٔ خانواده، مدل بدون آپشن = `base_code`؛ یکتایی با خطای فارسی؛ سقف ۹×۹ با خطای فارسی. پیش‌نمایش زنده در UI.
- **سریال/دسته:** `{PN}-{YYMMDD}-{####}` شمارندهٔ max+۱ به‌ازای مدل/روز (برخورد → شمارندهٔ بعدی، سقف ۹۹۹۹ → خطای فارسی)؛ دسته `{YYMMDD}-B{n}` سراسری؛ همه در یک تراکنش؛ همه `Synced=false`؛ قفل مدل+آپشن‌ها در اولین تولید.
- **مدل قفل‌شده:** آپشن‌ها/BaseCode/PartNumber در UI غیرفعال؛ نام و گارانتی قابل ویرایش؛ فقط `Synced=false`.
- **گارانتی:** الزامی ۱..۱۲۰ (CheckConstraint هم دارد)؛ متن خودکار (`1 Year Warranty`, `2 Years Warranty`, `6 Months Warranty`, `18 Months Warranty`, …) و قابل ویرایش دستی (ویرایش دستی دیگر بازنویسی خودکار نمی‌شود).
- **سینک:** خودکار بعد از هر تولید (قابل خاموش‌کردن از تنظیمات) + دستی از نوار بالا/تاریخچه؛ نشانگر «N رکورد سینک‌نشده» همیشه در نوار اصلی؛ خطای ردیفی `rejected` جداگانه نمایش داده می‌شود؛ آفلاین = کار محلی ادامه دارد.
- **چاپ:** Mutex سراسری `Global\GuaranteeApp.LabelPrint`؛ اگر BarTender باز باشد یا CSV قفل باشد یا پوشه غیرقابل‌نوشتن باشد → پیام فارسی بدون کرش؛ بدون پروفایل → دیالوگ «تنظیم قالب» (فقط بار اول)؛ فایل قالب گم‌شده → دوباره دیالوگ تنظیم؛ چک‌باکس «چاپ متن گارانتی» (پیش‌فرض از پروفایل، آخرین انتخاب روی پروفایل ذخیره می‌شود)؛ پیش‌انتخاب = آخرین پروفایل همان (مدل، نوع لیبل)؛ ثبت `Label1/2PrintedAt` + `PrintLogs` لحظهٔ باز شدن BarTender و `Synced=false`؛ عبارت UI همیشه «**ارسال‌شده به BarTender**»؛ آپلود پشتیبان قالب پس‌زمینه (شروع اپ، قبل از هر Job، دکمهٔ تنظیمات).
- **چاپ مجدد زیرمجموعه:** از تاریخچه، تیک سریال‌ها (بدون تیک = کل دسته)، بدون ساخت سریال جدید.
- **تاریخچه:** وضعیت ارسال لیبل ۱/۲ به‌صورت «۸/۱۰»، وضعیت سینک، «به‌روزرسانی دیتابیس» (سینک دستی)، «خروجی CSV» با ستون‌های 03 §۸ (UTF-8 BOM).
- **بازیابی:** فقط روی DB خالی (یا وجود state نیمه‌کاره)؛ ترتیب `options → models → batches → products → templates`؛ پیشرفت + لغو + ادامه از نقطهٔ قطع؛ یادآوری فارسی «تطبیق شمارندهٔ امروز با آخرین لیبل فیزیکی» بعد از پایان.
- **هیچ ردی از «اپراتور»** در UI، CSVها و دیتابیس نیست (`08` پذیرش).

## ۶. نحوهٔ بیلد/اجرا/تست

```bash
# بیلد (Debug یا Release) — بدون خطا و هشدار
dotnet build GuaranteeApp.slnx -c Release

# تست‌های واحد
dotnet test GuaranteeApp.slnx

# تست دود بدون UI (خروجی کنسول + exit code)
dotnet run --project GuaranteeApp -- --smoketest [dbPath]

# اجرای اپ
dotnet run --project GuaranteeApp
```

**Visual Studio 2022** (ورک‌لود «.NET desktop development»): فایل `GuaranteeApp.slnx` را باز کنید → `Ctrl+Shift+B`. خروجی:
`GuaranteeApp\bin\Release\net10.0-windows\GuaranteeApp.exe`

**خروجی برای سیستم بدون اینترنت/بدون Runtime نصب‌شده (self-contained):**
```bash
dotnet publish GuaranteeApp -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true
# خروجی: GuaranteeApp\bin\Release\net10.0-windows\win-x64\publish\GuaranteeApp.exe
```
خروجی پیش‌فرض Framework-Dependent است و روی سیستم هدف **.NET Desktop Runtime 10** لازم دارد (طبق `04 §۱۲`).

**مایگریشن:** `InitialCreate` موجود است؛ اپ در اولین اجرا `Database.Migrate()` می‌زند. برای مایگریشن جدید:
`cd GuaranteeApp && dotnet ef migrations add <Name>`

---

## ۷. تصمیم‌ها و فرض‌های علاوه بر مستندات (برای آگاهی نفر بعد)

1. **ImplicitUsings خاموش + `GlobalUsings.cs`** در هر دو پروژه: کامپایل موقت WPF (wpftmp) در بازسازی implicit usings دچار مشکل می‌شد (`System.IO` در دسترس نبود). این تصمیم عمداً گرفته شده؛ آن را «اشتباه» فرض نکنید و بدون تست برگردانید.
2. **تنها وابستگی چاپ:** پکیجی برای BarTender نصب نشده. `BarTenderLauncher.FindBarTenderExe()` مسیر را از تنظیمات → `C:\Program Files\Seagull\*\bartend.exe` → رجیستری Uninstall جستجو می‌کند؛ اگر پیدا نشد با ShellExecute فایل `.btw` باز می‌شود. **روش دقیق باز کردن (bartend.exe با آرگومان یا ShellExecute) طبق `04 §۱۰` هنوز باید در فاز ۰ روی قالب واقعی تأیید شود.**
3. **`ProductModelOption`** علاوه بر دو FK، دو navigation property (`ProductModel`, `Option`) دارد — برای `Include` در سرویس‌ها لازم بود. ستونی به دیتابیس اضافه نمی‌کند (PK مرکب همان `01 §۳`).
4. **آپلود قالب «بدون تغییر»:** اگر `data.updated:false` یا هش برابر باشد، رکورد بازنویسی نمی‌شود (`02 §۵.۴` رعایت شد).
5. **متن گارانتی:** اگر اپراتور متن را دستی عوض کند، تغییر ماه‌ها دیگر متن را بازنویسی نمی‌کند (رفتاری منطقی که در MD صریح نبود).
6. **حذف محلی** خانواده/آپشن/مدل به سرور منتقل نمی‌شود (طبق `00 §۶`)؛ حذف فقط با گاردهای فارسی (قفل/استفاده‌شده/سریال‌دار) ممکن است.
7. **تاریخ‌ها:** اپ از ساعت محلی ویندوز استفاده می‌کند (`DateTime.Today/Now`) — فرض: سیستم تولید روی منطقهٔ `Asia/Tehran` است (`00 §۲` بند ۱۹).
8. **`ExportService`** کل تاریخچه را خروجی می‌گیرد (ستون‌های 03 §۸)؛ خروجی به‌ازای دسته در UI فعلی بسته نشده ولی متد `batchId` را می‌پذیرد.

## ۸. موارد باز (نقطهٔ شروع نفر بعدی)

| مورد | مرجع |
|---|---|
| **فاز ۰:** آزمایش ۱۳گانهٔ BarTender + BIXOLON روی قالب واقعی (Records=All، روش باز کردن، قفل CSV، کیفیت QR) — قبل از پذیرش چاپ الزامی | `04 §۱۰` |
| تست زندهٔ سینک/بازیابی با سرور واقعی (وقتی فاز ۲ PHP آماده شد) طبق چک‌لیست‌های `08 §۲` | `08` |
| مقادیر واقعی `ApiBaseUrl`/`ApiKey`/`WarrantyUrlTemplate` در `appsettings.json` (فعلاً example) | `03 §۷`، `07 §۷` |
| فاز ۸: بستهٔ نصب، ACL پوشه‌های ProgramData، دستورالعمل یک‌صفحه‌ای با تصویر | `04 §۱۱`، `08` |
| اگر فاز ۰ روش بهتری برای باز کردن BarTender گفت، فقط `BarTenderLauncher.OpenTemplate` را عوض کنید | `04 §۹` |

## ۹. هماهنگی با بخش‌های دیگر

- **بک‌اند (فاز ۲):** بخش ۴ همین فایل = قرارداد دقیق. بدون این Endpointها اپ کامل کار نمی‌کند ولی آفلاین همه‌چیز محلی ذخیره و بعداً سینک می‌شود.
- **وب (فاز ۳) / ربات (فاز ۴):** نقطهٔ تماسشان فقط سریال‌ها/رویدادها در MySQL است؛ اپ دسکتاپ با آن‌ها مستقیم کاری ندارد. قالب‌ها در `label_template_backups` توسط `sync/templates` ذخیره می‌شوند.
- **استقرار (فاز ۱/`07`):** کلید API باید در جدول `api_keys` (SHA-256) ثبت شود و همان مقدار خام در `appsettings.json` اپ برود. سقف `post_max_size ≥ 8M` برای آپلود قالب لازم است.
- **پس از استقرار:** روی کامپیوتر تولید: تنظیمات را پر کنید → «بررسی BarTender» → «تست اتصال» → قالب‌ها را با دیالوگ «تنظیم قالب» معرفی کنید یا «بازیابی از سرور».

---
*نوشته‌شده پس از اتمام ساخت اپ دسکتاپ — بیلد ✅، ۲۹/۲۹ تست ✅، smoketest ✅، اجرای UI ✅.*



