# GuaranteeApp — اپ دسکتاپ (WPF)

> پیش‌نیاز: `00-Overview`، `01-Database`، `02-Backend-API`. چاپ لیبل در `04-BarTender-Printing` شرح داده شده است.

---

## ۱. فناوری و ساختار

- **.NET 10** (`net10.0-windows`)، WPF، MVVM، EF Core + SQLite، Offline-first. بدون Supabase، بدون ZXing، بدون طراح لیبل داخلی.
- قابلیت‌های اپ: مدیریت مدل/آپشن، تولید سریال و دسته، چاپ لیبل (از طریق BarTender)، تاریخچه، سینک، بازیابی، خروجی CSV.

```
GuaranteeApp/
├── Models/        OptionFamily, DeviceOption, ProductModel, ProductModelOption,
│                  ProductionBatch, Product, PrintLog, LabelProfile, ModelLabelPreference
├── Services/
│   ├── PartNumberService.cs        محاسبه Part Number، اندیس بعدی، قفل
│   ├── SerialNumberService.cs      سریال + دسته + قفل مدل
│   ├── WarrantyTextService.cs      ماه ← متن انگلیسی
│   ├── QrCodeService.cs            فقط BuildWarrantyUrl
│   ├── LabelProfileService.cs      پروفایل‌ها و پیش‌انتخاب (04)
│   ├── LabelCsvWriter.cs           (04)
│   ├── BarTenderLauncher.cs        (04)
│   ├── LabelPrintCoordinator.cs    (04)
│   ├── TemplateBackupService.cs    (04)
│   ├── CloudSyncService.cs         سینک به PHP API
│   ├── RestoreService.cs           بازیابی از سرور
│   ├── SettingsService.cs
│   └── ExportService.cs            CSV تاریخچه
├── ViewModels/ + Views/
│   MainWindow، ProductManagement، Production، History، Settings،
│   LabelTemplateSetupDialog، LabelPrintDialog، RestoreDialog
├── Data/ AppDbContext, DatabaseInitializer (Migrations + Seed)
└── appsettings.json
```

---

## ۲. مدیریت خانواده، آپشن و مدل (Part Number)

### ۲.۱ منطق کدگذاری
```
خانوادهٔ ۱ = لنز        → 2.8mm = 11 ، 4mm = 12
خانوادهٔ ۲ = نوع اتصال   → AHD = 21 ، IP = 22 ، WiFi = 23
مدل: لنز 4mm + WiFi  → کدها «12»+«23» → IP0756z-1223
مدل بدون آپشن         → Part Number = base_code (مثل XVR)
```
- در هر خانواده حداکثر یک آپشن برای هر مدل؛ کدها به ترتیب صعودی شمارهٔ خانواده.
- Part Number در کل پروژه یکتاست (خطای کاربرپسند در صورت تکرار).
- سقف: ۹ خانواده × ۹ مقدار. (در صورت نیاز آینده: مهاجرت به کد ۴رقمی برای مدل‌های جدید، بدون اثر روی قدیمی‌ها.)
- **Seed اولیه:** خانواده «لنز» (11=2.8mm، 12=4mm) و «نوع اتصال» (21=AHD، 22=IP، 23=WiFi).

### ۲.۲ `PartNumberService`
`ComputePartNumber(baseCode, optionIds)`، `NextFamilyNumber()`، `NextValueIndex(familyId)`، `LockModelAndOptions(model)`.

### ۲.۳ UI «مدیریت محصولات» (📦)
- **خانواده‌ها:** افزودن خانواده (شمارهٔ بعدی خودکار)، افزودن/حذف آپشن (کد نمایش `11 — لنز 2.8mm`)؛ آپشن/خانوادهٔ قفل‌شده یا استفاده‌شده در مدل حذف نمی‌شود.
- **مدل:** نام، `BaseCode`، انتخاب یک آپشن از هر خانواده، **پیش‌نمایش زندهٔ Part Number**، **مدت گارانتی (ماه، اجباری، ۱ تا ۱۲۰)** و **متن گارانتی** (خودکار از ماه، قابل ویرایش). ذخیره بدون گارانتی ممکن نیست.
- **مدل قفل‌شده:** آپشن‌ها و Part Number غیرفعال (🔒)؛ نام و گارانتی قابل ویرایش (و `Synced=false` می‌شوند). مدل بدون سریال قابل حذف است.

---

## ۳. تولید سریال و دسته

صفحهٔ تولید (🏭): انتخاب مدل + تعداد ← «تولید». **چاپ خودکار ندارد.**

`SerialNumberService.GenerateAsync(model, qty)` در یک تراکنش:
1. دستهٔ جدید با کد `{YYMMDD}-B{n}` (قانون `01-Database §۴.۳`) و `LocalUuid`.
2. N سریال `{PartNumber}-{YYMMDD}-{####}` با شمارندهٔ هر مدل در هر روز (max+۱؛ برخورد unique ← شمارندهٔ بعدی؛ سقف ۹۹۹۹ ← خطای فارسی).
3. قفل مدل و آپشن‌هایش (در تولید اولین سریال).
4. همهٔ رکوردها `Synced=false`؛ سپس **سینک خودکار** (§۵.۴).

صفحه پس از تولید: خلاصهٔ دسته + دو دکمهٔ «چاپ لیبل ۱» و «چاپ لیبل ۲» (بدون پیش‌نمایش داخلی؛ فرایند در `04`).

---

## ۴. تاریخچه (🕓)

- جدول دسته‌ها: کد دسته، مدل، تعداد، تاریخ، **وضعیت ارسال لیبل ۱/۲** (مثل «۸/۱۰»)، وضعیت سینک.
- هر سطر: «چاپ لیبل ۱»، «چاپ لیبل ۲»، «چاپ مجدد» (بدون سریال جدید).
- جزئیات دستهٔ منتخب: لیست سریال‌ها با **انتخاب زیرمجموعه** برای چاپ/چاپ مجدد (تیک هر سریال)؛ ستون زمان ارسال هر لیبل.
- عبارت UI همیشه «**ارسال‌شده به BarTender**» است (نه «چاپ‌شده»).
- دکمه‌ها: «به‌روزرسانی دیتابیس» (سینک دستی)، «خروجی CSV».
- هیچ ستون یا فیلتر اپراتور وجود ندارد.

---

## ۵. سینک (`CloudSyncService`)

### ۵.۱ اتصال
`HttpClient` با هدر `X-Api-Key`، `ApiBaseUrl` از تنظیمات. پاسخ `{success, data|error}` پردازش می‌شود (`02-Backend-API §۲`). خطای شبکه یا سرور ← رکورد `Synced=false` می‌ماند و بعداً دوباره ارسال می‌شود.

### ۵.۲ ترتیب
`options (به‌ازای هر خانوادهٔ Synced=false) → models → products (به‌ازای هر دسته) → templates (پشتیبان)`.

- **models:** شامل `warrantyMonths`, `warrantyLabelText`, `optionLocalUuids`, `isLocked`.
- **products:** هر دسته تا ۲۰۰ سریال در هر درخواست (دستهٔ بزرگ‌تر = چند درخواست با همان `batch`)؛ شامل `label1PrintedAt`/`label2PrintedAt`.
- پس از موفقیت، همان رکوردها `Synced=true`؛ سریال‌های `rejected` **جداگانه و با کد در UI نمایش داده می‌شوند** (نه فقط پیام کلی).
- سینک مجدد همان دسته خطای «تعارض» نمی‌دهد (سرور به‌روزرسانی می‌کند).

### ۵.۳ نشانگر
نوار اصلی همیشه «N رکورد سینک‌نشده» را نشان می‌دهد.

### ۵.۴ سینک خودکار
کلید `AutoSyncAfterProduction` (پیش‌فرض روشن): بعد از هر تولید یک سینک best-effort پس‌زمینه؛ آفلاین بودن مانع کار نیست.

---

## ۶. بازیابی از سرور (`RestoreService` + `RestoreDialog`)

هدف: کامپیوتر جدید ← اتصال به همان سرور ← بازگشت همه‌چیز.

1. فقط روی **دیتابیس محلی خالی** (بدون `Products` و `ProductionBatches`؛ فقط Seed) مجاز است؛ وگرنه پیام و رد.
2. دادهٔ Seed (خانواده‌ها/آپشن‌ها) پاک می‌شود. ترتیب ورود: `options → models → batches → products → templates` با فراخوانی `restore/*` (`02 §۶`).
3. `local_uuid` سرور حفظ می‌شود (سینک‌های بعدی Idempotent بمانند). همهٔ رکوردها `Synced=true`.
4. قالب‌ها: فایل `.btw` در پوشهٔ ثابت قالب‌ها نوشته، هش با `contentHash` سنجیده و `LabelProfiles` (با `PrintWarranty`) بازسازی می‌شود.
5. شمارندهٔ سریال/دسته خودکار درست ادامه می‌یابد (از `Products`/`ProductionBatches` محلی محاسبه می‌شود).
6. نوار پیشرفت، **لغو**، و **ادامه از نقطهٔ قطع** (ذخیرهٔ `afterId`).
7. بعد از بازیابی، به اپراتور یادآوری شود: شمارندهٔ آن روز با آخرین لیبل فیزیکی تطبیق داده شود (ریسک سریال‌های سینک‌نشده، `00 §۶`).

---

## ۷. تنظیمات (`appsettings.json` + صفحهٔ ⚙️)

| کلید | پیش‌فرض | توضیح |
|---|---|---|
| `ApiBaseUrl` | `https://example.com/guarantee/` | پایهٔ API (انتهای آن `/`) |
| `ApiKey` | — | کلید اپ (`02 §۳`) |
| `WarrantyUrlTemplate` | `https://example.com/guarantee/warranty.php?serial={serial}` | سریال هنگام ساخت QR URL-encode می‌شود |
| `LabelDataFolder` | `C:\ProgramData\GuaranteeApp\LabelData` | ثابت (مسیر داخل `.btw` ذخیره می‌شود) |
| `LabelTemplatesFolder` | `C:\ProgramData\GuaranteeApp\Templates` | ثابت |
| `ColumnContractVersion` | `1` | نسخهٔ قرارداد CSV |
| `BarTenderExePath` | تشخیص خودکار | |
| `AutoSyncAfterProduction` | `true` | |

صفحهٔ تنظیمات: فیلدهای بالا (بدون اپراتور)، دکمهٔ «تست اتصال به سرور»، دکمهٔ «بررسی BarTender» (نصب‌بودن، وجود/قابل‌نوشتن‌بودن پوشه‌ها، وجود پرینتر BIXOLON در لیست پرینترها)، «پشتیبان‌گیری قالب‌ها»، «بازیابی از سرور». پوشهٔ خروجی CSV (`Exports\`) جدا از `LabelData` است.

---

## ۸. خروجی CSV تاریخچه (`ExportService`)
ستون‌ها: `SerialNumber, PartNumberCode, ModelName, BatchCode, ProductionDate, Label1PrintedAt, Label2PrintedAt, Synced`. UTF-8 با BOM. (جدا از CSV لیبل `current.csv`.)

---

## ۹. تست و ابزار توسعه

- **Unit test:** `PartNumberService` (ترتیب کدها، مدل بدون آپشن)، `SerialNumberService` (شمارنده، برخورد، سقف)، `WarrantyTextService`، `LabelCsvWriter` (04).
- **حالت دود:** `dotnet run -- --smoketest [dbPath]` ← ساخت دیتابیس، seed، یک دسته و چند سریال، نوشتن `current.csv` آزمایشی، بدون UI.
- `dotnet build` بدون خطا/هشدار ارجاع به کد منسوخ.
- مایگریشن‌های EF از همان ابتدا نسخه‌دار (یک مایگریشن اولیه کامل؛ بدون کد سازگاری با نسخهٔ قدیمی).

---

## ۱۰. رفتار خطا
همه پیام‌ها فارسی و بدون کرش: سرور در دسترس نیست (کار محلی ادامه دارد)، کلید نامعتبر، پوشه قابل‌نوشتن نیست، تولید بیش از سقف روزانه، Part Number تکراری، تلاش برای ویرایش آپشن مدل قفل‌شده.
