Skip to main content

Overview

The Clients module manages all customer information, including contact details, credit limits, equipment tracking, and financial relationships. It integrates with Sales, Accounting, and Equipment modules for complete customer lifecycle management.

Credit Management

Credit limits and balance tracking

Equipment

Customer equipment registry

Financial

Sales history and receivables

Client Structure

Core Fields

app/Models/Clients/Client.php
protected $fillable = [
    'type',                    // individual or company
    'estado_cliente_id',       // Client status
    'name',                    // Legal name
    'commercial_name',         // DBA / trading name
    'email',
    'phone',
    'state_id',                // Geographic state
    'city',
    'address',
    'tax_identifier_type_id',  // RNC, Cédula, etc.
    'tax_id',                  // Tax ID number
    // Financial fields
    'credit_limit',
    'balance',                 // Current outstanding
    'payment_terms',           // Days for payment
    'accounting_account_id',   // Custom A/R account
];

Client Types

Personal customers:
  • Use personal name
  • Personal tax ID (Cédula)
  • Lower credit limits typically
  • Simplified tracking
Client::create([
    'type' => 'individual',
    'name' => 'Juan Pérez',
    'tax_identifier_type_id' => 1, // Cédula
    'tax_id' => '001-1234567-8',
    'credit_limit' => 5000,
]);

Credit Management

Credit Limit Validation

Before approving credit sales, validate available credit:
app/Http/Requests/Sales/StoreSaleRequest.php
public function withValidator($validator)
{
    $validator->after(function ($validator) {
        if ($this->payment_type === Sale::PAYMENT_CREDIT) {
            $client = Client::find($this->client_id);
            
            if ($client->balance + $this->total_amount > $client->credit_limit) {
                $available = $client->credit_limit - $client->balance;
                $validator->errors()->add(
                    'total_amount',
                    "Crédito insuficiente. Disponible: " . 
                    number_format($available, 2)
                );
            }
        }
    });
}
Credit sales will be rejected if the sale amount exceeds available credit. Increase the client’s credit limit or request cash payment.

Balance Tracking

The client’s balance is automatically updated:
app/Models/Clients/Client.php
public function refreshBalance(): bool
{
    $this->balance = $this->receivables()
        ->whereIn('status', [
            Receivable::STATUS_UNPAID,
            Receivable::STATUS_PARTIAL
        ])
        ->sum('current_balance');
        
    return $this->save();
}
When balance updates:
  • Credit sale created → Balance increases
  • Payment applied → Balance decreases
  • Receivable canceled → Balance decreases

Payment Terms

// 30-day terms
$client->payment_terms = 30;

// Calculate due date
$dueDate = $sale->sale_date->copy()->addDays($client->payment_terms);

Client Status

Track the current state of the client relationship:
app/Models/Configuration/EstadosCliente.php
protected $fillable = [
    'nombre',        // e.g., "Activo", "Moroso", "Inactivo"
    'clase_fondo',   // Background color for badge
    'clase_texto',   // Text color for badge
];
Common Statuses:

Active

Good standing, can purchase

Delinquent

Overdue payments, credit hold

Inactive

No longer doing business

Suspended

Administrative hold

Equipment Management

Track equipment installed at client locations:
app/Models/Clients/Equipment.php
protected $fillable = [
    'client_id',
    'equipment_type_id',
    'serial_number',
    'model',
    'installation_date',
    'status',            // active, maintenance, retired
    'notes',
];

Use Cases

Track coolers, display racks, or dispensers at retail locations:
Equipment::create([
    'client_id' => $clientId,
    'equipment_type_id' => 1,  // Cooler
    'serial_number' => 'COOL-2024-001',
    'model' => 'Vertical 6ft',
    'installation_date' => now(),
    'status' => 'active',
]);
Track machinery or equipment under service:
  • Installation date
  • Warranty information
  • Maintenance schedule
  • Parts inventory
Equipment leased to customers:
  • Lease terms
  • Monthly fees
  • Return conditions
  • Depreciation tracking

Point of Sale Locations

Manage multiple POS locations for a single client:
app/Models/Clients/PointOfSale.php
protected $fillable = [
    'client_id',
    'name',
    'code',
    'address',
    'phone',
    'contact_person',
    'is_active',
];
Example:
// Main client
$client = Client::find(1); // "ABC Supermarkets"

// Multiple locations
PointOfSale::create([
    'client_id' => $client->id,
    'name' => 'ABC - Centro',
    'code' => 'ABC-01',
    'address' => 'Av. Principal #123',
]);

PointOfSale::create([
    'client_id' => $client->id,
    'name' => 'ABC - Norte',
    'code' => 'ABC-02',
    'address' => 'Calle Comercio #456',
]);

Business Types

Categorize clients by industry or business model:
app/Models/Clients/BusinessType.php
protected $fillable = [
    'name',         // e.g., "Restaurant", "Retail", "Wholesale"
    'description',
];
Examples:
  • Retail Store
  • Supermarket
  • Restaurant
  • Hotel
  • Distributor
  • Government
  • Healthcare

Relationships

Sales History

$client = Client::with('sales')->find($id);

// Total sales
$totalSales = $client->sales()->sum('total_amount');

// Recent sales
$recentSales = $client->sales()
    ->latest()
    ->take(10)
    ->get();

Receivables

// Outstanding invoices
$outstanding = $client->receivables()
    ->whereIn('status', [
        Receivable::STATUS_UNPAID,
        Receivable::STATUS_PARTIAL
    ])
    ->get();

// Overdue
$overdue = $client->receivables()
    ->where('status', Receivable::STATUS_OVERDUE)
    ->get();

Payments

// Payment history
$payments = $client->payments()
    ->orderBy('payment_date', 'desc')
    ->get();

// Total paid this month
$monthlyPayments = $client->payments()
    ->whereMonth('payment_date', now()->month)
    ->sum('amount');

Accounting Integration

Custom A/R Accounts

Large clients can have dedicated receivable accounts:
$client->accounting_account_id = 25; // "1.1.02.01 - ABC Company A/R"
$client->save();
Benefits:
  • Separate tracking in trial balance
  • Custom reporting
  • Better visibility
app/Models/Clients/Client.php
public function hasCustomAccount(): bool
{
    return !is_null($this->accounting_account_id);
}

Default Account Fallback

if ($client->hasCustomAccount()) {
    $arAccount = $client->accountingAccount;
} else {
    $arAccount = AccountingAccount::where('code', '1.1.02')->first();
}

Queries & Reports

Clients by Credit Usage

$highRisk = Client::whereColumn('balance', '>', DB::raw('credit_limit * 0.8'))
    ->get();

foreach ($highRisk as $client) {
    $usage = ($client->balance / $client->credit_limit) * 100;
    echo "{$client->name}: {$usage}% credit usage\n";
}

Top Customers by Sales

$topClients = Client::withSum('sales as total_sales', 'total_amount')
    ->orderByDesc('total_sales')
    ->take(20)
    ->get();

Aging Analysis

$aging = Client::select([
    'clients.*',
    DB::raw('SUM(CASE WHEN DATEDIFF(NOW(), receivables.due_date) <= 0 THEN receivables.current_balance ELSE 0 END) as current'),
    DB::raw('SUM(CASE WHEN DATEDIFF(NOW(), receivables.due_date) BETWEEN 1 AND 30 THEN receivables.current_balance ELSE 0 END) as days_1_30'),
    DB::raw('SUM(CASE WHEN DATEDIFF(NOW(), receivables.due_date) > 30 THEN receivables.current_balance ELSE 0 END) as over_30'),
])
->join('receivables', 'receivables.client_id', '=', 'clients.id')
->whereIn('receivables.status', ['unpaid', 'partial', 'overdue'])
->groupBy('clients.id')
->get();

Client Portal (Future)

Potential features for client self-service:
  • View account balance
  • Download invoices
  • Make online payments
  • Track order history
  • Request quotes
  • Update contact information

Best Practices

Base credit limits on:
  • Purchase history
  • Payment behavior
  • Business size
  • Industry risk
// Start conservative
$newClient->credit_limit = 5000;

// Increase based on performance
if ($client->payment_score > 90) {
    $client->credit_limit *= 1.5;
}
Periodically verify that client balances match receivables:
foreach (Client::all() as $client) {
    $calculated = $client->receivables()
        ->whereIn('status', ['unpaid', 'partial'])
        ->sum('current_balance');

    if (abs($client->balance - $calculated) > 0.01) {
        $client->refreshBalance();
    }
}
Alert when clients approach credit limits:
$utilization = ($client->balance / $client->credit_limit) * 100;

if ($utilization > 80) {
    // Send alert
    // Consider increasing limit
    // Or restrict new sales
}
Always record serial numbers for equipment:
  • Warranty claims
  • Theft recovery
  • Maintenance tracking
  • Audit compliance

Sales Module

Creating sales for clients

Receivables

Credit sales tracking

Client Model API

Model reference

Accounting

Financial integration

Build docs developers (and LLMs) love