Virto Commerce Customer Module

June 3, 2026 · View on GitHub

CI status Quality gate Reliability rating Security rating Sqale rating

Overview

The Customer module is Virto Commerce's contacts management system. It stores and manages the people and companies that interact with a Virto Commerce solution — store customers, employees, partner organisations, and third-party vendors — in a single polymorphic hierarchy.

The module provides a complete back-office experience (the Companies and contacts blade with a <va-filter-panel>-powered filter for member type and created/modified date ranges), a REST API for polymorphic CRUD and search, pluggable search indexing, integration events for downstream modules, and fine-grained permissions.

Key features

  • Polymorphic member hierarchyContact, Organization, Employee, Vendor inherit from a shared Member base and live in a parent/child tree (companies can contain sub-companies, employees, and contacts).
  • Extensible member types — additional types can be registered by downstream modules via memberTypesResolverService.registerType(...) on the UI side and the polymorphic JSON converter on the API side.
  • Extensible filter panel — downstream modules can contribute custom rows to the Members blade's <va-filter-panel> via memberListFilterExtensionService.register(...). Each registered descriptor renders a partial inside the panel and hooks into hasActiveFilters / clearFilters / the Lucene token assembly used by getSearchCriteria. See samples/VirtoCommerce.CustomerSampleModule.Web for a working Job title example.
  • Polymorphic REST API — a single /api/members surface handles search/CRUD for every type, with type-specific convenience routes (/contacts, /organizations, /vendors, /employees).
  • Structured filter panel — filter members by type and Created/Modified date ranges (preset + custom) using Lucene-style keyword syntax (membertype:Organization, createddate:[2026-01-01 TO]).
  • Search indexing — member documents are indexed through the Search module with event-driven reindexing on create/update/delete.
  • Invite customers — generate invitation emails that onboard external contacts with a specific role (RegisterCompanyEmailNotification template).
  • Dynamic properties & SEO — all member types support dynamic properties; Vendor also ships with SEO widget integration.
  • Favourite addresses and customer preferences — per-member preference storage with its own CRUD/search services.
  • Multi-database support — ships adapters for SQL Server, MySQL, and PostgreSQL.
  • Organization-scoped permissionsAssociatedOrganizationsOnlyScope restricts access to members belonging to the caller's organisation.

Architecture

The module follows the standard Virto Commerce three-layer split (Core → Data → Web) with pluggable database adapters.

VirtoCommerce.CustomerModule.Core         — contracts: domain models, service interfaces, events, constants
VirtoCommerce.CustomerModule.Data         — EF Core implementation, repositories, handlers, indexing, import/export
VirtoCommerce.CustomerModule.Data.SqlServer
VirtoCommerce.CustomerModule.Data.MySql   — database-provider adapters (EF Core migrations per provider)
VirtoCommerce.CustomerModule.Data.PostgreSql
VirtoCommerce.CustomerModule.Web          — HTTP API controllers + AngularJS admin UI (blades, directives, i18n)

Domain model (Core/Model/)

Member (abstract, extends AuditableEntity) is the polymorphic root, discriminated by MemberType. Concrete types:

TypeNotable interfaces/fields
ContactIHasSecurityAccounts, IHasPersonName, IHasOrganizations — associated organisations, birthday, preferred language
OrganizationHierarchy via ParentId + OwnerId, business category
EmployeeIHasSecurityAccounts, IHasPersonName, IHasOrganizationsIsActive, EmployeeType, default organisation
VendorIHasSecurityAccounts — site URL, logo, group name

Every member carries collections of Address, Phone, Email, Note, Group, plus dynamic properties. Supporting models live alongside: CustomerPreference, CustomerRole, RelationType.

Services (Core/Services/)

ServicePurpose
IMemberServicePolymorphic CRUD (GetByIdsAsync, SaveChangesAsync, DeleteAsync)
IMemberSearchServiceKeyword + filter-based search (SearchMembersAsync) — powers the admin list blade
IMemberResolverMaps a platform user ID to the member record linked through security accounts
IFavoriteAddressServicePer-member favourite addresses
ICustomerPreferenceService, ICustomerPreferenceCrudService, ICustomerPreferenceSearchServiceNamed preferences keyed by member
IInviteCustomerServiceInvitation flow: send email + enumerate available roles

HTTP API (Web/Controllers/Api/)

CustomerModuleController serves /api/members (polymorphic) with convenience aliases at /api/contacts, /api/organizations, /api/vendors, /api/employees:

  • POST /members/search — keyword + filter search
  • GET /members/{id} · GET /members?ids=… · GET /members/organizations
  • POST /members · PUT /members · PATCH /members/{id} · POST /members/bulk · PUT /members/bulk
  • DELETE /members · POST /members/delete (delete-by-criteria)
  • GET /members/accounts/{userId} · GET /members/{id}/organizations
  • PUT /addresses
  • POST /members/customers/invite · GET /members/customers/invite/roles

CustomerPreferenceController serves /api/customer-preferences with search, CRUD, and delete endpoints.

Events (Core/Events/)

All inherit GenericChangedEntryEvent<T>:

  • MemberChangingEvent / MemberChangedEvent — before/after any member CRUD
  • CustomerPreferenceChangingEvent / CustomerPreferenceChangedEvent — before/after preference CRUD

The module also handles UserChangedEvent, UserRoleAddedEvent, UserRoleRemovedEvent to keep member security-account links in sync.

Web integration points

  • Main menu — "Contacts" entry (priority 180, permission customer:access).
  • Member-type registry — AngularJS memberTypesResolverService registers built-in types and exposes the list used by both member-add and the filter panel.
  • Filter-panel extension — AngularJS memberListFilterExtensionService exposes register(descriptor), unregister(id), clear(), getFilters(). A descriptor declares id, priority, templateUrl, and optional init(filter) / hasActiveFilter(filter) / clear(filter) / appendKeywordTokens(filter, tokens) hooks. Snapshots are captured at blade-init, so late registrations show on the next blade open.
  • Widgets — dynamic-properties widget on every detail blade; address/email/phone widgets; search-index widget; vendor SEO widget; account-contact widget on user-profile blade.
  • Permission scopeAssociatedOrganizationsOnlyScope registered with the platform scope resolver.
  • Polymorphic serialisationPolymorphJsonConverter switches on Member.MemberType.
  • Notification templateRegisterCompanyEmailNotification (extends EmailNotification) used by the invite flow.

Configuration

Database provider

Set via the platform's DatabaseProvider config key (appsettings.json or environment variable). Allowed values: SqlServer (default), MySql, PostgreSql. The matching VirtoCommerce.CustomerModule.Data.<Provider> adapter must be deployed alongside the module.

Module settings (Core/ModuleConstants.cs)

All settings live under the Customer group:

SettingTypeDefaultPurpose
Customer.MemberGroupsdictNew (allowed: VIP, Wholesaler)Tags available on members
Customer.ContactStatuses / OrganizationStatuses / VendorStatuses / EmployeeStatusesdictNew (allowed: New, Approved, Rejected, Deleted)Status dropdown values per type
Customer.ContactDefaultStatus · Customer.OrganizationDefaultStatusstringPer-store default status
Customer.ExportImport.PageSizeint50Batch size for bulk export/import
Customer.Search.EventBasedIndexation.EnablebooltrueReindex members on MemberChangedEvent
VirtoCommerce.Search.IndexingJobs.IndexationDate.MemberdatetimeLast-successful indexation timestamp (maintained by jobs)

Permissions (Core/ModuleConstants.cs)

Registered under scope Customer:

PermissionGrants
customer:accessList/enumerate members
customer:readView member details
customer:createCreate new members
customer:updateEdit members
customer:deleteDelete members
customer:inviteSend customer invitations

Scope AssociatedOrganizationsOnlyScope can be attached to any of the above to limit visibility to members inside the caller's organisations.

Documentation

References

Available resources

License

Copyright (c) Virto Solutions LTD. All rights reserved.

Licensed under the Virto Commerce Open Software License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://virtocommerce.com/opensourcelicense

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.