diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 56697694b435..3a8529262d43 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -277,11 +277,13 @@ class CompanyImport implements ShouldQueue 'errors' => [] ]; + $_company = Company::find($this->company->id); + $nmo = new NinjaMailerObject; - $nmo->mailable = new ImportCompleted($this->company, $data); - $nmo->company = $this->company; - $nmo->settings = $this->company->settings; - $nmo->to_user = $this->company->owner(); + $nmo->mailable = new ImportCompleted($_company, $data); + $nmo->company = $_company; + $nmo->settings = $_company->settings; + $nmo->to_user = $_company->owner(); NinjaMailerJob::dispatchNow($nmo); } @@ -1528,10 +1530,9 @@ class CompanyImport implements ShouldQueue } if (! array_key_exists($resource, $this->ids)) { - nlog($resource); $this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available."); - nlog($this->ids); + throw new \Exception("Resource {$resource} not available."); } @@ -1561,8 +1562,10 @@ class CompanyImport implements ShouldQueue $t = app('translator'); $t->replace(Ninja::transformTranslations($this->company->settings)); + $_company = Company::find($this->company->id); + $nmo = new NinjaMailerObject; - $nmo->mailable = new CompanyImportFailure($this->company, $message); + $nmo->mailable = new CompanyImportFailure($_company, $message); $nmo->company = $this->company; $nmo->settings = $this->company->settings; $nmo->to_user = $this->company->owner(); diff --git a/app/Mail/Import/ImportCompleted.php b/app/Mail/Import/ImportCompleted.php index 20da4e2c24c8..fbf7234bbbbe 100644 --- a/app/Mail/Import/ImportCompleted.php +++ b/app/Mail/Import/ImportCompleted.php @@ -58,6 +58,22 @@ class ImportCompleted extends Mailable 'logo' => $this->company->present()->logo(), 'settings' => $this->company->settings, 'company' => $this->company, + 'client_count' => $this->company->clients()->count(), + 'product_count' => $this->company->products()->count(), + 'invoice_count' => $this->company->invoices()->count(), + 'quote_count' => $this->company->quotes()->count(), + 'credit_count' => $this->company->credits()->count(), + 'project_count' => $this->company->projects()->count(), + 'task_count' => $this->company->tasks()->count(), + 'vendor_count' => $this->company->vendors()->count(), + 'payment_count' => $this->company->payments()->count(), + 'recurring_invoice_count' => $this->company->recurring_invoices()->count(), + 'expense_count' => $this->company->expenses()->count(), + 'company_gateway_count' => $this->company->company_gateways()->count(), + 'client_gateway_token_count' => $this->company->client_gateway_tokens()->count(), + 'tax_rate_count' => $this->company->tax_rates()->count(), + 'document_count' => $this->company->documents()->count(), + ]); return $this diff --git a/app/Models/Company.php b/app/Models/Company.php index b659e8980ce7..9b2a11cd5be4 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -118,9 +118,7 @@ class Company extends BaseModel 'client_registration_fields' => 'array', ]; - protected $with = [ - // 'tokens' - ]; + protected $with = []; public static $modules = [ self::ENTITY_RECURRING_INVOICE => 1, diff --git a/app/Services/Quote/ApplyNumber.php b/app/Services/Quote/ApplyNumber.php index 9366ee5f4f38..3ceca85242a6 100644 --- a/app/Services/Quote/ApplyNumber.php +++ b/app/Services/Quote/ApplyNumber.php @@ -37,7 +37,7 @@ class ApplyNumber switch ($this->client->getSetting('counter_number_applied')) { case 'when_saved': $quote = $this->trySaving($quote); - // $quote->number = $this->getNextQuoteNumber($this->client, $quote); + // $quote->number = $this->getNextQuoteNumber($this->client, $quote); break; case 'when_sent': if ($quote->status_id == Quote::STATUS_SENT) { diff --git a/app/Services/Report/ProfitLoss.php b/app/Services/Report/ProfitLoss.php index 74270403d106..5e027a43e3e8 100644 --- a/app/Services/Report/ProfitLoss.php +++ b/app/Services/Report/ProfitLoss.php @@ -12,6 +12,7 @@ namespace App\Services\Report; use App\Libraries\Currency\Conversion\CurrencyApi; +use App\Libraries\MultiDB; use App\Models\Company; use App\Models\Expense; use Illuminate\Support\Carbon; @@ -28,6 +29,14 @@ class ProfitLoss private $end_date; + private float $income = 0; + + private float $income_taxes = 0; + + private array $expenses; + + private array $income_map; + protected CurrencyApi $currency_api; /* @@ -45,8 +54,8 @@ class ProfitLoss last_quarter this_year custom - income_billed - true = Invoiced || false = Payments - expense_billed - true = Expensed || false = Expenses marked as paid + is_income_billed - true = Invoiced || false = Payments + is_expense_billed - true = Expensed || false = Expenses marked as paid include_tax - true tax_included || false - tax_excluded */ @@ -68,15 +77,66 @@ class ProfitLoss public function build() { - //get income + MultiDB::setDb($this->company->db); - //sift foreign currencies - calculate both converted foreign amounts to native currency and also also group amounts by currency. + if($this->is_income_billed){ //get invoiced amounts + + $this->filterIncome(); - //get expenses + }else { + + $this->filterPaymentIncome(); + } + + $this->expenseData(); + + return $this; + } + + public function getIncome() :float + { + return round($this->income,2); + } + + public function getIncomeMap() :array + { + return $this->income_map; + } + + public function getIncomeTaxes() :float + { + return round($this->income_taxes,2); + } + + public function getExpenses() :array + { + return $this->expenses; + } + + private function filterIncome() + { + $invoices = $this->invoiceIncome(); + + $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; } + private function filterPaymentIncome() + { + $payments = $this->paymentIncome(); + + return $this; + } /* //returns an array of objects @@ -86,27 +146,31 @@ class ProfitLoss +"total_taxes": "35.950000", +"currency_id": ""1"", +"net_converted_amount": "670.5300000000", + +"net_converted_taxes": "10" }, {#2444 +"amount": "200.000000", +"total_taxes": "0.000000", +"currency_id": ""23"", +"net_converted_amount": "1.7129479802", + +"net_converted_taxes": "10" }, {#2654 +"amount": "140.000000", +"total_taxes": "40.000000", +"currency_id": ""12"", +"net_converted_amount": "69.3275024282", + +"net_converted_taxes": "10" }, ] */ private function invoiceIncome() - { + { nlog(['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] ); return \DB::select( \DB::raw(" SELECT sum(invoices.amount) as amount, sum(invoices.total_taxes) as total_taxes, + (sum(invoices.total_taxes) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_taxes, sum(invoices.amount - invoices.total_taxes) as net_amount, IFNULL(JSON_EXTRACT( settings, '$.currency_id' ), :company_currency) AS currency_id, (sum(invoices.amount - invoices.total_taxes) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_amount @@ -130,12 +194,19 @@ class ProfitLoss // }, 0); } + + /** + +"payments": "12260.870000", + +"payments_converted": "12260.870000000000", + +"currency_id": 1, + */ private function paymentIncome() { return \DB::select( \DB::raw(" SELECT SUM(coalesce(payments.amount - payments.refunded,0)) as payments, - SUM(coalesce(payments.amount - payments.refunded,0)) * IFNULL(payments.exchange_rate ,1) as payments_converted + SUM(coalesce(payments.amount - payments.refunded,0)) * IFNULL(payments.exchange_rate ,1) as payments_converted, + payments.currency_id as currency_id FROM clients INNER JOIN payments ON @@ -145,14 +216,14 @@ class ProfitLoss AND payments.is_deleted = false AND payments.company_id = :company_id AND (payments.date BETWEEN :start_date AND :end_date) - GROUP BY payments.currency_id - ORDER BY payments.currency_id; + GROUP BY currency_id + ORDER BY currency_id; "), ['company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]); } - private function expenseCalc() + private function expenseData() { $expenses = Expense::where('company_id', $this->company->id) @@ -186,6 +257,8 @@ class ProfitLoss } + $this->expenses = $data; + } private function getTax($expense) @@ -247,23 +320,27 @@ class ProfitLoss private function setBillingReportType() { - if(array_key_exists('income_billed', $this->payload)) - $this->is_income_billed = boolval($this->payload['income_billed']); + if(array_key_exists('is_income_billed', $this->payload)) + $this->is_income_billed = boolval($this->payload['is_income_billed']); - if(array_key_exists('expense_billed', $this->payload)) - $this->is_expense_billed = boolval($this->payload['expense_billed']); + if(array_key_exists('is_expense_billed', $this->payload)) + $this->is_expense_billed = boolval($this->payload['is_expense_billed']); if(array_key_exists('include_tax', $this->payload)) $this->is_tax_included = boolval($this->payload['include_tax']); + $this->addDateRange(); + return $this; } - private function addDateRange($query) + private function addDateRange() { + $date_range = 'this_year'; - $date_range = $this->payload['date_range']; + if(array_key_exists('date_range', $this->payload)) + $date_range = $this->payload['date_range']; try{ @@ -323,6 +400,8 @@ class ProfitLoss } + return $this; + } } diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 2a4a9850c680..6fb2d23ef793 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -40,18 +40,6 @@ class CompanyFactory extends Factory 'default_password_timeout' => 30*60000, 'enabled_modules' => config('ninja.enabled_modules'), 'custom_fields' => (object) [ - //'invoice1' => 'Custom Date|date', - // 'invoice2' => '2|switch', - // 'invoice3' => '3|', - // 'invoice4' => '4', - // 'client1'=>'1', - // 'client2'=>'2', - // 'client3'=>'3|date', - // 'client4'=>'4|switch', - // 'company1'=>'1|date', - // 'company2'=>'2|switch', - // 'company3'=>'3', - // 'company4'=>'4', ], ]; } diff --git a/resources/views/email/import/completed.blade.php b/resources/views/email/import/completed.blade.php index acefb2c17802..d4681962a1be 100644 --- a/resources/views/email/import/completed.blade.php +++ b/resources/views/email/import/completed.blade.php @@ -5,66 +5,66 @@
If your logo imported correctly it will display below. If it didn't import, you'll need to reupload your logo
-{{ ctrans('texts.clients') }}: {{ $company->clients->count() }}
+ @if(isset($company)) +{{ ctrans('texts.clients') }}: {{ $client_count }}
@endif - @if(isset($company) && count($company->products) >=1) -{{ ctrans('texts.products') }}: {{ count($company->products) }}
+ @if(isset($company)) +{{ ctrans('texts.products') }}: {{ $product_count }}
@endif - @if(isset($company) && count($company->invoices) >=1) -{{ ctrans('texts.invoices') }}: {{ count($company->invoices) }}
+ @if(isset($company)) +{{ ctrans('texts.invoices') }}: {{ $invoice_count }}
@endif - @if(isset($company) && count($company->payments) >=1) -{{ ctrans('texts.payments') }}: {{ count($company->payments) }}
+ @if(isset($company)) +{{ ctrans('texts.payments') }}: {{ $payment_count }}
@endif - @if(isset($company) && count($company->recurring_invoices) >=1) -{{ ctrans('texts.recurring_invoices') }}: {{ count($company->recurring_invoices) }}
+ @if(isset($company)) +{{ ctrans('texts.recurring_invoices') }}: {{ $recurring_invoice_count }}
@endif - @if(isset($company) && count($company->quotes) >=1) -{{ ctrans('texts.quotes') }}: {{ count($company->quotes) }}
+ @if(isset($company)) +{{ ctrans('texts.quotes') }}: {{ $quote_count }}
@endif - @if(isset($company) && count($company->credits) >=1) -{{ ctrans('texts.credits') }}: {{ count($company->credits) }}
+ @if(isset($company)) +{{ ctrans('texts.credits') }}: {{ $credit_count }}
@endif - @if(isset($company) && count($company->projects) >=1) -{{ ctrans('texts.projects') }}: {{ count($company->projects) }}
+ @if(isset($company)) +{{ ctrans('texts.projects') }}: {{ $project_count }}
@endif - @if(isset($company) && count($company->tasks) >=1) -{{ ctrans('texts.tasks') }}: {{ count($company->tasks) }}
+ @if(isset($company)) +{{ ctrans('texts.tasks') }}: {{ $task_count }}
@endif - @if(isset($company) && count($company->vendors) >=1) -{{ ctrans('texts.vendors') }}: {{ count($company->vendors) }}
+ @if(isset($company)) +{{ ctrans('texts.vendors') }}: {{ $vendor_count }}
@endif - @if(isset($company) && count($company->expenses) >=1) -{{ ctrans('texts.expenses') }}: {{ count($company->expenses) }}
+ @if(isset($company)) +{{ ctrans('texts.expenses') }}: {{ $expense_count }}
@endif - @if(isset($company) && count($company->company_gateways) >=1) -{{ ctrans('texts.gateways') }}: {{ count($company->company_gateways) }}
+ @if(isset($company)) +{{ ctrans('texts.gateways') }}: {{ $company_gateway_count }}
@endif - @if(isset($company) && count($company->client_gateway_tokens) >=1) -{{ ctrans('texts.tokens') }}: {{ count($company->client_gateway_tokens) }}
+ @if(isset($company)) +{{ ctrans('texts.tokens') }}: {{ $client_gateway_token_count }}
@endif - @if(isset($company) && count($company->tax_rates) >=1) -{{ ctrans('texts.tax_rates') }}: {{ count($company->tax_rates) }}
+ @if(isset($company)) +{{ ctrans('texts.tax_rates') }}: {{ $tax_rate_count }}
@endif - @if(isset($company) && count($company->documents) >=1) -{{ ctrans('texts.documents') }}: {{ count($company->documents) }}
+ @if(isset($company)) +{{ ctrans('texts.documents') }}: {{ $document_count }}
@endif @if(isset($check_data)) diff --git a/tests/Feature/Export/ProfitAndLossReportTest.php b/tests/Feature/Export/ProfitAndLossReportTest.php index bab9925f9fa2..1d31bf04eb48 100644 --- a/tests/Feature/Export/ProfitAndLossReportTest.php +++ b/tests/Feature/Export/ProfitAndLossReportTest.php @@ -10,8 +10,12 @@ */ namespace Tests\Feature\Export; +use App\Factory\InvoiceFactory; +use App\Models\Account; +use App\Models\Client; use App\Models\Company; use App\Models\Invoice; +use App\Models\User; use App\Services\Report\ProfitLoss; use App\Utils\Traits\MakesHash; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -27,18 +31,19 @@ use Tests\TestCase; class ProfitAndLossReportTest extends TestCase { use MakesHash; - use MockAccountData; + + public $faker; public function setUp() :void { parent::setUp(); + $this->faker = \Faker\Factory::create(); + $this->withoutMiddleware( ThrottleRequests::class ); - $this->makeTestData(); - $this->withoutExceptionHandling(); $this->buildData(); @@ -46,6 +51,8 @@ class ProfitAndLossReportTest extends TestCase public $company; + public $user; + public $payload; /** @@ -62,7 +69,7 @@ class ProfitAndLossReportTest extends TestCase last_quarter this_year custom - income_billed - true = Invoiced || false = Payments + is_income_billed - true = Invoiced || false = Payments expense_billed - true = Expensed || false = Expenses marked as paid include_tax - true tax_included || false - tax_excluded @@ -70,15 +77,31 @@ class ProfitAndLossReportTest extends TestCase private function buildData() { + + + $account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000 + ]); + + $account->num_users = 3; + $account->save(); + + $this->user = User::factory()->create([ + 'account_id' => $account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail, + ]); + $this->company = Company::factory()->create([ - 'account_id' => $this->account->id, + 'account_id' => $account->id, ]); $this->payload = [ 'start_date' => '2000-01-01', 'end_date' => '2030-01-11', 'date_range' => 'custom', - 'income_billed' => true, + 'is_income_billed' => true, 'include_tax' => false ]; @@ -92,4 +115,53 @@ class ProfitAndLossReportTest extends TestCase $this->assertInstanceOf(ProfitLoss::class, $pl); } + + public function testInvoiceIncome() + { + + $client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'is_deleted' => 0, + ]); + + // Invoice::factory()->create([ + // 'client_id' => $client->id, + // 'user_id' => $this->user->id, + // 'company_id' => $this->company->id, + // 'amount' => 10, + // 'balance' => 10, + // 'status_id' => 2, + // 'total_taxes' => 1, + // 'date' => '2022-01-01', + // 'terms' => 'nada', + // 'discount' => 0, + // 'tax_rate1' => 0, + // 'tax_rate2' => 0, + // 'tax_rate3' => 0, + // 'tax_name1' => '', + // 'tax_name2' => '', + // 'tax_name3' => '', + // ]); + + $i = InvoiceFactory::create($this->company->id, $this->user->id); + $i->client_id = $client->id; + $i->amount = 10; + $i->balance = 10; + $i->status_id = 2; + $i->terms = "nada"; + $i->total_taxes = 1; + $i->save(); + + nlog(Invoice::where('company_id', $this->company->id)->get()->toArray()); + + $pl = new ProfitLoss($this->company, $this->payload); + $pl->build(); + + + $this->assertEquals(9.0, $pl->getIncome()); + $this->assertEquals(1, $pl->getIncomeTaxes()); + + + } } diff --git a/tests/Unit/ValidationRules/UniqueInvoiceNumberValidationTest.php b/tests/Unit/ValidationRules/UniqueInvoiceNumberValidationTest.php new file mode 100644 index 000000000000..c443fd6604cd --- /dev/null +++ b/tests/Unit/ValidationRules/UniqueInvoiceNumberValidationTest.php @@ -0,0 +1,81 @@ +withoutMiddleware( + ThrottleRequests::class + ); + + $this->makeTestData(); + + $this->withoutExceptionHandling(); + + } + + public function testValidEmailRule() + { + auth()->login($this->user); + auth()->user()->setCompany($this->company); + + Invoice::factory()->create([ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'paid_to_date' => 100, + 'status_id' => 4, + 'date' => now(), + 'due_date'=> now(), + 'number' => 'db_record' + ]); + + $data = [ + 'client_id' => $this->client->hashed_id, + 'paid_to_date' => 100, + 'status_id' => 4, + 'date' => now(), + 'due_date'=> now(), + 'number' => 'db_record' + ]; + + $rules = (new StoreInvoiceRequest())->rules(); + + $validator = Validator::make($data, $rules); + + $this->assertFalse($validator->passes()); + + } + + +} + +