File "PurchaseOrderApiController.php"

Full Path: /home/clickysoft/public_html/jmapi5.clickysoft.net/app/Http/Controllers/Api/V1/Admin/PurchaseOrderApiController.php
File size: 25.83 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace App\Http\Controllers\Api\V1\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\PurchaseOrderReportRequest;
use App\Http\Requests\Admin\StorePurchaseOrderRequest;
use App\Http\Requests\Admin\UpdatePurchaseOrderAdminNotesRequest;
use App\Http\Resources\Admin\PurchaseOrderDetailsResource;
use App\Http\Resources\Admin\PurchaseOrderResource;
use App\Mail\PurchaseOrderMail;
use App\Models\OfficeSupplies;
use App\Models\Order;
use App\Models\OrderItems;
use App\Models\Product;
use App\Models\ProductPrice;
use App\Models\ProductVariationCombination;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderDetails;
use App\Models\PurchaseOrderOrderDetails;
use App\Models\PurchaseOrderToCreate;
use App\Models\SiteSetting;
use App\Models\Status;
use App\Models\Variation;
use App\Models\Vendor;
use App\Notifications\OrderStatusUpdatedNotification;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\Response;

class PurchaseOrderApiController extends Controller
{
    public function index(Request $request)
    {
        abort_if(Gate::denies('purchase_order_access'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        $request->validate([
            'report_type' => 'nullable|in:' . implode(',', array_keys(PurchaseOrder::REPORTS_MODE))
        ]);

        $report_scope = PurchaseOrder::REPORTS_MODE[$request->get('report_type') ?? "current_year"];
        $purchase_orders = PurchaseOrder::when($request->filled('order_number'), function ($query) use ($request) {
            $query->where('order_number', 'like', "%{$request->get('order_number')}%");
        })->when($request->filled('status'), function ($query) use ($request) {
            $query->where('status', 'like', "%{$request->get('status')}%");
        })->when($request->filled('vendor'), function ($query) use ($request) {
            $query->whereHas('vendor', function ($query) use ($request) {
                $query->where('name', 'like', "%{$request->get('vendor')}%");
                $query->orWhere('id', $request->get('category'));
            });
        })->when($request->filled('product_number'), function ($query) use ($request) {
            $query->whereHas('orderDetails', function ($query) use ($request) {
                $query->where('product_number', 'like', "%{$request->get('product_number')}%");
            });
        })->$report_scope()
            ->orderBy('created_at', 'DESC')->paginate(50);

        $total_amount = $purchase_orders->sum('total_price');

        PurchaseOrderResource::withoutWrapping();
        return PurchaseOrderResource::collection($purchase_orders)->additional(["purchase_orders_total" => "$" . number_format($total_amount, 2)]);
    }

    public function get_purchase_order_items()
    {
        abort_if(Gate::denies('purchase_order_access'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        /*$data = PurchaseOrderToCreate::select('*', DB::raw('SUM(quantity) as total_quantity'))
            ->groupBy('product_id')
            ->groupBy('price_id')
            ->get();*/

        $data = PurchaseOrderToCreate::select('product_id', 'price_id', DB::raw('SUM(quantity) as total_quantity'))
            ->groupBy('price_id')
            ->get();

        return \response()->json(['data' => $this->getPurchaseItemsToCreate($data)], Response::HTTP_OK);
    }

    public function get_shipping_speed()
    {
        return \response()
            ->json(['data' => PurchaseOrder::SHIPPING_SPEED], Response::HTTP_OK);
    }

    public function get_po_statuses()
    {
        return \response()
            ->json(['data' => PurchaseOrder::PURCHASE_ORDER_STATUS], Response::HTTP_OK);
    }

    public function get_payment_terms()
    {
        return \response()
            ->json(['data' => PurchaseOrder::PAYMENT_TERMS], Response::HTTP_OK);
    }

    public function get_po_addresses()
    {
        $shipping_address = SiteSetting::where('key', 'Shipping Address')->first();
        $billing_address = SiteSetting::where('key', 'Billing Address')->first();
        return \response()
            ->json(['data' => [
                'shipping_address' => $shipping_address->value ?? '',
                'billing_address' => $billing_address->value ?? '',
            ]], Response::HTTP_OK);
    }

    public function change_po_status(Request $request, PurchaseOrder $purchaseOrder)
    {
        abort_if(Gate::denies('purchase_order_access'), Response::HTTP_FORBIDDEN, '403 Forbidden');
        if ($purchaseOrder->status == 'Received') {
            return \response()->json([
                'message' => 'Purchase order already marked as received.',
                'errors' => ['error' => ['Purchase order already marked as received']],
            ])
                ->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
        } else {
            /*$request->validate([
                'stock_location_id' => 'required|exists:stock_locations,id',
            ]);*/

            $purchaseOrder->status = 'Received';
            //            $purchaseOrder->stock_location_id = $request->get('stock_location_id');
            $purchaseOrder->save();

            return (new PurchaseOrderResource($purchaseOrder))
                ->response()
                ->setStatusCode(Response::HTTP_CREATED);
        }
    }

    public function store(StorePurchaseOrderRequest $request)
    {
        try {
            DB::beginTransaction();

            foreach ($request->products ?? [] as $product) {

                if ($product['product_type'] == 'configurable' && $product['variation_id'] != null) {
                    $product_exists = Variation::find($product['variation_id']);
                } else {
                    $product_exists = Product::find($product['product_id']);
                }
                $total_product_quantity = 0;

                foreach ($product['orders'] as $p) {
                    if (isset($p['quantity'])) {
                        $total_product_quantity += (int) $p['quantity'];
                    }

                    if (isset($p['extra_quantity'])) {
                        $total_product_quantity += (int) $p['extra_quantity'];
                    }
                }

                $product['total_quantity'] = $total_product_quantity;

                if ($product_exists) {

                    //Here we will get vendor depending on product type
                    $vendor_id = $product_exists->vendor_id;

                    if (isset($data[$vendor_id])) {
                        $data[$vendor_id]['products'][] = $product;
                    } else {
                        $data[$vendor_id] = [
                            'ordered_by_id' => auth()->id(),
                            'order_number' => 00,
                            'payment_terms' => $request->payment_terms,
                            'reference' => $request->reference,
                            'special_notes' => $request->special_notes,
                            'shipping_address' => $request->shipping_address,
                            'billing_address' => $request->billing_address ?? 'N/A',
                            'vendor_id' => $vendor_id,
                            'status' => 'Pending',
                            'products' => [$product],
                        ];
                    }
                }
            }

            //Handle office supplies products
            foreach ($request->office_supplies ?? [] as $office_supply) {
                $office_supply['product_type'] = "office_supplies";

                if (isset($data[$office_supply['vendor_id']])) {
                    $data[$office_supply['vendor_id']]['office_products'][] = $office_supply;
                } else {
                    $data[$office_supply['vendor_id']] = [
                        'ordered_by_id' => auth()->id(),
                        'order_number' => 00,
                        'payment_terms' => $request->payment_terms,
                        'reference' => $request->reference,
                        'special_notes' => $request->special_notes,
                        'shipping_address' => $request->shipping_address,
                        'billing_address' => $request->billing_address ?? 'N/A',
                        'vendor_id' => $office_supply['vendor_id'],
                        'status' => 'Pending',
                        'office_products' => [$office_supply],
                    ];
                }
            }

            $data = $this->preparePurchaseOrder($data ?? []);
            $this->deletePurchaseOrderItemsCreated($request->products ?? []);
            DB::commit();
            PurchaseOrderResource::withoutWrapping();
            return (PurchaseOrderResource::collection($data ?? []))
                ->response()
                ->setStatusCode(Response::HTTP_CREATED);
        } catch (\Exception $e) {
            Log::channel('db_errors')->info('Purchase Order Creation Error');
            Log::channel('db_errors')->info($e->getMessage() . " at line : " . $e->getLine());
            return response()
                ->json([
                    'message' => "Can not create purchase order.",
                    'errors' => ["error" => ["Unable to create purchase order."]]
                ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    public function show(PurchaseOrder $purchaseOrder)
    {
        abort_if(Gate::denies('purchase_order_show'), Response::HTTP_FORBIDDEN, '403 Forbidden');
        return (new PurchaseOrderDetailsResource($purchaseOrder))
            ->response()
            ->setStatusCode(Response::HTTP_CREATED);
    }

    public function update(Request $request, PurchaseOrder $purchaseOrder)
    {
        //
    }

    public function editAdminNotes(UpdatePurchaseOrderAdminNotesRequest $request, PurchaseOrder $purchaseOrder)
    {
        $purchaseOrder->admin_notes = $request->admin_notes;
        $purchaseOrder->save();
        return response()
            ->json(['message' => 'Admin notes updated successfully'], Response::HTTP_OK);
    }

    public function destroy(PurchaseOrder $purchaseOrder)
    {
        abort_if(Gate::denies('purchase_order_delete'), Response::HTTP_FORBIDDEN, '403 Forbidden');
        abort_if($purchaseOrder->status != 'Received', Response::HTTP_FORBIDDEN, 'Can not delete pending purchase orders');

        try {
            $purchaseOrder->delete();
            return response()
                ->json(['message' => 'Purchase order deleted successfully'], Response::HTTP_OK);
        } catch (\Exception $e) {
            Log::channel('db_errors')->info('Record Deletion Error : Purchase Order -> ' . $purchaseOrder->id);
            Log::channel('db_errors')->info($e->getMessage());
            return response()
                ->json([
                    'message' => "Record not deleted.",
                    'errors' => ["error" => ["Unable to delete purchase order."]]
                ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }

    public function preparePurchaseOrder($data)
    {
        $mail_data = $purchase_orders = [];
        foreach ($data ?? [] as $datum) {
            $total_quantity = $total_amount = 0;
            $office_products_total_quantity = $office_products_total_amount = 0;

            $vendor = Vendor::find($datum['vendor_id']);
            $purchase_orders[] = $purchase_order = PurchaseOrder::create($datum);
            foreach ($datum['products'] ?? [] as $product) {
                $product_price = ProductPrice::find($product['price_id']);
                $variation_price = Variation::find($product['variation_id']);

                if ($variation_price) {
                    $po_details[] = $po_detail = [
                        'purchase_order_id' => $purchase_order->id,
                        'product_id' => $variation_price->id,
                        'product_number' => $variation_price->sku,
                        'price_id' => $product_price->id,
                        'quantity' => $product['total_quantity'],
                        'product_type' => $product['product_type'],
                        'price' => $variation_price->vendor_price,
                        'total_price' => $product['total_quantity'] * $variation_price->vendor_price,
                    ];
                } else {
                    $po_details[] = $po_detail = [
                        'purchase_order_id' => $purchase_order->id,
                        'product_id' => $product['product_id'],
                        'product_number' => $product_price->supplier_prod_number,
                        'price_id' => $product_price->id,
                        'quantity' => $product['total_quantity'],
                        'product_type' => $product['product_type'],
                        'price' => $product_price->vendor_price,
                        'total_price' => $product['total_quantity'] * $product_price->vendor_price,
                    ];
                }
                $purchase_order_details = PurchaseOrderDetails::create($po_detail);
                $total_quantity += $po_detail['quantity'];
                $total_amount += $po_detail['quantity'] * $po_detail['price'];

                foreach ($product['orders'] as $po_order) {
                    $po_order_data = [
                        'purchase_order_details_id' => $purchase_order_details->id
                    ];

                    if (isset($po_order['order_id'])) {
                        $po_order_data['order_id'] = $po_order['order_id'];
                        $po_order_data['quantity'] = $po_order['quantity'];
                    }

                    if (isset($po_order['extra_quantity'])) {
                        $po_order_data['order_id'] = 0;
                        $po_order_data['quantity'] = $po_order['extra_quantity'];
                    }

                    PurchaseOrderOrderDetails::create($po_order_data);
                }
            }

            foreach ($datum['office_products'] ?? [] as $office_product) {
                $db_office_product = OfficeSupplies::find($office_product['product_id']);
                $os_details[] = $os_detail = [
                    'purchase_order_id' => $purchase_order->id,
                    'product_id' => $office_product['product_id'],
                    'product_number' => $db_office_product->sku,
                    'quantity' => $office_product['quantity'],
                    'price' => $db_office_product->price,
                    'product_type' => $office_product['product_type'],
                    'total_price' => $office_product['quantity'] * $db_office_product->price,
                ];

                $office_products_total_quantity += $office_product['quantity'];
                $office_products_total_amount += $office_product['quantity'] * $db_office_product->price;

                PurchaseOrderDetails::create($os_detail);
            }

            $purchase_order->total_quantity = $total_quantity + $office_products_total_quantity;
            $purchase_order->total_price = $total_amount + $office_products_total_amount;

            $purchase_order->save();

            //Update PO number
            $purchase_order->order_number = (new Order)->str_random($purchase_order->id);
            $purchase_order->save();

            $mail_data['p_order'] = $purchase_order;
            $mail_data['account_number'] = $vendor->account_number;
            $mail_data['p_order_products'] = $po_details ?? [];
            $mail_data['os_products'] = $os_details ?? [];
            $mail_data['secondary_email'] = $vendor->secondary_email;

            $path = $purchase_order->generateAttachment($mail_data);
            $mail_data['attachment_path'] = $path;

            //Send Mail to each vendor
            try {
                Mail::to($vendor->email)->send(new PurchaseOrderMail($mail_data));
            } catch (\Exception $e) {
                Log::channel('info_errors')->info('Mail Error');
                Log::channel('info_errors')->info($e->getMessage());
            }
        }
        return $purchase_orders;
    }

    public function getPurchaseItemsToCreate($data)
    {
        $products = [];
        foreach ($data as $datum) {
            $orders = [];
            $vs = ProductVariationCombination::where('product_price_id', $datum->price_id)->get();
            $bd_product = Product::find($datum->product_id);
            $price_id = ProductPrice::find($datum->price_id);
            $variations = [];
            foreach ($vs ?? [] as $variation) {
                $variations[] = [
                    'type' => $variation->variation?->type,
                    'value' => $variation->variation?->value,
                ];
            }
            foreach ($datum->orders as $order_item) {
                $order = $order_item->order;
                if (isset($orders[$order->id])) {
                    $orders[$order->id]['quantity'] += $order_item->quantity;
                } else {
                    $orders[$order->id] = [
                        'id' => $order->id,
                        'order_number' => $order->order_number,
                        'quantity' => $order_item->quantity,
                    ];
                }
            }

            if ($bd_product->product_type == 'configurable') {
                $price_variations_ids = ProductVariationCombination::where('product_price_id', $datum->price_id)
                    ->get()
                    ->pluck('variation_id')
                    ->toArray();
                $price_variations = Variation::whereIn('id', $price_variations_ids)->get();

                foreach ($price_variations as $price_variation) {

                    $products[] = [
                        'product_type' => 'configurable',
                        'variation_id' => $price_variation->id,
                        'product_id' => $bd_product->id,
                        'price_id' => $datum->price_id,
                        'product_name' => $price_variation->value,
                        'vendor_name' => $price_variation->vendor->name ?? "",
                        'product_number' => $price_variation->sku,
                        'vendor_price' => $price_variation->vendor_price,
                        'total_quantity' => $datum->total_quantity,
                        'total_price' => '$' . number_format($price_variation->vendor_price * $datum->total_quantity, 2),
                        'featured_image' => ($image = $bd_product->featured_image) ? [
                            'url' => $image->url,
                            'preview' => $image->preview,
                            'thumbnail' => $image->thumbnail,
                        ] : '',
                        'variations' => $variations,
                        'orders' => $orders ?? [],
                    ];
                }
            } else {

                if ($price_id != null) {
                    $products[] = [
                        'product_type' => 'standard',
                        'variation_id' => null,
                        'product_id' => $bd_product->id,
                        'price_id' => $datum->price_id,
                        'product_name' => $bd_product->name,
                        'vendor_name' => $bd_product->vendor->name ?? "",
                        'product_number' => $price_id->supplier_prod_number,
                        'vendor_price' => $price_id->vendor_price,
                        'total_quantity' => $datum->total_quantity,
                        'total_price' => '$' . number_format($price_id->vendor_price * $datum->quantity, 2),
                        'featured_image' => ($image = $bd_product->featured_image) ? [
                            'url' => $image->url,
                            'preview' => $image->preview,
                            'thumbnail' => $image->thumbnail,
                        ] : '',
                        'variations' => $variations,
                        'orders' => $orders ?? [],
                    ];
                }
            }
        }

        return $products;
    }

    public function sendOrderChangeMail(Order $order, Status $product_ordered_status, Status $product_received_status): void
    {
        $data = [
            'order_number' => $order->order_number,
            'customer_name' => $order->user->name ?? '',
            'previous_order_status' => $product_ordered_status->name,
            'current_order_status' => $product_received_status->name,
            'notes' => null,
        ];

        $order->user->notify((new OrderStatusUpdatedNotification($data))->delay(now()->addSeconds(5)));
    }

    public function poReport(PurchaseOrderReportRequest $request)
    {

        $date_range = reportDateRange($request);
        $startYear = $date_range['year_from']->format('Y');
        $endYear = $date_range['year_to']->format('Y');

        $group_vendors = $request->get('group_vendors') ?? false;

        if ($group_vendors) {
            $data = DB::table('purchase_orders as p')
                ->select('vendors.name as vendor_name', DB::raw('SUM(p.total_quantity) as quantity'), DB::raw('SUM(p.total_price) as total'))
                ->join('vendors', 'p.vendor_id', '=', 'vendors.id')
                ->whereBetween('p.created_at', [$startYear . '-01-01', $endYear . '-12-31'])
                ->groupBy('vendor_id')
                ->get();

            return $this->generateGroupedReport($data, year_from: $startYear, year_to: $endYear);
        } else {
            $data = DB::table('purchase_orders as p')
                ->select('p.*', 'vendors.name as vendor_name', 'users.name as ordered_by')
                ->join('vendors', 'p.vendor_id', '=', 'vendors.id')
                ->join('users', 'p.ordered_by_id', '=', 'users.id')
                ->whereBetween('p.created_at', [$startYear . '-01-01', $endYear . '-12-31'])
                ->orderByDesc('created_at')
                ->get();

            return $this->generateNonGroupedReport($data, year_from: $startYear, year_to: $endYear);
        }
    }

    public function generateGroupedReport($reports, $year_from = "", $year_to = "")
    {

        $path = "storage/reports/yoy-grouped-report.pdf";
        if (!is_dir(public_path('storage/reports'))) {
            Storage::disk('public')->makeDirectory('reports');
        }
        Pdf::loadView('layouts.reports.yoy-grouped', compact('reports', 'year_from', 'year_to'))
            ->save(public_path($path));
        return \response()->json(['data' => asset($path)])->setStatusCode(Response::HTTP_OK);
    }

    public function generateNonGroupedReport($reports, $year_from = "", $year_to = "")
    {
        $report_filtered = [];
        $totals = [];
        foreach ($reports as $report) {
            $report_filtered[$report->vendor_name][] = [
                'id' => $report->id,
                'ordered_by' => $report->ordered_by,
                'order_number' => $report->order_number,
                'payment_terms' => $report->payment_terms,
                'reference' => $report->reference,
                'vendor_name' => $report->vendor_name,
                'quantity' => $report->total_quantity,
                'price' => $report->total_price,
                'created_at' => $report->created_at,
            ];
            isset($totals[$report->vendor_name]['total_quantity'])
                ? $totals[$report->vendor_name]['total_quantity'] += $report->total_quantity
                : $totals[$report->vendor_name]['total_quantity'] = $report->total_quantity;

            isset($totals[$report->vendor_name]['total_price'])
                ? $totals[$report->vendor_name]['total_price'] += $report->total_price
                : $totals[$report->vendor_name]['total_price'] = $report->total_price;
        }

        $path = "storage/reports/yoy-non-grouped-report.pdf";
        if (!is_dir(public_path('storage/reports'))) {
            Storage::disk('public')->makeDirectory('reports');
        }
        Pdf::loadView('layouts.reports.yoy-non-grouped', compact('report_filtered', 'totals', 'year_from', 'year_to'))
            ->setPaper('A4', 'landscape')
            ->save(public_path($path));
        return \response()->json(['data' => asset($path)])->setStatusCode(Response::HTTP_OK);
    }

    public function deletePurchaseOrderItemsCreated($products)
    {
        foreach ($products as $product) {
            foreach ($product['orders'] as $order) {
                if (isset($order['order_id'])) {

                    //Update PO flag in order items
                    OrderItems::where([
                        'product_id' => $product['product_id'],
                        'price_id' => $product['price_id'],
                        'order_id' => $order['order_id'],
                    ])->update([
                        'po_created' => 1,
                    ]);

                    PurchaseOrderToCreate::where([
                        'product_id' => $product['product_id'],
                        'price_id' => $product['price_id'],
                        'order_id' => $order['order_id'],
                    ])->delete();
                }
            }
        }
    }

    public function removePOItem(Request $request)
    {
        $request->validate([
            'product_id' => 'required',
            'price_id' => 'required',
            'order_id' => 'required',
        ]);

        //Update PO flag in order items
        OrderItems::where([
            'product_id' => $request->get('product_id'),
            'price_id' => $request->get('price_id'),
            'order_id' => $request->get('order_id'),
        ])->update([
            'po_created' => 1,
        ]);

        PurchaseOrderToCreate::where([
            'product_id' => $request->get('product_id'),
            'price_id' => $request->get('price_id'),
            'order_id' => $request->get('order_id'),
        ])->delete();

        return response()
            ->json(['message' => 'Purchase order item deleted successfully'], Response::HTTP_OK);
    }
}