Documentation
¶
Index ¶
- Variables
- func IsValidUploadIntent(intent string) bool
- type AccountingRepoer
- type BranchRepoer
- type CompanyRepoer
- type Crudable
- type CrudableWithTx
- type CustomerRepoer
- type DiscountRepoer
- type Enqueuer
- type Event
- type EventEmitter
- type ExpenseRepoer
- type FileRepoer
- type FileStorage
- type IntentConfig
- type InventoryQuerier
- type InventoryRepoer
- type OrderFilter
- type OrderRepoer
- type POSStationRepoer
- type ProductRepoer
- type ReportsQuerier
- type SessionFilter
- type SessionRepoer
- type SupplierPaymentRepoer
- type SupplierRepoer
- type SupportsPagination
- type TxAware
- type URLPresigner
- type UploadIntent
- type UserRepoer
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound indicates the requested resource does not exist. ErrNotFound = errors.New("resource not found") // ErrConflict indicates the operation conflicts with existing state. ErrConflict = errors.New("resource conflict") // ErrForbidden indicates the caller lacks permission for the operation. ErrForbidden = errors.New("forbidden: insufficient permissions") // ErrInternal indicates an unexpected internal failure. ErrInternal = errors.New("internal error") // ErrNoProcurementPrice is returned when COGS cannot be computed because // one or more ingredients have no procurement price on record. ErrNoProcurementPrice = errors.New("no procurement price on record") // ErrInsufficientStock is returned when there isn't enough stock to fulfill // a request — either because available units cannot be computed (one or // more ingredients have no stock record yet) or because the requested // quantity exceeds current stock/available units. ErrInsufficientStock = errors.New("insufficient stock") // ErrInvalidProductType is returned when an operation is attempted on a // product of the wrong type (e.g. setting a recipe on a simple product). ErrInvalidProductType = errors.New("invalid product type for operation") // ErrUnitCostRequired is returned when a stock movement of type procurement // or opening_balance is recorded without a unit_cost. ErrUnitCostRequired = errors.New("unit_cost is required for this movement reason") // ErrUnitCostForbidden is returned when a unit_cost is supplied for a stock // movement type that does not accept one (waste, adjustment_up, adjustment_down). ErrUnitCostForbidden = errors.New("unit_cost must not be set for this movement reason") // ErrInitialStockIncomplete is returned when a product is created with // only one of initial quantity / initial unit cost set — both or neither // are required, since together they form one procurement movement. ErrInitialStockIncomplete = errors.New( "initial quantity and initial unit cost must both be set, or neither", ) // ErrStationBusy is returned when a session is opened on a station that // already has an open session. ErrStationBusy = errors.New("station already has an open session") // ErrSessionAlreadyClosed is returned when a close is attempted on a session // that is already in the closed state. ErrSessionAlreadyClosed = errors.New("session is already closed") // ErrOpenOrdersExist is returned when a close is attempted on a session that // still has orders in open status. ErrOpenOrdersExist = errors.New("session has open orders and cannot be closed") // ErrPaymentMismatch is returned when the sum of payment amounts does not // equal the order total. ErrPaymentMismatch = errors.New("payment total does not match order total") // ErrInvalidPaymentMethod is returned when a payment method is not in the // configured allowed set. ErrInvalidPaymentMethod = errors.New("payment method is not accepted") // ErrInvalidPayment is returned when a payment leg's tendered amount is // less than the amount being credited toward the order. ErrInvalidPayment = errors.New("tendered amount cannot be less than payment amount") // ErrOrderNotPaid is returned when an operation that requires a paid order // (void, return) is attempted on an order in another state. ErrOrderNotPaid = errors.New("order is not in paid status") // ErrOrderNotOpen is returned when an operation that requires an open order // (add item, checkout) is attempted on an order in another state, or when // an order is created on a closed session. ErrOrderNotOpen = errors.New("order is not in open status") // ErrOrderAlreadyVoided is returned when a void is attempted on an order // that is already voided. ErrOrderAlreadyVoided = errors.New("order is already voided") // ErrUnbalancedEntry is returned when a journal entry's debit total does not // equal its credit total (violates double-entry bookkeeping). ErrUnbalancedEntry = errors.New( "journal entry is unbalanced: debit total must equal credit total", ) // ErrDiscountExceedsTotal is returned when the computed discount amount equals // or exceeds the order item subtotal or order total it is being applied to. ErrDiscountExceedsTotal = errors.New("discount amount exceeds order total") // ErrDiscountCodeNotFound is returned when no discount matches a // cashier-entered coupon code for the order's company. ErrDiscountCodeNotFound = errors.New("discount code not found") // ErrDiscountInactive is returned when applying a discount that has // been deactivated. ErrDiscountInactive = errors.New("discount is not active") // ErrDiscountNotYetValid is returned when applying a discount before // its ValidFrom date. ErrDiscountNotYetValid = errors.New("discount is not valid yet") // ErrDiscountExpired is returned when applying a discount after its // ValidUntil date. ErrDiscountExpired = errors.New("discount has expired") // ErrDiscountExhausted is returned when applying a discount that has // already reached its MaxRedemptions cap. ErrDiscountExhausted = errors.New("discount has reached its redemption limit") // ErrInvalidIntent is returned when an upload is attempted against an // intent that isn't in the registered UploadIntent set. ErrInvalidIntent = errors.New("invalid upload intent") // ErrUnsupportedMimeType is returned when an uploaded file's declared or // sniffed content type isn't allowed for the requested intent. ErrUnsupportedMimeType = errors.New("unsupported file type for this upload intent") // ErrFileTooLarge is returned when an uploaded file exceeds the requested // intent's configured size limit. ErrFileTooLarge = errors.New("file exceeds the maximum size for this upload intent") // ErrCustomerNameRequired is returned when creating a customer without a name. ErrCustomerNameRequired = errors.New("name is required to create a customer") // ErrCustomerContactRequired is returned when creating a customer with // neither an email nor a phone number. ErrCustomerContactRequired = errors.New("email or phone is required to create a customer") // ErrNotCorrectable is returned when attempting to correct a movement whose // reason isn't a user-entered one (pos_sale/pos_void/pos_return/correction). ErrNotCorrectable = errors.New("this stock movement cannot be corrected") // ErrAlreadyCorrected is returned when attempting to correct a movement // that already has a reversal recorded against it. ErrAlreadyCorrected = errors.New("this stock movement has already been corrected") )
Functions ¶
func IsValidUploadIntent ¶
IsValidUploadIntent reports whether intent is a registered upload intent.
Types ¶
type AccountingRepoer ¶
type AccountingRepoer interface {
// GetAccountByCode returns the account with the given COA code.
GetAccountByCode(ctx context.Context, code string) (*model.Account, error)
// ListAccounts returns all accounts ordered by code.
ListAccounts(ctx context.Context) ([]model.Account, error)
// CreateJournalEntry persists a JournalEntry together with all its JournalLines
// in a single atomic operation. The entry's ID and each line's ID are set before
// insertion; entry.CreatedAt is set to the current UTC time.
CreateJournalEntry(ctx context.Context, entry *model.JournalEntry) error
// ListJournalEntriesByCompany returns a paginated list of journal entries.
// Lines are NOT hydrated — call ListJournalLinesByEntry separately.
ListJournalEntriesByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.JournalEntry], error)
// BalanceByAccount returns the sum of debits and credits per COA account
// for all journal entries that belong to companyID.
BalanceByAccount(ctx context.Context, companyID uuid.UUID) ([]model.AccountBalance, error)
}
AccountingRepoer persists chart-of-accounts data and double-entry journal entries.
type BranchRepoer ¶
type BranchRepoer interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.Branch, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.Branch], error)
Create(ctx context.Context, entity *model.Branch) error
Update(ctx context.Context, entity *model.Branch) error
Delete(ctx context.Context, id uuid.UUID) error
WithTx(ctx context.Context, fn func(BranchRepoer) error) error
}
BranchRepoer defines the persistence contract for branch records.
type CompanyRepoer ¶
type CompanyRepoer interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.Company, error)
GetByHandle(ctx context.Context, handle string) (*model.Company, error)
ListByOwner(
ctx context.Context,
ownerID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.Company], error)
Create(ctx context.Context, entity *model.Company) error
Update(ctx context.Context, entity *model.Company) error
Delete(ctx context.Context, id uuid.UUID) error
WithTx(ctx context.Context, fn func(CompanyRepoer) error) error
}
CompanyRepoer defines the persistence contract for company records. List is scoped by owner_id because a user may own multiple companies (pro).
type Crudable ¶
type Crudable[T any] interface { GetByID(ctx context.Context, id uuid.UUID) (*T, error) Paginate(ctx context.Context, q SupportsPagination) (*dto.PaginationResults[T], error) Create(ctx context.Context, entity *T) error Update(ctx context.Context, entity *T) error Delete(ctx context.Context, id uuid.UUID) error }
Crudable is a generic interface for repositories that support standard CRUD operations.
type CrudableWithTx ¶
CrudableWithTx combines Crudable with transaction support. T is the entity type; R is the repo type passed inside the transaction.
type CustomerRepoer ¶
type CustomerRepoer interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.Customer, error)
GetByCompanyAndEmail(
ctx context.Context,
companyID uuid.UUID,
email string,
) (*model.Customer, error)
GetByCompanyAndPhone(
ctx context.Context,
companyID uuid.UUID,
phone string,
) (*model.Customer, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Customer], error)
Create(ctx context.Context, entity *model.Customer) error
Update(ctx context.Context, entity *model.Customer) error
Delete(ctx context.Context, id uuid.UUID) error
WithTx(ctx context.Context, fn func(CustomerRepoer) error) error
}
CustomerRepoer defines the persistence contract for customer records.
type DiscountRepoer ¶
type DiscountRepoer interface {
Create(ctx context.Context, entity *model.Discount) error
GetByID(ctx context.Context, id uuid.UUID) (*model.Discount, error)
GetByCode(ctx context.Context, companyID uuid.UUID, code string) (*model.Discount, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Discount], error)
// ListSelectable returns company discounts that are active, uncoded,
// and currently within their validity window (if any) — what the POS
// dropdown may offer, computed in SQL rather than filtered in Go.
ListSelectable(ctx context.Context, companyID uuid.UUID, at time.Time) ([]model.Discount, error)
Update(ctx context.Context, entity *model.Discount) error
Delete(ctx context.Context, id uuid.UUID) error
CountRedemptions(ctx context.Context, discountID uuid.UUID) (int, error)
CreateRedemption(ctx context.Context, r *model.DiscountRedemption) error
DeleteRedemptionByOrder(ctx context.Context, orderID uuid.UUID) error
}
DiscountRepoer provides CRUD access to the named discount catalog.
type Enqueuer ¶
type Enqueuer interface {
// Send enqueues a named job with a JSON payload. Non-transactional.
Send(ctx context.Context, name string, payload []byte) error
// SendTx is like Send but within an existing *sql.Tx — atomic with the caller's write.
SendTx(ctx context.Context, tx *sql.Tx, name string, payload []byte) error
}
Enqueuer enqueues named background jobs. The OSS implementation wraps goqite (SQLite-backed). Pro injects its own via core.WithEnqueuer — e.g. River for PostgreSQL.
type EventEmitter ¶
EventEmitter dispatches domain events to subscribers. Implemented by the job queue in Phase 7; nil until then.
type ExpenseRepoer ¶
type ExpenseRepoer interface {
Create(ctx context.Context, entity *model.Expense) error
GetByID(ctx context.Context, id uuid.UUID) (*model.Expense, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Expense], error)
}
ExpenseRepoer defines the persistence contract for expense records.
type FileRepoer ¶
type FileRepoer interface {
Create(ctx context.Context, entity *model.File) error
GetByID(ctx context.Context, id, companyID uuid.UUID) (*model.File, error)
Delete(ctx context.Context, id, companyID uuid.UUID) error
}
FileRepoer provides CRUD access to uploaded file metadata.
type FileStorage ¶
type FileStorage interface {
Put(ctx context.Context, key string, r io.Reader, size int64) error
Get(ctx context.Context, key string) (io.ReadCloser, error)
Delete(ctx context.Context, key string) error
}
FileStorage stores and retrieves file bytes by key. Implementations never see or need an absolute path — the key is relative to the backend's root (e.g. a bucket, or a base directory on disk).
type IntentConfig ¶
type IntentConfig struct {
// Prefix is prepended to the generated filename to form the storage key,
// e.g. "avatars/" -> "<companyID>/avatars/<uuid>.webp".
Prefix string
AllowedMIMETypes []string
MaxSizeBytes int64
URLExpiry time.Duration
}
IntentConfig is the validation/config for one UploadIntent.
func LookupIntent ¶
func LookupIntent(intent UploadIntent) (IntentConfig, bool)
LookupIntent returns the config for a given intent, and whether it exists.
type InventoryQuerier ¶
type InventoryQuerier interface {
// LPP returns the Last Procurement Price for the given product (ingredient).
// Returns ErrNoProcurementPrice if no procurement stock movement exists yet.
LPP(ctx context.Context, productID uuid.UUID) (int64, error)
// CurrentStock returns the current stock level (in product units) for the
// given ingredient product.
// Returns ErrInsufficientStock if no stock record exists yet.
CurrentStock(ctx context.Context, productID uuid.UUID) (float64, error)
// StockAt returns the stock level at the given point in time.
StockAt(ctx context.Context, productID uuid.UUID, at time.Time) (float64, error)
}
InventoryQuerier provides read-only inventory queries used by ProductService. Implemented by InventoryRepo in Phase 4; may be nil in Phase 3.
type InventoryRepoer ¶
type InventoryRepoer interface {
Record(ctx context.Context, entity *model.StockMovement) error
GetByID(ctx context.Context, id uuid.UUID) (*model.StockMovement, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.StockMovement], error)
// ListByProduct returns paginated movements for productID, scoped to
// companyID so a caller cannot read another company's product history.
ListByProduct(
ctx context.Context,
productID, companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.StockMovement], error)
// CurrentStock returns the signed sum of all deltas for the product.
// Returns 0 when no movements exist (never ErrInsufficientStock at repo level).
CurrentStock(ctx context.Context, productID uuid.UUID) (float64, error)
StockAt(ctx context.Context, productID uuid.UUID, at time.Time) (float64, error)
// LPP returns the unit_cost of the most recent procurement movement.
// Returns ErrNoProcurementPrice when no procurement exists.
LPP(ctx context.Context, productID uuid.UUID) (int64, error)
// HasMovements reports whether any stock movement exists for the product.
HasMovements(ctx context.Context, productID uuid.UUID) (bool, error)
// IsReversed reports whether a correction movement already reverses movementID.
IsReversed(ctx context.Context, movementID uuid.UUID) (bool, error)
WithTx(ctx context.Context, fn func(InventoryRepoer) error) error
}
InventoryRepoer defines the persistence contract for stock movements. It also satisfies InventoryQuerier (LPP + CurrentStock are present on both).
type OrderFilter ¶
type OrderFilter struct {
DateFrom string // "YYYY-MM-DD" inclusive; "" = no lower bound
DateTo string // "YYYY-MM-DD" inclusive; "" = no upper bound
Status string // model.OrderStatus value; "" = any status
BranchID uuid.UUID // uuid.Nil = any branch
StationID uuid.UUID // uuid.Nil = any station
}
OrderFilter holds optional filter criteria for ListOrdersByCompanyFiltered. Zero values mean "no filter" for that field.
type OrderRepoer ¶
type OrderRepoer interface {
// CreateOrder persists a new open order. Sets ID, CreatedAt, UpdatedAt.
CreateOrder(ctx context.Context, entity *model.Order) error
// GetOrderByID retrieves an order by its ID.
GetOrderByID(ctx context.Context, id uuid.UUID) (*model.Order, error)
// UpdateOrder persists mutable order fields (status, total, receipt_number,
// voided_by, voided_at, updated_at).
UpdateOrder(ctx context.Context, entity *model.Order) error
// DeleteOrder removes an order record entirely.
DeleteOrder(ctx context.Context, id uuid.UUID) error
// ListOrdersBySession returns paginated orders for a session.
ListOrdersBySession(
ctx context.Context,
sessionID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.Order], error)
// ListOrdersByCompany returns paginated orders for a company.
ListOrdersByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.Order], error)
// ListOrdersByCompanyFiltered returns paginated orders for a company,
// narrowed by filter. Zero-value fields on filter impose no restriction.
ListOrdersByCompanyFiltered(
ctx context.Context,
companyID uuid.UUID,
filter *OrderFilter,
q SupportsPagination,
) (*dto.PaginationResults[model.Order], error)
// CountOrdersByCompanyAndDate returns the number of orders created by the
// company on the given date (format "YYYY-MM-DD"). Used for receipt numbering.
CountOrdersByCompanyAndDate(
ctx context.Context,
companyID uuid.UUID,
date string,
) (int64, error)
// CreateOrderItem persists a new order item. Sets ID.
CreateOrderItem(ctx context.Context, entity *model.OrderItem) error
// GetOrderItemByID retrieves an order item by its ID.
GetOrderItemByID(ctx context.Context, id uuid.UUID) (*model.OrderItem, error)
// ListOrderItemsByOrder returns all items for an order.
ListOrderItemsByOrder(ctx context.Context, orderID uuid.UUID) ([]model.OrderItem, error)
// DeleteOrderItem removes an order item.
DeleteOrderItem(ctx context.Context, id uuid.UUID) error
// UpdateOrderItem updates the quantity and subtotal of an existing order item.
UpdateOrderItem(ctx context.Context, entity *model.OrderItem) error
// CreatePayment persists a payment leg. Sets ID.
CreatePayment(ctx context.Context, entity *model.Payment) error
// ListPaymentsByOrder returns all payment legs for an order.
ListPaymentsByOrder(ctx context.Context, orderID uuid.UUID) ([]model.Payment, error)
// SumPaymentsByOrder returns the total of completed payments for an order.
SumPaymentsByOrder(ctx context.Context, orderID uuid.UUID) (int64, error)
// CreateOrderReturn persists a return record. Sets ID.
CreateOrderReturn(ctx context.Context, entity *model.OrderReturn) error
// GetOrderReturnByID retrieves a return by its ID.
GetOrderReturnByID(ctx context.Context, id uuid.UUID) (*model.OrderReturn, error)
// ListOrderReturnsByOrder returns all return records for an order.
ListOrderReturnsByOrder(ctx context.Context, orderID uuid.UUID) ([]model.OrderReturn, error)
// CreateOrderReturnItem persists a return item line. Sets ID.
CreateOrderReturnItem(ctx context.Context, entity *model.OrderReturnItem) error
// ListOrderReturnItemsByReturn returns all item lines for a return.
ListOrderReturnItemsByReturn(
ctx context.Context,
returnID uuid.UUID,
) ([]model.OrderReturnItem, error)
// WithTx runs fn inside a single database transaction, providing transactional
// views of both the order repo and the inventory repo. Used for Checkout, Void,
// and Return which must write to orders/payments and stock_movements atomically.
WithTx(ctx context.Context, fn func(orders OrderRepoer, inv InventoryRepoer) error) error
}
OrderRepoer defines persistence operations for orders, items, payments, and returns.
type POSStationRepoer ¶
type POSStationRepoer interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.POSStation, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.POSStation], error)
Create(ctx context.Context, entity *model.POSStation) error
Update(ctx context.Context, entity *model.POSStation) error
Delete(ctx context.Context, id uuid.UUID) error
WithTx(ctx context.Context, fn func(POSStationRepoer) error) error
}
POSStationRepoer defines the persistence contract for POS station records.
type ProductRepoer ¶
type ProductRepoer interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.Product, error)
// GetBySKU retrieves a product by its SKU, scoped to companyID. Used by
// the POS barcode-scan-to-cart flow.
GetBySKU(ctx context.Context, companyID uuid.UUID, sku string) (*model.Product, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Product], error)
ListByCompanyAndType(
ctx context.Context,
companyID uuid.UUID,
t model.ProductType,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Product], error)
// ListSellableByCompany returns paginated Simple+Recipe products (the
// types the POS product grid can add to a cart) as one correctly-ordered
// and counted set, rather than merging two independently-paginated lists.
ListSellableByCompany(
ctx context.Context,
companyID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Product], error)
// ListByCompanyAndCategory and ListByCompanyTypeAndCategory back the
// product list page's category filter. Kept as separate concrete
// queries (rather than an optional-filter param on the methods above)
// to match how the `type` filter is already split into its own methods
// in this repo.
ListByCompanyAndCategory(
ctx context.Context,
companyID, categoryID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Product], error)
ListByCompanyTypeAndCategory(
ctx context.Context,
companyID uuid.UUID,
t model.ProductType,
categoryID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Product], error)
Create(ctx context.Context, entity *model.Product) error
Update(ctx context.Context, entity *model.Product) error
Delete(ctx context.Context, id uuid.UUID) error
GetRecipeComponents(ctx context.Context, recipeID uuid.UUID) ([]model.RecipeComponent, error)
SetRecipeComponents(
ctx context.Context,
recipeID uuid.UUID,
components []model.RecipeComponent,
) error
GetRecipeOverheads(ctx context.Context, recipeID uuid.UUID) ([]model.RecipeOverhead, error)
SetRecipeOverheads(
ctx context.Context,
recipeID uuid.UUID,
overheads []model.RecipeOverhead,
) error
// GetOrCreateCategories resolves each name to a company-scoped category,
// reusing an existing one on a case-insensitive match (normalized in
// SQL) or creating it. Blank/whitespace-only names are skipped.
GetOrCreateCategories(
ctx context.Context,
companyID uuid.UUID,
names []string,
) ([]model.Category, error)
ListCategoriesByCompany(ctx context.Context, companyID uuid.UUID) ([]model.Category, error)
GetProductCategories(ctx context.Context, productID uuid.UUID) ([]model.Category, error)
// SetProductCategories atomically replaces a product's full category set.
SetProductCategories(
ctx context.Context,
productID uuid.UUID,
categoryIDs []uuid.UUID,
) error
// WithTx runs fn inside a single database transaction, providing both
// repos so a product write and a stock movement (e.g. initial stock at
// creation) can commit atomically — mirrors OrderRepoer.WithTx.
WithTx(ctx context.Context, fn func(products ProductRepoer, inv InventoryRepoer) error) error
}
ProductRepoer defines the persistence contract for product records, including recipe sub-entities (components and overheads).
type ReportsQuerier ¶
type ReportsQuerier interface {
SalesTotals(
ctx context.Context,
companyID uuid.UUID,
from, to string,
) (model.SalesSummary, error)
SalesByPeriod(
ctx context.Context,
companyID uuid.UUID,
from, to, groupBy string,
) ([]model.SalesPeriod, error)
RevenueByPaymentMethod(
ctx context.Context,
companyID uuid.UUID,
from, to string,
) ([]model.PaymentMethodRevenue, error)
TopProductsByRevenue(
ctx context.Context,
companyID uuid.UUID,
from, to string,
topN int,
) ([]model.TopProduct, error)
InventorySnapshot(
ctx context.Context,
companyID uuid.UUID,
) ([]model.InventoryLine, error)
// InventorySnapshotPaged is the paginated, searchable counterpart to
// InventorySnapshot, used by the web Inventory list page. productType
// and stockStatus are exact-match filters ("" = no filter); stockStatus
// is one of "low"/"out"/"ok", mirroring view/inventory/helpers.go's
// stockStatusLabel. categoryID nil means no category filter.
InventorySnapshotPaged(
ctx context.Context,
companyID uuid.UUID,
search string,
productType string,
categoryID *uuid.UUID,
stockStatus string,
q SupportsPagination,
) (*dto.PaginationResults[model.InventoryLine], error)
PLByPeriod(
ctx context.Context,
companyID uuid.UUID,
from, to string,
) (model.PLSummary, error)
}
ReportsQuerier provides read-only aggregate queries for management reports. All implementations use raw SQL; no sqlc involvement.
type SessionFilter ¶
SessionFilter narrows ListOpenByCompanyFiltered. uuid.Nil means "any branch".
type SessionRepoer ¶
type SessionRepoer interface {
// Create persists a new open session.
Create(ctx context.Context, entity *model.POSSession) error
// GetByID retrieves a session by its ID.
GetByID(ctx context.Context, id uuid.UUID) (*model.POSSession, error)
// GetOpenByStation retrieves the open session for a station, if any.
// Returns ErrNotFound if no open session exists.
GetOpenByStation(ctx context.Context, stationID uuid.UUID) (*model.POSSession, error)
// GetOpenByUser retrieves the open session opened by userID within
// companyID, if any. Returns ErrNotFound if no open session exists.
GetOpenByUser(ctx context.Context, companyID, userID uuid.UUID) (*model.POSSession, error)
// Close marks a session as closed at the given time.
Close(ctx context.Context, sessionID uuid.UUID, closedAt time.Time) error
// ListByCompany returns paginated sessions for a company.
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.POSSession], error)
// ListOpenByCompanyFiltered returns paginated open sessions for a
// company, optionally narrowed by filter.BranchID.
ListOpenByCompanyFiltered(
ctx context.Context,
companyID uuid.UUID,
filter *SessionFilter,
q SupportsPagination,
) (*dto.PaginationResults[model.POSSession], error)
// CreateReconciliation persists a session reconciliation record.
CreateReconciliation(ctx context.Context, entity *model.SessionReconciliation) error
// GetReconciliationBySession retrieves the reconciliation for a session.
GetReconciliationBySession(
ctx context.Context,
sessionID uuid.UUID,
) (*model.SessionReconciliation, error)
// SumCashPayments returns the total cash-method payments taken in a session.
// Phase 6 stub: always returns 0 until the payments table exists.
SumCashPayments(ctx context.Context, sessionID uuid.UUID) (int64, error)
// HasOpenOrders returns true if the session has any orders in open status.
// Phase 6 stub: always returns false until the orders table exists.
HasOpenOrders(ctx context.Context, sessionID uuid.UUID) (bool, error)
// DeleteOpenOrders hard-deletes every open order for the session
// (cascading to their items via FK) and returns the number deleted.
DeleteOpenOrders(ctx context.Context, sessionID uuid.UUID) (int64, error)
}
SessionRepoer defines persistence operations for POS sessions and reconciliations.
type SupplierPaymentRepoer ¶
type SupplierPaymentRepoer interface {
Create(ctx context.Context, entity *model.SupplierPayment) error
GetByID(ctx context.Context, id uuid.UUID) (*model.SupplierPayment, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.SupplierPayment], error)
ListBySupplier(
ctx context.Context,
supplierID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.SupplierPayment], error)
}
SupplierPaymentRepoer defines the persistence contract for supplier payment records.
type SupplierRepoer ¶
type SupplierRepoer interface {
GetByID(ctx context.Context, id uuid.UUID) (*model.Supplier, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
search string,
q SupportsPagination,
) (*dto.PaginationResults[model.Supplier], error)
Create(ctx context.Context, entity *model.Supplier) error
Update(ctx context.Context, entity *model.Supplier) error
Delete(ctx context.Context, id uuid.UUID) error
WithTx(ctx context.Context, fn func(SupplierRepoer) error) error
}
SupplierRepoer defines the persistence contract for supplier records.
type SupportsPagination ¶
SupportsPagination is satisfied by any DTO that can describe a page of results.
type TxAware ¶
type TxAware[T any] interface { // WithTx executes fn within a database transaction. // If fn returns an error the transaction is rolled back; otherwise it is committed. WithTx(ctx context.Context, fn func(T) error) error }
TxAware is a generic interface for repositories that support transactions. T is the repo type passed to the transaction function.
type URLPresigner ¶
type URLPresigner interface {
PresignGET(ctx context.Context, key string, ttl time.Duration) (string, error)
}
URLPresigner is an optional capability of a FileStorage backend that can mint a time-limited, direct-to-backend download URL (e.g. S3's presigned GET). Backends without a native presign mechanism, like the local filesystem driver, don't implement this — callers type-assert for it and fall back to another download strategy when absent.
type UploadIntent ¶
type UploadIntent string
UploadIntent is a predefined, validated purpose for an uploaded file. It determines the storage key prefix and the upload constraints (allowed MIME types, max size, presigned URL lifetime).
const ( // IntentAvatar is a user's profile picture. IntentAvatar UploadIntent = "avatar" // IntentCompanyLogo is a company's logo, shown in the sidebar. IntentCompanyLogo UploadIntent = "company_logo" // IntentProductImage is a product's photo, shown on its edit form and // in the POS product grid. IntentProductImage UploadIntent = "product_image" )
type UserRepoer ¶
type UserRepoer interface {
CrudableWithTx[model.User, UserRepoer]
GetByEmail(ctx context.Context, email string) (*model.User, error)
CountByCompany(ctx context.Context, companyID uuid.UUID) (int64, error)
// CountByRole reports how many users currently have the given role —
// used to gate registration when config.SingleOwnerMode is on (cap the
// system to one owner total).
CountByRole(ctx context.Context, role auth.UserRole) (int64, error)
ListByCompany(
ctx context.Context,
companyID uuid.UUID,
q SupportsPagination,
) (*dto.PaginationResults[model.User], error)
// InvalidateTokens sets the user's token_valid_after cutoff to now,
// rejecting any JWT issued before this call (see model.User.TokenValidAfter).
InvalidateTokens(ctx context.Context, userID uuid.UUID) error
}
UserRepoer defines the interface for interacting with user data storage. Implementations include a SQLite repo (OSS), a pgx/Postgres repo (Pro), and test stubs/mocks in the stubrepo and mockrepo packages.