diff --git a/app/Services/Report/ARDetailReport.php b/app/Services/Report/ARDetailReport.php index 0492e321d0cc..8c324cafa42b 100644 --- a/app/Services/Report/ARDetailReport.php +++ b/app/Services/Report/ARDetailReport.php @@ -81,6 +81,10 @@ class ARDetailReport extends BaseExport $this->csv->insertOne([ctrans('texts.aged_receivable_detailed_report')]); $this->csv->insertOne([ctrans('texts.created_on'),' ',$this->translateDate(now()->format('Y-m-d'), $this->company->date_format(), $this->company->locale())]); + if (count($this->input['report_keys']) == 0) { + $this->input['report_keys'] = $this->report_keys; + } + $this->csv->insertOne($this->buildHeader()); $query = Invoice::query() @@ -121,4 +125,17 @@ class ARDetailReport extends BaseExport Number::formatMoney($invoice->balance, $client), ]; } + + public function buildHeader() :array + { + $header = []; + + foreach ($this->input['report_keys'] as $value) { + + $header[] = ctrans("texts.{$value}"); + } + + return $header; + } + } \ No newline at end of file diff --git a/app/Services/Report/ARSummaryReport.php b/app/Services/Report/ARSummaryReport.php index a97456542754..58d509fbcf78 100644 --- a/app/Services/Report/ARSummaryReport.php +++ b/app/Services/Report/ARSummaryReport.php @@ -11,13 +11,202 @@ namespace App\Services\Report; -class ARSummaryReport +use App\Export\CSV\BaseExport; +use App\Libraries\MultiDB; +use App\Models\Client; +use App\Models\Company; +use App\Models\Invoice; +use App\Utils\Ninja; +use App\Utils\Number; +use App\Utils\Traits\MakesDates; +use Carbon\Carbon; +use Illuminate\Support\Facades\App; +use League\Csv\Writer; + +class ARSummaryReport extends BaseExport { - //name - //total - //current - //0-30 - //30-60 - //60-90 - //90+ + use MakesDates; + + public Writer $csv; + + public string $date_key = 'created_at'; + + public Client $client; + + public array $report_keys = [ + 'client_name', + 'client_number', + 'id_number', + 'current', + 'age_group_0', + 'age_group_30', + 'age_group_60', + 'age_group_90', + 'age_group_120', + 'total', + ]; + + /** + @param array $input + [ + 'date_range', + 'start_date', + 'end_date', + 'clients', + 'client_id', + ] + */ + public function __construct(public Company $company, public array $input) + { + } + + public function run() + { + MultiDB::setDb($this->company->db); + App::forgetInstance('translator'); + App::setLocale($this->company->locale()); + $t = app('translator'); + $t->replace(Ninja::transformTranslations($this->company->settings)); + + $this->csv = Writer::createFromString(); + + $this->csv->insertOne([]); + $this->csv->insertOne([]); + $this->csv->insertOne([]); + $this->csv->insertOne([]); + $this->csv->insertOne([ctrans('texts.aged_receivable_summary_report')]); + $this->csv->insertOne([ctrans('texts.created_on'),' ',$this->translateDate(now()->format('Y-m-d'), $this->company->date_format(), $this->company->locale())]); + + if (count($this->input['report_keys']) == 0) { + $this->input['report_keys'] = $this->report_keys; + } + + $this->csv->insertOne($this->buildHeader()); + + Client::query() + ->where('company_id', $this->company->id) + ->where('is_deleted', 0) + ->orderBy('balance', 'desc') + ->cursor() + ->each(function ($client) { + + $this->csv->insertOne($this->buildRow($client)); + + }); + + return $this->csv->toString(); + } + + private function buildRow(Client $client): array + { + $this->client = $client; + + return [ + $this->client->present()->name(), + $this->client->number, + $this->client->id_number, + $this->getCurrent(), + $this->getAgingAmount('30'), + $this->getAgingAmount('60'), + $this->getAgingAmount('90'), + $this->getAgingAmount('120'), + $this->getAgingAmount('120+'), + ]; + } + + private function getCurrent(): string + { + $amount = Invoice::withTrashed() + ->where('client_id', $this->client->id) + ->where('company_id', $this->client->company_id) + ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) + ->where('balance', '>', 0) + ->where('is_deleted', 0) + ->where('due_date', '<', now()->startOfDay()) + ->sum('balance'); + + return Number::formatMoney($amount, $this->client); + + } + /** + * Generate aging amount. + * + * @param mixed $range + * @return string + */ + private function getAgingAmount($range) + { + $ranges = $this->calculateDateRanges($range); + + $from = $ranges[0]; + $to = $ranges[1]; + + $amount = Invoice::withTrashed() + ->where('client_id', $this->client->id) + ->where('company_id', $this->client->company_id) + ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) + ->where('balance', '>', 0) + ->where('is_deleted', 0) + ->whereBetween('due_date', [$to, $from]) + ->sum('balance'); + + return Number::formatMoney($amount, $this->client); + } + + /** + * Calculate date ranges for aging. + * + * @param mixed $range + * @return array + */ + private function calculateDateRanges($range) + { + $ranges = []; + + switch ($range) { + case '30': + $ranges[0] = now()->startOfDay(); + $ranges[1] = now()->startOfDay()->subDays(30); + + return $ranges; + case '60': + $ranges[0] = now()->startOfDay()->subDays(30); + $ranges[1] = now()->startOfDay()->subDays(60); + + return $ranges; + case '90': + $ranges[0] = now()->startOfDay()->subDays(60); + $ranges[1] = now()->startOfDay()->subDays(90); + + return $ranges; + case '120': + $ranges[0] = now()->startOfDay()->subDays(90); + $ranges[1] = now()->startOfDay()->subDays(120); + + return $ranges; + case '120+': + $ranges[0] = now()->startOfDay()->subDays(120); + $ranges[1] = now()->startOfDay()->subYears(20); + + return $ranges; + default: + $ranges[0] = now()->startOfDay()->subDays(0); + $ranges[1] = now()->subDays(30); + + return $ranges; + } + } + + public function buildHeader() :array + { + $header = []; + + foreach ($this->input['report_keys'] as $value) { + + $header[] = ctrans("texts.{$value}"); + } + + return $header; + } + } diff --git a/app/Services/Report/ClientBalanceReport.php b/app/Services/Report/ClientBalanceReport.php index 993faf89df5b..498a018a2aea 100644 --- a/app/Services/Report/ClientBalanceReport.php +++ b/app/Services/Report/ClientBalanceReport.php @@ -71,6 +71,10 @@ class ClientBalanceReport extends BaseExport $this->csv->insertOne([ctrans('texts.customer_balance_report')]); $this->csv->insertOne([ctrans('texts.created_on'),' ',$this->translateDate(now()->format('Y-m-d'), $this->company->date_format(), $this->company->locale())]); + if (count($this->input['report_keys']) == 0) { + $this->input['report_keys'] = $this->report_keys; + } + $this->csv->insertOne($this->buildHeader()); Client::query() diff --git a/app/Services/Report/ClientSalesReport.php b/app/Services/Report/ClientSalesReport.php index 7845545cd74a..61e25ed58265 100644 --- a/app/Services/Report/ClientSalesReport.php +++ b/app/Services/Report/ClientSalesReport.php @@ -120,4 +120,17 @@ class ClientSalesReport extends BaseExport ]; } + + public function buildHeader() :array + { + $header = []; + + foreach ($this->input['report_keys'] as $value) { + + $header[] = ctrans("texts.{$value}"); + } + + return $header; + } + } diff --git a/app/Services/Report/UserSalesReport.php b/app/Services/Report/UserSalesReport.php index 71cec43081d4..9780e8a9ec82 100644 --- a/app/Services/Report/UserSalesReport.php +++ b/app/Services/Report/UserSalesReport.php @@ -106,5 +106,15 @@ class UserSalesReport extends BaseExport } + public function buildHeader() :array + { + $header = []; + foreach ($this->input['report_keys'] as $value) { + + $header[] = ctrans("texts.{$value}"); + } + + return $header; + } } diff --git a/tests/Feature/Export/ArDetailReportTest.php b/tests/Feature/Export/ArDetailReportTest.php new file mode 100644 index 000000000000..0d053f34c710 --- /dev/null +++ b/tests/Feature/Export/ArDetailReportTest.php @@ -0,0 +1,203 @@ +faker = \Faker\Factory::create(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $this->withoutExceptionHandling(); + } + + public $company; + + public $user; + + public $payload; + + public $account; + + public $client; + + /** + * start_date - Y-m-d + end_date - Y-m-d + date_range - + all + last7 + last30 + this_month + last_month + this_quarter + last_quarter + this_year + custom + is_income_billed - true = Invoiced || false = Payments + expense_billed - true = Expensed || false = Expenses marked as paid + include_tax - true tax_included || false - tax_excluded + */ + private function buildData() + { + $this->account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $this->account->num_users = 3; + $this->account->save(); + + $this->user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + ]); + + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + + $this->company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + ]); + + $this->company->settings = $settings; + $this->company->save(); + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'is_income_billed' => true, + 'include_tax' => false, + ]; + + $this->client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'is_deleted' => 0, + ]); + } + + public function testUserSalesInstance() + { + $this->buildData(); + + $pl = new ARDetailReport($this->company, $this->payload); + + $this->assertInstanceOf(ARDetailReport::class, $pl); + + $this->account->delete(); + } + + public function testSimpleReport() + { + $this->buildData(); + + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'client_id' => $this->client->id, + 'report_keys' => [] + ]; + + $i = Invoice::factory()->create([ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 0, + 'balance' => 0, + 'status_id' => 2, + 'total_taxes' => 1, + 'date' => now()->format('Y-m-d'), + 'terms' => 'nada', + 'discount' => 0, + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'uses_inclusive_taxes' => false, + 'line_items' => $this->buildLineItems(), + ]); + + $i = $i->calc()->getInvoice(); + + $pl = new ARDetailReport($this->company, $this->payload); + $response = $pl->run(); + + $this->assertIsString($response); + + $this->account->delete(); + } + + + private function buildLineItems() + { + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'test'; + $item->notes = 'test_product'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'pumpkin'; + $item->notes = 'test_pumpkin'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + return $line_items; + } +} diff --git a/tests/Feature/Export/ArSummaryReportTest.php b/tests/Feature/Export/ArSummaryReportTest.php new file mode 100644 index 000000000000..bf797b643d29 --- /dev/null +++ b/tests/Feature/Export/ArSummaryReportTest.php @@ -0,0 +1,203 @@ +faker = \Faker\Factory::create(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $this->withoutExceptionHandling(); + } + + public $company; + + public $user; + + public $payload; + + public $account; + + public $client; + + /** + * start_date - Y-m-d + end_date - Y-m-d + date_range - + all + last7 + last30 + this_month + last_month + this_quarter + last_quarter + this_year + custom + is_income_billed - true = Invoiced || false = Payments + expense_billed - true = Expensed || false = Expenses marked as paid + include_tax - true tax_included || false - tax_excluded + */ + private function buildData() + { + $this->account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $this->account->num_users = 3; + $this->account->save(); + + $this->user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + ]); + + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + + $this->company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + ]); + + $this->company->settings = $settings; + $this->company->save(); + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'is_income_billed' => true, + 'include_tax' => false, + ]; + + $this->client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'is_deleted' => 0, + ]); + } + + public function testUserSalesInstance() + { + $this->buildData(); + + $pl = new ARSummaryReport($this->company, $this->payload); + + $this->assertInstanceOf(ARSummaryReport::class, $pl); + + $this->account->delete(); + } + + public function testSimpleReport() + { + $this->buildData(); + + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'client_id' => $this->client->id, + 'report_keys' => [] + ]; + + $i = Invoice::factory()->create([ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 0, + 'balance' => 0, + 'status_id' => 2, + 'total_taxes' => 1, + 'date' => now()->format('Y-m-d'), + 'terms' => 'nada', + 'discount' => 0, + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'uses_inclusive_taxes' => false, + 'line_items' => $this->buildLineItems(), + ]); + + $i = $i->calc()->getInvoice(); + + $pl = new ARSummaryReport($this->company, $this->payload); + $response = $pl->run(); + + $this->assertIsString($response); + + $this->account->delete(); + } + + + private function buildLineItems() + { + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'test'; + $item->notes = 'test_product'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'pumpkin'; + $item->notes = 'test_pumpkin'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + return $line_items; + } +} diff --git a/tests/Feature/Export/UserSalesReportTest.php b/tests/Feature/Export/UserSalesReportTest.php new file mode 100644 index 000000000000..5003d5ad5f55 --- /dev/null +++ b/tests/Feature/Export/UserSalesReportTest.php @@ -0,0 +1,203 @@ +faker = \Faker\Factory::create(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $this->withoutExceptionHandling(); + } + + public $company; + + public $user; + + public $payload; + + public $account; + + public $client; + + /** + * start_date - Y-m-d + end_date - Y-m-d + date_range - + all + last7 + last30 + this_month + last_month + this_quarter + last_quarter + this_year + custom + is_income_billed - true = Invoiced || false = Payments + expense_billed - true = Expensed || false = Expenses marked as paid + include_tax - true tax_included || false - tax_excluded + */ + private function buildData() + { + $this->account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $this->account->num_users = 3; + $this->account->save(); + + $this->user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + ]); + + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + + $this->company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + ]); + + $this->company->settings = $settings; + $this->company->save(); + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'is_income_billed' => true, + 'include_tax' => false, + ]; + + $this->client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'is_deleted' => 0, + ]); + } + + public function testUserSalesInstance() + { + $this->buildData(); + + $pl = new UserSalesReport($this->company, $this->payload); + + $this->assertInstanceOf(UserSalesReport::class, $pl); + + $this->account->delete(); + } + + public function testSimpleReport() + { + $this->buildData(); + + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'client_id' => $this->client->id, + 'report_keys' => [] + ]; + + $i = Invoice::factory()->create([ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 0, + 'balance' => 0, + 'status_id' => 2, + 'total_taxes' => 1, + 'date' => now()->format('Y-m-d'), + 'terms' => 'nada', + 'discount' => 0, + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'uses_inclusive_taxes' => false, + 'line_items' => $this->buildLineItems(), + ]); + + $i = $i->calc()->getInvoice(); + + $pl = new UserSalesReport($this->company, $this->payload); + $response = $pl->run(); + + $this->assertIsString($response); + + $this->account->delete(); + } + + + private function buildLineItems() + { + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'test'; + $item->notes = 'test_product'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'pumpkin'; + $item->notes = 'test_pumpkin'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + return $line_items; + } +}