Compare commits

..

77 Commits

Author SHA1 Message Date
Loretta 2c18ca9fad EKÁER docs: VTSZ grouping, archive rules, reopened issue
- Clarified docs-archive skill to use year-month buckets and archive only Closed entries.
- Reopened and updated the Product.Gtin/VTSZ separation issue in EKAER_ISSUES.md.
- Added new TODO (T-V9G3) for grouping tradeCardItems by VTSZ and resolving group names from Category, with requirements and affected code.
- Updated README to reflect VTSZ-based grouping and productName resolution.
2026-06-15 18:01:48 +02:00
Loretta c95ec68c09 EKÁER integration, product search, and schema updates
- Added EKÁER (NAV) service registrations and config to PluginNopStartup
- Extended MgProductDto/IMgProductDto with Sku property
- Raised min search term length to 3 in autocomplete endpoints
- Increased ProductSearchAutoComplete maxResults to 300
- Refactored product search to filter by AvailableQuantity > 0
- Closed GTIN/VTSZ issue in EKAER_ISSUES.md, moved to data model topic
- Added SCHEMA.md with Toon domain model, including Currency property for partners
2026-06-03 16:59:23 +02:00
Loretta 1b11ca2579 Add XML doc conventions and update references
Added a section on XML documentation standards to CONVENTIONS.md, clarified references to core and Mango.Nop framework convention docs, and improved formatting. Enabled EmitCompilerGeneratedFiles in Mango.Nop.Core.csproj.
2026-05-23 09:27:34 +02:00
Loretta 65bf004808 Update AcBinarySerializable attribute signature
Updated all affected entities to use the new AcBinarySerializable attribute signature, adding an extra boolean parameter (false) as the last argument. No other logic or code was changed.
2026-05-15 08:41:59 +02:00
Loretta 474cb99754 [LOADED_DOCS: 3 files, no new loads]
Update AcBinarySerializable attribute to 5 params

Updated AcBinarySerializable to use a five-parameter signature across all relevant entities and DTOs. Adjusted CONVENTIONS.md to document the new attribute usage. Refactored MiscSignalRApiPlugin constructor to a single-line parameter list for clarity.
2026-05-11 20:25:42 +02:00
Loretta cb9a381400 [LOADED_DOCS: 2 files, no new loads]
Update DB connection, protocol flush policy, and docs

- Switch appsettings.json to production DB connection string
- Update copilot-instructions.md to reference Rules #1-6
- Set FlushPolicy.DoubleBuffered for AcBinary protocol in PluginNopStartup.cs
2026-05-05 15:06:06 +02:00
Loretta 1a0bd01500 [LOADED_DOCS: 1 files, no new loads]
Add repo prefixes and clarify workspace dependencies

Added prefix, type, and layer properties to each @repo block in copilot-instructions.md. Updated own-dep-repos arrays for clearer dependency paths. Adjusted [LOADED_DOCS: ...] prefix instructions to reflect correct file counts. These changes enhance multi-repo management and documentation protocol clarity.
2026-04-26 19:13:04 +02:00
Loretta 7fb87283ce [LOADED_DOCS: NONE]
Refactor skill loading protocol in copilot-instructions

Clarified session setup: only two reactive skills are pre-loaded, with three user-gated skills now lazy-loaded on demand. Updated documentation to add "AUTHORITY, RULE SCOPE, AND SKILL INVOCATION" section, revised skill trigger descriptions, and adjusted `[LOADED_DOCS: ...]` prefix logic. Documented docs-archive as a lazy-loaded skill. Added settings.local.json for local file removal permissions. No code logic changes—documentation and protocol only.
2026-04-26 13:44:27 +02:00
Loretta 599f8a6787 Refactor docs: topic folders, navigation, protocol sync
- Restructured documentation: added `docs/README.md` to each sub-project, moved LOGGING and SIGNALR docs into dedicated subfolders with their own `README.md`.
- Updated all cross-references to use new topic folder paths and canonical AyCode.Core doc locations.
- Updated `.csproj` files to auto-include all Markdown docs and project-level `README.md` files.
- Removed obsolete single-file docs, replaced with structured content in topic folders.
- Enforced AI Agent Protocol: session setup, output prefix, no-re-read, and mandatory `docs-check` skill after code changes.
- Added domain-critical reminders and navigation guidance to relevant `README.md` files.
2026-04-25 07:24:39 +02:00
Loretta 25e5ded777 Update AI Agent protocol docs and config/debug logic
- Switched to new `[LOADED_DOCS: N files (+K this turn: ...)]` prefix format in all protocol docs, with examples and enforcement details.
- Documented `docs-discovery` skill, protocol history, and documentation-first coding requirements.
- Added protocol documentation for `Nop.Plugin.Misc.AIPlugin`.
- Changed SQL connection string to use `FruitBank_DEV` in `appsettings.json`.
- Wrapped `IHubProtocol` debug output in `#if DEBUG` in `PluginNopStartup.cs`.
2026-04-24 08:20:19 +02:00
Loretta 6e3ca4a1e3 Add Mango.Nop.Core docs: architecture, conventions, rules
Added Mango.Nop.Core section to copilot-instructions.md detailing workspace dependencies, framework-first design, and separation from plugin logic. Introduced ARCHITECTURE.md to define framework vs. consumer boundaries, entity mirroring, and DTO parameterization. Added CONVENTIONS.md to formalize placement rules, naming conventions, and anti-patterns, referencing AyCode.Core for shared doctrine.
2026-04-23 16:11:49 +02:00
Loretta 8d3c2a8462 [LOADED_DOCS: .github\copilot-instructions.md]
Update DB config, docs, and SignalR binary protocol setup

- Switched appsettings.json connection string to production DB.
- Added "Shared Agent Skills" section to copilot-instructions.md in both main and Mango.Nop Libraries repos.
- Refactored PluginNopStartup.cs: improved using statements, updated SignalR to use AddAcBinaryProtocol with explicit options, removed manual IHubProtocol registration, and added protocol diagnostics.
- No business logic changes; focused on configuration, documentation, and infrastructure.
2026-04-22 22:45:05 +02:00
Loretta b494018e9c Strengthen doc-first protocol and clarify Claude mapping
Updated copilot-instructions.md to enforce stricter documentation-first and multi-repo rules, including cross-repo doc loading, per-question doc checks, and improved context recovery triggers. Broadened explicit consent to all file types. Updated CLAUDE.md with sequential execution override and tool mapping, ensuring Claude follows the same protocol and rule set.
2026-04-04 20:52:57 +02:00
Loretta 3725b4c2fd Enforce doc-first protocol, add SGen, modular plugin docs
- Enforced strict documentation-first AI agent protocol in `.github/copilot-instructions.md` (multi-repo, no auto doc edits, explicit consent, [LOADED_DOCS] prefix, [DOCUMENTATION CHECK] on code changes)
- Updated solution structure: added `docs` folder to solution items for LLM/AI context
- Integrated AyCode SGen (source-generated binary serialization) with forced runtime registration; documented SGen usage and exclusions
- Overhauled plugin `README.md` and added modular docs: `SCHEMA.md`, `DOMAIN_MODEL.md`, `MEASUREMENT.md`, `DATA_LAYER.md`, `AI_SERVICES.md`, `SIGNALR_ENDPOINTS.md`
- Updated `CLAUDE.md` to require reading copilot instructions first
- Switched appsettings connection string to production DB
- Minor doc clarifications, corrects, and project file updates
2026-04-02 22:19:29 +02:00
Loretta bb08c7ae61 Update LLM instruction files for token efficiency and cross-repo navigation
- CLAUDE.md: reduced to single-line pointer to copilot-instructions.md (eliminates redundant auto-loaded content)
- copilot-instructions.md: added @repo name field, relative paths in own-dep-repos, "do not re-read .md files" rule, and explicit permission to navigate external repos

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 16:15:02 +02:00
Loretta 5535011df9 Comprehensive documentation overhaul and expansion
- Added detailed .md docs for Core, Data, and Services projects: DTOs, NopDependencies, repositories, transactions, services, and logging bridge
- Updated all project README.md files with metadata, documentation tables, inheritance diagrams, and clearer dependency/reference explanations
- Root README.md now includes a documentation map and improved project/type tables
- Expanded and clarified `.github/copilot-instructions.md` as the single source of truth for domain rules, patterns, and conventions
- Updated ARCHITECTURE.md, CONVENTIONS.md, and GLOSSARY.md to document DTO strategies, transaction and logging patterns, project boundaries, and AyCode integration
- Added CLAUDE.md for Claude-specific code authoring guidance
- All .csproj files now include new docs as non-compilable content
- Ensured clear separation of concerns and improved AI/tooling discoverability across the codebase
2026-03-30 10:26:35 +02:00
Loretta 2303e99f95 Clarify docs: conventions, layering, timestamp interface
Updated documentation to clarify .md file update scope and add rules for documentation layering. Replaced inline dependency graph in README.md with a reference to ARCHITECTURE.md. Corrected timestamp interface references from ITimeStampUpdated to ITimeStampModified for consistency. Clarified nopCommerce dependency boundaries.
2026-03-29 18:30:05 +02:00
Loretta f47701af59 Add architecture docs for Mango.Nop & update dev DB config
Added detailed documentation: ARCHITECTURE.md, CONVENTIONS.md, GLOSSARY.md, and copilot-instructions.md for Mango.Nop.Core, Data, and Services. Updated/added README.md files for all Mango.Nop libraries and the FruitBank nopCommerce plugin, clarifying structure, key types, and usage. Switched appsettings.json connection string from production to development database. These changes improve developer onboarding and enforce architectural consistency.
2026-03-29 10:44:03 +02:00
Loretta 1a9e0dcf0b Merge branch '4.80' into FruitBank_v0.0.8.0 2026-03-28 16:22:34 +01:00
Loretta 1898f4d517 Update DLL HintPaths to use $(Configuration); prod DB switch
Replaced hardcoded Debug paths in all .csproj files with $(Configuration) for AyCode and FruitBank DLL references, enabling correct DLL resolution for any build configuration. Switched appsettings.json connection string to use the FruitBank_PROD database. Added a null check for StockTaking in StockSignalREndpointServer.cs to prevent possible null reference exceptions. Noted some encoding issues in .csproj comments and copyright fields. No other functional changes.
2026-03-24 18:30:43 +01:00
Loretta ced4b98908 Add serialization attributes and update to PROD config
Annotated core entities with AcBinarySerializable and ToonDescription for improved serialization and documentation. Updated appsettings.json to use the production database. Changed solution file to target Visual Studio 17. Added necessary using statements and metadata for entity classes.
2026-03-20 17:06:49 +01:00
Loretta e47c7c8f74 Remove IDE-specific workspace and layout files
Deleted .wsuo, DocumentLayout.json, and VSWorkspaceState.json files that store user-specific IDE state and metadata. This cleanup prevents sharing of personal or machine-specific settings in version control.
2026-02-03 18:24:17 +01:00
Loretta ca1ac2a2d1 Restore Product class and enums; add Toon metadata
Re-enable Product class and related enums in Nop.Core.Domain.Catalog, moving them from commented to active code. Add [ToonDescription] attributes to Product properties for enhanced documentation and serialization. Mark sensitive Customer fields with [ToonIgnore] to exclude them from Toon processing.
2026-01-17 09:19:46 +01:00
Loretta c0073815ae Add ToonDescription attributes to core entities and DTOs
Introduced ToonDescription annotations across DTO and entity classes to provide metadata, business rules, and documentation for classes and properties. Updated usings to include AyCode.Core.Serializers.Toons where needed. These changes improve code clarity and support tooling for documentation and code generation.
2026-01-15 11:34:06 +01:00
Loretta 954b99fcbc Add OrderStatus enum property to Order class
Introduced the OrderStatus property as an enum wrapper for OrderStatusId in the Order class. This enhances type safety and code readability by allowing direct use of the OrderStatus enum. The property is annotated with ToonDescription to clarify its purpose and business rule.
2026-01-15 11:33:51 +01:00
Loretta 7bcd601035 Add ToonDescription attributes for enhanced metadata support
Introduced Toon serialization/description attributes to CustomerDto, GenericAttribute, and OrderNote classes. Added table mapping and foreign key metadata, as well as necessary using statements, to improve serialization, documentation, and tooling capabilities.
2026-01-14 21:05:20 +01:00
Loretta 0eeefedee3 Add AyCode.Core.Helpers and Serializers usings to files
Added using statements for AyCode.Core.Helpers across several files and AyCode.Core.Serializers.Binaries in DevAdminSignalRHubSandbox.cs to enable new helper and serializer functionalities. No other code changes were made.
2025-12-19 07:14:58 +01:00
Loretta 99bfaf960b Minimal SignalR test hub and endpoint for isolated testing
Refactored Mango.Sandbox.EndPoints to enable minimal, dependency-light SignalR endpoint testing. Introduced DevAdminSignalRHubSandbox and TestSignalREndpoint for protocol/contract tests without full NopCommerce/FruitBank infra. Added SignalRClientSandbox and comprehensive MSTest coverage for all parameter types. Simplified Program.cs startup, updated project references, and added minimal logger. Original SignalREndpointTests replaced with focused, low-level and high-level tests. CORS and DTOs updated for compatibility.
2025-12-11 23:46:36 +01:00
Loretta 3c479e1ad5 Add stock-taking enhancements and validation updates
Enhanced stock-taking functionality by introducing the `CloseStockTaking` method in `StockTakingDbContext` to manage session closures with validation. Added the `IsReadyForClose` method to `IMgStockTaking` and its implementation in `MgStockTaking`. Integrated `FruitBankDbContext` for stock updates.

Improved logging in `StockSignalREndpointServer` and added a new SignalR endpoint for closing stock-taking sessions. Updated `AddStockTaking` to initialize properties for new entries.

Simplified loading logic in `OrderItemDtoDbTable` by commenting out unnecessary `.ThenLoad` chains. Enhanced validation in `RefreshStockTakingItemMeasuredValuesFromPallets` and adjusted return statements in `AddOrUpdateMeasuredStockTakingItemPallet` and `UpdateStockTakingItemPallet` to fetch updated entities.

These changes improve maintainability, robustness, and functionality across the codebase.
2025-12-08 15:49:50 +01:00
Loretta 120ed09738 .Net10, VS2026; StockTaking in progress... 2025-12-01 16:17:57 +01:00
Loretta b21197ca67 improvements, fixes 2025-11-26 09:42:07 +01:00
Loretta 6b09ba3d78 improvements, fixes, etc... 2025-11-24 08:27:02 +01:00
Loretta 4c5d5d55c4 improvements 2025-11-21 07:20:42 +01:00
Loretta 82c3b5a7cd StockQuantityHistoryExt, StockQuantityHistoryDto; improvements, fixes; 2025-11-13 19:58:39 +01:00
Loretta 7946a52219 improvements, fixes, etc.. 2025-11-11 20:51:28 +01:00
Loretta 5277a090fb Add MessagePack packages 2025-11-07 19:21:51 +01:00
Loretta ed7a2e8cd6 BaseEntity.ToString() 2025-11-07 05:56:47 +01:00
Loretta bef91e0131 ... 2025-11-06 15:11:59 +01:00
Loretta 2bd97c88ca Mango.Nop.Core cleanup; 2025-11-05 14:56:40 +01:00
Loretta 0da0565134 Add Mango.Nop.Data project to solution 2025-11-04 15:57:09 +01:00
Loretta ec71f81d4c SignalR improvements; etc... 2025-10-30 14:54:33 +01:00
Loretta df9157c4ab Price calculation improvements, fixes, etc... 2025-10-24 15:47:45 +02:00
Loretta 08069dda58 fixes 2025-10-24 12:01:48 +02:00
Loretta 7fb047fd28 Implement CheckAndUpdateProductManageInventoryMethodToManageStock to MgEventConsumer; improvements; 2025-10-24 08:12:35 +02:00
Loretta 56927f50ea GeneratePackageOnBuild false 2025-10-23 11:27:00 +02:00
Loretta 408fa0f87e refactoring, cleanup v1.1... 2025-10-22 16:20:36 +02:00
Loretta d52deeb2b4 refactoring, cleanup, etc... 2025-10-22 15:16:20 +02:00
Loretta 38e036553e noplogger improvements, fixes, etc... 2025-10-21 15:30:43 +02:00
Loretta fd39b0700a Add MeasurementOwnerId; RevisorId generic attributes to Order; improvements, fixes, etc.. 2025-10-20 16:46:47 +02:00
Loretta a0bb6117ae ShippingDocumentToFiles fix; CustomOrder create improvements; 2025-10-19 15:08:38 +02:00
Loretta 55f626c71d nuget packages update; fixes 2025-10-18 18:45:49 +02:00
Loretta c4742f0d86 improvements, fixes, etc... 2025-10-18 08:43:36 +02:00
Loretta f7f2d30915 improvements, fixes, etc... 2025-10-16 11:43:51 +02:00
Loretta 28de927bd9 ... 2 2025-10-15 08:08:12 +02:00
Loretta c397c68601 improvements, fixes, etc... 2025-10-13 14:18:14 +02:00
Loretta a5e3702616 fixes 2025-10-12 18:07:59 +02:00
Loretta a2ed202276 improvements, fixes, etc... 2025-10-12 07:47:55 +02:00
Loretta cefb19584d product event fixex; etc... 2025-10-11 17:51:30 +02:00
Loretta 28d9122818 improvements, etc 2025-10-11 12:52:55 +02:00
Loretta 6fecdd2317 ShippingItemPallets improvements 2025-10-05 14:55:36 +02:00
Loretta daa1421011 improvements, fixes, etc... 2025-10-03 07:19:24 +02:00
Loretta 96eb509ec5 improvements, fixes, etc... 2025-09-30 18:18:22 +02:00
Loretta 19cc506fc2 improvements, fixes, etc... 2025-09-30 07:24:36 +02:00
Loretta 08f91408c3 improvements, fixes, etc... 2025-09-29 13:33:39 +02:00
Loretta 32c37cf22c improvements, fixes 2025-09-23 12:17:23 +02:00
Loretta 871078c4ab improvements, fixes, etc... 2025-09-23 10:41:38 +02:00
Loretta dc8d41c4e8 nuget packages update 2025-09-19 14:44:25 +02:00
Loretta b0a99e139b Add FullName to CustomerDto 2025-09-17 05:31:49 +02:00
Loretta 5e2c30a4ec improvement, fixes, etc... 2025-09-12 13:34:54 +02:00
Loretta ef16260ebe improvements, fixes, etc... 2025-09-11 12:51:58 +02:00
Loretta 60f0071ade NopLogWriter fixes; SignalRLogger fixes; etc... 2025-09-05 06:18:01 +02:00
Loretta a1f7c8af2d Add appsettings.json to NopCommerce project! install reference packages; Implement endpoints; Fix CORS; etc... 2025-09-03 07:07:22 +02:00
Loretta 19662fd213 temp 2025-09-02 15:21:57 +02:00
Loretta 1c7980e209 remove Nop.Core, Nop.Data, Nop.Services from common library 2025-08-28 15:24:40 +02:00
Loretta eefb0392aa outputpath set to "bin\FruitBank" 2025-08-28 15:20:36 +02:00
Loretta 761001665f references fixed2 2025-08-28 14:53:58 +02:00
Loretta 9c363e360c references fixed 2025-08-28 14:51:35 +02:00
100 changed files with 5226 additions and 239 deletions

178
.github/copilot-instructions.md vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,47 +0,0 @@
{
"Version": 1,
"WorkspaceRootPath": "H:\\Applications\\Mango\\Source\\NopCommerce.Common\\4.70\\Libraries\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": -1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}"
},
{
"$type": "Bookmark",
"Name": "ST:129:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
},
{
"$type": "Bookmark",
"Name": "ST:128:0:{1fc202d4-d401-403c-9834-5b218574bb67}"
},
{
"$type": "Bookmark",
"Name": "ST:130:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
},
{
"$type": "Bookmark",
"Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
},
{
"$type": "Bookmark",
"Name": "ST:132:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
},
{
"$type": "Bookmark",
"Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}"
}
]
}
]
}
]
}

View File

@ -1,7 +0,0 @@
{
"ExpandedNodes": [
""
],
"SelectedNode": "\\H:\\Applications\\Mango\\Source\\NopCommerce.Common\\4.70\\Libraries",
"PreviewInSolutionExplorer": false
}

17
CLAUDE.md Normal file
View File

@ -0,0 +1,17 @@
CRITICAL: Your FIRST action in every session MUST be reading `.github/copilot-instructions.md`. Execute ALL session-start instructions found there before responding to any user query. It is the single source of truth for this repo.
## SEQUENTIAL EXECUTION OVERRIDE
The AI AGENT CORE PROTOCOL in copilot-instructions.md requires STRICT SEQUENTIAL execution. This OVERRIDES your default parallelization behavior. Do NOT parallelize doc reads with code searches. The sequence is:
1. Read copilot-instructions.md → process its rules FULLY
2. Read ALL docs/ .md files listed in the protocol → wait for completion
3. Output [LOADED_DOCS: ...] prefix
4. ONLY THEN respond to the user's query or search code
## Tool mapping for AI AGENT CORE PROTOCOL
The copilot-instructions.md references Copilot tool names. Map them to Claude Code tools:
- `get_file` / `file_search``Read`, `Glob`, `Grep`
- `code_search` / `get_symbols_by_name` / `find_symbol``Grep`, `Glob`
- `replace_string_in_file` / `edit_file``Edit`
- `create_file``Write`
Follow the protocol using YOUR tools. The rules (LOADED_DOCS prefix, hard-gate, no-re-read, context recovery, explicit consent) apply equally to Claude Code.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
namespace Mango.Nop.Core { public static class AcBinaryForcedInit { public static void ForceRegister() { System.Console.WriteLine("[SGEN TESTING] AcBinaryForcedInit called."); AyCode.Core.Serializers.Generated.AcBinaryGeneratedWritersInit.Register(); } } }

View File

@ -0,0 +1,60 @@
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
namespace Mango.Nop.Core.Dtos;
[AcBinarySerializable(false, true, false, true, false, false)]
[LinqToDB.Mapping.Table(Name = nameof(Customer))]
[System.ComponentModel.DataAnnotations.Schema.Table(nameof(Customer))]
[ToonDescription($"Data transfer object for {nameof(Customer)}", TypeRelation = ToonTypeRelation.DtoOf, RelatedTypes = [typeof(Customer)])]
public class CustomerDto : ModelDtoBase<Customer>, ISoftDeletedEntity
{
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ToonDescription(BusinessRule = "get => $\"{LastName} {FirstName}\"")]
public string FullName => $"{LastName} {FirstName}";
public int RegisteredInStoreId { get; set; }
public bool Deleted { get; set; }
public CustomerDto() :base()
{ }
public CustomerDto(int customerId) : base(customerId)
{ }
public CustomerDto(Customer customer) : base(customer)
{
}
public override void CopyDtoValuesToEntity(Customer entity)
{
base.CopyDtoValuesToEntity(entity);
entity.Username = Username;
entity.FirstName = FirstName;
entity.LastName = LastName;
entity.Email = Email;
entity.RegisteredInStoreId = RegisteredInStoreId;
entity.Deleted = Deleted;
}
public override void CopyEntityValuesToDto(Customer entity)
{
base.CopyEntityValuesToDto(entity);
Username = entity.Username;
FirstName = entity.FirstName;
LastName = entity.LastName;
Email = entity.Email;
RegisteredInStoreId = entity.RegisteredInStoreId;
Deleted = entity.Deleted;
}
}

View File

@ -0,0 +1,22 @@
using AyCode.Interfaces;
using AyCode.Interfaces.Entities;
using Nop.Core;
namespace Mango.Nop.Core.Dtos;
public interface IModelDtoBaseEmpty : IAcModelDtoBaseEmpty
{
}
public interface IModelDtoBase : IEntityInt, IModelDtoBaseEmpty
{
}
public interface IModelDtoBase<TMainEntity> : IModelDtoBase where TMainEntity : BaseEntity
{
TMainEntity CreateMainEntity();
void CopyDtoValuesToEntity(TMainEntity entity);
void CopyEntityValuesToDto(TMainEntity entity);
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AyCode.Interfaces.Entities;
using Nop.Core.Domain.Common;
namespace Mango.Nop.Core.Dtos
{
public interface IGenericAttributeDto : IModelDtoBase<GenericAttribute>
{
public int EntityId { get; set; }
public string KeyGroup { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public int StoreId { get; set; }
public DateTime? CreatedOrUpdatedDateUTC { get; set; }
}
public abstract class MgGenericAttributeDto : GenericAttribute, IGenericAttributeDto
{
public GenericAttribute CreateMainEntity()
{
var mainEntity = Activator.CreateInstance<GenericAttribute>();
CopyDtoValuesToEntity(mainEntity);
mainEntity.CreatedOrUpdatedDateUTC = DateTime.UtcNow;
return mainEntity;
}
public void CopyDtoValuesToEntity(GenericAttribute entity)
{
entity.Id = Id;
entity.Key = Key;
entity.Value = Value;
entity.EntityId = EntityId;
entity.KeyGroup = KeyGroup;
entity.StoreId = StoreId;
entity.CreatedOrUpdatedDateUTC = CreatedOrUpdatedDateUTC;
}
public void CopyEntityValuesToDto(GenericAttribute entity)
{
Id = entity.Id;
Key = entity.Key;
Value = entity.Value;
EntityId = entity.EntityId;
KeyGroup = entity.KeyGroup;
StoreId = entity.StoreId;
CreatedOrUpdatedDateUTC = entity.CreatedOrUpdatedDateUTC;
}
}
}

View File

@ -0,0 +1,116 @@
using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Toons;
using AyCode.Core.Helpers;
using LinqToDB.Mapping;
using Mango.Nop.Core.Entities;
using Mango.Nop.Core.Interfaces;
using Newtonsoft.Json;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using System.Linq.Expressions;
namespace Mango.Nop.Core.Dtos;
[ToonDescription("Base DTO for orders with items, customer and status tracking")]
public abstract class MgOrderDto<TOrderItemDto, TProductDto> : MgEntityBase, IModelDtoBase<Order>, IMgOrderDto<TOrderItemDto, TProductDto> where TOrderItemDto : IMgOrderItemDto<TProductDto> where TProductDto : IMgProductDto
{
public Guid OrderGuid { get; set; }
public int StoreId { get; set; }
public int CustomerId { get; set; }
public int OrderStatusId { get; set; }
public int ShippingStatusId { get; set; }
public decimal OrderDiscount { get; set; }
public decimal OrderTotal { get; set; }
public DateTime CreatedOnUtc { get; set; }
public DateTime? PaidDateUtc { get; set; }
public int PaymentStatusId { get; set; }
public string ShippingMethod { get; set; }
public string CustomOrderNumber { get; set; }
public string CustomValuesXml { get; set; }
public bool Deleted { get; set; }
[Association(ThisKey = nameof(CustomerId), OtherKey = nameof(Customer.Id), CanBeNull = false)]
public Customer Customer { get; set; } //TODO: CustomerDto!!! - J.
[Association(ThisKey = nameof(Id), OtherKey = nameof(OrderItem.OrderId), CanBeNull = true)]
public List<TOrderItemDto> OrderItemDtos { get; set; }
[Association(ThisKey = nameof(Id), OtherKey = nameof(OrderNote.OrderId), CanBeNull = true)]
public List<OrderNote> OrderNotes { get; set; }
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => OrderStatusId")]
public OrderStatus OrderStatus
{
get => (OrderStatus)OrderStatusId;
set => OrderStatusId = (int)value;
}
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ShippingStatusId")]
public ShippingStatus ShippingStatus
{
get => (ShippingStatus)ShippingStatusId;
set => ShippingStatusId = (int)value;
}
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => PaymentStatusId")]
public PaymentStatus PaymentStatus
{
get => (PaymentStatus)PaymentStatusId;
set => PaymentStatusId = (int)value;
}
protected MgOrderDto() :base()
{ }
protected MgOrderDto(int orderId)
{
Id = orderId;
}
protected MgOrderDto(Order order)
{
CopyEntityValuesToDto(order);
}
public virtual void CopyDtoValuesToEntity(Order entity)
{
//var config = new MapperConfiguration(cfg =>
//{
// cfg.CreateMap<MgOrderDto<Order, Order>();
// //cfg.CreateMap<Person, Person>()
// // .ForMember(dest => dest.Address, opt => opt.MapFrom(src => _mapper.Map<Address>(src.Address)));
//});
//var _mapper = config.CreateMapper();
//_mapper.Map<>
PropertyHelper.CopyPublicValueTypeProperties(this, entity);
}
public virtual void CopyEntityValuesToDto(Order entity)
{
PropertyHelper.CopyPublicValueTypeProperties(entity, this);
}
public virtual void CopyEntityValuesToDto(Order entity, List<TOrderItemDto> orderItemDtos)
{
CopyEntityValuesToDto(entity);
InitializeOrderItemDtos(orderItemDtos);
}
public virtual void InitializeOrderItemDtos(List<TOrderItemDto> orderItemDtos)
{
OrderItemDtos = orderItemDtos;
}
public virtual Order CreateMainEntity()
{
//base.CreateMainEntity();
var order = new Order();
CopyDtoValuesToEntity(order);
return order;
}
}

View File

@ -0,0 +1,79 @@
using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Toons;
using AyCode.Core.Helpers;
using LinqToDB.Mapping;
using Mango.Nop.Core.Entities;
using Mango.Nop.Core.Interfaces;
using Nop.Core;
using Nop.Core.Domain.Orders;
namespace Mango.Nop.Core.Dtos;
[ToonDescription("Base DTO for order items with product association")]
public abstract class MgOrderItemDto<TProductDto> : MgEntityBase, IModelDtoBase<OrderItem>, IMgOrderItemDto<TProductDto> where TProductDto : IMgProductDto
{
public Guid OrderItemGuid { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal UnitPriceInclTax { get; set; }
public decimal UnitPriceExclTax { get; set; }
public decimal PriceInclTax { get; set; }
public decimal PriceExclTax { get; set; }
public string AttributesXml { get; set; }
public decimal? ItemWeight { get; set; }
[ToonDescription(BusinessRule = "get => ProductDto?.Name ?? 'ProductDto is null!!!'")]
public string ProductName => ProductDto?.Name ?? "ProductDto is null!!!";
[Association(ThisKey = nameof(ProductId), OtherKey = nameof(BaseEntity.Id), CanBeNull = true)]
public TProductDto? ProductDto { get; set; }
protected MgOrderItemDto() :base()
{ }
protected MgOrderItemDto(int orderItemId)
{
Id = orderItemId;
}
protected MgOrderItemDto(OrderItem orderItem)
{
CopyEntityValuesToDto(orderItem);
}
public virtual void CopyDtoValuesToEntity(OrderItem entity)
{
PropertyHelper.CopyPublicValueTypeProperties(this, entity);
}
public virtual void CopyEntityValuesToDto(OrderItem entity)
{
PropertyHelper.CopyPublicValueTypeProperties(entity, this);
}
public virtual void CopyEntityValuesToDto(OrderItem entity, TProductDto productDto)
{
CopyEntityValuesToDto(entity);
InitializeProductDto(productDto);
}
public virtual void InitializeProductDto(TProductDto productDto)
{
ProductDto = productDto;
}
public virtual OrderItem CreateMainEntity()
{
//base.CreateMainEntity();
var orderItem = new OrderItem();
CopyDtoValuesToEntity(orderItem);
return orderItem;
}
}

View File

@ -0,0 +1,102 @@
using AyCode.Interfaces.Entities;
using AyCode.Core.Serializers.Toons;
using Mango.Nop.Core.Entities;
using Mango.Nop.Core.Interfaces;
//using Nop.Core.Domain.Catalog;
namespace Mango.Nop.Core.Dtos;
[ToonDescription("Base DTO for products with warehouse and pricing")]
public abstract class MgProductDto : MgEntityBase, /*Product,*/ IMgProductDto//IModelDtoBase<Product>//, IDiscountSupported<DiscountProductMapping>
{
//public int Id { get; set; }
public int ProductTypeId { get; set; }
public int ParentGroupedProductId { get; set; }
public string Name { get; set; }
public string ShortDescription { get; set; }
public string FullDescription { get; set; }
public string Sku { get; set; }
public int WarehouseId { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
public decimal ProductCost { get; set; }
public decimal Weight { get; set; }
public decimal Length { get; set; }
public decimal Width { get; set; }
public decimal Height { get; set; }
public bool Deleted { get; set; }
public bool SubjectToAcl { get; set; }
public bool LimitedToStores { get; set; }
protected MgProductDto() :base()
{ }
protected MgProductDto(int productId)
{
Id = productId;
}
//protected MgProductDto(Product product)
//{
// CopyEntityValuesToDto(product);
//}
//public virtual void CopyDtoValuesToEntity(Product entity)
//{
// entity.Id = Id;
// entity.ProductTypeId = ProductTypeId;
// entity.ParentGroupedProductId = ParentGroupedProductId;
// entity.Name = Name;
// entity.ShortDescription = ShortDescription;
// entity.FullDescription = FullDescription;
// entity.WarehouseId = WarehouseId;
// entity.StockQuantity = StockQuantity;
// entity.Weight = Weight;
// entity.Length = Length;
// entity.Width = Width;
// entity.Height = Height;
// entity.Deleted = Deleted;
//}
//public virtual void CopyEntityValuesToDto(Product entity)
//{
// Id = entity.Id;
// ProductTypeId = entity.ProductTypeId;
// ParentGroupedProductId = entity.ParentGroupedProductId;
// Name = entity.Name;
// ShortDescription = entity.ShortDescription;
// FullDescription = entity.FullDescription;
// WarehouseId = entity.WarehouseId;
// StockQuantity = entity.StockQuantity;
// Weight = entity.Weight;
// Length = entity.Length;
// Width = entity.Width;
// Height = entity.Height;
// Deleted = entity.Deleted;
//}
//public virtual Product CreateMainEntity()
//{
// //base.CreateMainEntity();
// var product = new Product();
// CopyDtoValuesToEntity(product);
// return product;
//}
}

View File

@ -0,0 +1,72 @@
using AyCode.Interfaces.Entities;
using AyCode.Core.Serializers.Toons;
using LinqToDB.Mapping;
using Mango.Nop.Core.Entities;
using Mango.Nop.Core.Interfaces;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Orders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mango.Nop.Core.Dtos
{
public interface IMgTStockQuantityHistoryDto<TProductDto> : IEntityInt, IMgStockQuantityHistory where TProductDto : IMgProductDto
{
public TProductDto ProductDto { get; set; }
}
[ToonDescription("Base DTO for stock quantity history with product")]
public abstract class MgStockQuantityHistoryDto<TProductDto> : MgEntityBase, IModelDtoBase<StockQuantityHistory>,
IMgTStockQuantityHistoryDto<TProductDto> where TProductDto : IMgProductDto
{
public int ProductId { get; set; }
public int QuantityAdjustment { get; set; }
public int StockQuantity { get; set; }
public string Message { get; set; }
public int? CombinationId { get; set; }
public int? WarehouseId { get; set; }
public DateTime CreatedOnUtc { get; set; }
[Association(ThisKey = nameof(ProductId), OtherKey = nameof(IMgProductDto.Id), CanBeNull = false)]
public TProductDto ProductDto { get; set; }
public void CopyDtoValuesToEntity(Order entity)
{
throw new NotImplementedException();
}
public void CopyDtoValuesToEntity(StockQuantityHistory entity)
{
throw new NotImplementedException();
}
public void CopyEntityValuesToDto(Order entity)
{
throw new NotImplementedException();
}
public void CopyEntityValuesToDto(StockQuantityHistory entity)
{
throw new NotImplementedException();
}
public Order CreateMainEntity()
{
throw new NotImplementedException();
}
StockQuantityHistory IModelDtoBase<StockQuantityHistory>.CreateMainEntity()
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,46 @@
using AyCode.Core.Interfaces;
using Nop.Core;
namespace Mango.Nop.Core.Dtos;
public abstract class ModelDtoBase : IModelDtoBase
{
public int Id { get; set; }
protected ModelDtoBase(){}
protected ModelDtoBase(int id) => Id = id;
}
public abstract class ModelDtoBase<TMainEntity> : ModelDtoBase, IModelDtoBase<TMainEntity> where TMainEntity : BaseEntity
{
protected ModelDtoBase() : base()
{
}
protected ModelDtoBase(int id) : base(id)
{
}
protected ModelDtoBase(TMainEntity mainEntity)
{
CopyEntityValuesToDto(mainEntity);
}
public virtual TMainEntity CreateMainEntity()
{
var mainEntity = Activator.CreateInstance<TMainEntity>();
CopyDtoValuesToEntity(mainEntity);
return mainEntity;
}
public virtual void CopyDtoValuesToEntity(TMainEntity entity)
{
entity.Id = Id;
}
public virtual void CopyEntityValuesToDto(TMainEntity entity)
{
Id = entity.Id;
}
}

View File

@ -1,8 +1,14 @@
using AyCode.Interfaces.Entities;
using AyCode.Core.Serializers.Toons;
using Nop.Core;
namespace Mango.Nop.Core.Entities;
public class MgEntityBase : BaseEntity, IEntityInt
[ToonDescription("Base entity class with Id property and ToString override")]
public abstract class MgEntityBase : BaseEntity, IEntityInt
{
public override string ToString()
{
return $"{GetType().Name}; Id: {Id}";
}
}

View File

@ -0,0 +1,31 @@
using System.ComponentModel.Design;
using AyCode.Core.Serializers.Toons;
using AyCode.Interfaces.Entities;
using AyCode.Interfaces.TimeStampInfo;
using LinqToDB.Mapping;
namespace Mango.Nop.Core.Entities;
public interface IMgStockTaking : IEntityInt, ITimeStampInfo
{
public DateTime StartDateTime { get; set; }
public bool IsClosed { get; set; }
public bool IsReadyForClose();
}
[ToonDescription("Base entity for stock taking sessions with items")]
public abstract class MgStockTaking<TStockTakingItem> : MgEntityBase, IMgStockTaking where TStockTakingItem : class, IMgStockTakingItem
{
public DateTime StartDateTime { get; set; }
public bool IsClosed { get; set; }
public abstract bool IsReadyForClose();
[Association(ThisKey = nameof(Id), OtherKey = nameof(IMgStockTakingItem.StockTakingId), CanBeNull = true)]
public List<TStockTakingItem>? StockTakingItems { get; set; }
public int Creator { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
}

View File

@ -0,0 +1,43 @@
using AyCode.Interfaces.Entities;
using AyCode.Core.Serializers.Toons;
using AyCode.Interfaces.TimeStampInfo;
using LinqToDB;
using LinqToDB.Mapping;
using Mango.Nop.Core.Dtos;
using Mango.Nop.Core.Interfaces;
namespace Mango.Nop.Core.Entities;
public interface IMgStockTakingItem : IEntityInt, ITimeStampInfo
{
public int StockTakingId { get; set; }
public int ProductId { get; set; }
public bool IsMeasured { get; set; }
public int OriginalStockQuantity { get; set; }
public int MeasuredStockQuantity { get; set; }
}
[ToonDescription("Base entity for stock taking items with product association")]
public abstract class MgStockTakingItem<TStockTaking, TProduct> : MgEntityBase, IMgStockTakingItem
where TStockTaking : class, IMgStockTaking where TProduct : class, IMgProductDto
{
public int StockTakingId { get; set; }
public int ProductId { get; set; }
public bool IsMeasured { get; set; }
public int OriginalStockQuantity { get; set; }
public int MeasuredStockQuantity { get; set; }
[Association(ThisKey = nameof(StockTakingId), OtherKey = nameof(IMgStockTaking.Id), CanBeNull = true)]
public TStockTaking? StockTaking{ get; set; }
[Association(ThisKey = nameof(ProductId), OtherKey = nameof(IMgProductDto.Id), CanBeNull = true)]
public TProduct? Product { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
}

View File

@ -0,0 +1,27 @@
using AyCode.Core.Enums;
using AyCode.Core.Extensions;
using Nop.Core;
namespace Mango.Nop.Core.Extensions;
public static class CollectionExtensionsNopBaseEntity
{
public static void UpdateBaseEntityCollection<TDataItem>(this IList<TDataItem> source, IList<TDataItem> dataItems, bool isRemove) where TDataItem : BaseEntity
{
if (source == null) throw new ArgumentNullException(nameof(source), $"source == null");
if (dataItems == null) throw new ArgumentNullException(nameof(dataItems), $"dataItems == null");
foreach (var dataItem in dataItems)
{
source.UpdateBaseEntityCollection(dataItem, isRemove);
}
}
public static TrackingState UpdateBaseEntityCollection<TDataItem>(this IList<TDataItem> source, TDataItem dataItem, bool isRemove) where TDataItem : BaseEntity
{
if (source == null) throw new ArgumentNullException(nameof(source), $"source == null");
var index = source.FindIndex(x => x.Id == dataItem.Id);
return source.UpdateCollectionByIndex(index, dataItem, isRemove);
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using AyCode.Utils.Extensions;
using Mango.Nop.Core.Utils;
using Nop.Core.Domain.Common;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Mango.Nop.Core.Extensions;
public static class GenericAttributeExtensions
{
public static TValue? GetValueOrNull<TValue>(this IEnumerable<GenericAttribute> src, string key) where TValue : struct
{
var ga = src.SingleOrDefault(x => x.Key == key);
if (ga == null || ga.Value.IsNullOrWhiteSpace()) return null;
return CommonHelper2.To<TValue>(ga.Value);
}
public static TValue GetValueOrDefault<TValue>(this IEnumerable<GenericAttribute> src, string key, TValue defaultValue = default) where TValue : struct
{
var gaValue = GetValueOrNull<TValue>(src, key);
return gaValue == null ? defaultValue : CommonHelper2.To<TValue>(gaValue);
}
public static bool TryGetValue<TValue>(this IEnumerable<GenericAttribute> src, string key, [NotNullWhen(true)] out TValue? value) where TValue : struct
{
value = null;
var gaValue = GetValueOrNull<TValue>(src, key);
if (gaValue == null) return false;
value = CommonHelper2.To<TValue>(gaValue);
return true;
}
public static GenericAttribute AddNewGenericAttribute(this ICollection<GenericAttribute> src, string keyGroup, string key, string value, int entityId, int storeId)
{
var ga = new GenericAttribute
{
KeyGroup = keyGroup,
Key = key,
Value = value,
EntityId = entityId,
StoreId = storeId,
CreatedOrUpdatedDateUTC = DateTime.UtcNow
};
src.Add(ga);
return ga;
}
}

View File

@ -0,0 +1,9 @@
using AyCode.Core.Interfaces;
using AyCode.Interfaces;
namespace Mango.Nop.Core.Interfaces.ForeignKeys;
public interface ICustomerForeignKey : IForeignKey
{
int CustomerId { get; set; }
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using AyCode.Core.Interfaces;
using Nop.Core.Domain.Common;
namespace Mango.Nop.Core.Interfaces.ForeignKeys;
public interface IGenericAttributeForeignList : IGenericAttributeForeignCollection<List<GenericAttribute>>
{
}
public interface IGenericAttributeForeignCollection<T> : IForeignCollection<T> where T : IEnumerable<GenericAttribute>
{
T GenericAttributes { get; }
}

View File

@ -1,6 +0,0 @@
namespace Mango.Nop.Core.Interfaces;
public interface IMgDbContextBase //: IAcDbContextBase
{
}

View File

@ -0,0 +1,41 @@
using AyCode.Interfaces.Entities;
using Mango.Nop.Core.Dtos;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Shipping;
namespace Mango.Nop.Core.Interfaces;
public interface IMgOrderDto<TOrderItemDto, TProductDto> : IEntityInt, ISoftDeletedEntity where TOrderItemDto : IMgOrderItemDto<TProductDto> where TProductDto : IMgProductDto
{
public Guid OrderGuid { get; set; }
public int StoreId { get; set; }
public int CustomerId { get; set; }
public int OrderStatusId { get; set; }
public OrderStatus OrderStatus { get; set; }
public int ShippingStatusId { get; set; }
public ShippingStatus ShippingStatus { get; set; }
public decimal OrderDiscount { get; set; }
public decimal OrderTotal { get; set; }
public DateTime CreatedOnUtc { get; set; }
public DateTime? PaidDateUtc { get; set; }
public string ShippingMethod { get; set; }
public string CustomOrderNumber { get; set; }
public string CustomValuesXml { get; set; }
public Customer Customer { get; } //TODO: CustomerDto!!! - J.
public List<TOrderItemDto> OrderItemDtos { get; }
public List<OrderNote> OrderNotes { get; }
void InitializeOrderItemDtos(List<TOrderItemDto> orderItemDtos);
void CopyEntityValuesToDto(Order entity, List<TOrderItemDto> orderItemDtos);
}

View File

@ -0,0 +1,25 @@
using AyCode.Interfaces.Entities;
using Nop.Core.Domain.Orders;
namespace Mango.Nop.Core.Interfaces;
public interface IMgOrderItemDto<TProductDto> : IEntityInt where TProductDto : IMgProductDto
{
public Guid OrderItemGuid { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public string AttributesXml { get; set; }
public decimal? ItemWeight { get; set; }
public string ProductName { get; }
public TProductDto? ProductDto { get; set; }
void InitializeProductDto(TProductDto productDto);
void CopyEntityValuesToDto(OrderItem entity, TProductDto productDto);
}

View File

@ -0,0 +1,29 @@
using AyCode.Interfaces.Entities;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Security;
using Nop.Core.Domain.Seo;
using Nop.Core.Domain.Stores;
namespace Mango.Nop.Core.Interfaces;
public interface IMgProductDto : IEntityInt, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported/*, IDiscountSupported<DiscountProductMapping>*/, ISoftDeletedEntity
{
int ProductTypeId { get; set; }
int ParentGroupedProductId { get; set; }
string Name { get; set; }
string ShortDescription { get; set; }
string FullDescription { get; set; }
string Sku { get; set; }
int WarehouseId { get; set; }
decimal Price { get; set; }
int StockQuantity { get; set; }
decimal ProductCost { get; set; }
decimal Weight { get; set; }
decimal Length { get; set; }
decimal Width { get; set; }
decimal Height { get; set; }
}

View File

@ -0,0 +1,12 @@
using AyCode.Core.Loggers;
namespace Mango.Nop.Core.Loggers;
public interface ILogger<TCategory> : ILogger
{
}
public interface ILogger : IAcLoggerBase
{
}

View File

@ -0,0 +1,31 @@
using AyCode.Core.Enums;
using AyCode.Core.Loggers;
namespace Mango.Nop.Core.Loggers;
public class Logger<TCategory> : Logger, ILogger<TCategory>
{
//public Logger() : base(typeof(TCategory).Name)
//{ }
public Logger(params IAcLogWriterBase[] logWriters) : base(typeof(TCategory).Name, logWriters)
{ }
public Logger(AppType appType, LogLevel logLevel, params IAcLogWriterBase[] logWriters) : base(appType, logLevel, typeof(TCategory).Name, logWriters)
{ }
}
public class Logger : AcLoggerBase, ILogger
{
public Logger(params IAcLogWriterBase[] logWriters) : this(null, logWriters)
{ }
public Logger(string? categoryName) : base(categoryName)
{ }
public Logger(string? categoryName, params IAcLogWriterBase[] logWriters) : base(categoryName, logWriters)
{ }
public Logger(AppType appType, LogLevel logLevel, string? categoryName, params IAcLogWriterBase[] logWriters) : base(appType, logLevel, categoryName, logWriters)
{ }
}

View File

@ -1,44 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath>bin\FruitBank</BaseOutputPath>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MessagePack.Annotations" Version="2.5.192" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="linq2db" Version="5.4.1" />
<PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Core\Nop.Core.csproj" />
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Data\Nop.Data.csproj" />
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Services\Nop.Services.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="AyCode.Core">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
</Reference>
<Reference Include="AyCode.Core.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Utils">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="AyCode.Core">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.dll</HintPath>
</Reference>
<Reference Include="AyCode.Core.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Utils">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Utils.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Folder Include="ExtendedFactories\" />
<Folder Include="ExtendedModels\" />
</ItemGroup>
<ItemGroup>
<None Include="docs\**\*.md" />
<None Include="**\README.md" Exclude="$(DefaultItemExcludes);docs\**" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
using AyCode.Interfaces;
using Mango.Nop.Core.Interfaces;
namespace Mango.Nop.Core.Models;
public class MgLoginModelRequest : IAcModelDtoBaseEmpty
{
public MgLoginModelRequest(){}
public MgLoginModelRequest(string email, string password)
{
Email = email;
Password = password;
}
public string Email { get; set; }
public string Password { get; set; }
}

View File

@ -0,0 +1,24 @@
using AyCode.Interfaces;
using AyCode.Utils.Extensions;
using Mango.Nop.Core.Dtos;
using Nop.Core.Domain.Customers;
namespace Mango.Nop.Core.Models;
public class MgLoginModelResponse : IAcModelDtoBaseEmpty
{
public CustomerDto? CustomerDto { get; set; }
public string ErrorMessage { get; set; }
public MgLoginModelResponse()
{ }
public MgLoginModelResponse(Customer customer, string errorMessage) : this(new CustomerDto(customer), errorMessage)
{ }
public MgLoginModelResponse(CustomerDto customerDto, string errorMessage)
{
CustomerDto = customerDto;
ErrorMessage = errorMessage;
}
public bool IsSuccesLogin => CustomerDto != null && ErrorMessage.IsNullOrWhiteSpace();
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AyCode.Core.Consts;
namespace Mango.Nop.Core
{
public abstract class NopCommonConst: AcConst
{
//public static string DefaultLocale = "en-US";
//public static string BaseUrl = "https://localhost:7144";
//public static string LoggerHubName = "loggerHub";
}
}

View File

@ -0,0 +1,22 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a backorder mode
/// </summary>
public enum BackorderMode
{
/// <summary>
/// No backorders
/// </summary>
NoBackorders = 0,
/// <summary>
/// Allow qty below 0
/// </summary>
AllowQtyBelow0 = 1,
/// <summary>
/// Allow qty below 0 and notify customer
/// </summary>
AllowQtyBelow0AndNotifyCustomer = 2,
}

View File

@ -0,0 +1,28 @@
//namespace Nop.Core.Domain.Common;
using AyCode.Core.Serializers.Toons;
using AyCode.Interfaces.Entities;
namespace Nop.Core;
/// <summary>
/// Represents the base class for entities
/// </summary>
public abstract partial class BaseEntity : IBaseEntity
{
/// <summary>
/// Gets or sets the entity identifier
/// </summary>
public int Id { get; set; }
public override string ToString()
{
return $"{GetType().Name} [Id: {Id}]";
}
}
public interface IBaseEntity //: IEntityInt
{
public int Id { get; set; }
}

View File

@ -0,0 +1,275 @@
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Tax;
namespace Nop.Core.Domain.Customers;
/// <summary>
/// Represents a customer
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
[ToonDescription("NopCommerce customer entity")]
public partial class Customer : BaseEntity, ISoftDeletedEntity
{
public Customer()
{
CustomerGuid = Guid.NewGuid();
}
/// <summary>
/// Gets or sets the customer GUID
/// </summary>
public Guid CustomerGuid { get; set; }
/// <summary>
/// Gets or sets the username
/// </summary>
//[ToonIgnore]
public string Username { get; set; }
/// <summary>
/// Gets or sets the email
/// </summary>
//[ToonIgnore]
public string Email { get; set; }
/// <summary>
/// Gets or sets the first name
/// </summary>
//[ToonIgnore]
public string FirstName { get; set; }
/// <summary>
/// Gets or sets the last name
/// </summary>
//[ToonIgnore]
public string LastName { get; set; }
/// <summary>
/// Gets or sets the gender
/// </summary>
public string Gender { get; set; }
/// <summary>
/// Gets or sets the date of birth
/// </summary>
public DateTime? DateOfBirth { get; set; }
/// <summary>
/// Gets or sets the company
/// </summary>
public string Company { get; set; }
/// <summary>
/// Gets or sets the street address
/// </summary>
public string StreetAddress { get; set; }
/// <summary>
/// Gets or sets the street address 2
/// </summary>
public string StreetAddress2 { get; set; }
/// <summary>
/// Gets or sets the zip
/// </summary>
public string ZipPostalCode { get; set; }
/// <summary>
/// Gets or sets the city
/// </summary>
public string City { get; set; }
/// <summary>
/// Gets or sets the county
/// </summary>
public string County { get; set; }
/// <summary>
/// Gets or sets the country id
/// </summary>
public int CountryId { get; set; }
/// <summary>
/// Gets or sets the state province id
/// </summary>
public int StateProvinceId { get; set; }
/// <summary>
/// Gets or sets the phone number
/// </summary>
public string Phone { get; set; }
/// <summary>
/// Gets or sets the fax
/// </summary>
public string Fax { get; set; }
/// <summary>
/// Gets or sets the vat number
/// </summary>
public string VatNumber { get; set; }
/// <summary>
/// Gets or sets the vat number status id
/// </summary>
public int VatNumberStatusId { get; set; }
/// <summary>
/// Gets or sets the time zone id
/// </summary>
public string TimeZoneId { get; set; }
/// <summary>
/// Gets or sets the custom attributes
/// </summary>
public string CustomCustomerAttributesXML { get; set; }
/// <summary>
/// Gets or sets the currency id
/// </summary>
public int? CurrencyId { get; set; }
/// <summary>
/// Gets or sets the language id
/// </summary>
public int? LanguageId { get; set; }
/// <summary>
/// Gets or sets the tax display type id
/// </summary>
public int? TaxDisplayTypeId { get; set; }
/// <summary>
/// Gets or sets the email that should be re-validated. Used in scenarios when a customer is already registered and wants to change an email address.
/// </summary>
public string EmailToRevalidate { get; set; }
/// <summary>
/// Gets or sets the admin comment
/// </summary>
public string AdminComment { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer is tax exempt
/// </summary>
public bool IsTaxExempt { get; set; }
/// <summary>
/// Gets or sets the affiliate identifier
/// </summary>
public int AffiliateId { get; set; }
/// <summary>
/// Gets or sets the vendor identifier with which this customer is associated (manager)
/// </summary>
public int VendorId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this customer has some products in the shopping cart
/// <remarks>The same as if we run ShoppingCartItems.Count > 0
/// We use this property for performance optimization:
/// if this property is set to false, then we do not need to load "ShoppingCartItems" navigation property for each page load
/// It's used only in a couple of places in the presentation layer
/// </remarks>
/// </summary>
public bool HasShoppingCartItems { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer is required to re-login
/// </summary>
public bool RequireReLogin { get; set; }
/// <summary>
/// Gets or sets a value indicating number of failed login attempts (wrong password)
/// </summary>
public int FailedLoginAttempts { get; set; }
/// <summary>
/// Gets or sets the date and time until which a customer cannot login (locked out)
/// </summary>
public DateTime? CannotLoginUntilDateUtc { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer is active
/// </summary>
public bool Active { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer has been deleted
/// </summary>
public bool Deleted { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer account is system
/// </summary>
public bool IsSystemAccount { get; set; }
/// <summary>
/// Gets or sets the customer system name
/// </summary>
public string SystemName { get; set; }
/// <summary>
/// Gets or sets the last IP address
/// </summary>
public string LastIpAddress { get; set; }
/// <summary>
/// Gets or sets the date and time of entity creation
/// </summary>
public DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of last login
/// </summary>
public DateTime? LastLoginDateUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of last activity
/// </summary>
public DateTime LastActivityDateUtc { get; set; }
/// <summary>
/// Gets or sets the store identifier in which customer registered
/// </summary>
public int RegisteredInStoreId { get; set; }
/// <summary>
/// Gets or sets the billing address identifier
/// </summary>
public int? BillingAddressId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer is required to change password
/// </summary>
public bool MustChangePassword { get; set; }
/// <summary>
/// Gets or sets the shipping address identifier
/// </summary>
public int? ShippingAddressId { get; set; }
#region Custom properties
/// <summary>
/// Gets or sets the vat number status
/// </summary>
public VatNumberStatus VatNumberStatus
{
get => (VatNumberStatus)VatNumberStatusId;
set => VatNumberStatusId = (int)value;
}
/// <summary>
/// Gets or sets the tax display type
/// </summary>
public TaxDisplayType? TaxDisplayType
{
get => TaxDisplayTypeId.HasValue ? (TaxDisplayType)TaxDisplayTypeId : null;
set => TaxDisplayTypeId = value.HasValue ? (int)value : null;
}
#endregion
}

View File

@ -0,0 +1,61 @@
using AyCode.Core.Serializers.Attributes;
namespace Nop.Core.Domain.Customers;
/// <summary>
/// Represents a customer role
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
public partial class CustomerRole : BaseEntity
{
/// <summary>
/// Gets or sets the customer role name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer role is marked as free shipping
/// </summary>
public bool FreeShipping { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer role is marked as tax exempt
/// </summary>
public bool TaxExempt { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer role is active
/// </summary>
public bool Active { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customer role is system
/// </summary>
public bool IsSystemRole { get; set; }
/// <summary>
/// Gets or sets the customer role system name
/// </summary>
public string SystemName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customers must change passwords after a specified time
/// </summary>
public bool EnablePasswordLifetime { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the customers of this role have other tax display type chosen instead of the default one
/// </summary>
public bool OverrideTaxDisplayType { get; set; }
/// <summary>
/// Gets or sets identifier of the default tax display type (used only with "OverrideTaxDisplayType" enabled)
/// </summary>
public int DefaultTaxDisplayTypeId { get; set; }
/// <summary>
/// Gets or sets a product identifier that is required by this customer role.
/// A customer is added to this customer role once a specified product is purchased.
/// </summary>
public int PurchasedWithProductId { get; set; }
}

View File

@ -0,0 +1,19 @@
namespace Nop.Core.Domain.Discounts;
public abstract partial class DiscountMapping : BaseEntity
{
/// <summary>
/// Gets the entity identifier
/// </summary>
public new int Id { get; }
/// <summary>
/// Gets or sets the discount identifier
/// </summary>
public int DiscountId { get; set; }
/// <summary>
/// Gets or sets the entity identifier
/// </summary>
public abstract int EntityId { get; set; }
}

View File

@ -0,0 +1,16 @@
using AyCode.Core.Serializers.Attributes;
namespace Nop.Core.Domain.Discounts;
/// <summary>
/// Represents a discount-product mapping class
/// </summary>
// SGen incompatible: DiscountMapping.Id is readonly (new int Id { get; }) — generated reader cannot set it (CS0200)
//[AcBinarySerializable(false, true, false, true, false)]
public partial class DiscountProductMapping : DiscountMapping
{
/// <summary>
/// Gets or sets the product identifier
/// </summary>
public override int EntityId { get; set; }
}

View File

@ -0,0 +1,45 @@
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
namespace Nop.Core.Domain.Common;
/// <summary>
/// Represents a generic attribute
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
[ToonDescription("NopCommerce generic attribute for key-value storage", Purpose = "A flexible key-value store used to extend entities with custom business logic data without changing the database schema")]
public partial class GenericAttribute : BaseEntity
{
/// <summary>
/// Gets or sets the entity identifier
/// </summary>
///
[ToonDescription(Constraints = "polymorphic-fk(KeyGroup)")]
public int EntityId { get; set; }
/// <summary>
/// Gets or sets the key group
/// </summary>
public string KeyGroup { get; set; }
/// <summary>
/// Gets or sets the key
/// </summary>
public string Key { get; set; }
/// <summary>
/// Gets or sets the value
/// </summary>
[ToonDescription(Purpose = "Raw string representation of the Key's value")]
public string Value { get; set; }
/// <summary>
/// Gets or sets the store identifier
/// </summary>
public int StoreId { get; set; }
/// <summary>
/// Gets or sets the created or updated date
/// </summary>
public DateTime? CreatedOrUpdatedDateUTC { get; set; }
}

View File

@ -0,0 +1,344 @@
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
namespace Nop.Core.Domain.Orders;
/// <summary>
/// Represents an order
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
[ToonDescription("NopCommerce order entity with payment and shipping")]
public partial class Order : BaseEntity, ISoftDeletedEntity
{
#region Properties
/// <summary>
/// Gets or sets the order identifier
/// </summary>
public Guid OrderGuid { get; set; }
/// <summary>
/// Gets or sets the store identifier
/// </summary>
public int StoreId { get; set; }
/// <summary>
/// Gets or sets the customer identifier
/// </summary>
public int CustomerId { get; set; }
/// <summary>
/// Gets or sets the billing address identifier
/// </summary>
public int BillingAddressId { get; set; }
/// <summary>
/// Gets or sets the shipping address identifier
/// </summary>
public int? ShippingAddressId { get; set; }
/// <summary>
/// Gets or sets the pickup address identifier
/// </summary>
public int? PickupAddressId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a customer chose "pick up in store" shipping option
/// </summary>
public bool PickupInStore { get; set; }
/// <summary>
/// Gets or sets an order status identifier
/// </summary>
public int OrderStatusId { get; set; }
/// <summary>
/// Gets or sets the shipping status identifier
/// </summary>
public int ShippingStatusId { get; set; }
/// <summary>
/// Gets or sets the payment status identifier
/// </summary>
public int PaymentStatusId { get; set; }
/// <summary>
/// Gets or sets the payment method system name
/// </summary>
public string PaymentMethodSystemName { get; set; }
/// <summary>
/// Gets or sets the customer currency code (at the moment of order placing)
/// </summary>
public string CustomerCurrencyCode { get; set; }
/// <summary>
/// Gets or sets the currency rate
/// </summary>
public decimal CurrencyRate { get; set; }
/// <summary>
/// Gets or sets the customer tax display type identifier
/// </summary>
public int CustomerTaxDisplayTypeId { get; set; }
/// <summary>
/// Gets or sets the VAT number (the European Union Value Added Tax)
/// </summary>
public string VatNumber { get; set; }
/// <summary>
/// Gets or sets the order subtotal (include tax)
/// </summary>
public decimal OrderSubtotalInclTax { get; set; }
/// <summary>
/// Gets or sets the order subtotal (exclude tax)
/// </summary>
public decimal OrderSubtotalExclTax { get; set; }
/// <summary>
/// Gets or sets the order subtotal discount (include tax)
/// </summary>
public decimal OrderSubTotalDiscountInclTax { get; set; }
/// <summary>
/// Gets or sets the order subtotal discount (exclude tax)
/// </summary>
public decimal OrderSubTotalDiscountExclTax { get; set; }
/// <summary>
/// Gets or sets the order shipping (include tax)
/// </summary>
public decimal OrderShippingInclTax { get; set; }
/// <summary>
/// Gets or sets the order shipping (exclude tax)
/// </summary>
public decimal OrderShippingExclTax { get; set; }
/// <summary>
/// Gets or sets the payment method additional fee (incl tax)
/// </summary>
public decimal PaymentMethodAdditionalFeeInclTax { get; set; }
/// <summary>
/// Gets or sets the payment method additional fee (exclude tax)
/// </summary>
public decimal PaymentMethodAdditionalFeeExclTax { get; set; }
/// <summary>
/// Gets or sets the tax rates
/// </summary>
public string TaxRates { get; set; }
/// <summary>
/// Gets or sets the order tax
/// </summary>
public decimal OrderTax { get; set; }
/// <summary>
/// Gets or sets the order discount (applied to order total)
/// </summary>
public decimal OrderDiscount { get; set; }
/// <summary>
/// Gets or sets the order total
/// </summary>
public decimal OrderTotal { get; set; }
/// <summary>
/// Gets or sets the refunded amount
/// </summary>
public decimal RefundedAmount { get; set; }
/// <summary>
/// Gets or sets the reward points history entry identifier when reward points were earned (gained) for placing this order
/// </summary>
public int? RewardPointsHistoryEntryId { get; set; }
/// <summary>
/// Gets or sets the checkout attribute description
/// </summary>
public string CheckoutAttributeDescription { get; set; }
/// <summary>
/// Gets or sets the checkout attributes in XML format
/// </summary>
public string CheckoutAttributesXml { get; set; }
/// <summary>
/// Gets or sets the customer language identifier
/// </summary>
public int CustomerLanguageId { get; set; }
/// <summary>
/// Gets or sets the affiliate identifier
/// </summary>
public int AffiliateId { get; set; }
/// <summary>
/// Gets or sets the customer IP address
/// </summary>
public string CustomerIp { get; set; }
/// <summary>
/// Gets or sets a value indicating whether storing of credit card number is allowed
/// </summary>
public bool AllowStoringCreditCardNumber { get; set; }
/// <summary>
/// Gets or sets the card type
/// </summary>
public string CardType { get; set; }
/// <summary>
/// Gets or sets the card name
/// </summary>
public string CardName { get; set; }
/// <summary>
/// Gets or sets the card number
/// </summary>
public string CardNumber { get; set; }
/// <summary>
/// Gets or sets the masked credit card number
/// </summary>
public string MaskedCreditCardNumber { get; set; }
/// <summary>
/// Gets or sets the card CVV2
/// </summary>
public string CardCvv2 { get; set; }
/// <summary>
/// Gets or sets the card expiration month
/// </summary>
public string CardExpirationMonth { get; set; }
/// <summary>
/// Gets or sets the card expiration year
/// </summary>
public string CardExpirationYear { get; set; }
/// <summary>
/// Gets or sets the authorization transaction identifier
/// </summary>
public string AuthorizationTransactionId { get; set; }
/// <summary>
/// Gets or sets the authorization transaction code
/// </summary>
public string AuthorizationTransactionCode { get; set; }
/// <summary>
/// Gets or sets the authorization transaction result
/// </summary>
public string AuthorizationTransactionResult { get; set; }
/// <summary>
/// Gets or sets the capture transaction identifier
/// </summary>
public string CaptureTransactionId { get; set; }
/// <summary>
/// Gets or sets the capture transaction result
/// </summary>
public string CaptureTransactionResult { get; set; }
/// <summary>
/// Gets or sets the subscription transaction identifier
/// </summary>
public string SubscriptionTransactionId { get; set; }
/// <summary>
/// Gets or sets the paid date and time
/// </summary>
public DateTime? PaidDateUtc { get; set; }
/// <summary>
/// Gets or sets the shipping method
/// </summary>
public string ShippingMethod { get; set; }
/// <summary>
/// Gets or sets the shipping rate computation method identifier or the pickup point provider identifier (if PickupInStore is true)
/// </summary>
public string ShippingRateComputationMethodSystemName { get; set; }
/// <summary>
/// Gets or sets the serialized CustomValues (values from ProcessPaymentRequest)
/// </summary>
public string CustomValuesXml { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
public bool Deleted { get; set; }
/// <summary>
/// Gets or sets the date and time of order creation
/// </summary>
public DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the custom order number without prefix
/// </summary>
public string CustomOrderNumber { get; set; }
/// <summary>
/// Gets or sets the reward points history record (spent by a customer when placing this order)
/// </summary>
public virtual int? RedeemedRewardPointsEntryId { get; set; }
#endregion
#region Custom properties
/// <summary>
/// Gets or sets the order status
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => OrderStatusId")]
public OrderStatus OrderStatus
{
get => (OrderStatus)OrderStatusId;
set => OrderStatusId = (int)value;
}
/// <summary>
/// Gets or sets the payment status
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => PaymentStatusId")]
public PaymentStatus PaymentStatus
{
get => (PaymentStatus)PaymentStatusId;
set => PaymentStatusId = (int)value;
}
/// <summary>
/// Gets or sets the shipping status
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ShippingStatusId")]
public ShippingStatus ShippingStatus
{
get => (ShippingStatus)ShippingStatusId;
set => ShippingStatusId = (int)value;
}
/// <summary>
/// Gets or sets the customer tax display type
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => CustomerTaxDisplayTypeId")]
public TaxDisplayType CustomerTaxDisplayType
{
get => (TaxDisplayType)CustomerTaxDisplayTypeId;
set => CustomerTaxDisplayTypeId = (int)value;
}
#endregion
}

View File

@ -0,0 +1,109 @@
namespace Nop.Core.Domain.Orders;
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
/// <summary>
/// Represents an order item
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
[ToonDescription("NopCommerce order item entity")]
public partial class OrderItem : BaseEntity
{
/// <summary>
/// Gets or sets the order item identifier
/// </summary>
public Guid OrderItemGuid { get; set; }
/// <summary>
/// Gets or sets the order identifier
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// Gets or sets the product identifier
/// </summary>
public int ProductId { get; set; }
/// <summary>
/// Gets or sets the quantity
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// Gets or sets the unit price in primary store currency (include tax)
/// </summary>
public decimal UnitPriceInclTax { get; set; }
/// <summary>
/// Gets or sets the unit price in primary store currency (exclude tax)
/// </summary>
public decimal UnitPriceExclTax { get; set; }
/// <summary>
/// Gets or sets the price in primary store currency (include tax)
/// </summary>
public decimal PriceInclTax { get; set; }
/// <summary>
/// Gets or sets the price in primary store currency (exclude tax)
/// </summary>
public decimal PriceExclTax { get; set; }
/// <summary>
/// Gets or sets the discount amount (include tax)
/// </summary>
public decimal DiscountAmountInclTax { get; set; }
/// <summary>
/// Gets or sets the discount amount (exclude tax)
/// </summary>
public decimal DiscountAmountExclTax { get; set; }
/// <summary>
/// Gets or sets the original cost of this order item (when an order was placed), qty 1
/// </summary>
public decimal OriginalProductCost { get; set; }
/// <summary>
/// Gets or sets the attribute description
/// </summary>
public string AttributeDescription { get; set; }
/// <summary>
/// Gets or sets the product attributes in XML format
/// </summary>
public string AttributesXml { get; set; }
/// <summary>
/// Gets or sets the download count
/// </summary>
public int DownloadCount { get; set; }
/// <summary>
/// Gets or sets a value indicating whether download is activated
/// </summary>
public bool IsDownloadActivated { get; set; }
/// <summary>
/// Gets or sets a license download identifier (in case this is a downloadable product)
/// </summary>
public int? LicenseDownloadId { get; set; }
/// <summary>
/// Gets or sets the total weight of one item
/// It's nullable for compatibility with the previous version of nopCommerce where was no such property
/// </summary>
public decimal? ItemWeight { get; set; }
/// <summary>
/// Gets or sets the rental product start date (null if it's not a rental product)
/// </summary>
public DateTime? RentalStartDateUtc { get; set; }
/// <summary>
/// Gets or sets the rental product end date (null if it's not a rental product)
/// </summary>
public DateTime? RentalEndDateUtc { get; set; }
}

View File

@ -0,0 +1,38 @@
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
namespace Nop.Core.Domain.Orders;
/// <summary>
/// Represents an order note
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
[ToonDescription("NopCommerce order note entity")]
public partial class OrderNote : BaseEntity
{
/// <summary>
/// Gets or sets the order identifier
/// </summary>
[ToonDescription($"Foreign key to parent {nameof(Order)}", ForeignKey = nameof(Order))]
public int OrderId { get; set; }
/// <summary>
/// Gets or sets the note
/// </summary>
public string Note { get; set; }
/// <summary>
/// Gets or sets the attached file (download) identifier
/// </summary>
public int DownloadId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a customer can see a note
/// </summary>
public bool DisplayToCustomer { get; set; }
/// <summary>
/// Gets or sets the date and time of order note creation
/// </summary>
public DateTime CreatedOnUtc { get; set; }
}

View File

@ -0,0 +1,618 @@
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Security;
using Nop.Core.Domain.Seo;
using Nop.Core.Domain.Stores;
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a product
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
[ToonDescription("Core nopCommerce product entity with catalog, pricing, and inventory management")]
public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported, IDiscountSupported<DiscountProductMapping>, ISoftDeletedEntity
{
/// <summary>
/// Gets or sets the product type identifier
/// </summary>
[ToonDescription(Purpose = "Defines if the product is Simple or Grouped", BusinessRule = "Enum: ProductType")]
public int ProductTypeId { get; set; }
/// <summary>
/// Gets or sets the parent product identifier. It's used to identify associated products (only with "grouped" products)
/// </summary>
[ToonDescription(Purpose = "Hierarchical link for grouped product variants", BusinessRule = "Foreign Key to Product.Id")]
public int ParentGroupedProductId { get; set; }
/// <summary>
/// Gets or sets the values indicating whether this product is visible in catalog or search results.
/// </summary>
[ToonDescription(Purpose = "Controls independent visibility in search/catalog", BusinessRule = "Used primarily for associated products")]
public bool VisibleIndividually { get; set; }
/// <summary>
/// Gets or sets the name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the short description
/// </summary>
public string ShortDescription { get; set; }
/// <summary>
/// Gets or sets the full description
/// </summary>
public string FullDescription { get; set; }
/// <summary>
/// Gets or sets the admin comment
/// </summary>
[ToonDescription(Purpose = "Internal administrative notes", BusinessRule = "Never exposed to customer")]
public string AdminComment { get; set; }
/// <summary>
/// Gets or sets a value of used product template identifier
/// </summary>
public int ProductTemplateId { get; set; }
/// <summary>
/// Gets or sets a vendor identifier
/// </summary>
public int VendorId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to show the product on home page
/// </summary>
public bool ShowOnHomepage { get; set; }
/// <summary>
/// Gets or sets the meta keywords
/// </summary>
public string MetaKeywords { get; set; }
/// <summary>
/// Gets or sets the meta description
/// </summary>
public string MetaDescription { get; set; }
/// <summary>
/// Gets or sets the meta title
/// </summary>
public string MetaTitle { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product allows customer reviews
/// </summary>
public bool AllowCustomerReviews { get; set; }
/// <summary>
/// Gets or sets the rating sum (approved reviews)
/// </summary>
public int ApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the rating sum (not approved reviews)
/// </summary>
public int NotApprovedRatingSum { get; set; }
/// <summary>
/// Gets or sets the total rating votes (approved reviews)
/// </summary>
public int ApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets the total rating votes (not approved reviews)
/// </summary>
public int NotApprovedTotalReviews { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is subject to ACL
/// </summary>
public bool SubjectToAcl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is limited/restricted to certain stores
/// </summary>
public bool LimitedToStores { get; set; }
/// <summary>
/// Gets or sets the SKU
/// </summary>
[ToonDescription(Purpose = "Stock Keeping Unit", BusinessRule = "Unique alphanumeric identifier")]
public string Sku { get; set; }
/// <summary>
/// Gets or sets the manufacturer part number
/// </summary>
public string ManufacturerPartNumber { get; set; }
/// <summary>
/// Gets or sets the Global Trade Item Number (GTIN).
/// </summary>
public string Gtin { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is gift card
/// </summary>
public bool IsGiftCard { get; set; }
/// <summary>
/// Gets or sets the gift card type identifier
/// </summary>
public int GiftCardTypeId { get; set; }
/// <summary>
/// Gets or sets gift card amount that can be used after purchase.
/// </summary>
public decimal? OverriddenGiftCardAmount { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product requires that other products are added to the cart
/// </summary>
public bool RequireOtherProducts { get; set; }
/// <summary>
/// Gets or sets a required product identifiers (comma separated)
/// </summary>
public string RequiredProductIds { get; set; }
/// <summary>
/// Gets or sets a value indicating whether required products are automatically added to the cart
/// </summary>
public bool AutomaticallyAddRequiredProducts { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is download
/// </summary>
[ToonDescription(Purpose = "Digital good flag")]
public bool IsDownload { get; set; }
/// <summary>
/// Gets or sets the download identifier
/// </summary>
public int DownloadId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this downloadable product can be downloaded unlimited number of times
/// </summary>
public bool UnlimitedDownloads { get; set; }
/// <summary>
/// Gets or sets the maximum number of downloads
/// </summary>
public int MaxNumberOfDownloads { get; set; }
/// <summary>
/// Gets or sets the number of days during customers keeps access to the file.
/// </summary>
public int? DownloadExpirationDays { get; set; }
/// <summary>
/// Gets or sets the download activation type
/// </summary>
public int DownloadActivationTypeId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product has a sample download file
/// </summary>
public bool HasSampleDownload { get; set; }
/// <summary>
/// Gets or sets the sample download identifier
/// </summary>
public int SampleDownloadId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product has user agreement
/// </summary>
public bool HasUserAgreement { get; set; }
/// <summary>
/// Gets or sets the text of license agreement
/// </summary>
public string UserAgreementText { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is recurring
/// </summary>
[ToonDescription(Purpose = "Subscription flag", BusinessRule = "Triggers automated billing cycles")]
public bool IsRecurring { get; set; }
/// <summary>
/// Gets or sets the cycle length
/// </summary>
public int RecurringCycleLength { get; set; }
/// <summary>
/// Gets or sets the cycle period
/// </summary>
public int RecurringCyclePeriodId { get; set; }
/// <summary>
/// Gets or sets the total cycles
/// </summary>
public int RecurringTotalCycles { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is rental
/// </summary>
[ToonDescription(Purpose = "Rental flag", BusinessRule = "Requires date selection in UI")]
public bool IsRental { get; set; }
/// <summary>
/// Gets or sets the rental length for some period (price for this period)
/// </summary>
public int RentalPriceLength { get; set; }
/// <summary>
/// Gets or sets the rental period (price for this period)
/// </summary>
public int RentalPricePeriodId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is ship enabled
/// </summary>
[ToonDescription(Purpose = "Physical shipping toggle")]
public bool IsShipEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is free shipping
/// </summary>
public bool IsFreeShipping { get; set; }
/// <summary>
/// Gets or sets a value this product should be shipped separately (each item)
/// </summary>
public bool ShipSeparately { get; set; }
/// <summary>
/// Gets or sets the additional shipping charge
/// </summary>
public decimal AdditionalShippingCharge { get; set; }
/// <summary>
/// Gets or sets a delivery date identifier
/// </summary>
public int DeliveryDateId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the product is marked as tax exempt
/// </summary>
public bool IsTaxExempt { get; set; }
/// <summary>
/// Gets or sets the tax category identifier
/// </summary>
public int TaxCategoryId { get; set; }
/// <summary>
/// Gets or sets a value indicating how to manage inventory
/// </summary>
[ToonDescription(Purpose = "Inventory logic selector", BusinessRule = "0:None, 1:Product, 2:Attributes")]
public int ManageInventoryMethodId { get; set; }
/// <summary>
/// Gets or sets a product availability range identifier
/// </summary>
public int ProductAvailabilityRangeId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether multiple warehouses are used for this product
/// </summary>
public bool UseMultipleWarehouses { get; set; }
/// <summary>
/// Gets or sets a warehouse identifier
/// </summary>
public int WarehouseId { get; set; }
/// <summary>
/// Gets or sets the stock quantity
/// </summary>
[ToonDescription(Purpose = "Physical quantity in warehouse", BusinessRule = "Decremented on order placement or paid")]
public int StockQuantity { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to display stock availability
/// </summary>
public bool DisplayStockAvailability { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to display stock quantity
/// </summary>
public bool DisplayStockQuantity { get; set; }
/// <summary>
/// Gets or sets the minimum stock quantity
/// </summary>
public int MinStockQuantity { get; set; }
/// <summary>
/// Gets or sets the low stock activity identifier
/// </summary>
public int LowStockActivityId { get; set; }
/// <summary>
/// Gets or sets the quantity when admin should be notified
/// </summary>
public int NotifyAdminForQuantityBelow { get; set; }
/// <summary>
/// Gets or sets a value backorder mode identifier
/// </summary>
[ToonDescription(Purpose = "Handling of out-of-stock purchases", BusinessRule = "Controls if StockQuantity can go negative")]
public int BackorderModeId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to back in stock subscriptions are allowed
/// </summary>
public bool AllowBackInStockSubscriptions { get; set; }
/// <summary>
/// Gets or sets the order minimum quantity
/// </summary>
public int OrderMinimumQuantity { get; set; }
/// <summary>
/// Gets or sets the order maximum quantity
/// </summary>
public int OrderMaximumQuantity { get; set; }
/// <summary>
/// Gets or sets the comma separated list of allowed quantities. null or empty if any quantity is allowed
/// </summary>
public string AllowedQuantities { get; set; }
/// <summary>
/// Gets or sets a value indicating whether we allow adding to the cart/wishlist only attribute combinations that exist and have stock greater than zero.
/// </summary>
public bool AllowAddingOnlyExistingAttributeCombinations { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to display attribute combination images only
/// </summary>
public bool DisplayAttributeCombinationImagesOnly { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this product is returnable
/// </summary>
public bool NotReturnable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to disable buy (Add to cart) button
/// </summary>
public bool DisableBuyButton { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to disable "Add to wishlist" button
/// </summary>
public bool DisableWishlistButton { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this item is available for Pre-Order
/// </summary>
public bool AvailableForPreOrder { get; set; }
/// <summary>
/// Gets or sets the start date and time of the product availability (for pre-order products)
/// </summary>
public DateTime? PreOrderAvailabilityStartDateTimeUtc { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to show "Call for Pricing" or "Call for quote" instead of price
/// </summary>
public bool CallForPrice { get; set; }
/// <summary>
/// Gets or sets the price
/// </summary>
[ToonDescription(Purpose = "Base selling price", BusinessRule = "Used as primary value before discounts")]
public decimal Price { get; set; }
/// <summary>
/// Gets or sets the old price
/// </summary>
public decimal OldPrice { get; set; }
/// <summary>
/// Gets or sets the product cost
/// </summary>
[ToonDescription(Purpose = "Purchase price from vendor", BusinessRule = "Used for profit/margin calculation")]
public decimal ProductCost { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a customer enters price
/// </summary>
public bool CustomerEntersPrice { get; set; }
/// <summary>
/// Gets or sets the minimum price entered by a customer
/// </summary>
public decimal MinimumCustomerEnteredPrice { get; set; }
/// <summary>
/// Gets or sets the maximum price entered by a customer
/// </summary>
public decimal MaximumCustomerEnteredPrice { get; set; }
/// <summary>
/// Gets or sets a value indicating whether base price (PAngV) is enabled.
/// </summary>
public bool BasepriceEnabled { get; set; }
/// <summary>
/// Gets or sets an amount in product for PAngV
/// </summary>
public decimal BasepriceAmount { get; set; }
/// <summary>
/// Gets or sets a unit of product for PAngV (MeasureWeight entity)
/// </summary>
public int BasepriceUnitId { get; set; }
/// <summary>
/// Gets or sets a reference amount for PAngV
/// </summary>
public decimal BasepriceBaseAmount { get; set; }
/// <summary>
/// Gets or sets a reference unit for PAngV (MeasureWeight entity)
/// </summary>
public int BasepriceBaseUnitId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this product is marked as new
/// </summary>
public bool MarkAsNew { get; set; }
/// <summary>
/// Gets or sets the start date and time of the new product
/// </summary>
public DateTime? MarkAsNewStartDateTimeUtc { get; set; }
/// <summary>
/// Gets or sets the end date and time of the new product
/// </summary>
public DateTime? MarkAsNewEndDateTimeUtc { get; set; }
/// <summary>
/// Gets or sets the weight
/// </summary>
public decimal Weight { get; set; }
/// <summary>
/// Gets or sets the length
/// </summary>
public decimal Length { get; set; }
/// <summary>
/// Gets or sets the width
/// </summary>
public decimal Width { get; set; }
/// <summary>
/// Gets or sets the height
/// </summary>
public decimal Height { get; set; }
/// <summary>
/// Gets or sets the available start date and time
/// </summary>
public DateTime? AvailableStartDateTimeUtc { get; set; }
/// <summary>
/// Gets or sets the available end date and time
/// </summary>
public DateTime? AvailableEndDateTimeUtc { get; set; }
/// <summary>
/// Gets or sets a display order.
/// </summary>
public int DisplayOrder { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is published
/// </summary>
public bool Published { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
[ToonDescription(Purpose = "Soft-delete flag", BusinessRule = "Records remain in DB but hidden from UI")]
public bool Deleted { get; set; }
/// <summary>
/// Gets or sets the date and time of product creation
/// </summary>
public DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of product update
/// </summary>
public DateTime UpdatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the product type
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ProductTypeId")]
public ProductType ProductType
{
get => (ProductType)ProductTypeId;
set => ProductTypeId = (int)value;
}
/// <summary>
/// Gets or sets the backorder mode
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => BackorderModeId")]
public BackorderMode BackorderMode
{
get => (BackorderMode)BackorderModeId;
set => BackorderModeId = (int)value;
}
/// <summary>
/// Gets or sets the download activation type
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => DownloadActivationTypeId")]
public DownloadActivationType DownloadActivationType
{
get => (DownloadActivationType)DownloadActivationTypeId;
set => DownloadActivationTypeId = (int)value;
}
/// <summary>
/// Gets or sets the gift card type
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => GiftCardTypeId")]
public GiftCardType GiftCardType
{
get => (GiftCardType)GiftCardTypeId;
set => GiftCardTypeId = (int)value;
}
/// <summary>
/// Gets or sets the low stock activity
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => LowStockActivityId")]
public LowStockActivity LowStockActivity
{
get => (LowStockActivity)LowStockActivityId;
set => LowStockActivityId = (int)value;
}
/// <summary>
/// Gets or sets the value indicating how to manage inventory
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => ManageInventoryMethodId")]
public ManageInventoryMethod ManageInventoryMethod
{
get => (ManageInventoryMethod)ManageInventoryMethodId;
set => ManageInventoryMethodId = (int)value;
}
/// <summary>
/// Gets or sets the cycle period for recurring products
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => RecurringCyclePeriodId")]
public RecurringProductCyclePeriod RecurringCyclePeriod
{
get => (RecurringProductCyclePeriod)RecurringCyclePeriodId;
set => RecurringCyclePeriodId = (int)value;
}
/// <summary>
/// Gets or sets the period for rental products
/// </summary>
[ToonDescription(Purpose = "Enum wrapper", BusinessRule = "get, set => RentalPricePeriod")]
public RentalPricePeriod RentalPricePeriod
{
get => (RentalPricePeriod)RentalPricePeriodId;
set => RentalPricePeriodId = (int)value;
}
}

View File

@ -0,0 +1,62 @@
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Toons;
using AyCode.Interfaces.Entities;
using LinqToDB.Mapping;
using Mango.Nop.Core.Interfaces;
namespace Nop.Core.Domain.Catalog;
public interface IMgStockQuantityHistory
{
public int ProductId { get; set; }
public int QuantityAdjustment { get; set; }
public int StockQuantity { get; set; }
public string Message { get; set; }
public int? CombinationId { get; set; }
public int? WarehouseId { get; set; }
public DateTime CreatedOnUtc { get; set; }
}
/// <summary>
/// Represents a stock quantity change entry
/// </summary>
[AcBinarySerializable(false, true, false, true, false, false)]
[Table(Name = nameof(StockQuantityHistory))]
[ToonDescription("NopCommerce stock movement log", Purpose = "Audit trail for physical and logical stock movements")]
public partial class StockQuantityHistory : BaseEntity, IMgStockQuantityHistory
{
/// <summary>
/// Gets or sets the stock quantity adjustment
/// </summary>
public int QuantityAdjustment { get; set; }
/// <summary>
/// Gets or sets current stock quantity
/// </summary>
public int StockQuantity { get; set; }
/// <summary>
/// Gets or sets the message
/// </summary>
public string Message { get; set; }
/// <summary>
/// Gets or sets the date and time of instance creation
/// </summary>
public DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the product identifier
/// </summary>
public int ProductId { get; set; }
/// <summary>
/// Gets or sets the product attribute combination identifier
/// </summary>
public int? CombinationId { get; set; }
/// <summary>
/// Gets or sets the warehouse identifier
/// </summary>
public int? WarehouseId { get; set; }
}

View File

@ -0,0 +1,17 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a download activation type
/// </summary>
public enum DownloadActivationType
{
/// <summary>
/// When order is paid
/// </summary>
WhenOrderIsPaid = 0,
/// <summary>
/// Manually
/// </summary>
Manually = 10,
}

View File

@ -0,0 +1,17 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a gift card type
/// </summary>
public enum GiftCardType
{
/// <summary>
/// Virtual
/// </summary>
Virtual = 0,
/// <summary>
/// Physical
/// </summary>
Physical = 1,
}

View File

@ -0,0 +1,12 @@
namespace Nop.Core.Domain.Security;
/// <summary>
/// Represents an entity which supports ACL
/// </summary>
public partial interface IAclSupported
{
/// <summary>
/// Gets or sets a value indicating whether the entity is subject to ACL
/// </summary>
bool SubjectToAcl { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace Nop.Core.Domain.Discounts;
/// <summary>
/// Represents an entity which supports discounts
/// </summary>
public partial interface IDiscountSupported<T> where T : DiscountMapping
{
int Id { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace Nop.Core.Domain.Localization;
/// <summary>
/// Represents a localized entity
/// </summary>
public partial interface ILocalizedEntity
{
}

View File

@ -0,0 +1,8 @@
namespace Nop.Core.Domain.Seo;
/// <summary>
/// Represents an entity which supports slug (SEO friendly one-word URLs)
/// </summary>
public partial interface ISlugSupported
{
}

View File

@ -0,0 +1,12 @@
//namespace Mango.Nop.Core.NopDependencies;
namespace Nop.Core.Domain.Common;
/// <summary>
/// Represents a soft-deleted (without actually deleting from storage) entity
/// </summary>
public partial interface ISoftDeletedEntity
{
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
bool Deleted { get; set; }
}

View File

@ -0,0 +1,12 @@
namespace Nop.Core.Domain.Stores;
/// <summary>
/// Represents an entity which supports store mapping
/// </summary>
public partial interface IStoreMappingSupported
{
/// <summary>
/// Gets or sets a value indicating whether the entity is limited/restricted to certain stores
/// </summary>
bool LimitedToStores { get; set; }
}

View File

@ -0,0 +1,22 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a low stock activity
/// </summary>
public enum LowStockActivity
{
/// <summary>
/// Nothing
/// </summary>
Nothing = 0,
/// <summary>
/// Disable buy button
/// </summary>
DisableBuyButton = 1,
/// <summary>
/// Unpublish
/// </summary>
Unpublish = 2,
}

View File

@ -0,0 +1,22 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a method of inventory management
/// </summary>
public enum ManageInventoryMethod
{
/// <summary>
/// Don't track inventory for product
/// </summary>
DontManageStock = 0,
/// <summary>
/// Track inventory for product
/// </summary>
ManageStock = 1,
/// <summary>
/// Track inventory for product by product attributes
/// </summary>
ManageStockByAttributes = 2,
}

View File

@ -0,0 +1,27 @@
namespace Nop.Core.Domain.Orders;
/// <summary>
/// Represents an order status enumeration
/// </summary>
public enum OrderStatus
{
/// <summary>
/// Pending
/// </summary>
Pending = 10,
/// <summary>
/// Processing
/// </summary>
Processing = 20,
/// <summary>
/// Complete
/// </summary>
Complete = 30,
/// <summary>
/// Cancelled
/// </summary>
Cancelled = 40
}

View File

@ -0,0 +1,37 @@
namespace Nop.Core.Domain.Payments;
/// <summary>
/// Represents a payment status enumeration
/// </summary>
public enum PaymentStatus
{
/// <summary>
/// Pending
/// </summary>
Pending = 10,
/// <summary>
/// Authorized
/// </summary>
Authorized = 20,
/// <summary>
/// Paid
/// </summary>
Paid = 30,
/// <summary>
/// Partially Refunded
/// </summary>
PartiallyRefunded = 35,
/// <summary>
/// Refunded
/// </summary>
Refunded = 40,
/// <summary>
/// Voided
/// </summary>
Voided = 50
}

View File

@ -0,0 +1,17 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a product type
/// </summary>
public enum ProductType
{
/// <summary>
/// Simple
/// </summary>
SimpleProduct = 5,
/// <summary>
/// Grouped (product with variants)
/// </summary>
GroupedProduct = 10,
}

View File

@ -0,0 +1,27 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a recurring product cycle period
/// </summary>
public enum RecurringProductCyclePeriod
{
/// <summary>
/// Days
/// </summary>
Days = 0,
/// <summary>
/// Weeks
/// </summary>
Weeks = 10,
/// <summary>
/// Months
/// </summary>
Months = 20,
/// <summary>
/// Years
/// </summary>
Years = 30,
}

View File

@ -0,0 +1,27 @@
namespace Nop.Core.Domain.Catalog;
/// <summary>
/// Represents a rental product period (for prices)
/// </summary>
public enum RentalPricePeriod
{
/// <summary>
/// Days
/// </summary>
Days = 0,
/// <summary>
/// Weeks
/// </summary>
Weeks = 10,
/// <summary>
/// Months
/// </summary>
Months = 20,
/// <summary>
/// Years
/// </summary>
Years = 30,
}

View File

@ -0,0 +1,32 @@
namespace Nop.Core.Domain.Shipping;
/// <summary>
/// Represents the shipping status enumeration
/// </summary>
public enum ShippingStatus
{
/// <summary>
/// Shipping not required
/// </summary>
ShippingNotRequired = 10,
/// <summary>
/// Not yet shipped
/// </summary>
NotYetShipped = 20,
/// <summary>
/// Partially shipped
/// </summary>
PartiallyShipped = 25,
/// <summary>
/// Shipped
/// </summary>
Shipped = 30,
/// <summary>
/// Delivered
/// </summary>
Delivered = 40
}

View File

@ -0,0 +1,17 @@
namespace Nop.Core.Domain.Tax;
/// <summary>
/// Represents the tax display type enumeration
/// </summary>
public enum TaxDisplayType
{
/// <summary>
/// Including tax
/// </summary>
IncludingTax = 0,
/// <summary>
/// Excluding tax
/// </summary>
ExcludingTax = 10
}

View File

@ -0,0 +1,27 @@
namespace Nop.Core.Domain.Tax;
/// <summary>
/// Represents the VAT number status enumeration
/// </summary>
public enum VatNumberStatus
{
/// <summary>
/// Unknown
/// </summary>
Unknown = 0,
/// <summary>
/// Empty
/// </summary>
Empty = 10,
/// <summary>
/// Valid
/// </summary>
Valid = 20,
/// <summary>
/// Invalid
/// </summary>
Invalid = 30
}

105
Mango.Nop.Core/README.md Normal file
View File

@ -0,0 +1,105 @@
# Mango.Nop.Core
@project {
type = "framework"
own-dep-projects = [
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Utils (in AyCode.Core repo)"
]
}
Shared domain library containing entities, DTOs, interfaces, and nopCommerce entity mirrors. **net9.0**. Zero nopCommerce runtime dependency.
## Documentation
| Document | Topic |
|---|---|
| `DTOS.md` | DTO system — two mapping strategies, all DTO types, GenericAttribute typed access |
| `NOP_DEPENDENCIES.md` | NopDependencies pattern — mirror copies, namespace rules, file list |
## Folder Structure
| Folder | Purpose |
|---|---|
| `Dtos/` | DTO classes shared across consumers |
| `Entities/` | Custom domain entities |
| `Extensions/` | Extension methods for `BaseEntity` collections and `GenericAttribute` |
| `Interfaces/` | DTO interfaces, soft-delete, foreign key markers |
| `Loggers/` | `ILogger` / `Logger` — logging abstraction wrapping AyCode logger |
| `Models/` | Login request/response models |
| `NopDependencies/` | Mirror copies of nopCommerce entity classes (same namespaces as originals) |
| `Services/` | `IMgLockService` interface |
| `Utils/` | `CommonHelper2` — email validation, type conversion utilities |
## Entity Hierarchy
```
Nop.Core.BaseEntity (NopDependencies/, implements IBaseEntity)
+-- MgEntityBase (Entities/, implements IEntityInt from AyCode)
+-- MgOrderDto<TOrderItemDto, TProductDto>
+-- MgOrderItemDto<TProductDto>
+-- MgProductDto
+-- MgStockQuantityHistoryDto<TProductDto>
+-- MgStockTaking<TStockTakingItem>
+-- MgStockTakingItem<TStockTaking, TProduct>
```
## Key Types (not in docs/)
### Entities
| Type | Key features |
|---|---|
| `MgEntityBase` | Inherits `BaseEntity`, implements `IEntityInt`. `ToString()` -> `"{TypeName}; Id: {Id}"` |
| `MgStockTaking<TStockTakingItem>` | `StartDateTime`, `IsClosed`, `List<TStockTakingItem>` navigation. Implements `ITimeStampInfo` |
| `MgStockTakingItem<TStockTaking, TProduct>` | `StockTakingId`, `ProductId`, `IsMeasured`, stock quantities. Navigations to parent and product |
### Extensions
| Method | Purpose |
|---|---|
| `UpdateBaseEntityCollection` | Add/update/remove entities in a list by Id match |
### Loggers
Extends AyCode logging infrastructure — for base types see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`.
| Type | Inherits | Purpose |
|---|---|---|
| `ILogger` | `IAcLoggerBase` | Mango-level logger interface |
| `ILogger<TCategory>` | `ILogger` | Generic category logger interface |
| `Logger` | `AcLoggerBase` | Logger implementation with `IAcLogWriterBase[]` writers |
| `Logger<TCategory>` | `Logger` | Typed logger — category name from `typeof(TCategory).Name` |
### Models
| Type | Purpose |
|---|---|
| `MgLoginModelRequest` | `Email` + `Password` for SignalR/API login |
| `MgLoginModelResponse` | `CustomerDto?` + `ErrorMessage`. Computed `IsSuccesLogin` |
### Other
| Type | Purpose |
|---|---|
| `NopCommonConst` | Abstract const class inheriting `AcConst` (placeholder) |
| `CommonHelper2` | Email validation, IP validation, type conversion (`To<T>`), string utilities |
## Dependencies
- `linq2db` — data access, `[Association]` / `[Table]` attributes
- `Microsoft.AspNetCore.Mvc.NewtonsoftJson` — JSON serialization
- Binary serialization: `AcBinarySerializer` (see `AyCode.Core/AyCode.Core/docs/BINARY/BINARY_FORMAT.md`). SignalR transport: `AcSignalR` (see `AyCode.Core/AyCode.Services/docs/SIGNALR/README.md`).
- `AyCode.Core.Serializers.SourceGenerator` (ProjectReference, `OutputItemType="Analyzer"`) — compile-time binary serializer. See [`AyCode.Core/docs/BINARY_SOURCE_GEN.md`](../../../../../../Aycode/Source/AyCode.Core/AyCode.Core/docs/BINARY_SOURCE_GEN.md)
- AyCode DLL references: `AyCode.Interfaces`, `AyCode.Core`, `AyCode.Utils`, `AyCode.Entities` (types only, no runtime nopCommerce dependency)
## AcBinary Source Generator (SGen)
9 types annotated with `[AcBinarySerializable]`. For general SGen docs see [`AyCode.Core/docs/BINARY_SOURCE_GEN.md`](../../../../../../Aycode/Source/AyCode.Core/AyCode.Core/docs/BINARY_SOURCE_GEN.md).
Requires `InternalsVisibleTo("Mango.Nop.Core")` in `AyCode.Core/Properties/AssemblyInfo.cs`.
### Excluded types
| Type | Reason |
|---|---|
| `DiscountProductMapping` | Base `DiscountMapping` hides `BaseEntity.Id` with readonly `new int Id { get; }` → CS0200. Uses compiled-expression fallback. |

View File

@ -1,29 +0,0 @@
using Mango.Nop.Core.Interfaces;
using Microsoft.AspNetCore.Http;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Core.Events;
using Nop.Data;
using Nop.Services.Events;
using Nop.Services.Logging;
namespace Mango.Nop.Core.Repositories;
public class MgDbContextBase : IMgDbContextBase
{
//TODO: ez itt nem ay igazi, kitalálni vmit! - J.
private readonly CacheKey _auctionAllKey = new("Nop.auction.all-{0}", AUCTION_PATTERN_KEY);
public const string AUCTION_PATTERN_KEY = "Nop.auction.";
protected ILogger Logger;
protected INopDataProvider DataProvider;
//protected IHttpContextAccessor HttpContextAccessor;
public MgDbContextBase(INopDataProvider dataProvider, ILogger logger)
{
Logger = logger;
DataProvider = dataProvider;
}
}

View File

@ -1,4 +1,4 @@
namespace Mango.Nop.Services;
namespace Mango.Nop.Core.Services;
public interface IMgLockService
{

View File

@ -0,0 +1,312 @@
using System.ComponentModel;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
namespace Mango.Nop.Core.Utils;
/// <summary>
/// Represents a common helper
/// </summary>
public partial class CommonHelper2
{
#region Fields
//we use regular expression based on RFC 5322 Official Standard (see https://emailregex.com/)
private const string EMAIL_EXPRESSION = @"^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|""(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*"")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$";
#endregion
#region Methods
/// <summary>
/// Get email validation regex
/// </summary>
/// <returns>Regular expression</returns>
[GeneratedRegex(EMAIL_EXPRESSION, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture, "en-US")]
public static partial Regex GetEmailRegex();
/// <summary>
/// Ensures the subscriber email or throw.
/// </summary>
/// <param name="email">The email.</param>
/// <returns></returns>
public static string EnsureSubscriberEmailOrThrow(string email)
{
var output = EnsureNotNull(email);
output = output.Trim();
output = EnsureMaximumLength(output, 255);
if (!IsValidEmail(output))
{
throw new Exception("Email is not valid.");
}
return output;
}
/// <summary>
/// Verifies that a string is in valid e-mail format
/// </summary>
/// <param name="email">Email to verify</param>
/// <returns>true if the string is a valid e-mail address and false if it's not</returns>
public static bool IsValidEmail(string email)
{
if (string.IsNullOrEmpty(email))
return false;
email = email.Trim();
return GetEmailRegex().IsMatch(email);
}
/// <summary>
/// Verifies that string is an valid IP-Address
/// </summary>
/// <param name="ipAddress">IPAddress to verify</param>
/// <returns>true if the string is a valid IpAddress and false if it's not</returns>
public static bool IsValidIpAddress(string ipAddress)
{
return IPAddress.TryParse(ipAddress, out var _);
}
///// <summary>
///// Generate random digit code
///// </summary>
///// <param name="length">Length</param>
///// <returns>Result string</returns>
//public static string GenerateRandomDigitCode(int length)
//{
// using var random = new SecureRandomNumberGenerator();
// var str = string.Empty;
// for (var i = 0; i < length; i++)
// str = string.Concat(str, random.Next(10).ToString());
// return str;
//}
///// <summary>
///// Returns an random integer number within a specified rage
///// </summary>
///// <param name="min">Minimum number</param>
///// <param name="max">Maximum number</param>
///// <returns>Result</returns>
//public static int GenerateRandomInteger(int min = 0, int max = int.MaxValue)
//{
// using var random = new SecureRandomNumberGenerator();
// return random.Next(min, max);
//}
/// <summary>
/// Ensure that a string doesn't exceed maximum allowed length
/// </summary>
/// <param name="str">Input string</param>
/// <param name="maxLength">Maximum length</param>
/// <param name="postfix">A string to add to the end if the original string was shorten</param>
/// <returns>Input string if its length is OK; otherwise, truncated input string</returns>
public static string EnsureMaximumLength(string str, int maxLength, string postfix = null)
{
if (string.IsNullOrEmpty(str))
return str;
if (str.Length <= maxLength)
return str;
var pLen = postfix?.Length ?? 0;
var result = str[0..(maxLength - pLen)];
if (!string.IsNullOrEmpty(postfix))
{
result += postfix;
}
return result;
}
/// <summary>
/// Ensures that a string only contains numeric values
/// </summary>
/// <param name="str">Input string</param>
/// <returns>Input string with only numeric values, empty string if input is null/empty</returns>
public static string EnsureNumericOnly(string str)
{
return string.IsNullOrEmpty(str) ? string.Empty : new string(str.Where(char.IsDigit).ToArray());
}
/// <summary>
/// Ensure that a string is not null
/// </summary>
/// <param name="str">Input string</param>
/// <returns>Result</returns>
public static string EnsureNotNull(string str)
{
return str ?? string.Empty;
}
/// <summary>
/// Indicates whether the specified strings are null or empty strings
/// </summary>
/// <param name="stringsToValidate">Array of strings to validate</param>
/// <returns>Boolean</returns>
public static bool AreNullOrEmpty(params string[] stringsToValidate)
{
return stringsToValidate.Any(string.IsNullOrEmpty);
}
/// <summary>
/// Compare two arrays
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="a1">Array 1</param>
/// <param name="a2">Array 2</param>
/// <returns>Result</returns>
public static bool ArraysEqual<T>(T[] a1, T[] a2)
{
//also see Enumerable.SequenceEqual(a1, a2);
if (ReferenceEquals(a1, a2))
return true;
if (a1 == null || a2 == null)
return false;
if (a1.Length != a2.Length)
return false;
var comparer = EqualityComparer<T>.Default;
return !a1.Where((t, i) => !comparer.Equals(t, a2[i])).Any();
}
/// <summary>
/// Sets a property on an object to a value.
/// </summary>
/// <param name="instance">The object whose property to set.</param>
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set the property to.</param>
public static void SetProperty(object instance, string propertyName, object value)
{
ArgumentNullException.ThrowIfNull(instance);
ArgumentNullException.ThrowIfNull(propertyName);
var instanceType = instance.GetType();
var pi = instanceType.GetProperty(propertyName)
?? throw new Exception($"No property '{propertyName}' found on the instance of type '{instanceType}'.");
if (!pi.CanWrite)
throw new Exception($"No property '{propertyName}' found on the instance of type '{instanceType}'.");
if (value != null && !value.GetType().IsAssignableFrom(pi.PropertyType))
value = To(value, pi.PropertyType);
pi.SetValue(instance, value, Array.Empty<object>());
}
/// <summary>
/// Converts a value to a destination type.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="destinationType">The type to convert the value to.</param>
/// <returns>The converted value.</returns>
public static object To(object value, Type destinationType)
{
return To(value, destinationType, CultureInfo.InvariantCulture);
}
/// <summary>
/// Converts a value to a destination type.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="destinationType">The type to convert the value to.</param>
/// <param name="culture">Culture</param>
/// <returns>The converted value.</returns>
public static object To(object value, Type destinationType, CultureInfo culture)
{
if (value == null)
return null;
var sourceType = value.GetType();
var destinationConverter = TypeDescriptor.GetConverter(destinationType);
if (destinationConverter.CanConvertFrom(value.GetType()))
return destinationConverter.ConvertFrom(null, culture, value);
var sourceConverter = TypeDescriptor.GetConverter(sourceType);
if (sourceConverter.CanConvertTo(destinationType))
return sourceConverter.ConvertTo(null, culture, value, destinationType);
if (destinationType.IsEnum && value is int)
return Enum.ToObject(destinationType, (int)value);
if (!destinationType.IsInstanceOfType(value))
return Convert.ChangeType(value, destinationType, culture);
return value;
}
/// <summary>
/// Converts a value to a destination type.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <typeparam name="T">The type to convert the value to.</typeparam>
/// <returns>The converted value.</returns>
public static T To<T>(object value)
{
//return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
return (T)To(value, typeof(T));
}
/// <summary>
/// Splits the camel-case word into separate one
/// </summary>
/// <param name="str">Input string</param>
/// <returns>Splitted string</returns>
public static string SplitCamelCaseWord(string str)
{
if (string.IsNullOrEmpty(str))
return string.Empty;
var result = str.ToCharArray()
.Select(p => p.ToString())
.Aggregate(string.Empty, (current, c) => current + (c == c.ToUpperInvariant() ? $" {c}" : c));
//ensure no spaces (e.g. when the first letter is upper case)
result = result.TrimStart();
return result;
}
/// <summary>
/// Get difference in years
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
public static int GetDifferenceInYears(DateTime startDate, DateTime endDate)
{
//source: http://stackoverflow.com/questions/9/how-do-i-calculate-someones-age-in-c
//this assumes you are looking for the western idea of age and not using East Asian reckoning.
var age = endDate.Year - startDate.Year;
if (startDate > endDate.AddYears(-age))
age--;
return age;
}
/// <summary>
/// Get DateTime to the specified year, month, and day using the conventions of the current thread culture
/// </summary>
/// <param name="year">The year</param>
/// <param name="month">The month</param>
/// <param name="day">The day</param>
/// <returns>An instance of the Nullable<System.DateTime></returns>
public static DateTime? ParseDate(int? year, int? month, int? day)
{
if (!year.HasValue || !month.HasValue || !day.HasValue)
return null;
DateTime? date = null;
try
{
date = new DateTime(year.Value, month.Value, day.Value, CultureInfo.CurrentCulture.Calendar);
}
catch { }
return date;
}
#endregion
}

View File

@ -0,0 +1,44 @@
# Architecture
## Framework vs. Consumer Boundary
This solution is **Layer 2 — Domain framework** (NopCommerce-plugin base). Built on Layer 0 (AyCode.Core), consumed by Layer 3 (specific plugin apps, plural and unknown). Full doctrine: AyCode.Core's `docs/ARCHITECTURE.md#framework-vs-consumer-boundary`.
### Layer position
```
Layer 0 — AyCode.Core universal primitives (serializers, logging, entities)
Layer 2 — Mango.Nop.Core this solution — NopCommerce-plugin domain framework
Layer 3 — Consumer plugin apps specific plugin projects with business logic
```
### Nop-plugin-framework-specific notes
- **`NopDependencies/`** mirrors NopCommerce entity classes under the original Nop namespaces — no runtime Nop dependency, compile-time type parity only
- **DTOs** use type parameters for consumer entity types (e.g. `MgOrderDto<TOrderItemDto, TProductDto>`) — concrete types are provided by plugin projects
- **Entity hierarchy**: `Nop.Core.BaseEntity` (mirrored) → `MgEntityBase` → generic DTOs/entities parameterized for consumers
- **`Logger` / `ILogger`** extend AyCode logging (`AcLoggerBase`) — plugin projects may subclass again if business-specific writers are needed
- **Zero runtime Nop dependency** — plugin apps bring their own Nop runtime
### What belongs here vs. in a plugin
**Yes, framework:**
- NopCommerce entity mirrors (for cross-plugin type sharing)
- Generic DTOs with type parameters
- Shared interfaces and extensions for NopCommerce domain entities
- Logger base with AyCode integration
**No, plugin only:**
- Plugin-specific business logic
- Plugin-named types (`XxxPlugin`, `XxxService` where Xxx is a specific product)
- Hardcoded plugin configuration, tenant, or product IDs
## Related Documents
| Topic | Document |
|---|---|
| Framework-first doctrine (full) | `../../../../../../../Aycode/Source/AyCode.Core/docs/ARCHITECTURE.md#framework-vs-consumer-boundary` |
| Placement rules, anti-patterns | `CONVENTIONS.md#framework-first-placement` |
| DTO system and GenericAttribute typed access | `DTOS.md` |
| NopDependencies mirror pattern | `NOP_DEPENDENCIES.md` |
| Folder structure and key types index | `../README.md` |

View File

@ -0,0 +1,28 @@
# Conventions
For core framework conventions (Ac prefix, naming patterns, Session/Transaction, etc.) see AyCode.Core's `docs/CONVENTIONS.md`.
## Framework-First Placement
Follow the doctrine in AyCode.Core's `docs/CONVENTIONS.md#framework-first-placement`. Same verdict table and hard rules apply.
**Nop-plugin-framework-specific additions:**
- `NopDependencies/` mirror classes stay in their original Nop namespaces — don't rename, don't add plugin-specific fields
- DTOs parameterize via type arguments, not concrete plugin types
- Any plugin-specific business logic belongs in the plugin project, not here
- Adding a new shared DTO: ensure it is useful for 2+ plugin projects (otherwise keep in the single plugin)
## Naming
- **`Mg` prefix**: for Mango-framework-level types (e.g. `MgEntityBase`, `MgOrderDto`, `MgProductDto`)
- **`IMg` prefix**: for Mango-framework-level interfaces (e.g. `IMgModelDtoBase`, `IMgSoftRemoveEntity`)
- **No prefix**: for NopCommerce entity mirrors in `NopDependencies/` — they stay in Nop namespace, same name as upstream
## XML Documentation
`<summary>` — brief, developer-facing, readable in VS IntelliSense tooltip. NO implementation details, NO wire-format / byte-level / perf specifics — those live in `docs/TOPIC/*.md`. Add `<example>` only when usage is non-obvious; otherwise omit.
## See Also
- Core framework naming (`Ac` / `IAc` prefixes), patterns, serialization conventions: AyCode.Core's `docs/CONVENTIONS.md`
- Framework-first verdict table (full): AyCode.Core's `docs/CONVENTIONS.md#framework-first-placement`

View File

@ -0,0 +1,74 @@
# DTO System
> Part of `Mango.Nop.Core`. See `Mango.Nop.Core/README.md` for project overview.
## Two Mapping Strategies
### Strategy 1: `ModelDtoBase<T>` (simple DTOs)
Used by: `CustomerDto`, `MgGenericAttributeDto`
```
ModelDtoBase → ModelDtoBase<Customer> → CustomerDto
```
- Manual `CopyEntityValuesToDto`/`CopyDtoValuesToEntity` overrides
- `CreateMainEntity()` uses `Activator.CreateInstance<T>()`
- No LinqToDB associations
### Strategy 2: `MgEntityBase` + `IModelDtoBase<T>` (complex DTOs)
Used by: `MgOrderDto`, `MgOrderItemDto`, `MgStockQuantityHistoryDto`
```
BaseEntity → MgEntityBase → MgOrderDto<TOrderItemDto, TProductDto> : IModelDtoBase<Order>
```
- Uses `PropertyHelper.CopyPublicValueTypeProperties()` for bulk value-type copy
- LinqToDB `[Association]` navigation properties
- Generic type parameters for child DTOs
### Strategy 3: Entity inheritance (MgProductDto)
```
BaseEntity → MgEntityBase → MgProductDto : IMgProductDto
```
- No `IModelDtoBase<Product>` (entity mapping methods are commented out)
- Direct property declarations mirroring `Product` fields
## DTO Types
| Type | Generic params | Maps to entity | Key features |
|---|---|---|---|
| `ModelDtoBase` | — | — | Abstract base, `int Id` only |
| `ModelDtoBase<TMainEntity>` | `TMainEntity : BaseEntity` | `TMainEntity` | `CreateMainEntity()`, `CopyEntityValuesToDto()`, `CopyDtoValuesToEntity()` |
| `MgOrderDto<TOrderItemDto, TProductDto>` | `TOrderItemDto : IMgOrderItemDto<TProductDto>`, `TProductDto : IMgProductDto` | `Order` | Has `Customer`, `List<TOrderItemDto>`, `List<OrderNote>` navigations. Enum wrappers: `OrderStatus`, `ShippingStatus`, `PaymentStatus` |
| `MgOrderItemDto<TProductDto>` | `TProductDto : IMgProductDto` | `OrderItem` | Has `TProductDto?` navigation. Computed `ProductName` property |
| `MgProductDto` | — | *(commented out)* | Base product DTO. Name, Price, StockQuantity, dimensions. No entity mapping methods (currently commented out) |
| `MgGenericAttributeDto` | — | `GenericAttribute` | Inherits `GenericAttribute` directly (not `ModelDtoBase`). Full bidirectional mapping |
| `MgStockQuantityHistoryDto<TProductDto>` | `TProductDto : IMgProductDto` | `StockQuantityHistory` | Has `TProductDto` navigation. Mapping methods `NotImplementedException` (stub) |
| `CustomerDto` | — | `Customer` | `Username`, `Email`, `FirstName`, `LastName`, `FullName` computed. Uses `[AcBinarySerializable]` and LinqToDB `[Table]` |
## DTO Interfaces
| Interface | Extends | Purpose |
|---|---|---|
| `IModelDtoBase` | `IEntityInt`, `IModelDtoBaseEmpty` | Marker for all DTOs with `int Id` |
| `IModelDtoBase<TMainEntity>` | `IModelDtoBase` | Bidirectional mapping contract: `CreateMainEntity()`, `CopyDtoValuesToEntity()`, `CopyEntityValuesToDto()` |
| `IMgOrderDto<TOrderItemDto, TProductDto>` | `IEntityInt`, `ISoftDeletedEntity` | Order DTO contract with navigation properties and initialization methods |
| `IMgOrderItemDto<TProductDto>` | `IEntityInt` | Order item DTO contract with product navigation |
| `IMgProductDto` | `IEntityInt`, `ILocalizedEntity`, `ISlugSupported`, `IAclSupported`, `IStoreMappingSupported`, `ISoftDeletedEntity` | Product DTO contract |
## GenericAttribute Typed Access
`GenericAttributeExtensions` provides typed access to nopCommerce's polymorphic key-value store:
| Method | Signature | Purpose |
|---|---|---|
| `GetValueOrNull<TValue>` | `(IEnumerable<GenericAttribute>, string key) -> TValue?` | Get typed value, returns null if not found |
| `GetValueOrDefault<TValue>` | `(IEnumerable<GenericAttribute>, string key, TValue default) -> TValue` | Same with default fallback |
| `TryGetValue<TValue>` | `(IEnumerable<GenericAttribute>, string key, out TValue?) -> bool` | Try-pattern for GA value |
| `AddNewGenericAttribute` | `(ICollection<GenericAttribute>, ...) -> GenericAttribute` | Create and add new GA with UTC timestamp |
**Rule:** Always use these extension methods — never parse `GenericAttribute.Value` strings manually.

View File

@ -0,0 +1,46 @@
# NopDependencies Pattern
> Part of `Mango.Nop.Core`. See `Mango.Nop.Core/README.md` for project overview.
## Why
`Mango.Nop.Core` has **zero nopCommerce runtime dependency**. This allows it to be referenced by projects that don't have the full nopCommerce stack (e.g. Blazor/MAUI clients that only need DTOs and interfaces).
To achieve this, `NopDependencies/` contains **mirror copies** of nopCommerce entity classes with the **same namespace** as the originals:
```csharp
// In NopDependencies/BaseEntity.cs — same namespace as nopCommerce
namespace Nop.Core;
public abstract partial class BaseEntity : IBaseEntity
{
public int Id { get; set; }
}
```
At compile time, projects referencing only `Mango.Nop.Core` get these mirror types. Projects with the full nopCommerce stack get the real types — they are type-compatible because they share the same namespace and shape.
## Rules
- **DO NOT modify** files in `NopDependencies/` unless the nopCommerce version changes
- **DO NOT change namespaces** — they must match the original nopCommerce types exactly
- All mirror classes are `partial` — they can be extended but should not be modified directly
## File List
| File | Namespace | Type |
|---|---|---|
| `BaseEntity.cs` | `Nop.Core` | `BaseEntity` (abstract, `IBaseEntity`) + `IBaseEntity` interface |
| `Catalogs/Customer.cs` | `Nop.Core.Domain.Customers` | `Customer` |
| `Catalogs/CustomerRole.cs` | `Nop.Core.Domain.Customers` | `CustomerRole` |
| `Catalogs/Order.cs` | `Nop.Core.Domain.Orders` | `Order` |
| `Catalogs/OrderItem.cs` | `Nop.Core.Domain.Orders` | `OrderItem` |
| `Catalogs/OrderNote.cs` | `Nop.Core.Domain.Orders` | `OrderNote` |
| `Catalogs/Product.cs` | `Nop.Core.Domain.Catalog` | `Product` |
| `Catalogs/GenericAttribute.cs` | `Nop.Core.Domain.Common` | `GenericAttribute` |
| `Catalogs/StockQuantityHistory.cs` | `Nop.Core.Domain.Catalog` | `StockQuantityHistory` |
| `Catalogs/DiscountMapping.cs` | `Nop.Core.Domain.Catalog` | `DiscountMapping` |
| `Catalogs/DiscountProductMapping.cs` | `Nop.Core.Domain.Catalog` | `DiscountProductMapping` |
**Enums:** `OrderStatus`, `PaymentStatus`, `ShippingStatus`, `ProductType`, `ManageInventoryMethod`, `BackorderMode`, `LowStockActivity`, `GiftCardType`, `RentalPricePeriod`, `RecurringProductCyclePeriod`, `DownloadActivationType`, `TaxDisplayType`, `VatNumberStatus`
**Interfaces:** `ISoftDeletedEntity`, `ILocalizedEntity`, `ISlugSupported`, `IAclSupported`, `IStoreMappingSupported`, `IDiscountSupported`

View File

@ -0,0 +1,19 @@
# Mango.Nop.Core documentation
Topic documentation for the `Mango.Nop.Core` project (Layer 2 framework — NopCommerce-adjacent base types).
## Reference docs (flat)
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — Project architecture
- [`CONVENTIONS.md`](CONVENTIONS.md) — Project-specific conventions
- [`DTOS.md`](DTOS.md) — DTO system, mapping strategies
- [`NOP_DEPENDENCIES.md`](NOP_DEPENDENCIES.md) — NopCommerce entity mirror pattern
## Navigation
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. All docs at this level are single-file reference; topic folders (e.g., LOGGING/) exist only at sub-projects that host a variant (see `../Mango.Nop.Services/docs/LOGGING/`).
## See also
- **Repo-level conventions**: `../../docs/CONVENTIONS.md`
- **Base framework**: `../../../../../Aycode/Source/AyCode.Core/` docs

View File

@ -1,4 +1,4 @@
namespace Mango.Nop.Core.Interfaces;
namespace Mango.Nop.Data.Interfaces;
public interface IMgDalBase //: IAcDalBase//: IDisposable
{

View File

@ -0,0 +1,22 @@
using Mango.Nop.Core.Loggers;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Orders;
using Nop.Data;
using System.Transactions;
namespace Mango.Nop.Data.Interfaces;
public interface IMgDbContextBase //: IAcDbContextBase
{
ILogger Logger { get; init; }
INopDataProvider DataProvider { get; init; }
IRepository<Order> Orders { get; set; }
IRepository<Product> Products { get; set; }
bool Transaction(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false);
bool TransactionSafe(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false);
Task<bool> TransactionAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false);
Task<bool> TransactionSafeAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false);
}

View File

@ -1,7 +1,7 @@
using Mango.Nop.Core.Entities;
using Nop.Data;
namespace Mango.Nop.Core.Interfaces;
namespace Mango.Nop.Data.Interfaces;
public interface IMgDbTableBase //: IAcDbTableBase
{

View File

@ -0,0 +1,53 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath>bin\FruitBank</BaseOutputPath>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Core\Nop.Core.csproj" />
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Data\Nop.Data.csproj" />
<ProjectReference Include="..\Mango.Nop.Core\Mango.Nop.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="AyCode.Core">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
</Reference>
<Reference Include="AyCode.Core.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Utils">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="docs\**\*.md" />
<None Include="**\README.md" Exclude="$(DefaultItemExcludes);docs\**" />
</ItemGroup>
</Project>

60
Mango.Nop.Data/README.md Normal file
View File

@ -0,0 +1,60 @@
# Mango.Nop.Data
@project {
type = "framework"
own-dep-projects = [
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Utils (in AyCode.Core repo)"
]
}
Data access layer with repository base classes and DB context abstractions. **net9.0**.
## Documentation
| Document | Topic |
|---|---|
| `REPOSITORIES.md` | MgDbTableBase, MgDtoDbTableBase — CRUD hooks, timestamps, delete rules, event bridging |
| `TRANSACTIONS.md` | MgDbContextBase — 4 transaction methods, lock strategy, callback contract |
## Folder Structure
| Folder | Purpose |
|---|---|
| `Interfaces/` | Repository interfaces: `IMgDalBase`, `IMgDbContextBase`, `IMgDbTableBase` |
| `Repositories/` | Repository base implementations: `MgDalBase`, `MgDbContextBase`, `MgDbTableBase`, `MgDtoDbTableBase` |
## Inheritance Chains
### Repository hierarchy
```
nopCommerce EntityRepository<TEntity> (Nop.Data)
+-- MgDbTableBase<TEntity>
+-- MgDtoDbTableBase<TDtoEntity, TMainEntity>
```
### Context hierarchy
```
MgDbContextBase (abstract, implements IMgDbContextBase)
+-- [Consumer DbContexts in plugins]
```
### DAL hierarchy
```
MgDalBase<TDbContext> (implements IMgDalBase<TDbContext>)
+-- [Consumer DALs in plugins]
```
## Interfaces
| Interface | Purpose |
|---|---|
| `IMgDbTableBase` | Marker interface for repository classes |
| `IMgDbContextBase` | Contract: `Logger`, `DataProvider`, `Orders`, `Products`, 4 transaction methods |
| `IMgDalBase` | `Name` property |
| `IMgDalBase<TDbContext>` | `Context`, `MutextLock` — typed access to DB context |
## Dependencies
- `Mango.Nop.Core` (ProjectReference)
- `Nop.Core`, `Nop.Data` (nopCommerce ProjectReferences)
- `Microsoft.AspNetCore.Mvc.NewtonsoftJson`

View File

@ -1,16 +1,14 @@
using System.Linq.Expressions;
using System.Transactions;
using Mango.Nop.Core.Interfaces;
using Mango.Nop.Data.Interfaces;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Configuration;
using Nop.Core.Events;
using Nop.Data;
using Nop.Data.DataProviders;
namespace Mango.Nop.Core.Repositories;
namespace Mango.Nop.Data.Repositories;
public class MgDalBase<TDbContext> : IMgDalBase<TDbContext> where TDbContext : IMgDbContextBase
public abstract class MgDalBase<TDbContext> : IMgDalBase<TDbContext> where TDbContext : IMgDbContextBase
{
public string Name { get; }
public TDbContext Context { get; }

View File

@ -0,0 +1,156 @@
using System.Transactions;
using AyCode.Core.Consts;
using AyCode.Core.Helpers;
using AyCode.Core.Loggers;
using AyCode.Utils.Extensions;
using Mango.Nop.Core.Loggers;
using Mango.Nop.Core.Services;
using Mango.Nop.Data.Interfaces;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Orders;
using Nop.Data;
namespace Mango.Nop.Data.Repositories;
public abstract class MgDbContextBase : IMgDbContextBase
{
//TODO: ez itt nem ay igazi, kitalálni vmit! - J.
private readonly CacheKey _auctionAllKey = new("Nop.auction.all-{0}", AUCTION_PATTERN_KEY);
public const string AUCTION_PATTERN_KEY = "Nop.auction.";
protected readonly IMgLockService LockService;
public ILogger Logger { get; init; }
public INopDataProvider DataProvider { get; init; }
public IRepository<Order> Orders { get; set; }
public IRepository<OrderItem> OrderItems { get; set; }
public IRepository<Product> Products { get; set; }
//public IHttpContextAccessor HttpContextAccessor { get; init; }
public MgDbContextBase(IRepository<Product> productRepository, IRepository<Order> orderRepository, IRepository<OrderItem> orderItemRepository, INopDataProvider dataProvider, IMgLockService lockService, ILogger logger)
{
LockService = lockService;
Logger = logger;// new Logger<MgDbContextBase>(logWriters.ToArray());
DataProvider = dataProvider;
Products = productRepository;
Orders = orderRepository;
OrderItems = orderItemRepository;
}
private static TransactionScope CreateTransactionScope(TransactionScopeOption transactionScopeOption = TransactionScopeOption.Required)
{
//TransactionManager.ImplicitDistributedTransactions = true;
var transactionOptions = new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TransactionManager.DefaultTimeout
};
return new TransactionScope(transactionScopeOption, transactionOptions, TransactionScopeAsyncFlowOption.Enabled);
//return new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled);
}
public bool Transaction(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false)
=> TransactionInner(callbackTransactionBody, throwException);
/// <summary>
/// Using LoskService, global lock!
/// </summary>
/// <param name="callbackTransactionBody"></param>
/// <param name="throwException"></param>
/// <returns></returns>
public bool TransactionSafe(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false)
{
using (LockService.SemaphoreSlim.UseWait())
{
return TransactionInner(callbackTransactionBody, throwException);
}
}
public Task<bool> TransactionAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false)
=> TaskHelper.ToThreadPoolTask(() => TransactionInnerAsync(callbackTransactionBody, throwException));
/// <summary>
/// Using LoskService, global lock!
/// </summary>
/// <param name="callbackTransactionBody"></param>
/// <param name="throwException"></param>
/// <returns></returns>
public Task<bool> TransactionSafeAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false)
{
return TaskHelper.ToThreadPoolTask(async () =>
{
using (await LockService.SemaphoreSlim.UseWaitAsync())
{
return await TransactionInnerAsync(callbackTransactionBody, throwException);
}
});
}
private async Task<bool> TransactionInnerAsync(Func<TransactionScope, Task<bool>> callbackTransactionBody, bool throwException = false)
{
bool result;
try
{
using (var transaction = CreateTransactionScope())// new TransactionScope( /*TransactionScopeOption.RequiresNew, */TransactionScopeAsyncFlowOption.Enabled))
{
result = await callbackTransactionBody(transaction);
if (result) transaction.Complete();
}
if (!result) Logger.Warning($"TransactionInnerAsync({this}) transaction ROLLBACK!");
}
catch (Exception ex)
{
if (throwException) throw;
result = false;
Logger.Error($"TransactionInnerAsync({this}) transaction ROLLBACK! ex: {ex.Message}{AcEnv.NL}", ex);
}
return result;
}
private bool TransactionInner(Func<TransactionScope, bool> callbackTransactionBody, bool throwException = false)
{
bool result;
try
{
using(var transaction = CreateTransactionScope()) //new TransactionScope( /*TransactionScopeOption.RequiresNew, */TransactionScopeAsyncFlowOption.Enabled)
{
result = callbackTransactionBody(transaction);
if (result) transaction.Complete();
}
if (!result) Logger.Warning($"TransactionInner({this}) transaction ROLLBACK!");
}
catch (Exception ex)
{
if (throwException) throw;
result = false;
Logger.Error($"TransactionInnerAsync({this}) transaction error! ex: {ex.Message}{AcEnv.NL}", ex);
}
return result;
}
public override string ToString()
{
return GetType().Name;
}
}

View File

@ -1,31 +1,35 @@
using System.Linq.Expressions;
using AyCode.Interfaces.Entities;
using AyCode.Interfaces.Entities;
using AyCode.Interfaces.TimeStampInfo;
using Mango.Nop.Core.Interfaces;
using Mango.Nop.Core.Loggers;
using Mango.Nop.Data.Interfaces;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Configuration;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Events;
using Nop.Data;
using Nop.Services.Logging;
using System.Linq.Expressions;
namespace Mango.Nop.Core.Repositories;
namespace Mango.Nop.Data.Repositories;
public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings, ILogger logger)
public abstract class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
: EntityRepository<TEntity>(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings), IMgDbTableBase where TEntity : BaseEntity
{
protected ILogger Logger = logger;
//protected ILogger Logger = logger;
protected IEventPublisher EventPublisher = eventPublisher;
protected INopDataProvider DataProvider = dataProvider;
protected IShortTermCacheManager ShortTermCacheManager = shortTermCacheManager;
public virtual IQueryable<TEntity> GetAll() => Table;
#region SetTimeStampInfos
private static void SetTimeStampCreated(TEntity entity)
{
if (typeof(TEntity).GetInterface(nameof(ITimeStampCreated)) != null)
{
((entity as ITimeStampCreated)!).Created = DateTime.UtcNow;
(entity as ITimeStampCreated)!.Created = DateTime.UtcNow;
}
}
@ -33,12 +37,16 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
{
if (typeof(TEntity).GetInterface(nameof(ITimeStampModified)) != null)
{
((entity as ITimeStampModified)!).Modified = DateTime.UtcNow;
(entity as ITimeStampModified)!.Modified = DateTime.UtcNow;
}
}
#endregion SetTimeStampInfos
//TODO: Transaction-be tenni az event-eket! - J.
#region OnCrudEvents
private void OnInsert(IList<TEntity> entities)
{
foreach (var entity in entities) OnInsert(entity);
@ -61,7 +69,7 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
{
SetTimeStampModified(entity);
_staticCacheManager.ClearAsync().GetAwaiter().GetResult();
_staticCacheManager.ClearAsync().GetAwaiter().GetResult(); //TODO: EZ MI??? - J.
//_staticCacheManager.RemoveByPrefix(MgDbContextBase.AUCTION_PATTERN_KEY);
}
@ -79,6 +87,7 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
//_staticCacheManager.RemoveByPrefix(MgDbContextBase.AUCTION_PATTERN_KEY);
}
#endregion OnCrudEvents
#region Insert
@ -106,9 +115,11 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
OnInsert(entities);
return base.InsertAsync(entities, publishEvent);
}
#endregion Insert
#region Update
public override void Update(TEntity entity, bool publishEvent = true)
{
OnUpdate(entity);
@ -132,29 +143,25 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
OnUpdate(entities);
return base.UpdateAsync(entities, publishEvent);
}
#endregion Update
#region Delete
protected override Task DeleteAsync(IList<TEntity> entities)
{
OnDelete(entities);
return base.DeleteAsync(entities);
}
protected override Task DeleteAsync<T>(IList<T> entities)
{
if(typeof(T) == typeof(TEntity)) OnDelete(entities.Cast<TEntity>().ToList());
return base.DeleteAsync(entities);
}
protected override IQueryable<TEntity> AddDeletedFilter(IQueryable<TEntity> query, in bool includeDeleted)
{
foreach (var entity in query) OnDelete(entity);
//EZ NEM DELETE METHOD! A GET-EKNÉL HASZNÁLJA A ISFOTDELETE FILTER-HEZ! - J.
//foreach (var entity in query) OnDelete(entity);
return base.AddDeletedFilter(query, in includeDeleted);
}
public virtual async Task DeleteAsync(int entityId, bool publishEvent = true)
{
var entity = await GetByIdAsync(entityId);
await DeleteAsync(entity, publishEvent);
}
public override Task DeleteAsync(TEntity entity, bool publishEvent = true)
{
OnDelete(entity);
@ -167,22 +174,46 @@ public class MgDbTableBase<TEntity>(IEventPublisher eventPublisher, INopDataProv
return base.DeleteAsync(entities, publishEvent);
}
/// <summary>
/// It does not call the PublishEvent() and OnDelete() functions!
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public override Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate)
{
//TODO:ide mit kéne?! - J.
return base.DeleteAsync(predicate);
}
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool publishEvent)
{
if (publishEvent)
{
var deletingEntities = await Table.Where(predicate).ToListAsync();
await base.DeleteAsync(deletingEntities, true);
return;
}
await DeleteAsync(predicate);
}
public override void Delete(TEntity entity, bool publishEvent = true)
{
OnDelete(entity);
base.Delete(entity, publishEvent);
}
public override void Delete(IList<TEntity> entities, bool publishEvent = true)
{
OnDelete(entities);
base.Delete(entities, publishEvent);
}
public override int Delete(Expression<Func<TEntity, bool>> predicate)
{
//TODO:ide mit kéne?! - J.
return base.Delete(predicate);
}
#endregion Delete
}

View File

@ -0,0 +1,77 @@
using System.Linq.Expressions;
using LinqToDB;
using Mango.Nop.Core.Loggers;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Configuration;
using Nop.Core.Domain.Orders;
using Nop.Core.Events;
using Nop.Data;
namespace Mango.Nop.Data.Repositories;
public abstract class MgDtoDbTableBase<TDtoEntity, TMainEntity> : MgDbTableBase<TDtoEntity> where TDtoEntity : BaseEntity/*, IMgModelDtoBase<TDtoEntity>*/ where TMainEntity : BaseEntity
{
public Type MainEntityType { get; } = typeof(Order);
public MgDtoDbTableBase(IEventPublisher eventPublisher, INopDataProvider dataProvider, IShortTermCacheManager shortTermCacheManager, IStaticCacheManager staticCacheManager, AppSettings appSettings)
: base(eventPublisher, dataProvider, shortTermCacheManager, staticCacheManager, appSettings)
{
}
public async Task<TMainEntity?> GetMainEntityById(int id) => await _dataProvider.GetTable<TMainEntity>().FirstOrDefaultAsync(x => x.Id == id);
public virtual async Task<int> DeleteMainEntityById(int id, bool publishEvent = true)
{
var affectedRows = 0;
var mainEntity = await GetMainEntityById(id);
if (mainEntity == null) return affectedRows;
affectedRows = await _dataProvider.GetTable<TMainEntity>().DeleteAsync(x => x.Id == id);
if (publishEvent) await _eventPublisher.PublishAsync(new EntityDeletedEvent<TMainEntity>(mainEntity));
return affectedRows;
}
public Task HandleEventAsync(EntityDeletedEvent<TDtoEntity> eventMessage)
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
public async Task HandleEventAsync(EntityInsertedEvent<TDtoEntity> eventMessage)
{
var mainEntity = await GetMainEntityById(eventMessage.Entity.Id);
if (mainEntity == null) throw new Exception($"MgDtoDbTableBase<{typeof(TDtoEntity).Name}, {MainEntityType.Name}>->EntityInsertedEvent<{typeof(TDtoEntity).Name}>(); (mainEntity == null); Id: {eventMessage.Entity.Id}");
await _eventPublisher.PublishAsync(new EntityInsertedEvent<TMainEntity>(mainEntity));
}
public async Task HandleEventAsync(EntityUpdatedEvent<TDtoEntity> eventMessage)
{
var mainEntity = await GetMainEntityById(eventMessage.Entity.Id);
if (mainEntity == null) throw new Exception($"MgDtoDbTableBase<{typeof(TDtoEntity).Name}, {MainEntityType.Name}>->EntityUpdatedEvent<{typeof(TDtoEntity).Name}>(); (mainEntity == null); Id: {eventMessage.Entity.Id}");
await _eventPublisher.PublishAsync(new EntityUpdatedEvent<TMainEntity>(mainEntity));
}
public override Task DeleteAsync(int entityId, bool publishEvent = true) => DeleteMainEntityById(entityId, publishEvent);
public override Task DeleteAsync(TDtoEntity entity, bool publishEvent = true) => DeleteMainEntityById(entity.Id, publishEvent);
public override Task DeleteAsync(IList<TDtoEntity> entities, bool publishEvent = true)
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
public override Task<int> DeleteAsync(Expression<Func<TDtoEntity, bool>> predicate)
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
public override Task DeleteAsync(Expression<Func<TDtoEntity, bool>> predicate, bool publishEvent)
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
public override void Delete(TDtoEntity entity, bool publishEvent = true)
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
public override void Delete(IList<TDtoEntity> entities, bool publishEvent = true)
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
public override int Delete(Expression<Func<TDtoEntity, bool>> predicate)
=> throw new Exception($"To delete, you must use the DeleteMainEntityById<{MainEntityType.Name}> function instead of '{typeof(TDtoEntity).Name}'!");
}

View File

@ -0,0 +1,17 @@
# Mango.Nop.Data documentation
Topic documentation for the `Mango.Nop.Data` project (Layer 2, data access layer).
## Reference docs (flat)
- [`REPOSITORIES.md`](REPOSITORIES.md) — Repository pattern usage
- [`TRANSACTIONS.md`](TRANSACTIONS.md) — Transaction pattern usage
## Navigation
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. All docs at this level are single-file reference.
## See also
- **Repo-level conventions**: `../../docs/CONVENTIONS.md`
- **Core DTOs**: `../Mango.Nop.Core/docs/DTOS.md`

View File

@ -0,0 +1,42 @@
# Repository Pattern
> Part of `Mango.Nop.Data`. See `Mango.Nop.Data/README.md` for project overview.
> For transaction patterns see `docs/TRANSACTIONS.md`.
## MgDbTableBase\<TEntity\>
Repository base wrapping nopCommerce `EntityRepository<TEntity>`.
**Constructor:**
```csharp
MgDbTableBase(IEventPublisher, INopDataProvider, IShortTermCacheManager, IStaticCacheManager, AppSettings)
```
### Features
| Feature | Detail |
|---|---|
| `GetAll()` | Returns `IQueryable<TEntity>` from `Table` property |
| Automatic timestamps | `ITimeStampCreated.Created` set on insert; `ITimeStampModified.Modified` set on insert/update |
| CRUD hooks | `OnInsert(entity)`, `OnUpdate(entity)`, `OnDelete(entity)` — virtual, overridable |
| Cache clear | `OnUpdate` clears `IStaticCacheManager` (currently clears all — TODO noted in code) |
| All CRUD overrides | Overrides all sync/async `Insert`, `Update`, `Delete` methods to call hooks before `base.*` |
| `DeleteAsync(int entityId)` | Convenience: load by id then delete |
| `DeleteAsync(predicate, bool publishEvent)` | When `publishEvent=true`, loads entities first then deletes with events |
## MgDtoDbTableBase\<TDtoEntity, TMainEntity\>
DTO-aware repository for when the DTO entity (`TDtoEntity`) maps to a different main nopCommerce entity (`TMainEntity`). This is needed because LinqToDB tables are registered for DTOs, but nopCommerce events must fire on the main entity type.
### Features
| Feature | Detail |
|---|---|
| `GetMainEntityById(id)` | Loads `TMainEntity` from `INopDataProvider.GetTable<TMainEntity>()` |
| `DeleteMainEntityById(id)` | Deletes the **main entity** (not the DTO) and publishes `EntityDeletedEvent<TMainEntity>` |
| Delete overrides | **All Delete methods throw** — forces callers to use `DeleteMainEntityById()` instead |
| Event bridging | `EntityInsertedEvent<TDtoEntity>` -> loads main entity -> publishes `EntityInsertedEvent<TMainEntity>` |
| Event bridging | `EntityUpdatedEvent<TDtoEntity>` -> loads main entity -> publishes `EntityUpdatedEvent<TMainEntity>` |
| Event bridging | `EntityDeletedEvent<TDtoEntity>` -> **throws** (must use `DeleteMainEntityById`) |
**Critical rule:** Never call `Delete` on a `MgDtoDbTableBase` repository directly. Always use `DeleteMainEntityById(int id)`.

View File

@ -0,0 +1,53 @@
# Transaction Pattern
> Part of `Mango.Nop.Data`. See `Mango.Nop.Data/README.md` for project overview.
> For repository base classes see `docs/REPOSITORIES.md`.
## MgDbContextBase
Abstract database context base. NOT an EF Core DbContext — wraps `INopDataProvider` and `IRepository<T>` nopCommerce repos.
**Constructor:**
```csharp
MgDbContextBase(IRepository<Product>, IRepository<Order>, IRepository<OrderItem>, INopDataProvider, IMgLockService, ILogger)
```
### Standard Repositories
| Property | Type |
|---|---|
| `Orders` | `IRepository<Order>` |
| `OrderItems` | `IRepository<OrderItem>` |
| `Products` | `IRepository<Product>` |
| `DataProvider` | `INopDataProvider` (LinqToDB raw queries) |
| `Logger` | Mango `ILogger` |
| `LockService` | `IMgLockService` (global `SemaphoreSlim`) |
### 4 Transaction Methods
| Method | Lock | Async |
|---|---|---|
| `Transaction(callback)` | No | No |
| `TransactionSafe(callback)` | `SemaphoreSlim` | No |
| `TransactionAsync(callback)` | No | Yes (thread pool) |
| `TransactionSafeAsync(callback)` | `SemaphoreSlim` | Yes (thread pool) |
**Callback contract:** `Func<TransactionScope, (Task<)bool(>)>` — return `true` to commit (`Complete()`), `false` to rollback.
**Isolation level:** `ReadCommitted`
**Error handling:** Catches exceptions, logs, returns `false` (unless `throwException = true`).
**TransactionSafe variants:** Use `LockService.SemaphoreSlim` for global serialization. Use for order creation, stock adjustment — any operation where concurrent modifications would corrupt data.
**Async variants:** Run on thread pool via `TaskHelper.ToThreadPoolTask()`.
## MgDalBase\<TDbContext\>
Data Access Layer orchestrator. Thin wrapper exposing:
| Property | Type | Purpose |
|---|---|---|
| `Name` | `string` | DAL instance name |
| `Context` | `TDbContext` | The DB context (typed) |
| `MutextLock` | `Mutex` | Cross-process locking |

View File

@ -1,7 +0,0 @@
namespace Mango.Nop.Services
{
public class Class1
{
}
}

View File

@ -0,0 +1,157 @@
using AyCode.Core.Helpers;
using AyCode.Entities;
using AyCode.Entities.Server.LogItems;
using AyCode.Utils.Extensions;
using LinqToDB;
using LinqToDB.Data;
using LinqToDB.DataProvider;
using LinqToDB.DataProvider.SqlServer;
using Mango.Nop.Data.Repositories;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Logging;
using Nop.Data;
using Nop.Data.DataProviders;
using System.Data.Common;
using System.Transactions;
using LogLevel = AyCode.Core.Loggers.LogLevel;
using LogLevelNop = Nop.Core.Domain.Logging.LogLevel;
namespace Mango.Nop.Services.Loggers
{
public interface INopLoggerMsSqlNopDataProvider
{
}
public class NopLoggerMsSqlNopDataProvider : MsSqlNopDataProvider, INopLoggerMsSqlNopDataProvider
{
//protected override IDataProvider LinqToDbDataProvider => SqlServerTools.GetDataProvider(SqlServerVersion.v2012, SqlServerProvider.MicrosoftDataSqlClient);
//protected override DbConnection CreateDbConnection(string connectionString = null)
//{
// connectionString = "Data Source=195.26.231.218;Initial Catalog=MangoManagement;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=v6f_?xNfg9N1;Trust Server Certificate=True";
// return base.GetInternalDbConnection(connectionString);
//}
public void InsertLogItem<TEntity>(TEntity entity) where TEntity : notnull
{
using var transaction = CreateTransactionScope(TransactionScopeAsyncFlowOption.Suppress);
using var dataContext = CreateDataConnection();
dataContext.Insert(entity);
transaction.Complete();
}
public async Task InsertLogItemAsync<TEntity>(TEntity entity) where TEntity : notnull
{
using var transaction = CreateTransactionScope(TransactionScopeAsyncFlowOption.Enabled);
await using var dataContext = CreateDataConnection();
await dataContext.InsertAsync(entity);
transaction.Complete();
}
private static TransactionScope CreateTransactionScope(TransactionScopeAsyncFlowOption transactionScopeAsyncFlowOption)
{
var transactionOptions = new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadUncommitted,
Timeout = TransactionManager.DefaultTimeout
};
return new TransactionScope(TransactionScopeOption.Suppress, transactionOptions, transactionScopeAsyncFlowOption);
}
}
public class NopLogWriter : AcLogItemWriterBase<AcLogItem>//AcTextLogWriterBase
{
private readonly SemaphoreSlim _lockSlim = new(0);
private NopLoggerMsSqlNopDataProvider _dataProvider;
private readonly global::Nop.Services.Logging.ILogger _nopLogger;
protected readonly CommonSettings _commonSettings;
protected readonly CustomerSettings _customerSettings;
protected readonly IWebHelper _webHelper;
public NopLogWriter(INopLoggerMsSqlNopDataProvider nopLoggerMsSqlNopDataProvider, CommonSettings commonSettings, CustomerSettings customerSettings, IWebHelper webHelper,
global::Nop.Services.Logging.ILogger nopLogger) : this(nopLoggerMsSqlNopDataProvider, commonSettings, customerSettings, webHelper,nopLogger, null)
{ }
public NopLogWriter(INopLoggerMsSqlNopDataProvider nopLoggerMsSqlNopDataProvider,
CommonSettings commonSettings, CustomerSettings customerSettings, IWebHelper webHelper, global::Nop.Services.Logging.ILogger nopLogger, string? categoryName = null) : base(categoryName)
{
_nopLogger = nopLogger;
_dataProvider = (NopLoggerMsSqlNopDataProvider)nopLoggerMsSqlNopDataProvider;
_commonSettings = commonSettings;
_customerSettings = customerSettings;
_webHelper = webHelper;
}
//public NopLogWriter(ILogger nopLogger, AppType appType, LogLevel logLevel, string? categoryName = null) : base(appType, logLevel, categoryName)
//{
// _nopLogger=nopLogger;
//}
protected override void WriteLogItemCallback(AcLogItem logItem)
{
//using (_lockSlim.UseWait())
{
switch (logItem.LogLevel)
{
case LogLevel.Detail:
case LogLevel.Trace:
case LogLevel.Debug:
//if (_nopLogger.IsEnabled(LogLevelNop.Debug)) InsertLog(LogLevelNop.Debug, logItem.Text, logItem.Exception, null);
//break;
case LogLevel.Info:
if (_nopLogger.IsEnabled(LogLevelNop.Information)) InsertLog(LogLevelNop.Information, logItem.Text, logItem.Exception, null);
//_nopLogger.Information(logItem.Text); //.Forget();
break;
case LogLevel.Suggest:
case LogLevel.Warning:
if (_nopLogger.IsEnabled(LogLevelNop.Warning)) InsertLog(LogLevelNop.Warning, logItem.Text, logItem.Exception, null);
//_nopLogger.Warning(logItem.Text); //.Forget();
break;
case LogLevel.Error:
if (_nopLogger.IsEnabled(LogLevelNop.Error)) InsertLog(LogLevelNop.Error, logItem.Text, logItem.Exception, null);
//_nopLogger.Error(logItem.Text); //.Forget();//, logItem.Exception);
break;
case LogLevel.Disabled:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
private void InsertLog(LogLevelNop logLevel, string shortMessage, string fullMessage = "", Customer customer = null)
{
//check ignore word/phrase list?
//if (IgnoreLog(shortMessage) || IgnoreLog(fullMessage))
// return;
_dataProvider.InsertLogItem(PrepareLog(logLevel, shortMessage, fullMessage, customer));
//_dataProvider.InsertLogItemAsync(PrepareLog(logLevel, shortMessage, fullMessage, customer)).Forget();
}
private Log PrepareLog(LogLevelNop logLevel, string shortMessage, string fullMessage = "", Customer customer = null)
{
return new Log
{
LogLevel = logLevel,
ShortMessage = shortMessage,
FullMessage = fullMessage,
IpAddress = _customerSettings.StoreIpAddresses ? _webHelper.GetCurrentIpAddress() : string.Empty,
CustomerId = customer?.Id,
PageUrl = _webHelper.GetThisPageUrl(true),
ReferrerUrl = _webHelper.GetUrlReferrer(),
CreatedOnUtc = DateTime.UtcNow
};
}
}
}

View File

@ -1,46 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath>bin\FruitBank</BaseOutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MessagePack.Annotations" Version="2.5.192" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Core\Nop.Core.csproj" />
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Data\Nop.Data.csproj" />
<ProjectReference Include="..\..\..\..\NopCommerce\Libraries\Nop.Services\Nop.Services.csproj" />
<ProjectReference Include="..\..\..\..\NopCommerce\Presentation\Nop.Web.Framework\Nop.Web.Framework.csproj" />
<ProjectReference Include="..\Mango.Nop.Core\Mango.Nop.Core.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Core\Nop.Core.csproj" />
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Data\Nop.Data.csproj" />
<ProjectReference Include="..\..\..\..\FruitBank\Libraries\Nop.Services\Nop.Services.csproj" />
<ProjectReference Include="..\..\..\..\FruitBank\Presentation\Nop.Web.Framework\Nop.Web.Framework.csproj" />
<ProjectReference Include="..\Mango.Nop.Core\Mango.Nop.Core.csproj" />
<ProjectReference Include="..\Mango.Nop.Data\Mango.Nop.Data.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="AyCode.Core">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.dll</HintPath>
</Reference>
<Reference Include="AyCode.Core.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Core.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Entities.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Interfaces.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Utils">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\Debug\net8.0\AyCode.Utils.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Reference Include="AyCode.Core">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.dll</HintPath>
</Reference>
<Reference Include="AyCode.Core.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Core.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.dll</HintPath>
</Reference>
<Reference Include="AyCode.Entities.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Entities.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.dll</HintPath>
</Reference>
<Reference Include="AyCode.Interfaces.Server">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Interfaces.Server.dll</HintPath>
</Reference>
<Reference Include="AyCode.Utils">
<HintPath>..\..\..\..\..\..\Aycode\Source\AyCode.Core\AyCode.Services.Server\bin\FruitBank\$(Configuration)\net9.0\AyCode.Utils.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="docs\**\*.md" />
<None Include="**\README.md" Exclude="$(DefaultItemExcludes);docs\**" />
</ItemGroup>
</Project>

View File

@ -1,10 +1,14 @@
using Microsoft.AspNetCore.Http;
using AyCode.Core.Loggers;
using Mango.Nop.Core.Interfaces;
using Mango.Nop.Core.Loggers;
using Mango.Nop.Data.Interfaces;
using Mango.Nop.Data.Repositories;
using Microsoft.AspNetCore.Http;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Events;
using Nop.Services.Events;
using Nop.Services.Logging;
using Nop.Web.Framework.Events;
namespace Mango.Nop.Services;
@ -12,8 +16,9 @@ namespace Mango.Nop.Services;
/// <summary>
/// Represents plugin event consumer
/// </summary>
public class MgEventConsumer(IHttpContextAccessor httpContextAccessor, ILogger logger) :
public abstract class MgEventConsumerBase(IMgDbContextBase ctx, IHttpContextAccessor httpContextAccessor, IEnumerable<IAcLogWriterBase> logWriters) :
IConsumer<EntityUpdatedEvent<Product>>,
IConsumer<EntityInsertedEvent<Product>>,
IConsumer<CustomerRegisteredEvent>,
IConsumer<OrderPlacedEvent>,
IConsumer<PageRenderingEvent>,
@ -24,28 +29,36 @@ public class MgEventConsumer(IHttpContextAccessor httpContextAccessor, ILogger l
{
#region Fields
protected ILogger Logger { get; } = logger;
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
protected ILogger Logger { get; } = new Logger<MgEventConsumerBase>(logWriters.ToArray());
protected readonly IHttpContextAccessor HttpContextAccessor = httpContextAccessor;
#endregion
protected virtual async Task<Product> CheckAndUpdateProductManageInventoryMethodToManageStock(Product product)
{
if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock) return product;
product.ManageInventoryMethod = ManageInventoryMethod.ManageStock;
await ctx.Products.UpdateAsync(product, false);
return product;
}
public virtual async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage)
{
}
{ }
public virtual async Task HandleEventAsync(EntityInsertedEvent<Product> eventMessage)
{ }
public virtual async Task HandleEventAsync(CustomerRegisteredEvent eventMessage)
{
}
{ }
public virtual async Task HandleEventAsync(OrderPlacedEvent eventMessage)
{
}
{ }
public virtual async Task HandleEventAsync(PageRenderingEvent eventMessage)
{
}
{ }
public virtual async Task HandleEventAsync(ProductSearchEvent eventMessage)
{
}
{ }
}

View File

@ -1,15 +0,0 @@
namespace Mango.Nop.Services;
public abstract class MgLockService : IMgLockService
{
public SemaphoreSlim SemaphoreSlim { get; protected init; }
protected MgLockService() : this(new SemaphoreSlim(1))
{
}
protected MgLockService(SemaphoreSlim semaphoreSlim)
{
SemaphoreSlim = semaphoreSlim;
}
}

View File

@ -0,0 +1,17 @@
using Mango.Nop.Core.Services;
namespace Mango.Nop.Services;
public abstract class MgLockServiceBase : IMgLockService
{
public SemaphoreSlim SemaphoreSlim { get; protected init; }
protected MgLockServiceBase() : this(new SemaphoreSlim(1))
{
}
protected MgLockServiceBase(SemaphoreSlim semaphoreSlim)
{
SemaphoreSlim = semaphoreSlim;
}
}

View File

@ -1,6 +1,6 @@
namespace Mango.Nop.Services;
public abstract class MgSessionItem(string sessionKey) : IMgSessionItem
public abstract class MgSessionItemBase(string sessionKey) : IMgSessionItem
{
public string SessionId { get; protected set; } = sessionKey;
public string? SignaRConnectionId { get; set; }

View File

@ -3,7 +3,7 @@ using AyCode.Utils.Extensions;
namespace Mango.Nop.Services;
public abstract class MgSessionService<TSessionItem> : IMgSessionService<TSessionItem> where TSessionItem : class, IMgSessionItem
public abstract class MgSessionServiceBase<TSessionItem> : IMgSessionService<TSessionItem> where TSessionItem : class, IMgSessionItem
{
protected ConcurrentDictionary<string, TSessionItem> Sessions { get; } = new();

View File

@ -0,0 +1,29 @@
# Mango.Nop.Services
@project {
type = "framework"
own-dep-projects = [
"AyCode.Core, AyCode.Core.Server, AyCode.Entities, AyCode.Entities.Server, AyCode.Interfaces, AyCode.Interfaces.Server, AyCode.Utils (in AyCode.Core repo)"
]
}
Service base classes for nopCommerce plugin development — background tasks, session management, events, locking, logging. **net9.0**.
## Documentation
| Document | Topic |
|---|---|
| `SERVICES.md` | MgBackgroundServiceBase, MgSessionServiceBase, MgEventConsumerBase, MgLockServiceBase |
| `LOGGING/README.md` | NopLogWriter — AyCode-to-nopCommerce log bridge, TransactionScope(Suppress) |
## Folder Structure
| Folder | Purpose |
|---|---|
| `Loggers/` | `NopLogWriter`, `NopLoggerMsSqlNopDataProvider` — AyCode -> nopCommerce log bridge |
| *(root)* | `MgBackgroundServiceBase`, `MgSessionServiceBase`, `MgEventConsumerBase`, `MgLockServiceBase`, interfaces |
## Dependencies
- `Mango.Nop.Core`, `Mango.Nop.Data` (ProjectReferences)
- `Nop.Core`, `Nop.Data`, `Nop.Services`, `Nop.Web.Framework` (nopCommerce ProjectReferences)

View File

@ -0,0 +1,40 @@
# NopLogWriter — Logging Bridge
> Part of `Mango.Nop.Services`. See `Mango.Nop.Services/README.md` for project overview.
> For AyCode base logging types (`AcLogItemWriterBase`, `AcLogItem`, log levels) see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`.
## Overview
Bridges AyCode logging to nopCommerce's `Nop.Core.Domain.Logging.Log` table via direct DB insert.
## NopLogWriter
| Feature | Detail |
|---|---|
| Inherits | `AcLogItemWriterBase<AcLogItem>` (AyCode.Core) |
| Log level mapping | AyCode `Detail/Trace/Debug/Info` -> nopCommerce `Information`; `Suggest/Warning` -> `Warning`; `Error` -> `Error` |
| Direct DB insert | Uses `NopLoggerMsSqlNopDataProvider` with `TransactionScope(Suppress)` to avoid transaction conflicts |
**Constructor:** `(INopLoggerMsSqlNopDataProvider, CommonSettings, CustomerSettings, IWebHelper, Nop.Services.Logging.ILogger, string? categoryName)`
## NopLoggerMsSqlNopDataProvider
Extends `MsSqlNopDataProvider`. Provides isolated DB access for log writes.
| Method | Purpose |
|---|---|
| `InsertLogItem<T>()` | Sync insert with own `TransactionScope(Suppress, ReadUncommitted)` |
| `InsertLogItemAsync<T>()` | Async insert with own `TransactionScope(Suppress, ReadUncommitted)` |
The `TransactionScope(Suppress)` is critical — without it, log writes would participate in the caller's transaction, causing nesting conflicts and potential deadlocks.
## Integration Point
```
Application code -> Mango.Nop.Core.Loggers.ILogger (extends IAcLoggerBase)
-> Logger<TCategory> (extends AcLoggerBase, delegates to IAcLogWriterBase[])
-> NopLogWriter -> Nop Log table (direct SQL insert)
-> [Other AyCode log writers: console, file, SignalR, etc.]
```
**Exception:** `MgBackgroundServiceBase` uses `Nop.Services.Logging.ILogger` directly (nopCommerce logger), not the Mango wrapper.

View File

@ -0,0 +1,21 @@
# Mango.Nop.Services documentation
Topic documentation for the `Mango.Nop.Services` project (Layer 2, service patterns).
## Reference docs (flat)
- [`SERVICES.md`](SERVICES.md) — Service pattern usage
## Topic folders
- [`LOGGING/`](LOGGING/README.md) — Logger bridge between NopCommerce's logger and AyCode.Core's logger (project-specific variant)
## Navigation
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. Single-file reference docs remain flat; project-specific variants of framework topics (like LOGGING) live in named subfolders.
## See also
- **Base logger** (framework): `../../../../../Aycode/Source/AyCode.Core/AyCode.Core/docs/LOGGING/README.md`
- **Remote logger variant**: `../../../../../Aycode/Source/AyCode.Core/AyCode.Services/docs/LOGGING/README.md`
- **Server-side logger variant**: `../../../../../Aycode/Source/AyCode.Core/AyCode.Core.Server/docs/LOGGING/README.md`

View File

@ -0,0 +1,71 @@
# Service Base Classes
> Part of `Mango.Nop.Services`. See `Mango.Nop.Services/README.md` for project overview.
> For logging bridge see `docs/LOGGING/README.md`.
## MgBackgroundServiceBase
Abstract hosted background service. Inherits `Microsoft.Extensions.Hosting.BackgroundService`.
**Constructor:** `(ILogger, IServiceProvider, int executeIntervalMs)`
| Feature | Detail |
|---|---|
| Loop pattern | `ExecuteAsync` loops: `Task.Delay(ExecuteIntervalMs)` -> `OnExecuteAsync()`, with pause support |
| `OnExecuteAsync(CancellationToken)` | Abstract — subclass implements the actual work |
| `Pause(bool)` | Pauses/resumes the loop without stopping the service |
| `ExecuteIntervalMs` | Configurable interval between iterations |
| Exception handling | Catches and logs errors per iteration, never crashes the loop |
| Logging | Uses nopCommerce `Nop.Services.Logging.ILogger` (**not** `Mango.Nop.Core.Loggers.ILogger`) |
**Interface:** `IMgBackgroundService : IHostedService, IDisposable`
## MgSessionServiceBase\<TSessionItem\>
In-memory session management using `ConcurrentDictionary<string, TSessionItem>`.
| Method | Signature | Purpose |
|---|---|---|
| `GetOrCreateSessionItem` | `(string sessionId) -> TSessionItem?` | Get existing or create new via `Activator.CreateInstance` |
| `TryAddSessionItem` | `(TSessionItem) -> bool` | Add if not exists, throws if duplicate |
| `TryGetSessionItem` | `(string sessionId, out TSessionItem) -> bool` | Try-pattern lookup |
| `TryRemoveSessionItem` | `(string sessionId, out TSessionItem) -> bool` | Remove and return |
| `TryGetSessionItemBySignlaRConnectionId` | `(string connectionId, out TSessionItem?) -> bool` | Find session by SignalR connection |
| `Count` | `() -> int` | Active session count |
**Interface:** `IMgSessionService<TSessionItem> where TSessionItem : IMgSessionItem`
## MgSessionItemBase
Base session item. Primary constructor: `(string sessionKey)`.
| Property | Type | Purpose |
|---|---|---|
| `SessionId` | `string` | Unique session identifier |
| `SignaRConnectionId` | `string?` | Associated SignalR connection ID |
| `RequestCount` | `int` | Request counter |
**Interface:** `IMgSessionItem``SessionId`, `SignaRConnectionId`, `RequestCount`
## MgEventConsumerBase
Abstract nopCommerce event consumer. Subscribes to:
| Event | Handler |
|---|---|
| `EntityUpdatedEvent<Product>` | `HandleEventAsync(...)` — virtual, empty default |
| `EntityInsertedEvent<Product>` | `HandleEventAsync(...)` — virtual, empty default |
| `CustomerRegisteredEvent` | `HandleEventAsync(...)` — virtual, empty default |
| `OrderPlacedEvent` | `HandleEventAsync(...)` — virtual, empty default |
| `PageRenderingEvent` | `HandleEventAsync(...)` — virtual, empty default |
| `ProductSearchEvent` | `HandleEventAsync(...)` — virtual, empty default |
Built-in helper: `CheckAndUpdateProductManageInventoryMethodToManageStock(Product)` — ensures product has `ManageStock` inventory method.
**Constructor:** `(IMgDbContextBase ctx, IHttpContextAccessor, IEnumerable<IAcLogWriterBase> logWriters)`
## MgLockServiceBase
Simple in-process lock using `SemaphoreSlim(1)`. Implements `IMgLockService` (defined in `Mango.Nop.Core.Services`).
Used by `MgDbContextBase.TransactionSafe*` variants for global serialization.

51
README.md Normal file
View File

@ -0,0 +1,51 @@
# Mango.Nop Libraries
> For library domain rules see: `.github/copilot-instructions.md`
> For detailed docs see: `docs/`
Shared nopCommerce extension libraries providing domain entities, DTOs, data access, and service base classes. All target **net9.0** (nopCommerce 4.80.9 requirement).
## Projects
| Project | Purpose | Key Types |
|---|---|---|
| `Mango.Nop.Core` | Domain entities, DTOs, interfaces, nopCommerce entity mirrors (zero nopCommerce runtime dep) | `MgEntityBase`, `ModelDtoBase<T>`, `MgOrderDto<,>`, `MgOrderItemDto<>`, `MgProductDto`, `CustomerDto`, `MgGenericAttributeDto`, `MgStockTaking<>`, `GenericAttributeExtensions` |
| `Mango.Nop.Data` | Data access layer — repository base classes, DB context, transactions | `MgDbTableBase<T>`, `MgDtoDbTableBase<,>`, `MgDbContextBase`, `MgDalBase<T>` |
| `Mango.Nop.Services` | Service base classes — background services, session, events, locking, logging | `MgBackgroundServiceBase`, `MgSessionServiceBase<T>`, `MgEventConsumerBase`, `MgLockServiceBase`, `NopLogWriter` |
## Dependency Graph
See `docs/ARCHITECTURE.md` for full dependency graph, project roles, DTO mapping strategies, and transaction patterns.
```
AyCode.Core (DLL references)
Mango.Nop.Core (net9.0, zero nopCommerce runtime dependency)
Mango.Nop.Data (net9.0) → Nop.Core, Nop.Data
Mango.Nop.Services (net9.0) → Nop.Core, Nop.Data, Nop.Services, Nop.Web.Framework
```
## Reference Modes
- **Full stack** (ProjectReference, all 3 libraries) — for nopCommerce plugins that need entity access, data layer, and services.
- **Types only** (DLL reference, `Mango.Nop.Core` only) — for projects that only need DTOs, entities, and interfaces without the nopCommerce runtime dependency.
## Documentation Map
| File | Purpose |
|---|---|
| `.github/copilot-instructions.md` | **Domain rules** — single source of truth for all conventions |
| `docs/ARCHITECTURE.md` | Dependency graph, project roles, DTO strategies, transaction patterns, logging architecture |
| `docs/CONVENTIONS.md` | Naming, patterns, project boundaries, AyCode integration points |
| `docs/GLOSSARY.md` | Term definitions — entity/DTO system, data access, services, AyCode types |
| `Mango.Nop.Core/README.md` | Core project overview — entity hierarchy, loggers, extensions, models |
| `Mango.Nop.Core/docs/DTOS.md` | DTO system — two mapping strategies, all DTO types, GenericAttribute access |
| `Mango.Nop.Core/docs/NOP_DEPENDENCIES.md` | NopDependencies pattern — mirror copies, namespace rules, file list |
| `Mango.Nop.Data/README.md` | Data project overview — repository and context hierarchy, interfaces |
| `Mango.Nop.Data/docs/REPOSITORIES.md` | MgDbTableBase, MgDtoDbTableBase — CRUD hooks, timestamps, delete rules |
| `Mango.Nop.Data/docs/TRANSACTIONS.md` | MgDbContextBase — 4 transaction methods, lock strategy, callback contract |
| `Mango.Nop.Services/README.md` | Services project overview |
| `Mango.Nop.Services/docs/SERVICES.md` | MgBackgroundServiceBase, MgSessionServiceBase, MgEventConsumerBase, MgLockServiceBase |
| `Mango.Nop.Services/docs/LOGGING/README.md` | NopLogWriter — AyCode-to-nopCommerce log bridge |

137
docs/ARCHITECTURE.md Normal file
View File

@ -0,0 +1,137 @@
# Architecture
## Dependency Graph
```
AyCode.Core (net9.0, DLL refs: AyCode.Interfaces, .Entities, .Core, .Utils, + .Server variants)
Mango.Nop.Core (net9.0, no nopCommerce runtime dependency)
Mango.Nop.Data (net9.0) → Nop.Core, Nop.Data
Mango.Nop.Services (net9.0) → Nop.Core, Nop.Data, Nop.Services, Nop.Web.Framework
```
**Rule:** Dependencies flow upward only. `Mango.Nop.Core` has zero nopCommerce runtime dependency — it uses mirror copies in `NopDependencies/`.
## Project Roles
### Mango.Nop.Core — Domain Layer
Zero nopCommerce runtime dependency. Contains:
- **DTOs** (`Dtos/`) — `ModelDtoBase<TMainEntity>` with bidirectional entity mapping (`CopyEntityValuesToDto`, `CopyDtoValuesToEntity`, `CreateMainEntity`)
- **Entities** (`Entities/`) — `MgEntityBase` (inherits `BaseEntity`, implements `IEntityInt`)
- **NopDependencies** (`NopDependencies/`) — mirror copies of nopCommerce entities (`BaseEntity`, `Customer`, `Order`, `Product`, `GenericAttribute`, enums). Allows referencing without full nopCommerce dependency chain.
- **Extensions** (`Extensions/`) — `GenericAttributeExtensions` for typed attribute access, `CollectionExtensionsNopBaseEntity` for collection operations
- **Interfaces** (`Interfaces/`) — DTO interfaces, soft-delete, foreign key markers
- **Models** (`Models/`) — Login request/response
- **Loggers** (`Loggers/`) — `ILogger` abstraction wrapping AyCode `IAcLoggerBase`
### Mango.Nop.Data — Data Access Layer
Wraps nopCommerce data infrastructure with custom base classes:
- **`MgDbTableBase<TEntity>`** — extends nopCommerce `EntityRepository<TEntity>`, adds `GetAll()`, timestamp handling (`ITimeStampCreated`, `ITimeStampModified`), event publishing
- **`MgDtoDbTableBase<TDtoEntity, TMainEntity>`** — DTO-aware repository. **Critical: Delete operations throw — must use `DeleteMainEntityById()`.** Event bridging: DTO events → main entity events
- **`MgDbContextBase`** — DB context base with `Transaction/TransactionSafe/TransactionAsync/TransactionSafeAsync` methods. TransactionSafe variants use global `SemaphoreSlim` lock
- **`MgDalBase<TDbContext>`** — Data Access Layer orchestrator with `Context`, `Name`, `MutextLock`
### Mango.Nop.Services — Service Layer
Service base classes for nopCommerce plugin development:
- **`MgBackgroundServiceBase`** — hosted background task with configurable interval, pause support, per-iteration error handling
- **`MgSessionServiceBase<TSessionItem>`** / `MgSessionItemBase` — in-memory session management via `ConcurrentDictionary`, SignalR connection tracking
- **`MgEventConsumerBase`** — nopCommerce entity event handler base (Product insert/update, CustomerRegistered, OrderPlaced, PageRendering, ProductSearch)
- **`MgLockServiceBase`** — `SemaphoreSlim(1)` lock wrapper
- **`NopLogWriter`** — logging bridge: AyCode log levels → nopCommerce `Log` table via direct DB insert with `TransactionScope(Suppress)`
## NopDependencies Pattern
`Mango.Nop.Core/NopDependencies/` contains mirror copies of nopCommerce entity classes with the **same namespace** as the original nopCommerce types:
```csharp
// In NopDependencies/BaseEntity.cs — same namespace as nopCommerce
namespace Nop.Core;
public abstract partial class BaseEntity : IBaseEntity
{
public int Id { get; set; }
}
```
This allows `Mango.Nop.Core` to be referenced by projects that don't have a direct nopCommerce dependency (e.g., Blazor/MAUI clients), while maintaining type compatibility with the real nopCommerce entities at the server level.
**Files in NopDependencies:**
- `BaseEntity.cs` + `IBaseEntity` — root entity base (`Nop.Core`)
- `Catalogs/``Customer`, `CustomerRole`, `Order`, `OrderItem`, `OrderNote`, `Product`, `GenericAttribute`, `StockQuantityHistory`, `DiscountMapping`, `DiscountProductMapping`
- Enums — `OrderStatus`, `PaymentStatus`, `ShippingStatus`, `ProductType`, `ManageInventoryMethod`, `BackorderMode`, `LowStockActivity`, `GiftCardType`, `RentalPricePeriod`, `RecurringProductCyclePeriod`, `DownloadActivationType`, `TaxDisplayType`, `VatNumberStatus`
- Interfaces — `ISoftDeletedEntity`, `ILocalizedEntity`, `ISlugSupported`, `IAclSupported`, `IStoreMappingSupported`, `IDiscountSupported`
## DTO Mapping Strategies
Two patterns coexist:
### Strategy 1: `ModelDtoBase<T>` (simple DTOs)
Used by: `CustomerDto`, `MgGenericAttributeDto`
```
ModelDtoBase → ModelDtoBase<Customer> → CustomerDto
```
- Manual `CopyEntityValuesToDto`/`CopyDtoValuesToEntity` overrides
- No LinqToDB associations
### Strategy 2: `MgEntityBase` + `IModelDtoBase<T>` (complex DTOs)
Used by: `MgOrderDto`, `MgOrderItemDto`, `MgStockQuantityHistoryDto`
```
BaseEntity → MgEntityBase → MgOrderDto<TOrderItemDto, TProductDto> : IModelDtoBase<Order>
```
- Uses `PropertyHelper.CopyPublicValueTypeProperties()` for bulk copy
- LinqToDB `[Association]` navigation properties
- Generic type parameters for child DTOs
### Strategy 3: Entity inheritance (MgProductDto)
```
BaseEntity → MgEntityBase → MgProductDto : IMgProductDto
```
- No `IModelDtoBase<Product>` (entity mapping methods are commented out)
- Direct property declarations mirroring `Product` fields
## Transaction Pattern
`MgDbContextBase` provides 4 transaction methods:
| Method | Lock | Async |
|---|---|---|
| `Transaction(callback)` | No | No |
| `TransactionSafe(callback)` | `SemaphoreSlim` | No |
| `TransactionAsync(callback)` | No | Yes (thread pool) |
| `TransactionSafeAsync(callback)` | `SemaphoreSlim` | Yes (thread pool) |
**Callback contract:** `Func<TransactionScope, (Task<)bool(>)>` — return `true` to commit, `false` to rollback.
**Isolation level:** `ReadCommitted`
**Error handling:** Catches exceptions, logs, returns `false` (unless `throwException = true`)
## Logging Architecture
Base logging infrastructure (`IAcLoggerBase`, `IAcLogWriterBase`, `AcLoggerBase`, `AcLogItemWriterBase`, `AcLogItem`) is defined in AyCode.Core — see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md` for base types, log levels, and writer contracts.
This layer provides the nopCommerce-specific bridge:
```
Application code → Mango.Nop.Core.Loggers.ILogger (extends IAcLoggerBase)
→ Logger<TCategory> (extends AcLoggerBase, delegates to IAcLogWriterBase[])
→ NopLogWriter (in Services, extends AcLogItemWriterBase<AcLogItem>) → Nop Log table (direct SQL insert)
→ [Other AyCode log writers: console, file, SignalR, etc. — see AyCode.Core/AyCode.Core/docs/LOGGING/README.md]
```
**Important:** `MgBackgroundServiceBase` uses nopCommerce's `Nop.Services.Logging.ILogger` directly (not the Mango wrapper). Other services use `Mango.Nop.Core.Loggers.ILogger`.
## Reference Modes
```
Mango.Nop Libraries
┌──────────────────────┐
│ Core Data Services │
└──┬──────┬──────┬─────┘
│ │ │
┌───────────────┤ │ │
│ (DLL, Core │ │ │
│ only) │ │ │
▼ ▼ ▼ ▼
Types-only clients Full-stack nopCommerce
(Blazor/MAUI/etc.) plugins (server-side)
```

89
docs/CONVENTIONS.md Normal file
View File

@ -0,0 +1,89 @@
# Conventions
## Naming
- **`Mg` prefix** for all custom types: `MgEntityBase`, `MgOrderDto`, `MgDbTableBase`, `MgDalBase`, etc.
- **`I` + name** for interfaces: `IMgDalBase`, `IMgDbTableBase`, `IMgOrderDto`, `IMgLockService`.
- **`Dto` suffix** for DTOs wrapping nopCommerce entities: `MgOrderDto`, `MgProductDto`, `MgOrderItemDto`.
- **`DbTable` suffix** for repository classes: `MgDbTableBase`, `MgDtoDbTableBase`.
- **`Base` suffix** for abstract base classes: `MgEntityBase`, `ModelDtoBase`, `MgBackgroundServiceBase`.
- **`Nop` prefix** for nopCommerce bridge types: `NopLogWriter`, `NopLoggerMsSqlNopDataProvider`, `NopCommonConst`.
## XML Documentation
`<summary>` — brief, developer-facing, readable in VS IntelliSense tooltip. NO implementation details, NO wire-format / byte-level / perf specifics — those live in `docs/TOPIC/*.md`. Add `<example>` only when usage is non-obvious; otherwise omit.
## Patterns
### DTO Bidirectional Mapping
- `ModelDtoBase<TMainEntity>` provides `CopyEntityValuesToDto(entity)`, `CopyDtoValuesToEntity(entity)`, and `CreateMainEntity()`. Override all three in concrete DTOs.
- Simple DTOs (e.g. `CustomerDto`) — manual property-by-property copy in overrides.
- Complex DTOs (e.g. `MgOrderDto`) — use `PropertyHelper.CopyPublicValueTypeProperties(source, target)` for automatic value-type property copy, then manually set reference/navigation properties.
### LinqToDB Associations
- DTOs with navigation properties use `[Association(ThisKey, OtherKey, CanBeNull)]` from LinqToDB.
- `ThisKey` = local FK property name, `OtherKey` = target entity property name (typically `Id` or via interface member like `nameof(IMgProductDto.Id)`).
### NopDependencies Mirror
- Entity classes in `NopDependencies/` use the **same namespace** as the original nopCommerce types. Do not change namespaces.
- All mirror classes are `partial` — they can be extended but should not be modified directly.
### GenericAttribute Typed Access
- Use `GenericAttributeExtensions.GetValueOrDefault<T>()` / `GetValueOrNull<T>()` / `TryGetValue<T>()` instead of raw string parsing.
- Use `AddNewGenericAttribute()` to create new attributes with automatic UTC timestamp.
### Repository Base Chain
- `MgDbTableBase<TEntity>``EntityRepository<TEntity>` (nopCommerce). Override virtual `OnInsert`/`OnUpdate`/`OnDelete` hooks, don't replace the chain.
- `MgDtoDbTableBase<TDtoEntity, TMainEntity>``MgDbTableBase<TDtoEntity>`. **Never call Delete directly** — always use `DeleteMainEntityById(int id)`.
### Timestamp Interfaces
- Entities implementing `ITimeStampCreated` get `Created = DateTime.UtcNow` on insert.
- Entities implementing `ITimeStampModified` get `Modified = DateTime.UtcNow` on insert and update.
- `ITimeStampInfo` combines both (`Creator`, `Created`, `Modified`).
### Transaction Pattern
- Use `MgDbContextBase.TransactionSafeAsync()` for operations needing global lock (order creation, stock adjustment).
- Callback returns `bool``true` commits, `false` rolls back.
- Exceptions are caught and logged by default (return `false`). Pass `throwException: true` only for fail-fast scenarios.
### Event Consumer Pattern
- Inherit `MgEventConsumerBase` and override relevant `HandleEventAsync` methods.
- Base class handles DI of `IMgDbContextBase`, `IHttpContextAccessor`, and `IAcLogWriterBase[]`.
- Base provides `CheckAndUpdateProductManageInventoryMethodToManageStock(Product)` helper.
### Session Pattern
- Inherit `MgSessionServiceBase<TSessionItem>` and `MgSessionItemBase`.
- Sessions stored in `ConcurrentDictionary<string, TSessionItem>` — thread-safe, in-memory only.
- Session items track `SessionId`, `SignaRConnectionId`, `RequestCount`.
### Logging
- Base logging infrastructure (`IAcLoggerBase`, `IAcLogWriterBase`, `AcLoggerBase`, `AcLogItemWriterBase`) — see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`.
- Services create loggers via `new Logger<TCategory>(logWriters.ToArray())` in constructor.
- `logWriters` injected as `IEnumerable<IAcLogWriterBase>` — typically contains `NopLogWriter` + other writers.
- **Exception:** `MgBackgroundServiceBase` uses `Nop.Services.Logging.ILogger` directly (nopCommerce logger), not the Mango wrapper.
### Serialization Attributes
- `[ToonDescription(...)]` — AyCode metadata for AI/doc tooling. `Purpose`, `BusinessRule`, `TypeRelation`, `RelatedTypes` properties.
- `[AcBinarySerializable(false, true, false, true, false)]` — AcBinarySerializer config (see `AyCode.Core/AyCode.Core/docs/BINARY/BINARY_FORMAT.md`). Parameters control serialization behavior for AcSignalR transport (see `AyCode.Core/AyCode.Services/docs/SIGNALR/README.md`).
- LinqToDB `[Table(Name = "...")]` and `[Association(...)]` — DB mapping.
## Project Boundaries
- `Mango.Nop.Core` — NO nopCommerce runtime dependency. Only NopDependencies mirrors. No `Nop.Data`, `Nop.Services` references.
- `Mango.Nop.Data` — depends on nopCommerce data layer (`Nop.Core`, `Nop.Data`). No `Nop.Services` reference.
- `Mango.Nop.Services` — depends on full nopCommerce stack (includes `Nop.Services`, `Nop.Web.Framework`).
## AyCode Integration Points
Key AyCode types used across these libraries:
- `IEntityInt` (AyCode.Interfaces.Entities) — `int Id` entity contract
- `IBaseEntity` — defined locally in NopDependencies, mirrors AyCode's concept
- `IAcLoggerBase`, `IAcLogWriterBase`, `AcLoggerBase`, `AcLogItemWriterBase` — logging infrastructure (see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`)
- `IAcModelDtoBaseEmpty` — DTO marker interface
- `IAcSoftRemoveEntity`, `IAcSoftRemoveEntityInt` — soft-delete contracts
- `IForeignKey`, `IForeignCollection<T>` — FK marker interfaces
- `ITimeStampCreated`, `ITimeStampModified`, `ITimeStampInfo` — timestamp contracts
- `PropertyHelper.CopyPublicValueTypeProperties()` — reflection-based property copy
- `TaskHelper.ToThreadPoolTask()` — wraps async work in thread pool
- `AcConst` — abstract constants base
- `ToonDescription` — metadata attribute for AI tooling

63
docs/GLOSSARY.md Normal file
View File

@ -0,0 +1,63 @@
# Glossary
Terminology for the Mango.Nop Libraries. Read this before working on unfamiliar areas.
## Entity & DTO System
| Term | Definition |
|---|---|
| **BaseEntity** | nopCommerce root entity base with `int Id`. Mirror copy in `NopDependencies/`. Namespace: `Nop.Core`. |
| **IBaseEntity** | Interface for `BaseEntity` (defined alongside it in NopDependencies). Only `int Id`. |
| **MgEntityBase** | Custom entity base — inherits `BaseEntity`, implements `IEntityInt` (AyCode). `ToString()``"{TypeName}; Id: {Id}"`. |
| **ModelDtoBase** | Non-generic DTO base — only `int Id`. Implements `IModelDtoBase`. |
| **ModelDtoBase\<T\>** | Generic DTO base with bidirectional mapping: `CopyEntityValuesToDto()` reads from entity, `CopyDtoValuesToEntity()` writes to entity, `CreateMainEntity()` creates entity from DTO. Uses `Activator.CreateInstance<T>()`. |
| **IModelDtoBase** | `IEntityInt` + `IModelDtoBaseEmpty`. Marker for all DTOs with `int Id`. |
| **IModelDtoBase\<T\>** | Contract: `CreateMainEntity()`, `CopyDtoValuesToEntity()`, `CopyEntityValuesToDto()`. |
| **NopDependencies** | Mirror copies of nopCommerce entity classes in `Mango.Nop.Core`. Same namespaces as originals. Eliminates full nopCommerce dependency for consumers that only need types. |
| **GenericAttribute** | nopCommerce polymorphic key-value store. `KeyGroup` = owner type name, `EntityId` = owner ID, `Key` = attribute name, `Value` = string value. `CreatedOrUpdatedDateUTC` tracks last change. |
| **PropertyHelper.CopyPublicValueTypeProperties** | AyCode utility — reflection-based copy of all public value-type properties between two objects. Used by complex DTOs (`MgOrderDto`, `MgOrderItemDto`). |
| **ToonDescription** | AyCode metadata attribute for AI/doc tooling. Properties: `Purpose`, `BusinessRule`, `TypeRelation`, `RelatedTypes`. |
| **AcBinarySerializable** | AyCode binary serialization attribute (AcBinarySerializer, see `AyCode.Core/AyCode.Core/docs/BINARY/BINARY_FORMAT.md`). Configures how a type is serialized for AcSignalR transport (see `AyCode.Core/AyCode.Services/docs/SIGNALR/README.md`). |
## Data Access
| Term | Definition |
|---|---|
| **MgDbTableBase\<T\>** | Repository base wrapping nopCommerce `EntityRepository<T>`. Adds `GetAll()` (`IQueryable<T>`), automatic timestamp management via `ITimeStampCreated`/`ITimeStampModified` interfaces, CRUD hooks (`OnInsert`, `OnUpdate`, `OnDelete`). |
| **MgDtoDbTableBase\<TDto, TMain\>** | DTO-aware repository. **Delete operations throw** — must use `DeleteMainEntityById()`. Bridges DTO events → main entity events (`EntityInsertedEvent`, `EntityUpdatedEvent`). |
| **MgDbContextBase** | Database context base. Exposes `IRepository<Order>`, `IRepository<OrderItem>`, `IRepository<Product>`, `INopDataProvider`. Provides 4 transaction methods (`Transaction`, `TransactionSafe`, `TransactionAsync`, `TransactionSafeAsync`). |
| **MgDalBase\<T\>** | Data Access Layer orchestrator — `Name`, `Context` (typed `TDbContext`), `MutextLock` (cross-process `Mutex`). |
| **TransactionSafe** | Transaction with global `SemaphoreSlim` lock. Prevents concurrent modifications. Use for order creation, stock changes. |
| **EntityRepository\<T\>** | nopCommerce built-in repository base (`Nop.Data`). `MgDbTableBase` extends this. |
| **INopDataProvider** | nopCommerce interface for raw LinqToDB data access. Used by `MgDbContextBase` and `MgDtoDbTableBase`. |
## Services
| Term | Definition |
|---|---|
| **MgBackgroundServiceBase** | Base for hosted background services. Loop: `Task.Delay(interval)``OnExecuteAsync()`. Supports pause. Per-iteration exception handling. Uses nopCommerce `ILogger`. |
| **MgSessionServiceBase\<T\>** | In-memory session management via `ConcurrentDictionary<string, T>`. Thread-safe. Methods: `GetOrCreateSessionItem`, `TryAddSessionItem`, `TryGetSessionItem`, `TryRemoveSessionItem`, `TryGetSessionItemBySignlaRConnectionId`. |
| **MgSessionItemBase** | Session item with `SessionId`, `SignaRConnectionId`, `RequestCount`. Primary constructor: `(string sessionKey)`. |
| **MgEventConsumerBase** | nopCommerce event consumer base. Subscribes to: `EntityUpdatedEvent<Product>`, `EntityInsertedEvent<Product>`, `CustomerRegisteredEvent`, `OrderPlacedEvent`, `PageRenderingEvent`, `ProductSearchEvent`. All handlers virtual with empty default. |
| **MgLockServiceBase** | `SemaphoreSlim(1)` wrapper. Implements `IMgLockService`. Used by `MgDbContextBase.TransactionSafe*`. |
| **NopLogWriter** | Bridges AyCode logging (see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`) to nopCommerce `Log` table.
| **NopLoggerMsSqlNopDataProvider** | Extends `MsSqlNopDataProvider`. Provides `InsertLogItem<T>()` / `InsertLogItemAsync<T>()` with own `TransactionScope(Suppress, ReadUncommitted)` to avoid transaction nesting. |
## AyCode Types (external, from DLL references)
| Term | Definition |
|---|---|
| **IEntityInt** | `int Id` entity contract (AyCode.Interfaces.Entities) |
| **IAcLoggerBase** | Logger interface — see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md` (`AyCode.Core.Loggers`) |
| **IAcLogWriterBase** | Log writer interface — pluggable output sink (see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`) |
| **AcLoggerBase** | Logger base class with category name and writer array (see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`) |
| **AcLogItemWriterBase\<T\>** | Log writer base for structured log items (see `AyCode.Core/AyCode.Core/docs/LOGGING/README.md`) |
| **IAcModelDtoBaseEmpty** | Empty DTO marker interface |
| **IAcSoftRemoveEntity** | Soft-delete contract (AyCode) |
| **IForeignKey** | FK marker interface |
| **IForeignCollection\<T\>** | FK collection marker |
| **ITimeStampCreated** | `Created` datetime property |
| **ITimeStampModified** | `Modified` datetime property |
| **ITimeStampInfo** | Combines `Creator` (int), `Created`, `Modified` |
| **AcConst** | Abstract constants base class |
| **TaskHelper.ToThreadPoolTask** | Wraps `Func<Task<T>>` into `Task.Run` on thread pool |

23
docs/README.md Normal file
View File

@ -0,0 +1,23 @@
# Mango.Nop Libraries documentation
Top-level documentation for the Mango.Nop shared libraries (Layer 2 — shared NopCommerce plugin framework).
## Reference docs (flat)
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — Repo architecture overview
- [`CONVENTIONS.md`](CONVENTIONS.md) — Coding conventions
- [`GLOSSARY.md`](GLOSSARY.md) — Domain glossary
## Sub-projects with docs
- `Mango.Nop.Core/docs/` — Core entity mirrors, DTOs, architecture
- `Mango.Nop.Data/docs/` — Repository and transaction patterns
- `Mango.Nop.Services/docs/` — Service patterns, logger bridge
## Navigation
Per the AI Agent Core Protocol (folder navigation rule), start from this README when browsing `docs/`. Single-file reference docs remain flat at this level; multi-file topics live in named subfolders at the sub-project level.
## See also
- **Base framework**: `../../../../Aycode/Source/AyCode.Core/` (see each project's `docs/` folder).