diff --git a/app/Services/Report/ProfitLoss.php b/app/Services/Report/ProfitLoss.php index 2c506a1613f6..60b9a1485b1b 100644 --- a/app/Services/Report/ProfitLoss.php +++ b/app/Services/Report/ProfitLoss.php @@ -36,9 +36,15 @@ class ProfitLoss private float $credit = 0; + private float $credit_invoice = 0; + private float $credit_taxes = 0; - private array $expenses; + private array $invoice_payment_map = []; + + private array $expenses = []; + + private array $expense_break_down = []; private array $income_map; @@ -94,7 +100,7 @@ class ProfitLoss $this->filterInvoicePaymentIncome(); } - $this->expenseData(); + $this->expenseData()->buildExpenseBreakDown(); return $this; } @@ -119,6 +125,11 @@ class ProfitLoss return $this->expenses; } + public function getExpenseBreakDown() :array + { + return $this->expense_break_down; + } + private function filterIncome() { $invoices = $this->invoiceIncome(); @@ -139,17 +150,27 @@ class ProfitLoss private function filterInvoicePaymentIncome() { - $invoices = $this->invoicePaymentIncome(); + $this->paymentEloquentIncome(); - $this->income = 0; - $this->income_taxes = 0; - $this->income_map = $invoices; + foreach($this->invoice_payment_map as $map) { + $this->income += $map->amount_payment_paid_converted - $map->tax_amount_converted; + $this->income_taxes += $map->tax_amount_converted; - foreach($invoices as $invoice){ - $this->income += $invoice->net_converted_amount; - $this->income_taxes += $invoice->net_converted_taxes; + $this->credit += $map->amount_credit_paid_converted - $map->tax_amount_credit_converted; + $this->credit_taxes += $map->tax_amount_credit_converted; } + // $invoices = $this->invoicePaymentIncome(); + + // $this->income = 0; + // $this->income_taxes = 0; + // $this->income_map = $invoices; + + // foreach($invoices as $invoice){ + // $this->income += $invoice->net_converted_amount; + // $this->income_taxes += $invoice->net_converted_taxes; + // } + return $this; } @@ -214,8 +235,7 @@ class ProfitLoss private function paymentEloquentIncome() { - $amount_payment_paid = 0; - $amount_credit_paid = 0; + $this->invoice_payment_map = []; Payment::where('company_id', $this->company->id) ->whereIn('status_id', [1,4,5]) @@ -226,32 +246,65 @@ class ProfitLoss }) ->with(['company','client']) ->cursor() - ->each(function ($payment) use($amount_payment_paid, $amount_credit_paid){ + ->each(function ($payment){ $company = $payment->company; $client = $payment->client; - + + $map = new \stdClass; + $amount_payment_paid = 0; + $amount_credit_paid = 0; + $amount_payment_paid_converted = 0; + $amount_credit_paid_converted = 0; + $tax_amount = 0; + $tax_amount_converted = 0; + $tax_amount_credit = 0; + $tax_amount_credit_converted = $tax_amount_credit_converted = 0; + foreach($payment->paymentables as $pivot) { if($pivot->paymentable instanceOf \App\Models\Invoice){ + $invoice = $pivot->paymentable; + $amount_payment_paid += $pivot->amount - $pivot->refunded; - //calc tax amount - pro rata if necessary + $amount_payment_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1); + + $tax_amount += ($amount_payment_paid / $invoice->amount) * $invoice->total_taxes; + $tax_amount_converted += (($amount_payment_paid / $invoice->amount) * $invoice->total_taxes) / $payment->exchange_rate; + } if($pivot->paymentable instanceOf \App\Models\Credit){ $amount_credit_paid += $pivot->amount - $pivot->refunded; + $amount_credit_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1); + $tax_amount_credit += ($amount_payment_paid / $invoice->amount) * $invoice->total_taxes; + $tax_amount_credit_converted += (($amount_payment_paid / $invoice->amount) * $invoice->total_taxes) / $payment->exchange_rate; } } - }); - } + $map->amount_payment_paid = $amount_payment_paid; + $map->amount_payment_paid_converted = $amount_payment_paid_converted; + $map->tax_amount = $tax_amount; + $map->tax_amount_converted = $tax_amount_converted; + $map->amount_credit_paid = $amount_credit_paid; + $map->amount_credit_paid_converted = $amount_credit_paid_converted; + $map->tax_amount_credit = $tax_amount_credit; + $map->tax_amount_credit_converted = $tax_amount_credit_converted; + $map->currency_id = $payment->currency_id; + $this->invoice_payment_map[] = $map; + + }); + + return $this; + + } /** => [ @@ -272,6 +325,7 @@ class ProfitLoss ] */ + private function invoicePaymentIncome() { return \DB::select( \DB::raw(" @@ -332,31 +386,52 @@ class ProfitLoss ->cursor(); - return $this->calculateExpenses($expenses); - - } - - - private function calculateExpenses($expenses) - { - - $data = []; - + $this->expenses = []; foreach($expenses as $expense) { - $data[] = [ - 'total' => $expense->amount, - 'converted_total' => $converted_total = $this->getConvertedTotal($expense->amount, $expense->exchange_rate), - 'tax' => $tax = $this->getTax($expense), - 'net_converted_total' => $expense->uses_inclusive_taxes ? ( $converted_total - $tax ) : $converted_total, - 'category_id' => $expense->category_id, - 'category_name' => $expense->category ? $expense->category->name : "No Category Defined", - ]; + $map = new \stdClass; + + $map->total = $expense->amount; + $map->converted_total = $converted_total = $this->getConvertedTotal($expense->amount, $expense->exchange_rate); + $map->tax = $tax = $this->getTax($expense); + $map->net_converted_total = $expense->uses_inclusive_taxes ? ( $converted_total - $tax ) : $converted_total; + $map->category_id = $expense->category_id; + $map->category_name = $expense->category ? $expense->category->name : "No Category Defined"; + $map->currency_id = $expense->currency_id ?: $expense->company->settings->currency_id; + + $this->expenses[] = $map; } - $this->expenses = $data; + + return $this; + } + + private function buildExpenseBreakDown() + { + $data = []; + + foreach($this->expenses as $expense) + { + if(!array_key_exists($expense->category_id, $data)) + $data[$expense->category_id] = []; + + if(!array_key_exists('total', $data[$expense->category_id])) + $data[$expense->category_id]['total'] = 0; + + if(!array_key_exists('tax', $data[$expense->category_id])) + $data[$expense->category_id]['tax'] = 0; + + $data[$expense->category_id]['total'] = $data[$expense->category_id]['total'] + $expense->net_converted_total; + $data[$expense->category_id]['category_name'] = $expense->category_name; + $data[$expense->category_id]['tax'] = $data[$expense->category_id]['tax'] + $expense->tax; + + } + + $this->expense_break_down = $data; + + return $this; } diff --git a/tests/Feature/Export/ProfitAndLossReportTest.php b/tests/Feature/Export/ProfitAndLossReportTest.php index 5904c4227e31..05badb9b5309 100644 --- a/tests/Feature/Export/ProfitAndLossReportTest.php +++ b/tests/Feature/Export/ProfitAndLossReportTest.php @@ -15,11 +15,14 @@ use App\DataMapper\CompanySettings; use App\Factory\InvoiceFactory; use App\Models\Account; use App\Models\Client; +use App\Models\ClientContact; use App\Models\Company; +use App\Models\Expense; use App\Models\Invoice; use App\Models\User; use App\Services\Report\ProfitLoss; use App\Utils\Traits\MakesHash; +use Database\Factories\ClientContactFactory; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Support\Facades\Storage; use League\Csv\Writer; @@ -273,6 +276,10 @@ class ProfitAndLossReportTest extends TestCase 'settings' => $settings, ]); + $contact = ClientContact::factory()->create([ + 'client_id' => $client->id + ]); + $i = Invoice::factory()->create([ 'client_id' => $client->id, 'user_id' => $this->user->id, @@ -304,4 +311,57 @@ class ProfitAndLossReportTest extends TestCase $this->account->delete(); } + + public function testSimpleExpense() + { + $this->buildData(); + + $e = Expense::factory()->create([ + 'amount' => 10, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'date' => '2022-01-01', + ]); + + $pl = new ProfitLoss($this->company, $this->payload); + $pl->build(); + + $expenses = $pl->getExpenses(); + + $expense = $expenses[0]; + + $this->assertEquals(10, $expense->total); + + $this->account->delete(); + + + } + + public function testSimpleExpenseBreakdown() + { + $this->buildData(); + + $e = Expense::factory()->create([ + 'amount' => 10, + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'date' => '2022-01-01', + 'exchange_rate' => 1, + 'currency_id' => $this->company->settings->currency_id + ]); + + $pl = new ProfitLoss($this->company, $this->payload); + $pl->build(); + + $expenses = $pl->getExpenses(); + + $bd = $pl->getExpenseBreakDown(); + + $this->assertEquals(array_sum(array_column($bd,'total')), 10); + + $this->account->delete(); + + } + + }