16 KiB
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;
IsMeasurableis 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→ProductAIWidgetViewComponentPublicWidgetZones.ProductDetailsBottom→ProductAIWidgetViewComponentAdminWidgetZones.ProductDetailsBlock→ProductAttributesViewComponentAdminWidgetZones.OrderDetailsBlock→OrderAttributesViewComponent
8. DI Registration Patterns (PluginNopStartup)
Important overrides / replacements:
// 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
DevAdminSignalRHubon/{FruitBankConstClient.DefaultHubName}(WebSockets only)LoggerSignalRHubon/{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 dataStockTakingDbContext– 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.*– navigationPlugins.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):
--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.cshtmlhas 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 viaHead.cshtml - Uses
fa fa-bolticon
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.WhenAllfor 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:
- File uploaded → SHA256 hash calculated before AI call
- Hash checked against DB → if duplicate, load existing data (skip AI, save API cost)
- PDF converted to image(s) via
PdfToImageServiceif needed - OpenAI vision API extracts structured product/partner data
- Multi-stage matching: string search → historical shipping data → AI semantic match
- UI shows visual matched/unmatched indicators + autocomplete for manual correction
IsMeasurableis 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 addedHead.cshtml– includesquick-order-menu.csscss/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
- Always bilingual – every new locale resource key goes into InstallAsync (EN + HU) and both XML files.
- Mobile-first for warehouse tools – large touch targets, step-by-step UX, pulse animations.
- Server-side business rules – never expose
IsMeasurableor similar computed flags in frontend forms. - Batch DB calls – use
Task.WhenAll, never query inside a loop. - Hash before AI – always deduplicate files by SHA256 hash before calling any AI API.
- CarHaven design tokens – use CSS variables (
--theme-color,--active-color,--dark,--light-bg,DM Sansfont) consistently. - No
TransactionSafeAsyncon code paths that use async/await through thread pool. - 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)