# FruitBank Plugin – Claude Skill Reference

> **Purpose:** This file is a reference document for Claude to quickly understand the FruitBank NopCommerce plugin codebase, patterns, and conventions so that new work sessions can ramp up without re-reading the entire codebase from scratch.

---

## 1. Project Identity

| Property | Value |
|---|---|
| Plugin system name | `Misc.FruitBankPlugin` |
| DLL | `Nop.Plugin.Misc.FruitBankPlugin.dll` |
| Namespace root | `Nop.Plugin.Misc.FruitBankPlugin` |
| NopCommerce version | **4.80** |
| Author | Adam Gelencser |
| Plugin source path | `D:\REPOS\MANGO\source\Nopcommerce.Common\4.70\Plugins\Nop.Plugin.Misc.AIPlugin` |
| Theme path | `D:\REPOS\MANGO\source\FruitBank\Presentation\Nop.Web\Themes\CarHaven` |

The plugin is called **AIPlugin** on disk (folder/csproj) but the assembly and namespace use `FruitBankPlugin`. Both names are the same thing.

---

## 2. Business Domain

FruitBank is a **Hungarian fruit and vegetable wholesale company** running a private B2B NopCommerce webshop. The typical user is a warehouse employee or admin working on mobile. Key business concepts:

- **Partners** – business customers (companies), matched by name across multiple systems
- **Shipping documents** – PDF/image documents received from suppliers, parsed by AI
- **Measurable products** – products that require physical weighing before price is finalized; `IsMeasurable` is determined server-side only
- **Stock taking** – periodic inventory audit workflow with discrepancy reports
- **InnVoice** – external accounting/invoicing system, synced via `InnVoiceOrderService` / `InnVoiceApiService`
- **Voice ordering** – warehouse staff dictate orders in Hungarian; transcribed via Whisper

---

## 3. Folder Structure

```
Nop.Plugin.Misc.AIPlugin/
├── Areas/Admin/
│   ├── Controllers/          # All admin-area controllers
│   ├── Components/           # Admin view components
│   ├── Factories/            # CustomOrderModelFactory, CustomProductModelFactory
│   ├── Models/               # Admin view models (extended Nop models)
│   ├── Validators/
│   └── Views/                # Admin Razor views; custom layouts: _FruitBankAdminLayout.cshtml
├── Controllers/              # Public-facing controllers (QuickOrder, Checkout, FruitBankData)
├── Components/               # Widget view components (ProductAI, ProductAttributes, OrderAttributes)
├── css/ / js/                # Static assets for the plugin
├── Domains/
│   └── DataLayer/            # LinqToDB table classes + DbContexts
│       ├── FruitBankDbContext.cs
│       ├── StockTakingDbContext.cs
│       └── *DbTable.cs       # One file per custom table
├── Infrastructure/
│   ├── PluginNopStartup.cs   # DI registration + SignalR + middleware
│   ├── RouteProvider.cs
│   ├── ViewLocationExpander.cs
│   └── FruitBankMessageTokenProvider.cs   # Overrides IMessageTokenProvider
├── Services/                 # Business logic services
├── Localization/
│   ├── quickorder.en.xml
│   └── quickorder.hu.xml
├── FruitBankPlugin.cs        # Main plugin class (IWidgetPlugin)
├── FruitBankSettings.cs      # Plugin settings (ApiKey etc.)
├── FruitBankConst.cs         # Constants
└── plugin.json
```

---

## 4. Key Services

### AI / LLM
| Service | Purpose |
|---|---|
| `OpenAIApiService` | Primary OpenAI integration – chat completions, Whisper transcription |
| `OpenAiService` | Lightweight wrapper using `gpt-4o-mini` for simple prompts |
| `CerebrasAPIService` | Alternative LLM provider |
| `ReplicateService` | Replicate.com API (image/audio models); registered with a hardcoded Bearer token |
| `AICalculationService` | AI-assisted price/measurement calculations |

### Storage
| Service | Purpose |
|---|---|
| `FileStorageService` | Generic file storage: SHA256 hash dedup, GZip compression, path building |
| `IFileStorageProvider` / `LocalFileStorageProvider` | Strategy pattern storage backend (currently local disk / wwwroot/uploads) |

**FileStorageService patterns:**
- Calculates SHA256 on upload BEFORE any AI processing → prevents duplicate API calls
- Skips GZip for already-compressed formats (jpg, pdf, mp4, zip, etc.)
- Path format: `{userId}/{featureName}/{entityType}-{entityId}/{fileName}_{id}.ext`
- DB record created first to get ID, then file is saved; rolled back on failure

### Order / Measurement
| Service | Purpose |
|---|---|
| `OrderMeasurementService` / `IOrderMeasurementService` | Handles orders that contain measurable products |
| `MeasurementService` / `IMeasurementService` | Core weighing logic |
| `InnVoiceOrderService` | Syncs orders with InnVoice accounting system |
| `InnVoiceApiService` | HTTP client for the InnVoice REST API |

### Infrastructure
| Service | Purpose |
|---|---|
| `FruitBankAttributeService` | Custom product/order attribute helpers |
| `LockService` / `ILockService` | Singleton distributed lock |
| `PdfToImageService` | Converts PDF pages to images for AI vision processing |
| `EventConsumer` | Handles `OrderPlacedEvent` |
| `FruitBankHub` | SignalR hub for real-time admin notifications |

---

## 5. Admin Controllers

| Controller | Purpose |
|---|---|
| `CustomOrderController` | Extended order management: **split order** feature (audit-based + manual selection modes), order notes, SignalR events |
| `CustomDashboardController` | AI-powered admin dashboard with `GetWelcomeMessageAsync` (store summary, order totals, stock discrepancies, OpenWeatherMap weather) |
| `ShippingController` | Shipping document management + AI PDF extraction workflow |
| `VoiceOrderController` | Voice-to-order admin tool (mobile-optimized) |
| `FruitBankAudioController` | Audio upload/processing endpoint for Whisper transcription |
| `InvoiceController` | Invoice generation and management |
| `InnVoiceOrderController` | InnVoice order sync UI |
| `InnVoiceOrderSyncController` | InnVoice sync API endpoints |
| `ManagementPageController` | General management page |
| `FileManagerController` + `FileManagerScriptsApiController` | File manager UI |
| `FileStorageController` | File storage API endpoints |
| `AppDownloadController` | App download/distribution page |
| `FruitBankPluginAdminController` | Plugin configuration page |
| `CustomProductController` | Extended product admin (IsMeasurable etc.) |

---

## 6. Public Controllers

| Controller | Route | Purpose |
|---|---|---|
| `QuickOrderController` | `/gyors-rendeles` | Customer-facing quick order page with voice + text search |
| `CheckoutController` | `/checkout/*` | Custom checkout flow override |
| `FruitBankDataController` | `/fruitbankdata/*` | Public data API endpoints; also implements `IFruitBankDataControllerServer` |

---

## 7. Widget Zones

The plugin registers widgets in:
- `PublicWidgetZones.ProductBoxAddinfoBefore` → `ProductAIWidgetViewComponent`
- `PublicWidgetZones.ProductDetailsBottom` → `ProductAIWidgetViewComponent`
- `AdminWidgetZones.ProductDetailsBlock` → `ProductAttributesViewComponent`
- `AdminWidgetZones.OrderDetailsBlock` → `OrderAttributesViewComponent`

---

## 8. DI Registration Patterns (PluginNopStartup)

Important overrides / replacements:
```csharp
// Replaces the default NopCommerce price calculator
services.Replace(ServiceDescriptor.Scoped<IPriceCalculationService, CustomPriceCalculationService>());

// Overrides email order table rendering
services.Replace(ServiceDescriptor.Scoped<IMessageTokenProvider, FruitBankMessageTokenProvider>());

// Overrides generic attribute service
services.AddScoped<IGenericAttributeService, GenericAttributeService>();

// Overrides order model and product model factories
services.AddScoped<IOrderModelFactory, CustomOrderModelFactory>();
services.AddScoped<IProductModelFactory, CustomProductModelFactory>();

// Overrides WorkflowMessageService (order emails)
services.AddScoped<IWorkflowMessageService, WorkflowMessageService>();
```

SignalR is configured with:
- MaximumReceiveMessageSize / StatefulReconnectBufferSize: 30 MB
- `DevAdminSignalRHub` on `/{FruitBankConstClient.DefaultHubName}` (WebSockets only)
- `LoggerSignalRHub` on `/{FruitBankConstClient.LoggerHubName}`

---

## 9. Database / Data Layer

Custom tables use **LinqToDB** (not EF Core) through wrapper DbTable classes registered in DI. Two DbContext wrappers:
- `FruitBankDbContext` – main plugin data
- `StockTakingDbContext` – stock taking workflow data

Key custom tables:
| DbTable class | Purpose |
|---|---|
| `PartnerDbTable` | Business partners (wholesale customers) |
| `ShippingDbTable` | Shipping records |
| `ShippingDocumentDbTable` | Parsed shipping document metadata |
| `ShippingItemDbTable` | Line items from shipping documents |
| `ShippingDocumentToFilesDbTable` | Junction: document ↔ file |
| `FilesDbTable` | Generic file records (hash, compression flag, raw text) |
| `OrderDtoDbTable` / `OrderItemDtoDbTable` | Order DTO projections |
| `OrderItemPalletDbTable` / `ShippingItemPalletDbTable` / etc. | Pallet tracking for measurement workflow |
| `StockTakingDbTable` / `StockTakingItemDbTable` | Stock audit records |
| `StockQuantityHistoryDtoDbTable` | Stock movement history |
| `MeasuringItemPalletBaseDbTable` | Base pallet measuring data |

**N+1 query prevention:** Always batch DB calls with `Task.WhenAll`. Never query per-item inside a loop.

---

## 10. Localization

All resource keys follow the prefix: `Plugins.Misc.FruitBankPlugin.*`

- Keys are registered programmatically in `FruitBankPlugin.InstallAsync()` for **both EN and HU**
- XML locale files in `/Localization/`: `quickorder.en.xml`, `quickorder.hu.xml`
- When adding new keys: update **all three places** (InstallAsync + both XML files)
- Hungarian is the **primary** language; English is secondary
- Use `_localizationService.AddOrUpdateLocaleResourceAsync("key", "value", "HU")` pattern

Common key prefixes:
- `Plugins.Misc.FruitBankPlugin.Menu.*` – navigation
- `Plugins.Misc.FruitBankPlugin.QuickOrder.*` – quick order page (extensive set)

---

## 11. Quick Order Page (`/gyors-rendeles`)

**Controller:** `QuickOrderController`  
**View:** `/Views/QuickOrder/`  
**CSS:** `/css/quick-order.css` (in plugin) + deployed to CarHaven theme

Design system tokens (CarHaven theme):
```css
--theme-color: #2d7a3a   /* green */
--active-color: #f4a236  /* amber */
--dark:         #1a3c22
--light-bg:     #f5f7f2
font-family:    'DM Sans'
border-radius:  8px
```

Product cards use full-width flex rows: `.product-card { flex-direction: row }` with `.pc-body` (left, grows) and `.pc-actions` (right, fixed).

Navigation menu integration:
- CarHaven `TopMenu/Default.cshtml` has a `<li class="quick-order-menu-item">` for both desktop (`.notmobile`) and mobile (`.mobile`) menu blocks
- Guarded by `@if (Model.DisplayCustomerInfoMenuItem)` (login-gated)
- Menu item styled in `quick-order-menu.css` (amber, bold) included via `Head.cshtml`
- Uses `fa fa-bolt` icon

Voice input:
- Records audio in browser, POSTs to `FruitBankAudioController`
- Whisper transcription with Hungarian vocabulary hints (partner names + produce terms)
- **Prompt character limit is 224** – use keyword extraction, not full company names
- Fallback: manual text search input

---

## 12. Split Order Feature

Admin page on order detail. Two modes selectable via radio buttons:

| Mode | Behaviour |
|---|---|
| **Audit-based** | Available only when order has both "started" and "non-started" audit items. Audited items stay; non-audited items move to new order. |
| **Manual selection** | Always available (except for fully audited orders). Checkbox per item; user chooses what moves. |

Split button is always enabled (except audited orders). Mode availability is communicated visually if a mode is disabled.

**Critical lesson:** `TransactionSafeAsync` caused deadlocks because `TaskHelper.ToThreadPoolTask` creates async/await context switching in ASP.NET. The transaction wrapper was removed. Avoid wrapping split logic in `TransactionSafeAsync`.

After split: inventory adjustments, order notes written, SignalR notification sent to admin clients.

---

## 13. AI Admin Dashboard (`GetWelcomeMessageAsync`)

Located in `CustomDashboardController`. Generates a structured OpenAI prompt containing:
- Store data summary
- Today's order totals
- Stock discrepancy summary (from stock taking audit)
- OpenWeatherMap weather data (real API key configured in settings)

Patterns used:
- Typed C# records for data transfer
- `Task.WhenAll` for parallel DB calls
- Batched product history queries (no N+1)
- Bilingual (Hungarian/English) system prompt with JSON field guide for the AI
- `salesAdjustmentSum` – be careful not to double-count

---

## 14. Shipping Document Processing

AI-driven workflow for extracting partner + product data from uploaded PDFs/images:

1. File uploaded → SHA256 hash calculated **before AI call**
2. Hash checked against DB → if duplicate, load existing data (skip AI, save API cost)
3. PDF converted to image(s) via `PdfToImageService` if needed
4. OpenAI vision API extracts structured product/partner data
5. Multi-stage matching: string search → historical shipping data → AI semantic match
6. UI shows visual matched/unmatched indicators + autocomplete for manual correction
7. `IsMeasurable` is **server-side only** – never expose in frontend forms

---

## 15. NopCommerce 4.80 Gotchas

| Issue | Correct approach |
|---|---|
| LinqToDB table name for custom entities | **Never** rely on `[Table]` attribute. Register the mapping in the plugin's `Mapping/NameCompatibility.cs` file: `{ typeof(MyEntity), "fbMyTable" }`. This is the only place LinqToDB reads the table name from in this codebase. |
| `ICustomerAttributeService` does not exist | Use direct `XDocument.Parse` on the XML stored in `GenericAttribute.Value` |
| `ParseAttributeValuesAsync` returns empty for free-text attributes | It's designed for predefined selection attributes (ID lookup). For free-text: parse XML directly: `<Attributes><CustomerAttribute ID="1"><CustomerAttributeValue><Value>...</Value>...` |
| `TransactionSafeAsync` + async = deadlock | `TaskHelper.ToThreadPoolTask` inside it causes context switching deadlocks in ASP.NET; remove transaction wrapper for affected code |
| Email order table customization | Override `IMessageTokenProvider` with `FruitBankMessageTokenProvider` (already done); base class constructor has many parameters – pass all through exactly |
| `OrderPlaced.CustomerNotification` email template | Configured in NopCommerce admin under Content → Message Templates; code hook is `WorkflowMessageService.SendOrderPlacedCustomerNotificationAsync` |

---

## 16. Theme (CarHaven)

Path: `D:\REPOS\MANGO\source\FruitBank\Presentation\Nop.Web\Themes\CarHaven`

Relevant files modified by this plugin's work:
- `TopMenu/Default.cshtml` – quick order nav item added
- `Head.cshtml` – includes `quick-order-menu.css`
- `css/quick-order-menu.css` – amber nav item styling

---

## 17. External APIs / Credentials

| Service | Usage | Notes |
|---|---|---|
| OpenAI | Chat completions (gpt-4o-mini / gpt-4o), Whisper transcription | API key in `FruitBankSettings.ApiKey` |
| OpenWeatherMap | Weather data for dashboard welcome message | Real API key confirmed in settings |
| Replicate | Image/audio AI models | Bearer token hardcoded in `PluginNopStartup` HTTP client registration |
| InnVoice | Hungarian accounting/invoicing system | REST API via `InnVoiceApiService` |

---

## 18. Conventions & Patterns to Follow

1. **Always bilingual** – every new locale resource key goes into InstallAsync (EN + HU) and both XML files.
2. **Mobile-first** for warehouse tools – large touch targets, step-by-step UX, pulse animations.
3. **Server-side business rules** – never expose `IsMeasurable` or similar computed flags in frontend forms.
4. **Batch DB calls** – use `Task.WhenAll`, never query inside a loop.
5. **Hash before AI** – always deduplicate files by SHA256 hash before calling any AI API.
6. **CarHaven design tokens** – use CSS variables (`--theme-color`, `--active-color`, `--dark`, `--light-bg`, `DM Sans` font) consistently.
7. **No `TransactionSafeAsync`** on code paths that use async/await through thread pool.
8. **Expect corrections** – Adam knows the codebase deeply; treat any correction as authoritative and apply it without re-litigating.

---

*Last updated: 2026-03-18 | Maintained by: Claude (auto-generated from codebase + project chat history)*
