service

package
v0.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package service contains the business logic for okpos. Service methods depend only on data.*Repoer interfaces and domain types; they never import database-specific packages (pgx, sqlite, etc.).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AvailableQuantity added in v0.1.2

func AvailableQuantity(
	ctx context.Context,
	productRepo data.ProductRepoer,
	inv data.InventoryQuerier,
	product *model.Product,
) (float64, error)

AvailableQuantity returns how many units of product can currently be sold. For simple products this is the direct current stock. For recipe products it's how many complete units can be made from current ingredient stock: floor(min(ingredient_stock / component_quantity)) across recipe components.

func ComputeDiscountAmount

func ComputeDiscountAmount(d *model.Discount, basisCents int64) int64

ComputeDiscountAmount returns the discount in cents for the given basis amount. For fixed discounts, returns d.Value directly. For percentage discounts, returns (basisCents * d.Value) / 100 (integer division).

Types

type AuthService

type AuthService struct {
	// contains filtered or unexported fields
}

AuthService handles login and token refresh.

func NewAuthService

func NewAuthService(userRepo data.UserRepoer, a *auth.Auth) *AuthService

NewAuthService creates an AuthService.

func (*AuthService) Login

func (s *AuthService) Login(ctx context.Context, email, password string) (*auth.JWTTokens, error)

Login validates credentials and returns a JWT token pair.

func (*AuthService) Logout

func (s *AuthService) Logout(ctx context.Context, userID uuid.UUID) error

Logout revokes every access and refresh token already issued to the user by advancing their token_valid_after cutoff (UserRepoer.InvalidateTokens), so a token that was merely discarded client-side (or stolen) can no longer be used to authenticate or refresh. There's no per-device session store, so this revokes all of the user's sessions, not just the caller's.

func (*AuthService) RefreshTokens

func (s *AuthService) RefreshTokens(
	ctx context.Context,
	refreshToken string,
) (*auth.JWTTokens, error)

RefreshTokens validates a refresh token and issues a new access+refresh pair.

func (*AuthService) Register

func (s *AuthService) Register(
	ctx context.Context,
	req *dto.RegisterDTO,
) (*model.User, *auth.JWTTokens, error)

Register creates a new owner account and returns a JWT token pair. Returns data.ErrConflict if the email is already registered.

type BranchService

type BranchService struct {
	// contains filtered or unexported fields
}

BranchService handles branch-related business operations.

func NewBranchService

func NewBranchService(repo data.BranchRepoer, companyRepo data.CompanyRepoer) *BranchService

func (*BranchService) Create

func (s *BranchService) Create(
	ctx context.Context,
	req *dto.CreateBranchDTO,
) (*model.Branch, error)

Create registers a new branch. Validates that the company exists.

func (*BranchService) Delete

func (s *BranchService) Delete(ctx context.Context, id uuid.UUID) error

func (*BranchService) GetByID

func (s *BranchService) GetByID(ctx context.Context, id uuid.UUID) (*model.Branch, error)

func (*BranchService) GetByIDForCompany

func (s *BranchService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.Branch, error)

GetByIDForCompany fetches a branch and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*BranchService) ListByCompany

func (s *BranchService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Branch], error)

func (*BranchService) Update

func (s *BranchService) Update(
	ctx context.Context,
	id uuid.UUID,
	req *dto.UpdateBranchDTO,
) (*model.Branch, error)

type CompanyService

type CompanyService struct {
	// contains filtered or unexported fields
}

CompanyService handles company-related business operations.

func NewCompanyService

func NewCompanyService(repo data.CompanyRepoer) *CompanyService

func (*CompanyService) Create

func (s *CompanyService) Create(
	ctx context.Context,
	ownerID uuid.UUID,
	req *dto.CreateCompanyDTO,
) (*model.Company, error)

Create registers a new company. ownerID is taken from the caller's JWT, not the DTO.

func (*CompanyService) Delete

func (s *CompanyService) Delete(ctx context.Context, id uuid.UUID) error

func (*CompanyService) GetByID

func (s *CompanyService) GetByID(ctx context.Context, id uuid.UUID) (*model.Company, error)

func (*CompanyService) GetByIDForOwner

func (s *CompanyService) GetByIDForOwner(
	ctx context.Context,
	id, ownerID uuid.UUID,
) (*model.Company, error)

GetByIDForOwner fetches a company and verifies ownerID actually owns it. Returns data.ErrForbidden if the company exists but belongs to someone else — companies have no CompanyID of their own to compare (they're the tenant root), so ownership is checked via OwnerID instead.

func (*CompanyService) ListByOwner

func (s *CompanyService) ListByOwner(
	ctx context.Context,
	ownerID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Company], error)

func (*CompanyService) Update

func (s *CompanyService) Update(
	ctx context.Context,
	id uuid.UUID,
	req *dto.UpdateCompanyDTO,
) (*model.Company, error)

type CustomerService

type CustomerService struct {
	// contains filtered or unexported fields
}

CustomerService handles customer-related business operations.

func NewCustomerService

func NewCustomerService(repo data.CustomerRepoer) *CustomerService

func (*CustomerService) Create

func (*CustomerService) Delete

func (s *CustomerService) Delete(ctx context.Context, id uuid.UUID) error

func (*CustomerService) FindOrCreate added in v0.1.2

func (s *CustomerService) FindOrCreate(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.FindOrCreateCustomerDTO,
) (*model.Customer, error)

FindOrCreate looks up a customer within companyID by email (if provided) then by phone (if provided); if no match is found, it creates a new customer from req.Name/Email/Phone. Name is only required on the create path — a matched lookup returns the existing record as-is.

func (*CustomerService) GetByID

func (s *CustomerService) GetByID(ctx context.Context, id uuid.UUID) (*model.Customer, error)

func (*CustomerService) GetByIDForCompany

func (s *CustomerService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.Customer, error)

GetByIDForCompany fetches a customer and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*CustomerService) ListByCompany

func (s *CustomerService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Customer], error)

func (*CustomerService) Update

func (s *CustomerService) Update(
	ctx context.Context,
	id uuid.UUID,
	req *dto.UpdateCustomerDTO,
) (*model.Customer, error)

type DiscountService

type DiscountService struct {
	// contains filtered or unexported fields
}

DiscountService handles CRUD operations on the named discount catalog.

func NewDiscountService

func NewDiscountService(repo data.DiscountRepoer) *DiscountService

NewDiscountService constructs a DiscountService.

func (*DiscountService) Create

Create persists a new discount. Validates type and value range.

func (*DiscountService) Delete

func (s *DiscountService) Delete(ctx context.Context, id uuid.UUID) error

Delete removes a discount by ID.

func (*DiscountService) GetByID

func (s *DiscountService) GetByID(ctx context.Context, id uuid.UUID) (*model.Discount, error)

GetByID retrieves a discount by its ID.

func (*DiscountService) GetByIDForCompany

func (s *DiscountService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.Discount, error)

GetByIDForCompany fetches a discount and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*DiscountService) ListByCompany

func (s *DiscountService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Discount], error)

ListByCompany returns paginated discounts for a company.

func (*DiscountService) ListSelectableForOrder added in v0.1.4

func (s *DiscountService) ListSelectableForOrder(
	ctx context.Context, companyID uuid.UUID,
) ([]model.Discount, error)

ListSelectableForOrder returns discounts a cashier may pick from a company's dropdown at checkout — active, uncoded, and currently within their validity window (if any).

func (*DiscountService) RedemptionCount added in v0.1.4

func (s *DiscountService) RedemptionCount(ctx context.Context, discountID uuid.UUID) (int, error)

RedemptionCount returns how many orders have redeemed a discount.

func (*DiscountService) Update

func (s *DiscountService) Update(
	ctx context.Context,
	id uuid.UUID,
	req *dto.UpdateDiscountDTO,
) (*model.Discount, error)

Update persists mutable discount fields.

type ExpenseService

type ExpenseService struct {
	// contains filtered or unexported fields
}

ExpenseService handles expense-related business operations.

func NewExpenseService

func NewExpenseService(repo data.ExpenseRepoer, emitter data.EventEmitter) *ExpenseService

func (*ExpenseService) GetByID

func (s *ExpenseService) GetByID(ctx context.Context, id uuid.UUID) (*model.Expense, error)

func (*ExpenseService) GetByIDForCompany

func (s *ExpenseService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.Expense, error)

GetByIDForCompany fetches an expense and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*ExpenseService) ListByCompany

func (s *ExpenseService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Expense], error)

func (*ExpenseService) Record

func (s *ExpenseService) Record(
	ctx context.Context,
	req *dto.CreateExpenseDTO,
) (*model.Expense, error)

type FileService

type FileService struct {
	// contains filtered or unexported fields
}

FileService validates and persists uploaded files against the intent registry, delegating byte storage to a data.FileStorage backend and producing presigned download URLs for either backend transparently.

func NewFileService

func NewFileService(
	repo data.FileRepoer,
	storage data.FileStorage,
	a *auth.Auth,
	fileAccessTTL time.Duration,
) *FileService

NewFileService constructs a FileService.

func (*FileService) Delete

func (s *FileService) Delete(ctx context.Context, id, companyID uuid.UUID) error

Delete removes both the storage object and the metadata row for a file, scoped to companyID. Storage is deleted first so a failure leaves the DB row (and thus the file) intact for a retry, rather than orphaning bytes with no record pointing at them.

func (*FileService) Open

func (s *FileService) Open(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.File, io.ReadCloser, error)

Open retrieves a file's metadata and a reader over its bytes, scoped to companyID. Used by the public download handler.

func (*FileService) PresignedURL

func (s *FileService) PresignedURL(ctx context.Context, file *model.File) (string, error)

PresignedURL returns a time-limited download URL for file, using the storage backend's native presign when available (S3), or a file-access JWT pointing back at this app's own download route otherwise (local FS).

func (*FileService) Upload

func (s *FileService) Upload(
	ctx context.Context,
	companyID, uploaderID uuid.UUID,
	intent data.UploadIntent,
	declaredMIME string,
	size int64,
	r io.Reader,
) (*model.File, string, error)

Upload validates r against intent's registered constraints, stores it, records a files row, and returns the record plus a presigned download URL.

type InventoryService

type InventoryService struct {
	// contains filtered or unexported fields
}

InventoryService handles stock movement operations. It also implements data.InventoryQuerier (LPP + CurrentStock) so it can be passed directly to ProductService.

func NewInventoryService

func NewInventoryService(
	repo data.InventoryRepoer,
	productRepo data.ProductRepoer,
	supplierRepo data.SupplierRepoer,
	emitter data.EventEmitter,
) *InventoryService

func (*InventoryService) CorrectMovement added in v0.1.2

func (s *InventoryService) CorrectMovement(
	ctx context.Context,
	companyID, movementID uuid.UUID,
	req *dto.RecordStockMovementDTO,
) (*model.StockMovement, error)

CorrectMovement fixes a mistaken entry (e.g. a typo'd quantity or unit cost) without ever mutating the ledger: it records a reversal that exactly cancels the original movement, then a fresh correct movement of the same reason — both in one transaction. req.ProductID and req.Reason are overwritten from the original movement; only Quantity/UnitCost/Notes/ SupplierID are taken from the caller.

Returns ErrForbidden if the movement doesn't belong to companyID, ErrNotCorrectable if the reason isn't user-entered or is itself a correction, and ErrAlreadyCorrected if it has already been reversed.

func (*InventoryService) CurrentStock

func (s *InventoryService) CurrentStock(ctx context.Context, productID uuid.UUID) (float64, error)

CurrentStock returns the signed sum of stock deltas for the product. Returns ErrInsufficientStock if no movements have been recorded yet.

func (*InventoryService) GetByID

func (s *InventoryService) GetByID(
	ctx context.Context,
	id uuid.UUID,
) (*model.StockMovement, error)

func (*InventoryService) GetByIDForCompany

func (s *InventoryService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.StockMovement, error)

GetByIDForCompany fetches a stock movement and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*InventoryService) IsCorrectable added in v0.1.2

func (s *InventoryService) IsCorrectable(reason model.MovementReason) bool

IsCorrectable reports whether a movement of the given reason is eligible for CorrectMovement.

func (*InventoryService) LPP

func (s *InventoryService) LPP(ctx context.Context, productID uuid.UUID) (int64, error)

LPP returns the last procurement price for the product.

func (*InventoryService) ListByCompany

func (s *InventoryService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.StockMovement], error)

func (*InventoryService) ListByProduct

func (s *InventoryService) ListByProduct(
	ctx context.Context,
	productID, companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.StockMovement], error)

func (*InventoryService) RecordAdjustmentDown

func (s *InventoryService) RecordAdjustmentDown(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.RecordStockMovementDTO,
) (*model.StockMovement, error)

RecordAdjustmentDown records a negative stock adjustment. UnitCost must be nil.

func (*InventoryService) RecordAdjustmentUp

func (s *InventoryService) RecordAdjustmentUp(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.RecordStockMovementDTO,
) (*model.StockMovement, error)

RecordAdjustmentUp records a positive stock adjustment. UnitCost must be nil.

func (*InventoryService) RecordMovement

func (s *InventoryService) RecordMovement(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.RecordStockMovementDTO,
) (*model.StockMovement, error)

RecordMovement dispatches to the appropriate method based on Reason.

func (*InventoryService) RecordOpeningBalance

func (s *InventoryService) RecordOpeningBalance(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.RecordStockMovementDTO,
) (*model.StockMovement, error)

RecordOpeningBalance records an opening balance movement. UnitCost is required.

func (*InventoryService) RecordProcurement

func (s *InventoryService) RecordProcurement(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.RecordStockMovementDTO,
) (*model.StockMovement, error)

RecordProcurement records an incoming stock movement of type "procurement". UnitCost is required.

func (*InventoryService) RecordStockCount added in v0.1.4

func (s *InventoryService) RecordStockCount(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.RecordStockCountDTO,
) (*model.StockMovement, float64, error)

RecordStockCount reconciles a physical count against current system stock, recording an adjustment_up or adjustment_down for the difference. Returns a nil movement and delta 0 when the count matches current stock (within stockCountEpsilon) — no discrepancy, nothing recorded.

func (*InventoryService) RecordWaste

func (s *InventoryService) RecordWaste(
	ctx context.Context,
	companyID uuid.UUID,
	req *dto.RecordStockMovementDTO,
) (*model.StockMovement, error)

RecordWaste records a waste movement (negative delta). UnitCost must be nil. Resolves LPP to compute valueLost and optionally emits an event.

func (*InventoryService) StockAt

func (s *InventoryService) StockAt(
	ctx context.Context,
	productID uuid.UUID,
	at time.Time,
) (float64, error)

StockAt returns the stock level at a given point in time.

type OrderService

type OrderService struct {
	// contains filtered or unexported fields
}

OrderService handles the order lifecycle: create, add items, checkout, void, return.

func NewOrderService

func NewOrderService(
	repo data.OrderRepoer,
	productRepo data.ProductRepoer,
	sessionRepo data.SessionRepoer,
	discountRepo data.DiscountRepoer,
	inventoryRepo data.InventoryRepoer,
	paymentMethods []model.PaymentMethod,
	cashDrawerMethods []model.PaymentMethod,
	emitter data.EventEmitter,
) *OrderService

NewOrderService constructs an OrderService. Pass nil for paymentMethods to use model.DefaultPaymentMethods. Pass nil for cashDrawerMethods to use model.DefaultCashDrawerMethods. Pass nil for inventoryRepo to skip stock-availability checks on add-to-cart and checkout (e.g. in tests that don't exercise inventory).

func (*OrderService) AddItem

func (s *OrderService) AddItem(
	ctx context.Context,
	orderID uuid.UUID,
	req *dto.AddOrderItemDTO,
) (*model.OrderItem, error)

AddItem adds a product to an open order. Returns ErrOrderNotOpen if the order is not open. Returns ErrInvalidProductType if the product is an ingredient.

func (*OrderService) ApplyOrderDiscount

func (s *OrderService) ApplyOrderDiscount(
	ctx context.Context,
	orderID uuid.UUID,
	req *dto.ApplyOrderDiscountDTO,
) (*model.Order, error)

ApplyOrderDiscount applies a named or ad-hoc discount to the order total. Replaces any previously applied order-level discount. Returns ErrOrderNotOpen if the order is not open. Returns ErrDiscountExceedsTotal if the discount would wipe out the order total.

func (*OrderService) AssignCustomer added in v0.1.2

func (s *OrderService) AssignCustomer(
	ctx context.Context,
	orderID, customerID uuid.UUID,
) (*model.Order, error)

AssignCustomer attaches a customer to an open order, replacing any previously assigned customer. The caller is responsible for verifying customerID belongs to the order's company before calling this. Returns ErrOrderNotOpen if the order is not open.

func (*OrderService) Checkout

func (s *OrderService) Checkout(
	ctx context.Context,
	orderID uuid.UUID,
	userID uuid.UUID,
	req *dto.CheckoutDTO,
) (*model.Order, error)

Checkout finalizes a paid order in a single transaction: validates payment total and methods, deducts stock, records payments, generates a receipt number, and marks the order paid.

func (*OrderService) CreateOrder

func (s *OrderService) CreateOrder(
	ctx context.Context,
	companyID uuid.UUID,
	_ uuid.UUID,
	req *dto.CreateOrderDTO,
) (*model.Order, error)

CreateOrder creates a new open order within the given session. Returns ErrNotFound if the session does not exist. Returns ErrOrderNotOpen if the session is already closed.

func (*OrderService) DecrementItem

func (s *OrderService) DecrementItem(ctx context.Context, orderID, itemID uuid.UUID) error

DecrementItem decreases an item's quantity by 1. If the quantity reaches 0, the item is removed entirely.

func (*OrderService) GetOrderByID

func (s *OrderService) GetOrderByID(ctx context.Context, id uuid.UUID) (*model.Order, error)

GetOrderByID retrieves an order by ID.

func (*OrderService) GetOrderByIDForCompany

func (s *OrderService) GetOrderByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.Order, error)

GetOrderByIDForCompany fetches an order and verifies it belongs to companyID, returning data.ErrForbidden otherwise. Order items and payments have no CompanyID of their own — verifying the parent order here is what scopes them too, since every item/payment action is keyed on an orderID first.

func (*OrderService) ListItemsByOrder

func (s *OrderService) ListItemsByOrder(
	ctx context.Context,
	orderID uuid.UUID,
) ([]model.OrderItem, error)

ListItemsByOrder returns all items on an order.

func (*OrderService) ListOrdersByCompany

func (s *OrderService) ListOrdersByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Order], error)

ListOrdersByCompany returns paginated orders for a company.

func (*OrderService) ListOrdersByCompanyFiltered

func (s *OrderService) ListOrdersByCompanyFiltered(
	ctx context.Context,
	companyID uuid.UUID,
	filter *data.OrderFilter,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Order], error)

ListOrdersByCompanyFiltered returns paginated orders for a company, narrowed by filter.

func (*OrderService) ListOrdersBySession

func (s *OrderService) ListOrdersBySession(
	ctx context.Context,
	sessionID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Order], error)

ListOrdersBySession returns paginated orders for a session.

func (*OrderService) RemoveCustomer added in v0.1.2

func (s *OrderService) RemoveCustomer(
	ctx context.Context,
	orderID uuid.UUID,
) (*model.Order, error)

RemoveCustomer detaches any customer from an open order. Returns ErrOrderNotOpen if the order is not open.

func (*OrderService) RemoveItem

func (s *OrderService) RemoveItem(ctx context.Context, orderID, itemID uuid.UUID) error

RemoveItem removes an item from an open order. Returns ErrOrderNotOpen if the order is not open. Returns ErrNotFound if the item does not belong to this order.

func (*OrderService) RemoveOrderDiscount

func (s *OrderService) RemoveOrderDiscount(
	ctx context.Context,
	orderID uuid.UUID,
) (*model.Order, error)

RemoveOrderDiscount removes an order-level discount, restoring the full items total. Returns ErrOrderNotOpen if the order is not open.

func (*OrderService) Return

func (s *OrderService) Return(
	ctx context.Context,
	orderID uuid.UUID,
	returnedBy uuid.UUID,
	req *dto.ReturnOrderDTO,
) (*model.OrderReturn, error)

Return processes a partial or full return against a paid order. Manager/owner only. Returns ErrOrderNotPaid if the order is not paid.

func (*OrderService) Void

func (s *OrderService) Void(
	ctx context.Context,
	orderID uuid.UUID,
	voidedBy uuid.UUID,
	req *dto.VoidOrderDTO,
) (*model.Order, error)

Void voids a paid order, restoring stock. Manager/owner only. Returns ErrOrderNotPaid if the order is not in paid status. Returns ErrOrderAlreadyVoided if already voided.

type POSStationService

type POSStationService struct {
	// contains filtered or unexported fields
}

POSStationService handles POS station-related business operations.

func NewPOSStationService

func NewPOSStationService(
	repo data.POSStationRepoer,
	branchRepo data.BranchRepoer,
) *POSStationService

func (*POSStationService) Create

Create registers a new POS station. Validates that the branch belongs to the given company.

func (*POSStationService) Delete

func (s *POSStationService) Delete(ctx context.Context, id uuid.UUID) error

func (*POSStationService) GetByID

func (s *POSStationService) GetByID(ctx context.Context, id uuid.UUID) (*model.POSStation, error)

func (*POSStationService) GetByIDForCompany

func (s *POSStationService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.POSStation, error)

GetByIDForCompany fetches a station and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*POSStationService) ListByCompany

func (s *POSStationService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.POSStation], error)

func (*POSStationService) Update

type ProductService

type ProductService struct {
	// contains filtered or unexported fields
}

ProductService handles product-related business operations including recipes, COGS computation, and available units calculation.

func NewProductService

func NewProductService(repo data.ProductRepoer, inventory data.InventoryQuerier) *ProductService

func (*ProductService) AvailableUnits

func (s *ProductService) AvailableUnits(ctx context.Context, recipeID uuid.UUID) (float64, error)

AvailableUnits returns how many complete units of a recipe product can be made given current ingredient stock levels. Returns ErrInsufficientStock if the inventory querier is nil or any ingredient has no stock record yet.

func (*ProductService) ComputeCOGS

func (s *ProductService) ComputeCOGS(ctx context.Context, recipeID uuid.UUID) (int64, error)

ComputeCOGS returns the cost of goods sold for a recipe product. Returns ErrNoProcurementPrice if the inventory querier is nil or any ingredient has no procurement price recorded.

func (*ProductService) Create

func (s *ProductService) Create(
	ctx context.Context,
	req *dto.CreateProductDTO,
) (*model.Product, error)

func (*ProductService) CurrentStock

func (s *ProductService) CurrentStock(ctx context.Context, id uuid.UUID) (float64, error)

CurrentStock returns the current stock level for a product. Returns ErrInsufficientStock if inventory is nil or no movements have been recorded.

func (*ProductService) Delete

func (s *ProductService) Delete(ctx context.Context, id uuid.UUID) error

func (*ProductService) GetByID

func (s *ProductService) GetByID(ctx context.Context, id uuid.UUID) (*model.Product, error)

func (*ProductService) GetByIDForCompany

func (s *ProductService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.Product, error)

GetByIDForCompany fetches a product and verifies it belongs to companyID, returning data.ErrForbidden otherwise. Recipe components/overheads have no CompanyID of their own — verifying their parent product here is what scopes them too, since they're never reached except via their product ID.

func (*ProductService) GetBySKU

func (s *ProductService) GetBySKU(
	ctx context.Context,
	companyID uuid.UUID,
	sku string,
) (*model.Product, error)

GetBySKU retrieves a product by its SKU, scoped to companyID. Used by the POS barcode-scan-to-cart flow.

func (*ProductService) GetRecipe

func (s *ProductService) GetRecipe(
	ctx context.Context,
	recipeID uuid.UUID,
) (*dto.RecipeDTO, error)

GetRecipe returns the recipe components and overheads for a recipe product.

func (*ProductService) LPP

func (s *ProductService) LPP(ctx context.Context, id uuid.UUID) (int64, error)

LPP returns the last procurement price for a product. Returns ErrNoProcurementPrice if inventory is nil or no procurement exists.

func (*ProductService) ListByCompany

func (s *ProductService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	productType string,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Product], error)

func (*ProductService) SetRecipe

func (s *ProductService) SetRecipe(
	ctx context.Context,
	recipeID uuid.UUID,
	req *dto.SetRecipeDTO,
) error

SetRecipe atomically replaces all recipe components and overheads for a recipe product.

func (*ProductService) StockAt

func (s *ProductService) StockAt(ctx context.Context, id uuid.UUID, at time.Time) (float64, error)

StockAt returns the stock level for a product at the given point in time.

func (*ProductService) Update

func (s *ProductService) Update(
	ctx context.Context,
	id uuid.UUID,
	req *dto.UpdateProductDTO,
) (*model.Product, error)

type ReportsService

type ReportsService struct {
	// contains filtered or unexported fields
}

ReportsService assembles management reports from aggregate repo queries.

func NewReportsService

func NewReportsService(repo data.ReportsQuerier) *ReportsService

NewReportsService returns a ReportsService backed by the given querier.

func (*ReportsService) InventoryReport

func (s *ReportsService) InventoryReport(
	ctx context.Context,
	q *dto.InventoryReportQuery,
) (*model.InventoryReport, error)

InventoryReport returns a stock snapshot with last-purchase-price and low-stock flags.

func (*ReportsService) PLReport

func (s *ReportsService) PLReport(
	ctx context.Context,
	q *dto.PLReportQuery,
) (*model.PLReport, error)

PLReport returns a profit-and-loss summary for the requested period.

func (*ReportsService) SalesReport

func (s *ReportsService) SalesReport(
	ctx context.Context,
	q *dto.SalesReportQuery,
) (*model.SalesReport, error)

SalesReport returns a full sales analytics report for the requested period.

type SessionService

type SessionService struct {
	// contains filtered or unexported fields
}

SessionService handles POS session lifecycle operations.

func NewSessionService

func NewSessionService(
	repo data.SessionRepoer,
	stationRepo data.POSStationRepoer,
	emitter data.EventEmitter,
) *SessionService

NewSessionService constructs a SessionService.

func (*SessionService) Close

func (s *SessionService) Close(
	ctx context.Context,
	sessionID uuid.UUID,
	closerID uuid.UUID,
	req *dto.CloseSessionDTO,
) (*model.SessionReconciliation, error)

Close closes a POS session and writes a SessionReconciliation record. Returns ErrNotFound if the session does not exist, ErrSessionAlreadyClosed if it is already closed, and ErrOpenOrdersExist if there are open orders (Phase 6 stub: never returns this error until the orders table exists).

func (*SessionService) ForceClose

func (s *SessionService) ForceClose(
	ctx context.Context,
	sessionID, companyID, actorID uuid.UUID,
) (*model.SessionReconciliation, error)

ForceClose forcibly closes a session on an admin's behalf: any open (unpaid) order on the session is hard-deleted first — safe because stock is only decremented at Checkout, never when an item is merely added to an open order — and then the session is closed exactly like a normal Close, with DeclaredCash set to the computed expected cash (there is no cashier present to physically count the drawer, so the resulting reconciliation always has a zero Difference). Returns ErrNotFound if the session does not exist, ErrForbidden if it belongs to a different company, and ErrSessionAlreadyClosed if it is already closed.

func (*SessionService) GetByID

func (s *SessionService) GetByID(ctx context.Context, id uuid.UUID) (*model.POSSession, error)

GetByID retrieves a session by ID.

func (*SessionService) GetByIDForCompany

func (s *SessionService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.POSSession, error)

GetByIDForCompany fetches a session and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*SessionService) GetOpenByUser

func (s *SessionService) GetOpenByUser(
	ctx context.Context,
	companyID, userID uuid.UUID,
) (*model.POSSession, error)

GetOpenByUser retrieves the open session opened by userID within companyID, if any.

func (*SessionService) GetReconciliation

func (s *SessionService) GetReconciliation(
	ctx context.Context,
	sessionID uuid.UUID,
) (*model.SessionReconciliation, error)

GetReconciliation retrieves the reconciliation record for a session.

func (*SessionService) ListByCompany

func (s *SessionService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.POSSession], error)

ListByCompany returns paginated sessions for a company.

func (*SessionService) ListOpenByCompanyFiltered

func (s *SessionService) ListOpenByCompanyFiltered(
	ctx context.Context,
	companyID uuid.UUID,
	filter *data.SessionFilter,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.POSSession], error)

ListOpenByCompanyFiltered returns paginated open sessions for a company, optionally narrowed by branch.

func (*SessionService) Open

func (s *SessionService) Open(
	ctx context.Context,
	companyID uuid.UUID,
	userID uuid.UUID,
	req *dto.OpenSessionDTO,
) (*model.POSSession, error)

Open creates a new POS session for the given station. Only cashier and owner roles may call this — enforced at the handler layer. Returns ErrNotFound if the station does not exist, ErrStationBusy if an open session already exists for that station.

type SupplierPaymentService

type SupplierPaymentService struct {
	// contains filtered or unexported fields
}

SupplierPaymentService handles payments made to suppliers.

func NewSupplierPaymentService

func NewSupplierPaymentService(
	repo data.SupplierPaymentRepoer,
	supplierRepo data.SupplierRepoer,
	emitter data.EventEmitter,
) *SupplierPaymentService

func (*SupplierPaymentService) ListByCompany

func (*SupplierPaymentService) ListBySupplier

func (s *SupplierPaymentService) ListBySupplier(
	ctx context.Context,
	supplierID, companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.SupplierPayment], error)

ListBySupplier returns a supplier's payment history, scoped to companyID — verifies the supplier actually belongs to the caller before listing its (potentially sensitive) payment history.

func (*SupplierPaymentService) RecordPayment

func (s *SupplierPaymentService) RecordPayment(
	ctx context.Context,
	supplierID uuid.UUID,
	companyID uuid.UUID,
	req *dto.RecordSupplierPaymentDTO,
) (*model.SupplierPayment, error)

type SupplierService

type SupplierService struct {
	// contains filtered or unexported fields
}

SupplierService handles supplier-related business operations.

func NewSupplierService

func NewSupplierService(repo data.SupplierRepoer) *SupplierService

func (*SupplierService) Create

func (*SupplierService) Delete

func (s *SupplierService) Delete(ctx context.Context, id uuid.UUID) error

func (*SupplierService) GetByID

func (s *SupplierService) GetByID(ctx context.Context, id uuid.UUID) (*model.Supplier, error)

func (*SupplierService) GetByIDForCompany

func (s *SupplierService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.Supplier, error)

GetByIDForCompany fetches a supplier and verifies it belongs to companyID, returning data.ErrForbidden otherwise.

func (*SupplierService) ListByCompany

func (s *SupplierService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.Supplier], error)

func (*SupplierService) Update

func (s *SupplierService) Update(
	ctx context.Context,
	id uuid.UUID,
	req *dto.UpdateSupplierDTO,
) (*model.Supplier, error)

type UserService

type UserService struct {
	// contains filtered or unexported fields
}

UserService implements all user-related business operations.

func NewUserService

func NewUserService(repo data.UserRepoer) *UserService

NewUserService creates a new UserService backed by the given repo.

func (*UserService) Create

func (s *UserService) Create(ctx context.Context, req *dto.CreateUserDTO) (*model.User, error)

Create registers a new user. The entity's ID and timestamps are set by the repo layer. CompanyID is required when Role is manager or cashier.

func (*UserService) Delete

func (s *UserService) Delete(ctx context.Context, id uuid.UUID) error

Delete removes a user by ID. Returns data.ErrNotFound if the user does not exist.

func (*UserService) GetByID

func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*model.User, error)

GetByID fetches a single user by its UUID. Returns data.ErrNotFound if absent.

func (*UserService) GetByIDForCompany

func (s *UserService) GetByIDForCompany(
	ctx context.Context,
	id, companyID uuid.UUID,
) (*model.User, error)

GetByIDForCompany fetches a user and verifies it belongs to companyID, returning data.ErrForbidden otherwise. Owner-role users have a nil CompanyID and never match here — this is only for company team members.

func (*UserService) List

List returns a paginated list of users.

func (*UserService) ListByCompany

func (s *UserService) ListByCompany(
	ctx context.Context,
	companyID uuid.UUID,
	q *dto.PaginationDTO,
) (*dto.PaginationResults[model.User], error)

ListByCompany returns a paginated list of users belonging to companyID.

func (*UserService) Update

func (s *UserService) Update(
	ctx context.Context,
	id uuid.UUID,
	req *dto.UpdateUserDTO,
) (*model.User, error)

Update applies the given changes to an existing user.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL