Skip to main content
Technical ManualLooking for step-by-step instructions? In the User Manual: Cash DrawerDaily CloseCheck CashingLotteryFuel

Retail

The Retail module holds a store's daily operations: the cash drawer (BOOK-28 to BOOK-30, BOOK-32), the daily close (CARD-01, CARD-02, CARD-05, RPT-15) check cashing (BOOK-33) the lottery (BOOK-39 to BOOK-43) and fuel (gallons, tanks, deliveries and their prepaid sales tax).

ProjectsCrestApps.Almohaseb.Retail (module), CrestApps.Almohaseb.Retail.Core (models, index, rules, service)
FeaturesCrestApps.Almohaseb.Retail.CashDrawer (Cash drawer; depends on the foundation and OrchardCore.Contents) and CrestApps.Almohaseb.Retail.DailyClose (Daily close; depends on the cash drawer and Sales & receivables), and CrestApps.Almohaseb.Retail.CheckCashing (Check cashing; depends on the cash drawer and Accounting) CrestApps.Almohaseb.Retail.Lottery (Lottery; depends on the cash drawer and Sales & receivables) and CrestApps.Almohaseb.Retail.Fuel (Fuel; depends on the cash drawer, Sales & receivables and Purchases), all enabled by the c-store recipe
CollectionRetail

Data​

TypeKindHolds
AlmohasebDrawerCashReasonContent type (AlmohasebDrawerCashReasonPart)Name, Effect (Opening, Add, Subtract, Closing), AllowsMany (forced off for opening and closing), SortOrder, IsActive
AlmohasebCurrencyDenominationContent type (AlmohasebCurrencyDenominationPart)Name, Value, Currency, SortOrder, IsActive
DrawerCashEntryDocumentDate (business date), ReasonId, Effect (copied from the reason), Amount, Counts (DenominationId, Value as counted, Quantity), Memo, CreatedBy, CreatedUtc

The reasons and denominations are data: the legacy configuration ids (CashDrawerReasonIds, start/end pairs) became the Effect of each reason. The c-store recipe seeds Opening count, Change from the safe, Paid out, Safe drop, Closing count and the US bills and coins. CashDrawerMigrations (version 1) creates the types and DrawerCashEntryIndex (ItemId, Date, ReasonId, Effect, Amount).

Rules​

CashDrawerRules is pure:

  • Summarize(date, entries) gives a DrawerDay: the last Opening and Closing amounts, the sum of Add and of Subtract; BeforeTakings = opening + added − taken; CashTakings = closing − before takings (null until counted). The legacy total summed the amounts with minus reasons subtracted and end reasons left out. The over or short needs the sales, so it is the daily close's.
  • Validate(entry, reason, sameDay) returns DrawerEntryErrors: DateRequired, ReasonRequired (missing or inactive), AmountRequired (at least 0.01, two decimals), OncePerDay (a reason without AllowsMany already on that day; the legacy looked at the filtered range, not the day), CountMismatch (negative quantity, or the counts not adding up to the amount).

ICashDrawerService / CashDrawerService: ListAsync(date), SummarizeAsync(from, to), GetAsync, SaveAsync (drops zero quantities; with counts, the amount is their total; copies the reason's effect), DeleteAsync, and CarryToNextDayAsync(date), which creates the next day's opening entry from the last closing count, count by count, refused with NoClosing, NextDayOpened (the legacy compared dates with their time, so it rarely caught this) or NoOpeningReason. ICashDrawerLookup (ContentCashDrawerLookup) reads the published reasons and the active denominations.

The drawer is not posted: paid-outs are entered as bills or expenses, and the over or short is posted by the daily close.

Daily close​

AlmohasebCardTender (content type, AlmohasebCardTenderPart: Name, AccountId (optional asset account), TracksCount, SortOrder, IsActive) is the business's list of card machines and EBT/WIC tenders; the c-store recipe seeds Card terminal 1, EBT food (with count), EBT cash and WIC, without accounts. The legacy fixed fields (four machines, FS, cash part, WIC) and their settings became this list. DailyCloseSettings.OverShortAccountId (Settings → Daily close, group almohaseb-daily-close) is the over and short account.

DailyClose (document close-yyyyMMdd, one per date; the legacy reconciliation date had no uniqueness): Tenders (TenderId, Amount, Count), OverShort, OverShortAccountId, Revision. DailyCloseIndex (ItemId, Date, OverShort; DailyCloseMigrations).

DailyCloseFigures(date, salesTotal, creditSales, tenders, drawer): CashSales = sales − credit sales counted in the daily sales − tenders; ExpectedClosing = drawer before takings + cash sales; OverShort = closing − expected. This restates the legacy cash reconciliation (RPT-15 Difference) on ledger data: purchases and expenses paid from the register are drawer taken out entries.

IDailyCloseService / DailyCloseService: GetFiguresAsync(date, tenders) reads IDailySalesService.GetAsync(date).Total, the CreditSales of the day with InDailySales, and the drawer; CloseAsync(date, tenders, user) validates (BadAmount, UnknownTender, NoClosingCount), posts and saves only on success. DailyClosePostingRule (Retail.DailyClose): Dr each tender account for its total, Cr Undeposited Funds; an over Dr Undeposited Funds, Cr the over and short account; a short the reverse. Nothing to post returns no entry. Closing again re-posts in place.

DailyCloseController (Almohaseb/Store/DailyClose, GET date (default yesterday), POST Amounts[tender], Counts[tender], refresh), shown with Close the business day (CloseAlmohasebDays), also has the checklist of the day: daily sales, opening count, closing count, tenders (the legacy nine-step wizard, CARD-02, as links to each screen).

IDailyCashMovementProvider.GetCashOutAsync(date) lets another feature tell the close about cash it paid out of the register; DailyCloseFigures.OtherCashOut sums them and ExpectedClosing subtracts it.

Check cashing​

CashedCheck (document): Date, Amount, Fee, CheckNumber, Maker, ExternalId (the check cashing software's id; a second check with the same id is refused, AlreadyLoaded; the legacy SCCS load had no dedupe, CONN-08), FromRegister (the legacy "Affects Total"), Status (Cashed, Deposited, Returned), DepositId, Memo, Revision; CashPaid = amount − fee. CheckDeposit: Number (sequence Retail.CheckDeposit, D- from 1001; the legacy suggested "Deposit n" with an operator-precedence bug), Date, CheckIds, Total. CashedCheckIndex (Date, Status; ExternalId) and CheckDepositIndex come from CheckCashingMigrations.

ICheckCashingService / CheckCashingService: SaveAsync validates (DateRequired, AmountRequired, BadFee (0 to the amount), Locked for deposited or returned checks) and posts the fee; DeleteAsync unposts the fee first and keeps deposited or returned checks; MarkReturnedAsync; DepositAsync(ids, date) (only checks to deposit; NothingToDeposit); DeleteDepositAsync releases the checks; ListDepositsAsync, GetDepositAsync. The same class is the IDailyCashMovementProvider of the daily close: the cash paid for the day's checks from the register, returned checks excepted.

CashedCheckFeePostingRule (Retail.CheckCashingFee, source CashedCheckFee): the cash paid and the check held both stay in Undeposited Funds, so only the fee is posted, Dr Undeposited Funds, Cr the fee account of Settings > Check cashing (CheckCashingSettings.FeeAccountId, an income or other income account); none when it is not set.

CheckCashingController: Almohaseb/Store/Checks (GET from, to, status, edit), /Save, /Selected (POST ids, command = deposit, returned, delete, depositDate), /Deposits, /Deposits/{id} (the slip, a page without the admin layout), /Deposits/{id}/Delete.

Lottery​

AlmohasebLotteryGame (content type, AlmohasebLotteryGamePart: Name, GameNumber (three digits, the start of the barcode), TicketPrice, TicketsPerBook (1 to 1,000), IsActive) and AlmohasebLotteryBox (AlmohasebLotteryBoxPart: Name, Position, IsActive) are the business's lists; the c-store recipe seeds Box 1 to Box 12 (lbx-01..lbx-12) and no games, since games differ by state and change often. LotterySettings (Settings → Lottery, group almohaseb-lottery): SalesCategoryIds and PayoutsCategoryIds, the sales categories compared with the counts; the c-store recipe sets cat-lottery and cat-payouts. ILotteryLookup (ContentLotteryLookup) reads the published games and boxes and the settings.

DocumentHolds
LotteryBookGameId, BookNumber (unique within the game), TicketPrice and Tickets (copied from the game when received), Status (Received, Active, SoldOut, Returned), BoxId, ReceivedDate, ActivatedDate, ClosedDate, StartTicket
LotteryCountlottery-yyyyMMdd, one per date: Lines (BookId, OpeningTicket, ClosingTicket, TicketPrice; Sold = closing − opening, Amount), Total
LotteryTerminalDayterminal-yyyyMMdd, one per date: DrawSales, DrawCancels, DrawCashes, Promotions, Coupons, InstantCashes; NetDrawSales = sales − cancels − promotions − coupons, Cashes = draw + instant winnings

LotteryMigrations (version 1) creates the types, LotteryBookIndex (GameId, BookNumber; Status, BoxId) and LotteryDayIndex (Kind 0 count / 1 terminal, Date).

LotteryBarcode.Parse reads 9 digits (game 3, book 6) or 12 (plus the ticket 3), spaces and dashes ignored; anything else is not a barcode (the legacy took substrings and threw on a short scan).

ILotteryService / LotteryService:

  • ReceiveAsync(barcode, gameId, bookNumber, date, user): from the barcode (its game must be active, BadBarcode) or a game and a number (GameRequired, BookNumberRequired); DuplicateBook for the same game and number.
  • ActivateAsync(bookId, boxId, date, startTicket): only a received book (BookNotAvailable) into an active box (BoxRequired) without an active book (BoxTaken), the start ticket within the book (BadTicket).
  • ReturnAsync(bookId, date): a received or active book.
  • GetCountAsync(date): the books on display that day (activated by then, not closed before it; one sold out that day is counted that day, one returned that day is not), in box order, each opening at its closing in the latest earlier count, else at its start ticket. A saved count keeps its closings and gains books activated since.
  • SaveCountAsync(date, closingTickets, user): recomputes the openings (so a corrected earlier day flows into this one), refuses a closing below the opening or above the book (BadTicket), marks a book closing at its last ticket SoldOut and puts one corrected below it the same day back to Active.
  • GetTerminalAsync, SaveTerminalAsync (amounts zero or more, two decimals, BadAmount).
  • GetFiguresAsync(date): LotteryDayFigures with the instant sales counted, the terminal day, and the daily sales of the settings' categories: Sales = instant + net draw sales, SalesDifference = sales − rung, PayoutsDifference = terminal winnings − payouts rung.

Nothing is posted: the lottery sales and payouts reach the books through the daily sales.

LotteryController: Almohaseb/Store/Lottery (GET/POST date, closing[bookId]), /Books (GET status = empty for received and active, received, active, soldout, returned, all), /Books/Receive (Barcode, GameId, BookNumber, Date), /Books/Activate (bookId, boxId, startTicket), /Books/Return, /Terminal (GET/POST).

Fuel​

AlmohasebFuelGrade (content type, AlmohasebFuelGradePart: Name, Product (Gasoline, Diesel, Other; the prepaid sales tax differs), SalesCategoryId (the register category compared with, optional), SortOrder, IsActive) is the business's list; the c-store recipe seeds Regular 87, Mid-grade 89, Premium 91 and Diesel (fgr-*) without categories, since the c-store categories have no fuel line. IFuelLookup (ContentFuelLookup) reads the published grades and, for suppliers, the vendors of IVendorLookup.ListForBillsAsync (so the feature depends on Purchases).

DocumentHolds
FuelDayfuel-yyyyMMdd, one per date: Lines (GradeId, Gallons (three decimals), Amount, TankReading (gallons at the close, optional); Price = amount ÷ gallons to three decimals)
FuelDeliveryDate, VendorId, InvoiceNumber, Memo, Lines (GradeId, Gallons, Cost, PrepaidTaxRate in dollars per gallon, four decimals; PrepaidTax = gallons × rate to the cent, away from zero); Cost, PrepaidTax, Total

FuelMigrations (version 1) creates the type, FuelDayIndex (Date) and FuelDeliveryIndex (Date; VendorId, InvoiceNumber).

IFuelService / FuelService:

  • GetDayAsync(date): the saved day, plus an empty line for each active grade without one, in the grades' order.
  • SaveDayAsync(date, lines, user): drops lines with nothing on them; DateRequired, UnknownGrade (unknown or twice), BadGallons, BadAmount, BadReading.
  • SaveDeliveryAsync(delivery, user): drops lines without gallons or cost; NothingDelivered, UnknownSupplier, UnknownGrade, BadGallons, BadAmount, BadRate, DuplicateInvoice (same supplier and invoice number), NotFound. DeleteDeliveryAsync, ListDeliveriesAsync(from, to), GetLatestRatesAsync() (the rate of the latest delivery of each grade, suggested on the next).
  • GetSummaryAsync(from, to): FuelSummary with a FuelGradeSummary per grade with sales, deliveries or a reconciliation (GallonsSold, Sales, AveragePrice, GallonsDelivered, Cost, AverageCost, PrepaidTax), PrepaidTaxByProduct, GallonsDeliveredByProduct, and RegisterSales (the daily sales of the grades' categories; IsCompared is false when no grade has one) with SalesDifference.

FuelTankReconciliation: from the latest reading before the period to the latest one in it, Expected = opening + delivered − sold after the opening date up to the closing date, Variance = closing − expected. Without both readings there is none.

Nothing is posted: the fuel sales reach the books through the daily sales and the supplier invoice through a bill. The prepaid tax by product is what the Michigan schedules (5083/5085, STX-09) read in Phase 5.

FuelController: Almohaseb/Store/Fuel (GET/POST date, default yesterday; Lines[i].GradeId, .Gallons, .Amount, .TankReading), /Deliveries (GET from, to, edit), /Deliveries/Save (Form.*, Form.Lines[i].*), /Deliveries/{id}/Delete, /Summary (from, to, the current month by default).

Reports​

Each store feature adds a CrestApps Reports report in category Store through a startup with [Feature(...)] and [RequireFeatures(ReportsConstants.Feature)]:

ReportNameFeatureReads
CardTendersReport (RPT-16)almohaseb-card-tendersDaily closeIDailyCloseService.ListAsync(from, to): each close's tender totals, the counts of tenders with TracksCount, the over or short
CheckDepositsReport (RPT-19)almohaseb-check-depositsCheck cashingThe deposits dated in the range with their checks; the checks still to deposit
LotterySummaryReport (RPT-22)almohaseb-lottery-summaryLotteryILotteryService.ListFiguresAsync(from, to): a LotteryDayFigures for each day with a count, terminal figures or lottery sales
FuelReportalmohaseb-fuelFuelIFuelService.GetSummaryAsync

The legacy slip-printer outputs (RPT-17, RPT-18, RPT-20) are replaced by the printable deposit slip and the export of these reports.

Dashboard​

DailyCloseWidgetStartup ([Feature(DailyClose)], [RequireFeatures("OrchardCore.AdminDashboard")]) adds the AlmohasebDailyCloseWidget type (RPT-23: the legacy reconciliation panel): TitlePart, DashboardPart and AlmohasebDailyCloseWidgetPart (Days, 1–31, default 7). It lists the days ending yesterday, latest first, each with its close's over or short or Not closed, linked to the daily close of that date (IDailyCloseService.ListAsync); it needs ViewAlmohasebCashDrawer. The c-store recipe seeds it as dsh-close (2 × 2).

Admin​

The Store menu (almohasebStore, icon fa-store): Daily close (almohasebDailyClose), Cash drawer (almohasebCashDrawer), Checks cashed (almohasebChecksCashed), Check deposits (almohasebCheckDeposits), Lottery count (almohasebLotteryCount), Lottery books (almohasebLotteryBooks), Lottery terminal (almohasebLotteryTerminal), Fuel sales (almohasebFuelSales), Fuel deliveries (almohasebFuelDeliveries), Fuel summary (almohasebFuelSummary), Drawer reasons, Denominations, Card tenders, Lottery games, Lottery boxes and Fuel grades (the content lists); and Settings > Daily close, Settings > Check cashing and Settings > Lottery.

Route (under the admin prefix)Route namePermission
Almohaseb/Store/CashDrawer (date, edit)AlmohasebCashDrawerView the cash drawer
Almohaseb/Store/CashDrawer/Save, /Delete/{id}, /CarryAlmohasebCashDrawerSave, AlmohasebCashDrawerDelete, AlmohasebCashDrawerCarryEnter the cash drawer

Permissions​

PermissionKeyImplied by
Enter the cash drawerManageAlmohasebCashDrawer
View the cash drawerViewAlmohasebCashDrawerManageAlmohasebCashDrawer
Close the business dayCloseAlmohasebDays
Enter cashed checks and deposit themManageAlmohasebCheckCashing
View cashed checks and depositsViewAlmohasebCheckCashingManageAlmohasebCheckCashing
Receive, activate and count lottery booksManageAlmohasebLottery
View the lotteryViewAlmohasebLotteryManageAlmohasebLottery
Enter fuel sales, tank readings and deliveriesManageAlmohasebFuel
View fuel sales and deliveriesViewAlmohasebFuelManageAlmohasebFuel

See also​