Skip to main content
ShelfWise uses a service layer architecture to keep controllers thin and encapsulate business logic. All services are located in app/Services/ and handle complex operations, data validation, and coordination between models.

Core Principles

  • Thin Controllers: All business logic resides in services
  • Transaction Management: Critical operations use database transactions
  • Cache Invalidation: Services handle cache management automatically
  • Tenant Isolation: All operations are tenant-scoped for security
  • Audit Logging: Services log all significant operations

ProductService

Manages product creation, updates, variants, and packaging types. Location: app/Services/ProductService.php

create()

Creates a new product with variants and packaging types.
data
array
required
Product data including name, type, variants, and packaging
tenant
Tenant
required
The tenant instance
shop
Shop
required
The shop instance
return
Product
Created product with relationships loaded
use App\Services\ProductService;

$productService = app(ProductService::class);

$product = $productService->create([
    'name' => 'Pain Relief Tablets',
    'product_type_slug' => 'pharmacy',
    'category_id' => 5,
    'description' => '500mg paracetamol tablets',
    'has_variants' => false,
    'sku' => 'MED-001',
    'price' => 15.99,
    'cost_price' => 8.50,
    'base_unit_name' => 'Pack',
    'custom_attributes' => [
        'requires_prescription' => false,
        'drug_class' => 'analgesic',
    ],
], $tenant, $shop);

update()

Updates an existing product.
product
Product
required
The product to update
data
array
required
Updated product data
return
Product
Updated product with fresh relationships
$product = $productService->update($product, [
    'name' => 'Updated Product Name',
    'description' => 'New description',
    'is_active' => true,
]);

updateVariant()

Updates a product variant. Automatically adjusts packaging type prices if variant price changes.
variant
ProductVariant
required
The variant to update
data
array
required
Updated variant data
return
ProductVariant
Updated variant with relationships
$variant = $productService->updateVariant($variant, [
    'price' => 25.00,
    'cost_price' => 12.00,
    'reorder_level' => 50,
]);

OrderService

Handles order lifecycle management including creation, status transitions, and fulfillment. Location: app/Services/OrderService.php

createOrder()

Creates a new order with items.
tenant
Tenant
required
Tenant instance
shop
Shop
required
Shop instance
items
array
required
Array of order items
createdBy
User
required
User creating the order
customer
User
Customer user (optional)
return
Order
Created order with items and totals calculated
use App\Services\OrderService;

$orderService = app(OrderService::class);

$order = $orderService->createOrder(
    tenant: $tenant,
    shop: $shop,
    items: [
        [
            'sellable_type' => ProductVariant::class,
            'sellable_id' => 123,
            'quantity' => 2,
            'unit_price' => 50.00,
        ],
    ],
    createdBy: auth()->user(),
    customer: $customer,
    customerNotes: 'Handle with care',
    shippingCost: 10.00
);

confirmOrder()

Confirms an order and reserves inventory.
order
Order
required
Order to confirm
user
User
required
User confirming the order
return
Order
Confirmed order with reserved inventory
When an order is confirmed:
  1. Uses pessimistic locking to prevent race conditions
  2. Checks available stock (quantity - reserved_quantity)
  3. Atomically increments reserved_quantity
  4. Throws exception if insufficient stock
  5. Updates order status to CONFIRMED
$order = $orderService->confirmOrder($order, auth()->user());

fulfillOrder()

Fulfills a confirmed order by deducting inventory and creating stock movements.
order
Order
required
Confirmed order to fulfill
user
User
required
User fulfilling the order
return
Order
Fulfilled order with status updated to PROCESSING
$order = $orderService->fulfillOrder($order, auth()->user());

cancelOrder()

Cancels an order and releases reserved inventory.
order
Order
required
Order to cancel
user
User
required
User canceling the order
reason
string
Cancellation reason
return
Order
Cancelled order
$order = $orderService->cancelOrder(
    $order, 
    auth()->user(), 
    'Customer requested cancellation'
);

CustomerService

Manages customer accounts, addresses, credit limits, and search. Location: app/Services/CustomerService.php

create()

Creates a new customer account.
data
array
required
Customer data
tenant
Tenant
required
Tenant instance
return
Customer
Created customer with address
use App\Services\CustomerService;

$customerService = app(CustomerService::class);

$customer = $customerService->create([
    'first_name' => 'John',
    'last_name' => 'Doe',
    'email' => '[email protected]',
    'phone' => '+2348012345678',
    'password' => 'password123',
    'preferred_shop_id' => 1,
    'credit_limit' => 50000.00,
    'address' => [
        'street' => '123 Main Street',
        'city' => 'Lagos',
        'state' => 'Lagos',
        'postal_code' => '100001',
    ],
], $tenant);

list()

Get paginated customer list with filters.
tenant
Tenant
required
Tenant instance
requester
User
required
User requesting the list
filters
array
Filter options
return
LengthAwarePaginator
Paginated customer collection
$customers = $customerService->list($tenant, auth()->user(), [
    'search' => 'john',
    'status' => 'active',
    'per_page' => 25,
]);

POSService

Handles Point-of-Sale operations including quick sales and session management. Location: app/Services/POSService.php

createQuickSale()

Creates an immediate sale with payment and stock deduction.
shop
Shop
required
Shop where sale is occurring
items
array
required
Array of sale items
customerId
int
Customer ID (optional)
paymentMethod
string
default:"cash"
Payment method (cash, card, mobile_money)
amountTendered
float
default:"0"
Cash amount tendered
options
array
Additional options (discount_amount, notes, etc.)
return
Order
Completed order with DELIVERED status
  1. Validates stock availability using pessimistic locking
  2. Creates order with DELIVERED status
  3. Calculates totals including tax and discounts
  4. Creates order items
  5. Records stock movements
  6. Creates payment record
  7. Returns completed order
use App\Services\POSService;

$posService = app(POSService::class);

$order = $posService->createQuickSale(
    shop: $shop,
    items: [
        [
            'variant_id' => 123,
            'quantity' => 2,
            'unit_price' => 50.00,
            'packaging_type_id' => 1,
        ],
    ],
    customerId: 45,
    paymentMethod: 'cash',
    amountTendered: 120.00,
    options: [
        'discount_amount' => 10.00,
        'notes' => 'Loyalty discount applied',
    ]
);

getSessionSummary()

Get sales summary for current user’s POS session.
shop
Shop
required
Shop instance
startDate
string
Start date (YYYY-MM-DD)
endDate
string
End date (YYYY-MM-DD)
return
array
Session statistics including total sales, revenue, and payment method breakdowns
$summary = $posService->getSessionSummary(
    $shop,
    startDate: '2024-03-01',
    endDate: '2024-03-01'
);

// Returns:
// [
//   'total_sales' => 45,
//   'total_revenue' => 125000.00,
//   'total_tax' => 9375.00,
//   'cash_sales' => 80000.00,
//   'card_sales' => 45000.00,
//   'mobile_money_sales' => 0.00,
//   'orders' => Collection,
// ]

CartService

Manages shopping cart operations for e-commerce storefronts. Location: app/Services/CartService.php

getCart()

Retrieves or creates a cart for the current session or customer.
shop
Shop
required
Shop instance
customerId
int
Customer ID for logged-in users
return
Cart
Cart instance
use App\Services\CartService;

$cartService = app(CartService::class);

// Guest cart (session-based)
$cart = $cartService->getCart($shop);

// Customer cart
$cart = $cartService->getCart($shop, $customerId);

addItem()

Adds a product to the cart.
cart
Cart
required
Cart instance
variantId
int
required
Product variant ID
quantity
int
default:"1"
Quantity to add
packagingTypeId
int
Packaging type ID (optional)
return
CartItem
Created or updated cart item
$cartItem = $cartService->addItem(
    cart: $cart,
    variantId: 123,
    quantity: 2,
    packagingTypeId: 5
);

getCartSummary()

Calculates cart totals including shipping and tax.
cart
Cart
required
Cart instance
return
array
Cart summary with items, subtotal, shipping, tax, and total
$summary = $cartService->getCartSummary($cart);

// Returns:
// [
//   'items' => Collection,
//   'subtotal' => 5000.00,
//   'shipping_fee' => 500.00,
//   'tax' => 375.00,
//   'total' => 5875.00,
//   'item_count' => 3,
// ]

mergeGuestCartIntoCustomerCart()

Merges a guest cart into a customer cart upon login.
sessionId
string
required
Guest session ID
customerId
int
required
Customer ID
shopId
int
required
Shop ID
return
Cart
Merged customer cart
$cart = $cartService->mergeGuestCartIntoCustomerCart(
    sessionId: session()->getId(),
    customerId: auth()->id(),
    shopId: $shop->id
);

Additional Services

Location: app/Services/StockMovementService.phpHandles all inventory adjustments with audit trails.

Key Methods

  • adjustStock() - Record inventory changes
  • recordSale() - Record sale stock movement
  • checkStockAvailability() - Validate stock levels
  • getAvailableStock() - Get current available quantity
use App\Services\StockMovementService;
use App\Enums\StockMovementType;

$service = app(StockMovementService::class);

$service->adjustStock(
    variant: $variant,
    location: $location,
    quantity: 50,
    type: StockMovementType::ADJUSTMENT,
    user: auth()->user(),
    referenceNumber: 'ADJ-001',
    notes: 'Stock count adjustment'
);

Service Best Practices

All services use database transactions for data consistency:
return DB::transaction(function () use ($data) {
    // Multiple database operations
    $model = Model::create($data);
    $related = $model->related()->create($relatedData);
    
    // All succeed or all fail
    return $model;
});
Services automatically invalidate relevant caches:
Cache::tags([
    "tenant:{$tenantId}:products:list",
    "tenant:{$tenantId}:product:{$productId}",
])->flush();
All queries are scoped to the current tenant:
Product::query()
    ->where('tenant_id', $tenant->id)
    ->where('shop_id', $shop->id)
    ->get();
Services throw exceptions with clear messages:
if (!$order->canEdit()) {
    throw new Exception(
        'Order cannot be edited in current status: ' . 
        $order->status->value
    );
}

Build docs developers (and LLMs) love