398 lines
16 KiB
PHP
398 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\PurchaseInvoice;
|
|
use App\Models\PurchaseInvoiceItem;
|
|
use App\Models\PurchaseInvoiceItemTax;
|
|
use App\Models\User;
|
|
use App\Models\Warehouse;
|
|
use App\Http\Requests\StorePurchaseInvoiceRequest;
|
|
use App\Http\Requests\UpdatePurchaseInvoiceRequest;
|
|
use Workdo\ProductService\Models\ProductServiceItem;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Inertia\Inertia;
|
|
use App\Events\CreatePurchaseInvoice;
|
|
use App\Events\UpdatePurchaseInvoice;
|
|
use App\Events\DestroyPurchaseInvoice;
|
|
use App\Events\PostPurchaseInvoice;
|
|
use App\Events\EditPurchaseInvoice;
|
|
|
|
|
|
class PurchaseInvoiceController extends Controller
|
|
{
|
|
private function checkInvoiceAccess(PurchaseInvoice $purchaseInvoice)
|
|
{
|
|
if(Auth::user()->can('manage-any-purchase-invoices')) {
|
|
return true;
|
|
} elseif(Auth::user()->can('manage-own-purchase-invoices')) {
|
|
if($purchaseInvoice->creator_id != Auth::id() && $purchaseInvoice->vendor_id != Auth::id()) {
|
|
return false;
|
|
}
|
|
if($purchaseInvoice->creator_id != Auth::id() && Auth::user()->type == 'vendor' && $purchaseInvoice->status == 'draft') {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
public function index(Request $request)
|
|
{
|
|
if(Auth::user()->can('manage-purchase-invoices')){
|
|
$query = PurchaseInvoice::with(['vendor', 'items'])
|
|
->where(function($q) {
|
|
if(Auth::user()->can('manage-any-purchase-invoices')) {
|
|
$q->where('created_by', creatorId());
|
|
} elseif(Auth::user()->can('manage-own-purchase-invoices')) {
|
|
$q->where('creator_id', Auth::id())->orWhere('vendor_id',Auth::id());
|
|
if(Auth::user()->type == 'vendor') {
|
|
$q->where('status','!=', 'draft');
|
|
}
|
|
} else {
|
|
$q->whereRaw('1 = 0');
|
|
}
|
|
});
|
|
|
|
// Apply filters
|
|
if ($request->vendor_id) {
|
|
$query->where('vendor_id', $request->vendor_id);
|
|
}
|
|
if ($request->warehouse_id) {
|
|
$query->where('warehouse_id', $request->warehouse_id);
|
|
}
|
|
if ($request->status) {
|
|
if ($request->status === 'overdue') {
|
|
$query->where('due_date', '<', now())
|
|
->whereIn('status', ['posted', 'partial'])
|
|
->where('balance_amount', '>', 0);
|
|
} else {
|
|
$query->where('status', $request->status);
|
|
}
|
|
}
|
|
if ($request->search) {
|
|
$query->where('invoice_number', 'like', '%' . $request->search . '%');
|
|
}
|
|
if ($request->date_range) {
|
|
$dates = explode(' - ', $request->date_range);
|
|
if (count($dates) === 2) {
|
|
$query->whereBetween('invoice_date', [$dates[0], $dates[1]]);
|
|
}
|
|
}
|
|
|
|
// Apply sorting
|
|
$sortField = $request->get('sort', 'created_at');
|
|
$sortDirection = $request->get('direction', 'desc');
|
|
|
|
// Validate sort field to prevent SQL injection
|
|
$allowedSortFields = ['invoice_number', 'invoice_date', 'due_date', 'subtotal', 'tax_amount', 'total_amount', 'balance_amount', 'status', 'created_at'];
|
|
if (!in_array($sortField, $allowedSortFields) || empty($sortField)) {
|
|
$sortField = 'created_at';
|
|
}
|
|
|
|
$query->orderBy($sortField, $sortDirection);
|
|
|
|
$perPage = $request->get('per_page', 10);
|
|
$invoices = $query->paginate($perPage);
|
|
$vendors = User::where('type', 'vendor')->select('id', 'name', 'email')->where('created_by', creatorId())->get();
|
|
$warehouses = Warehouse::where('is_active', true)->select('id', 'name')->where('created_by', creatorId())->get();
|
|
|
|
return Inertia::render('Purchase/Index', [
|
|
'invoices' => $invoices,
|
|
'vendors' => $vendors,
|
|
'warehouses' => $warehouses,
|
|
'filters' => $request->only(['vendor_id', 'warehouse_id', 'status', 'search', 'date_range'])
|
|
]);
|
|
}
|
|
else{
|
|
return back()->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
public function create()
|
|
{
|
|
if(Auth::user()->can('create-purchase-invoices')){
|
|
$vendors = User::where('type', 'vendor')->select('id', 'name', 'email')->where('created_by', creatorId())->get();
|
|
$products = ProductServiceItem::select('id', 'name', 'sku', 'purchase_price', 'tax_ids', 'unit', 'type')
|
|
->where('is_active', true)->where('created_by', creatorId())
|
|
->get()
|
|
->map(function ($product) {
|
|
return [
|
|
'id' => $product->id,
|
|
'name' => $product->name,
|
|
'sku' => $product->sku,
|
|
'purchase_price' => $product->purchase_price,
|
|
'unit' => $product->unit,
|
|
'type' => $product->type,
|
|
'taxes' => $product->taxes->map(function ($tax) {
|
|
return [
|
|
'id' => $tax->id,
|
|
'tax_name' => $tax->tax_name,
|
|
'rate' => $tax->rate
|
|
];
|
|
})
|
|
];
|
|
});
|
|
|
|
$warehouses = Warehouse::where('is_active', true)->select('id', 'name', 'address')->where('created_by', creatorId())->get();
|
|
|
|
return Inertia::render('Purchase/Create', [
|
|
'vendors' => $vendors,
|
|
'products' => $products,
|
|
'warehouses' => $warehouses,
|
|
]);
|
|
}
|
|
else{
|
|
return back()->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
public function store(StorePurchaseInvoiceRequest $request)
|
|
{
|
|
if(Auth::user()->can('create-purchase-invoices')){
|
|
$totals = $this->calculateTotals($request->items);
|
|
|
|
$invoice = new PurchaseInvoice();
|
|
$invoice->invoice_date = $request->invoice_date;
|
|
$invoice->due_date = $request->due_date;
|
|
$invoice->vendor_id = $request->vendor_id;
|
|
$invoice->warehouse_id = $request->warehouse_id;
|
|
$invoice->payment_terms = $request->payment_terms;
|
|
$invoice->notes = $request->notes;
|
|
$invoice->subtotal = $totals['subtotal'];
|
|
$invoice->tax_amount = $totals['tax_amount'];
|
|
$invoice->discount_amount = $totals['discount_amount'];
|
|
$invoice->total_amount = $totals['total_amount'];
|
|
$invoice->balance_amount = $totals['total_amount'];
|
|
$invoice->creator_id = Auth::id();
|
|
$invoice->created_by = creatorId();
|
|
$invoice->save();
|
|
|
|
// Create invoice items
|
|
$this->createInvoiceItems($invoice->id, $request->items);
|
|
|
|
try {
|
|
CreatePurchaseInvoice::dispatch($request, $invoice);
|
|
} catch (\Throwable $th) {
|
|
return back()->with('error', $th->getMessage());
|
|
}
|
|
|
|
return redirect()->route('purchase-invoices.index')->with('success', __('The purchase invoice has been created successfully.'));
|
|
|
|
}
|
|
else{
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
public function show(PurchaseInvoice $purchaseInvoice)
|
|
{
|
|
if(Auth::user()->can('view-purchase-invoices') && $purchaseInvoice->created_by == creatorId()){
|
|
if(!$this->checkInvoiceAccess($purchaseInvoice)) {
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Permission denied'));
|
|
}
|
|
|
|
$purchaseInvoice->load(['vendor', 'vendorDetails', 'items.product', 'items.taxes', 'warehouse']);
|
|
|
|
return Inertia::render('Purchase/View', [
|
|
'invoice' => $purchaseInvoice
|
|
]);
|
|
}
|
|
else{
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
public function edit(PurchaseInvoice $purchaseInvoice)
|
|
{
|
|
if(Auth::user()->can('edit-purchase-invoices') && $purchaseInvoice->created_by == creatorId()){
|
|
if(!$this->checkInvoiceAccess($purchaseInvoice)) {
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Permission denied'));
|
|
}
|
|
|
|
if ($purchaseInvoice->status != 'draft') {
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Cannot update posted invoice.'));
|
|
}
|
|
|
|
$purchaseInvoice->load(['items.taxes']);
|
|
|
|
EditPurchaseInvoice::dispatch($purchaseInvoice);
|
|
|
|
$vendors = User::where('type', 'vendor')->select('id', 'name', 'email')->where('created_by', creatorId())->get();
|
|
$products = ProductServiceItem::select('id', 'name', 'sku', 'purchase_price', 'tax_ids', 'unit', 'type')
|
|
->where('is_active', true)->where('created_by', creatorId())
|
|
->get()
|
|
->map(function ($product) {
|
|
return [
|
|
'id' => $product->id,
|
|
'name' => $product->name,
|
|
'sku' => $product->sku,
|
|
'purchase_price' => $product->purchase_price,
|
|
'unit' => $product->unit,
|
|
'type' => $product->type,
|
|
'taxes' => $product->taxes->map(function ($tax) {
|
|
return [
|
|
'id' => $tax->id,
|
|
'tax_name' => $tax->tax_name,
|
|
'rate' => $tax->rate
|
|
];
|
|
})
|
|
];
|
|
});
|
|
|
|
$warehouses = Warehouse::where('is_active', true)->select('id', 'name', 'address')->where('created_by', creatorId())->get();
|
|
|
|
return Inertia::render('Purchase/Edit', [
|
|
'invoice' => $purchaseInvoice,
|
|
'vendors' => $vendors,
|
|
'products' => $products,
|
|
'warehouses' => $warehouses,
|
|
]);
|
|
}
|
|
else{
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
public function update(UpdatePurchaseInvoiceRequest $request, PurchaseInvoice $purchaseInvoice)
|
|
{
|
|
if(Auth::user()->can('edit-purchase-invoices') && $purchaseInvoice->created_by == creatorId()){
|
|
if ($purchaseInvoice->status != 'draft') {
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Cannot update posted invoice.'));
|
|
}
|
|
$totals = $this->calculateTotals($request->items);
|
|
|
|
$purchaseInvoice->invoice_date = $request->invoice_date;
|
|
$purchaseInvoice->due_date = $request->due_date;
|
|
$purchaseInvoice->vendor_id = $request->vendor_id;
|
|
$purchaseInvoice->warehouse_id = $request->warehouse_id;
|
|
$purchaseInvoice->payment_terms = $request->payment_terms;
|
|
$purchaseInvoice->notes = $request->notes;
|
|
$purchaseInvoice->subtotal = $totals['subtotal'];
|
|
$purchaseInvoice->tax_amount = $totals['tax_amount'];
|
|
$purchaseInvoice->discount_amount = $totals['discount_amount'];
|
|
$purchaseInvoice->total_amount = $totals['total_amount'];
|
|
$purchaseInvoice->balance_amount = $totals['total_amount'];
|
|
$purchaseInvoice->save();
|
|
|
|
// Delete existing items and recreate
|
|
$purchaseInvoice->items()->delete();
|
|
$this->createInvoiceItems($purchaseInvoice->id, $request->items);
|
|
|
|
// Dispatch event for packages to handle their fields
|
|
UpdatePurchaseInvoice::dispatch($request, $purchaseInvoice);
|
|
|
|
return redirect()->route('purchase-invoices.index')->with('success', __('The purchase invoice details are updated successfully.'));
|
|
}
|
|
else{
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
public function destroy(PurchaseInvoice $purchaseInvoice)
|
|
{
|
|
if(Auth::user()->can('delete-purchase-invoices')){
|
|
if ($purchaseInvoice->status === 'posted') {
|
|
return back()->withErrors(['error' => __('Cannot delete posted invoice.')]);
|
|
}
|
|
|
|
// Dispatch event before deletion
|
|
DestroyPurchaseInvoice::dispatch($purchaseInvoice);
|
|
|
|
$purchaseInvoice->delete();
|
|
|
|
return redirect()->route('purchase-invoices.index')->with('success', __('The purchase invoice has been deleted.'));
|
|
}
|
|
else{
|
|
return redirect()->route('purchase-invoices.index')->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
private function calculateTotals($items)
|
|
{
|
|
$subtotal = 0;
|
|
$totalTax = 0;
|
|
$totalDiscount = 0;
|
|
|
|
foreach ($items as $item) {
|
|
$lineTotal = $item['quantity'] * $item['unit_price'];
|
|
$discountAmount = ($lineTotal * ($item['discount_percentage'] ?? 0)) / 100;
|
|
$afterDiscount = $lineTotal - $discountAmount;
|
|
$taxAmount = ($afterDiscount * ($item['tax_percentage'] ?? 0)) / 100;
|
|
|
|
$subtotal += $lineTotal;
|
|
$totalDiscount += $discountAmount;
|
|
$totalTax += $taxAmount;
|
|
}
|
|
|
|
return [
|
|
'subtotal' => $subtotal,
|
|
'tax_amount' => $totalTax,
|
|
'discount_amount' => $totalDiscount,
|
|
'total_amount' => $subtotal + $totalTax - $totalDiscount
|
|
];
|
|
}
|
|
|
|
private function createInvoiceItems($invoiceId, $items)
|
|
{
|
|
foreach ($items as $itemData) {
|
|
$item = new PurchaseInvoiceItem();
|
|
$item->invoice_id = $invoiceId;
|
|
$item->product_id = $itemData['product_id'];
|
|
$item->quantity = $itemData['quantity'];
|
|
$item->unit_price = $itemData['unit_price'];
|
|
$item->discount_percentage = $itemData['discount_percentage'] ?? 0;
|
|
$item->tax_percentage = $itemData['tax_percentage'] ?? 0;
|
|
$item->save();
|
|
|
|
// Store individual taxes
|
|
if (isset($itemData['taxes']) && is_array($itemData['taxes'])) {
|
|
foreach ($itemData['taxes'] as $tax) {
|
|
$purchaseInvoiceItemTax = new PurchaseInvoiceItemTax();
|
|
$purchaseInvoiceItemTax->item_id = $item->id;
|
|
$purchaseInvoiceItemTax->tax_name = $tax['tax_name'];
|
|
$purchaseInvoiceItemTax->tax_rate = $tax['tax_rate'] ?? $tax['rate'] ?? 0;
|
|
$purchaseInvoiceItemTax->save();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function post(PurchaseInvoice $purchaseInvoice)
|
|
{
|
|
if(Auth::user()->can('post-purchase-invoices')){
|
|
if ($purchaseInvoice->status !== 'draft') {
|
|
return back()->withErrors(['error' => __('Only draft invoices can be posted.')]);
|
|
}
|
|
|
|
try {
|
|
PostPurchaseInvoice::dispatch($purchaseInvoice);
|
|
} catch (\Throwable $th) {
|
|
return back()->with('error', $th->getMessage());
|
|
}
|
|
|
|
$purchaseInvoice->update(['status' => 'posted']);
|
|
|
|
return back()->with('success', __('The purchase invoice has been posted successfully.'));
|
|
}
|
|
else{
|
|
return back()->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
|
|
public function print(PurchaseInvoice $purchaseInvoice)
|
|
{
|
|
if(Auth::user()->can('print-purchase-invoices')){
|
|
$purchaseInvoice->load(['vendor', 'vendorDetails', 'items.product', 'items.taxes', 'warehouse']);
|
|
|
|
return Inertia::render('Purchase/Print', [
|
|
'invoice' => $purchaseInvoice
|
|
]);
|
|
}
|
|
else{
|
|
return back()->with('error', __('Permission denied'));
|
|
}
|
|
}
|
|
}
|