Add Stargas supplier dashboard with performance analytics
This commit is contained in:
157
src/app/Http/Controllers/DashboardController.php
Normal file
157
src/app/Http/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('dashboard');
|
||||
}
|
||||
|
||||
public function summary(): JsonResponse
|
||||
{
|
||||
$totalSuppliers = DB::table('PL_SUPPLIERACCOUNT')->count();
|
||||
$totalProducts = DB::table('STK_STOCKITEM')->where('ISACTVE', 'Y')->count();
|
||||
$totalCustomers = DB::table('SL_CUSTOMERACCOUNT')->count();
|
||||
|
||||
$purchaseSpend = DB::table('PL_BILLTRAN')
|
||||
->selectRaw('COALESCE(SUM(QTYTOINVOICE * COSTPRICE / COSTPRICEPER), 0) as total')
|
||||
->value('total');
|
||||
|
||||
$salesRevenue = DB::table('SL_SALESINVOICETRAN')
|
||||
->selectRaw('COALESCE(SUM(QTYTOINVOICE * SELLINGPRICE / SELLINGPRICEPER), 0) as total')
|
||||
->value('total');
|
||||
|
||||
$salesCost = DB::table('SL_SALESINVOICETRAN')
|
||||
->selectRaw('COALESCE(SUM(QTYTOINVOICE * COSTPRICE / COSTPRICEPER), 0) as total')
|
||||
->value('total');
|
||||
|
||||
$grossProfit = $salesRevenue - $salesCost;
|
||||
$margin = $salesRevenue > 0 ? round(($grossProfit / $salesRevenue) * 100, 1) : 0;
|
||||
|
||||
$totalInvoices = DB::table('PL_BILL')->count();
|
||||
|
||||
return response()->json([
|
||||
'total_suppliers' => $totalSuppliers,
|
||||
'total_products' => $totalProducts,
|
||||
'total_customers' => $totalCustomers,
|
||||
'purchase_spend' => round($purchaseSpend, 2),
|
||||
'sales_revenue' => round($salesRevenue, 2),
|
||||
'gross_profit' => round($grossProfit, 2),
|
||||
'margin_percent' => $margin,
|
||||
'total_invoices' => $totalInvoices,
|
||||
]);
|
||||
}
|
||||
|
||||
public function topSuppliers(): JsonResponse
|
||||
{
|
||||
$suppliers = DB::table('PL_BILLTRAN as il')
|
||||
->join('PL_BILL as i', 'il.REFNO', '=', 'i.REFNO')
|
||||
->selectRaw('i.ACCNO as code, i.SUPLNME as name, COUNT(DISTINCT i.REFNO) as invoice_count, SUM(il.QTYTOINVOICE * il.COSTPRICE / il.COSTPRICEPER) as total_spend, SUM(il.QTYTOINVOICE) as total_qty')
|
||||
->groupBy('i.ACCNO', 'i.SUPLNME')
|
||||
->orderByDesc('total_spend')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
return response()->json($suppliers);
|
||||
}
|
||||
|
||||
public function topProducts(): JsonResponse
|
||||
{
|
||||
$products = DB::table('SL_SALESINVOICETRAN as sl')
|
||||
->join('STK_STOCKITEM as p', 'sl.STOCKCODE', '=', 'p.STOCKCODE')
|
||||
->selectRaw('sl.STOCKCODE as code, p.DESCRIPTION as name, SUM(sl.QTYTOINVOICE) as total_qty_sold, SUM(sl.QTYTOINVOICE * sl.SELLINGPRICE / sl.SELLINGPRICEPER) as total_revenue, SUM(sl.QTYTOINVOICE * sl.COSTPRICE / sl.COSTPRICEPER) as total_cost')
|
||||
->groupBy('sl.STOCKCODE', 'p.DESCRIPTION')
|
||||
->orderByDesc('total_revenue')
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
$item->margin = $item->total_revenue > 0
|
||||
? round((($item->total_revenue - $item->total_cost) / $item->total_revenue) * 100, 1)
|
||||
: 0;
|
||||
return $item;
|
||||
});
|
||||
|
||||
return response()->json($products);
|
||||
}
|
||||
|
||||
public function spendOverTime(): JsonResponse
|
||||
{
|
||||
$data = DB::table('PL_BILLTRAN as il')
|
||||
->join('PL_BILL as i', 'il.REFNO', '=', 'i.REFNO')
|
||||
->selectRaw("DATE_FORMAT(i.DOCDTETME, '%Y-%m') as month, SUM(il.QTYTOINVOICE * il.COSTPRICE / il.COSTPRICEPER) as total_spend")
|
||||
->whereNotNull('i.DOCDTETME')
|
||||
->groupByRaw("DATE_FORMAT(i.DOCDTETME, '%Y-%m')")
|
||||
->orderBy('month')
|
||||
->get();
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
public function salesOverTime(): JsonResponse
|
||||
{
|
||||
$data = DB::table('SL_SALESINVOICETRAN as sl')
|
||||
->join('SL_SALESINVOICE as si', 'sl.REFNO', '=', 'si.REFNO')
|
||||
->selectRaw("DATE_FORMAT(si.DOCDTETME, '%Y-%m') as month, SUM(sl.QTYTOINVOICE * sl.SELLINGPRICE / sl.SELLINGPRICEPER) as total_revenue, SUM(sl.QTYTOINVOICE * sl.COSTPRICE / sl.COSTPRICEPER) as total_cost")
|
||||
->whereNotNull('si.DOCDTETME')
|
||||
->groupByRaw("DATE_FORMAT(si.DOCDTETME, '%Y-%m')")
|
||||
->orderBy('month')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
$item->gross_profit = round($item->total_revenue - $item->total_cost, 2);
|
||||
return $item;
|
||||
});
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
public function categoryBreakdown(): JsonResponse
|
||||
{
|
||||
$data = DB::table('SL_SALESINVOICETRAN as sl')
|
||||
->join('STK_STOCKITEM as p', 'sl.STOCKCODE', '=', 'p.STOCKCODE')
|
||||
->leftJoin('STK_STOCKCATEGORY as c', 'p.CATEGORY', '=', 'c.STCKCTGRYCDE')
|
||||
->selectRaw("COALESCE(c.STCKCTGRYDESC, 'Uncategorized') as category, SUM(sl.QTYTOINVOICE * sl.SELLINGPRICE / sl.SELLINGPRICEPER) as revenue, SUM(sl.QTYTOINVOICE * sl.COSTPRICE / sl.COSTPRICEPER) as cost, SUM(sl.QTYTOINVOICE) as qty")
|
||||
->groupByRaw("COALESCE(c.STCKCTGRYDESC, 'Uncategorized')")
|
||||
->orderByDesc('revenue')
|
||||
->get();
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
public function supplierProducts(string $supplierCode): JsonResponse
|
||||
{
|
||||
$products = DB::table('PL_BILLTRAN as il')
|
||||
->join('PL_BILL as i', 'il.REFNO', '=', 'i.REFNO')
|
||||
->join('STK_STOCKITEM as p', 'il.STOCKCODE', '=', 'p.STOCKCODE')
|
||||
->where('i.ACCNO', $supplierCode)
|
||||
->selectRaw('il.STOCKCODE as code, p.DESCRIPTION as name, SUM(il.QTYTOINVOICE) as total_qty, SUM(il.QTYTOINVOICE * il.COSTPRICE / il.COSTPRICEPER) as total_spend, COUNT(DISTINCT i.REFNO) as invoice_count')
|
||||
->groupBy('il.STOCKCODE', 'p.DESCRIPTION')
|
||||
->orderByDesc('total_spend')
|
||||
->limit(20)
|
||||
->get();
|
||||
|
||||
$timeline = DB::table('PL_BILLTRAN as il')
|
||||
->join('PL_BILL as i', 'il.REFNO', '=', 'i.REFNO')
|
||||
->where('i.ACCNO', $supplierCode)
|
||||
->selectRaw("DATE_FORMAT(i.DOCDTETME, '%Y-%m') as month, SUM(il.QTYTOINVOICE * il.COSTPRICE / il.COSTPRICEPER) as total_spend")
|
||||
->whereNotNull('i.DOCDTETME')
|
||||
->groupByRaw("DATE_FORMAT(i.DOCDTETME, '%Y-%m')")
|
||||
->orderBy('month')
|
||||
->get();
|
||||
|
||||
$supplierName = DB::table('PL_SUPPLIERACCOUNT')
|
||||
->where('SUPLCDE', $supplierCode)
|
||||
->value('SUPLNME');
|
||||
|
||||
return response()->json([
|
||||
'supplier_code' => $supplierCode,
|
||||
'supplier_name' => $supplierName,
|
||||
'products' => $products,
|
||||
'timeline' => $timeline,
|
||||
]);
|
||||
}
|
||||
}
|
||||
16
src/app/Models/Category.php
Normal file
16
src/app/Models/Category.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
protected $table = 'STK_STOCKCATEGORY';
|
||||
public $timestamps = false;
|
||||
|
||||
public function products()
|
||||
{
|
||||
return $this->hasMany(Product::class, 'CATEGORY', 'STCKCTGRYCDE');
|
||||
}
|
||||
}
|
||||
16
src/app/Models/Customer.php
Normal file
16
src/app/Models/Customer.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Customer extends Model
|
||||
{
|
||||
protected $table = 'SL_CUSTOMERACCOUNT';
|
||||
public $timestamps = false;
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(CustomerInvoice::class, 'ACCNO', 'DBTRCDE');
|
||||
}
|
||||
}
|
||||
25
src/app/Models/CustomerInvoice.php
Normal file
25
src/app/Models/CustomerInvoice.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CustomerInvoice extends Model
|
||||
{
|
||||
protected $table = 'SL_SALESINVOICE';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $casts = [
|
||||
'DOCDTETME' => 'datetime',
|
||||
];
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'ACCNO', 'DBTRCDE');
|
||||
}
|
||||
|
||||
public function lines()
|
||||
{
|
||||
return $this->hasMany(CustomerInvoiceLine::class, 'REFNO', 'REFNO');
|
||||
}
|
||||
}
|
||||
27
src/app/Models/CustomerInvoiceLine.php
Normal file
27
src/app/Models/CustomerInvoiceLine.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CustomerInvoiceLine extends Model
|
||||
{
|
||||
protected $table = 'SL_SALESINVOICETRAN';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $casts = [
|
||||
'QTYTOINVOICE' => 'decimal:2',
|
||||
'SELLINGPRICE' => 'decimal:2',
|
||||
'COSTPRICE' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(CustomerInvoice::class, 'REFNO', 'REFNO');
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class, 'STOCKCODE', 'STOCKCODE');
|
||||
}
|
||||
}
|
||||
31
src/app/Models/Product.php
Normal file
31
src/app/Models/Product.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
protected $table = 'STK_STOCKITEM';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $casts = [
|
||||
'COSTPRICE' => 'decimal:2',
|
||||
'SELLINGPRICE1' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(Category::class, 'CATEGORY', 'STCKCTGRYCDE');
|
||||
}
|
||||
|
||||
public function supplierInvoiceLines()
|
||||
{
|
||||
return $this->hasMany(SupplierInvoiceLine::class, 'STOCKCODE', 'STOCKCODE');
|
||||
}
|
||||
|
||||
public function customerInvoiceLines()
|
||||
{
|
||||
return $this->hasMany(CustomerInvoiceLine::class, 'STOCKCODE', 'STOCKCODE');
|
||||
}
|
||||
}
|
||||
16
src/app/Models/Supplier.php
Normal file
16
src/app/Models/Supplier.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Supplier extends Model
|
||||
{
|
||||
protected $table = 'PL_SUPPLIERACCOUNT';
|
||||
public $timestamps = false;
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(SupplierInvoice::class, 'ACCNO', 'SUPLCDE');
|
||||
}
|
||||
}
|
||||
25
src/app/Models/SupplierInvoice.php
Normal file
25
src/app/Models/SupplierInvoice.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SupplierInvoice extends Model
|
||||
{
|
||||
protected $table = 'PL_BILL';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $casts = [
|
||||
'DOCDTETME' => 'datetime',
|
||||
];
|
||||
|
||||
public function supplier()
|
||||
{
|
||||
return $this->belongsTo(Supplier::class, 'ACCNO', 'SUPLCDE');
|
||||
}
|
||||
|
||||
public function lines()
|
||||
{
|
||||
return $this->hasMany(SupplierInvoiceLine::class, 'REFNO', 'REFNO');
|
||||
}
|
||||
}
|
||||
26
src/app/Models/SupplierInvoiceLine.php
Normal file
26
src/app/Models/SupplierInvoiceLine.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SupplierInvoiceLine extends Model
|
||||
{
|
||||
protected $table = 'PL_BILLTRAN';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $casts = [
|
||||
'QTYTOINVOICE' => 'decimal:2',
|
||||
'COSTPRICE' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(SupplierInvoice::class, 'REFNO', 'REFNO');
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class, 'STOCKCODE', 'STOCKCODE');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user