Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/SKILL.md

353 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)*