352 lines
16 KiB
Markdown
352 lines
16 KiB
Markdown
# 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 |
|
||
|---|---|
|
||
| `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)*
|