mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-11-04 04:17:34 -05:00 
			
		
		
		
	PnL Expense tests
This commit is contained in:
		
							parent
							
								
									fd67d8202e
								
							
						
					
					
						commit
						7df6b8f940
					
				@ -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;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user