Items
The Items module keeps the sales categories of a business: the legacy "departments" the daily sales and purchases are entered by. They are an Orchard Core content type. Products and services for invoices come with the service sales module.
| Projects | CrestApps.Almohaseb.Items (module), CrestApps.Almohaseb.Items.Core (part, index, rules, directory) |
| Feature | CrestApps.Almohaseb.Items (Sales categories), enabled by both business recipes |
| Legacy | BOOK-21 Departments, BOOK-22 Departments Merge |
Features and dependencies
CrestApps.Almohaseb.Items depends on CrestApps.Almohaseb, CrestApps.Almohaseb.Ledger (each category has a ledger account) and OrchardCore.Contents. It is a managed feature, for the same reason as Contacts: business recipes cannot enable dependency-only features.
The SalesCategory type
ItemsMigrations.CreateAsync (version 1) defines AlmohasebSalesCategory: creatable, listable, securable, not draftable or versionable, with AlmohasebSalesCategoryPart. Version 2 seeds the Sales Tax category. Version 3 defines AlmohasebSalesTaxExemption (see Exemption reasons).
| Property | Type | Notes |
|---|---|---|
Name | string | 100; unique; also the display text. |
Kind | SalesCategoryKind | Retail (0), Service (1), Purchase (2): the legacy RETAIL, SERVICE and PURCHASE department types. |
RevenuePercent | decimal | 0 to 100, default 100: the share of the sales that is income (lottery commission). |
AlwaysMinus | bool | Always subtracted; never taxable. |
IsTaxable, TaxRate | bool, decimal | The rate is a percentage with at most four decimals (6 = 6%). The legacy app stored a fraction in the same field (Clover wrote 0.0825). |
ExemptionId | string | The legacy "item type": the content item id of a AlmohasebSalesTaxExemption. |
IncludedInSalesTax, IncludedInFinancialStatements | bool | The legacy "Included in 508*" and "Included in F.S". Both default to true. |
UsedOnPurchases | bool | Retail only: also on the purchases screen (the legacy "connected department"). |
AccountId | string | The ledger account. |
ParentId | string | The category it is merged into (replaces tbl_parent_departments and ItemChildTo). |
SortOrder | int | The position within the regular categories. |
SystemKey | string | SalesTax, Deposit or BottleRefund for system categories (ItemsConstants.SystemKeys). |
IsActive | bool |
SalesCategoryPartIndex (latest version) holds ContentItemId, Name, Kind, SystemKey, ParentId, UsedOnPurchases and IsActive, with database indexes on IsActive, Kind, SystemKey, ParentId and ContentItemId. The table definition is the public static method ItemsMigrations.CreateIndexTableAsync.
Exemption reasons
The reasons sales are exempt differ by state, so they are data, not an enum: the AlmohasebSalesTaxExemption content type (creatable, listable, securable) with AlmohasebSalesTaxExemptionPart (Name, ReturnLine, IsActive). The business recipes seed the Michigan 5080 deduction lines 5a to 5k with fixed content item ids (exm-resale, exm-food, ...); the jurisdiction packs will seed other states. A business can add its own reasons and fields. Books > Sales tax exemptions (id almohasebSalesTaxExemptions) is the content list.
System categories
SystemSalesCategories.All defines the three system categories and their locked fields. The migration creates only Sales Tax, which every business has and the daily sales find by key; it is safe to run again. Deposit and Bottle Refund are c-store categories, so the c-store recipe creates them with their SystemKey.
| Key | Name | Treatment | Account (ledger system key) |
|---|---|---|---|
SalesTax | Sales Tax | Not taxable, not on the return | SalesTaxPayable |
Deposit | Deposit | Not taxable, on the return | Sales |
BottleRefund | Bottle Refund | Always minus, not on the return | Sales |
SystemSalesCategories.ApplyLockedFields is applied on every save of a system category, so a forged post cannot change its kind, sales tax treatment, parent or active flag; the name, position, revenue share, statement flag and account stay editable. Unlike the legacy app, which found them by title, system categories are found by key and can be renamed. SalesCategoryPartHandler.RemovingAsync cancels the deletion of a system category and of a category others are merged into, with a notice.
Rules
SalesCategoryRules (the legacy ItemValidator and the department grid's linked cells):
Normalize: an always-minus category is not taxable and has no rate or exemption; a taxable category has no exemption; a non-taxable one has no rate; only retail categories are used on purchases.Validate(SalesCategoryValidationCode): name required and at most 100; revenue share 0 to 100; a taxable category has a rate above 0 and at most 100, with at most four decimals; a non-taxable category on the return that is not always minus, not a system category and not a purchase category has an exemption.ValidateParent: the parent exists, has the same kind, is not merged itself and is not the category; a category others are merged into cannot be merged.Order: the legacy entry-screen order — regular categories bySortOrderand name, then Deposit, Bottle Refund, the always-minus categories, and Sales Tax last.
The duplicate name check runs in SalesCategoryPartDisplayDriver against the index.
Services
| Service | Lifetime | What it does |
|---|---|---|
ISalesCategoryDirectory / SalesCategoryDirectory | Scoped | ListForSalesAsync() (active retail and service categories), ListForPurchasesAsync() (active purchase categories and retail ones used on purchases), both in SalesCategoryRules.Order; FindBySystemKeyAsync(key). |
Admin
Books > Sales categories (id almohasebSalesCategories) is the content list of the type, shown with ListContent_SalesCategory. The driver adds a SalesCategorySummary shape to SummaryAdmin.
Permissions
The module declares no permission of its own; the securable type has the Orchard Core content permissions (List Sales category content items, Edit Sales category for others and so on).
Recipes and deployment
Both business recipes enable CrestApps.Almohaseb.Items and use a Content step to seed the starter data: the Michigan exemption reasons, starter sales categories (c-store: Grocery, Food, Beer and wine, Liquor, Tobacco, Lottery, Deposit, Bottle Refund, Lottery payouts, Inventory; service: Labor, Parts and materials, Materials) and starter vendor categories. The seeded items have fixed ids (cat-grocery, exm-food, vnc-suppliers, ...) so other recipe items can refer to them, and no account: a category without one posts to the system Sales account. A recipe that creates a system category must set SystemKey. Categories and reasons are content items, so the content deployment steps export and import them too.
Extension points
- Pickers on entry screens: use
ISalesCategoryDirectory. - More data on a category or a reason: attach a part or fields to the
AlmohasebSalesCategoryorAlmohasebSalesTaxExemptiontype, in a migration or in the content type editor.