From 2012e0dd5e64f2cddc75f9a2b60ee698b8aec016 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 11 Sep 2023 11:05:05 +1000 Subject: [PATCH 01/34] Add classifications for clients/vendors and company --- app/DataMapper/CompanySettings.php | 3 + app/Factory/ClientFactory.php | 3 +- app/Factory/VendorFactory.php | 1 + .../Requests/Client/StoreClientRequest.php | 3 +- .../Requests/Client/UpdateClientRequest.php | 1 + .../Requests/Vendor/StoreVendorRequest.php | 1 + .../Requests/Vendor/UpdateVendorRequest.php | 1 + app/Models/Client.php | 1 + app/Models/Vendor.php | 1 + app/Transformers/ClientTransformer.php | 1 + app/Transformers/VendorTransformer.php | 1 + ...add_client_and_company_classifications.php | 31 +++ lang/en/texts.php | 6 + tests/Feature/ClassificationTest.php | 220 ++++++++++++++++++ tests/MockUnitData.php | 20 +- 15 files changed, 286 insertions(+), 8 deletions(-) create mode 100644 database/migrations/2023_09_11_003230_add_client_and_company_classifications.php create mode 100644 tests/Feature/ClassificationTest.php diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 99638a0fa981..e4e8ff3085c5 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -481,8 +481,11 @@ class CompanySettings extends BaseSettings public $enable_e_invoice = false; + public $classification = ''; // individual, company, partnership, trust, charity, government, other + public static $casts = [ 'enable_e_invoice' => 'bool', + 'classification' => 'string', 'default_expense_payment_type_id' => 'string', 'e_invoice_type' => 'string', 'mailgun_endpoint' => 'string', diff --git a/app/Factory/ClientFactory.php b/app/Factory/ClientFactory.php index 37d23311add5..fbb6f36ab528 100644 --- a/app/Factory/ClientFactory.php +++ b/app/Factory/ClientFactory.php @@ -32,7 +32,8 @@ class ClientFactory $client->is_deleted = 0; $client->client_hash = Str::random(40); $client->settings = ClientSettings::defaults(); - + $client->classification = ''; + return $client; } } diff --git a/app/Factory/VendorFactory.php b/app/Factory/VendorFactory.php index 021d91053c6e..04c009267b21 100644 --- a/app/Factory/VendorFactory.php +++ b/app/Factory/VendorFactory.php @@ -28,6 +28,7 @@ class VendorFactory $vendor->country_id = 4; $vendor->is_deleted = 0; $vendor->vendor_hash = Str::random(40); + $vendor->classification = ''; return $vendor; } diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index c06305dd8e77..3a1d3841bfc4 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -93,7 +93,8 @@ class StoreClientRequest extends Request $rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; - + $rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other'; + return $rules; } diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index 0344d7aaf205..0c2f3628874f 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -60,6 +60,7 @@ class UpdateClientRequest extends Request $rules['size_id'] = 'integer|nullable'; $rules['country_id'] = 'integer|nullable'; $rules['shipping_country_id'] = 'integer|nullable'; + $rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other'; if ($this->id_number) { $rules['id_number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id); diff --git a/app/Http/Requests/Vendor/StoreVendorRequest.php b/app/Http/Requests/Vendor/StoreVendorRequest.php index 2de5edeca00c..3ce8ba472198 100644 --- a/app/Http/Requests/Vendor/StoreVendorRequest.php +++ b/app/Http/Requests/Vendor/StoreVendorRequest.php @@ -60,6 +60,7 @@ class StoreVendorRequest extends Request } $rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id'; + $rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other'; return $rules; } diff --git a/app/Http/Requests/Vendor/UpdateVendorRequest.php b/app/Http/Requests/Vendor/UpdateVendorRequest.php index 867b4541c0bf..b50e8cf79229 100644 --- a/app/Http/Requests/Vendor/UpdateVendorRequest.php +++ b/app/Http/Requests/Vendor/UpdateVendorRequest.php @@ -61,6 +61,7 @@ class UpdateVendorRequest extends Request } $rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id'; + $rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other'; return $rules; } diff --git a/app/Models/Client.php b/app/Models/Client.php index 2087a27186da..5abb007ad357 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -169,6 +169,7 @@ class Client extends BaseModel implements HasLocalePreference 'routing_id', 'is_tax_exempt', 'has_valid_vat_number', + 'classification', ]; protected $with = [ diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 25ebbc4d9e29..243a38691370 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -113,6 +113,7 @@ class Vendor extends BaseModel 'custom_value4', 'number', 'language_id', + 'classification', ]; protected $casts = [ diff --git a/app/Transformers/ClientTransformer.php b/app/Transformers/ClientTransformer.php index f34b805ff581..711c9cfec5ba 100644 --- a/app/Transformers/ClientTransformer.php +++ b/app/Transformers/ClientTransformer.php @@ -151,6 +151,7 @@ class ClientTransformer extends EntityTransformer 'is_tax_exempt' => (bool) $client->is_tax_exempt, 'routing_id' => (string) $client->routing_id, 'tax_info' => $client->tax_data ?: new \stdClass, + 'classification' => $client->classification ?: '', ]; } } diff --git a/app/Transformers/VendorTransformer.php b/app/Transformers/VendorTransformer.php index 051e8fb2f497..29aeafa703cd 100644 --- a/app/Transformers/VendorTransformer.php +++ b/app/Transformers/VendorTransformer.php @@ -104,6 +104,7 @@ class VendorTransformer extends EntityTransformer 'created_at' => (int) $vendor->created_at, 'number' => (string) $vendor->number ?: '', 'language_id' => (string) $vendor->language_id ?: '', + 'classification' => (string) $vendor->classification ?: '', ]; } } diff --git a/database/migrations/2023_09_11_003230_add_client_and_company_classifications.php b/database/migrations/2023_09_11_003230_add_client_and_company_classifications.php new file mode 100644 index 000000000000..ba41f214219f --- /dev/null +++ b/database/migrations/2023_09_11_003230_add_client_and_company_classifications.php @@ -0,0 +1,31 @@ +string('classification')->nullable(); + }); + + Schema::table('vendors', function (Blueprint $table) { + $table->string('classification')->nullable(); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/lang/en/texts.php b/lang/en/texts.php index 8be95187c7be..c17129c75120 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5158,6 +5158,12 @@ $LANG = array( 'click_or_drop_files_here' => 'Click or drop files here', 'set_public' => 'Set public', 'set_private' => 'Set private', + 'individual' => 'Individual', + 'business' => 'Business', + 'partnership' => 'partnership', + 'trust' => 'Trust', + 'charity' => 'Charity', + 'government' => 'Government', ); return $LANG; diff --git a/tests/Feature/ClassificationTest.php b/tests/Feature/ClassificationTest.php new file mode 100644 index 000000000000..fe9df9600756 --- /dev/null +++ b/tests/Feature/ClassificationTest.php @@ -0,0 +1,220 @@ +faker = \Faker\Factory::create(); + + $this->makeTestData(); + + + } + + public function testClientClassification() + { + $data = [ + 'name' => 'Personal Company', + 'classification' => 'individual' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/clients', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('individual', $arr['data']['classification']); + } + + public function testValidationClassification() + { + $data = [ + 'name' => 'Personal Company', + 'classification' => 'this_is_not_validated' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/clients', $data); + + $response->assertStatus(422); + + } + + public function testValidation2Classification() + { + $this->client->classification = 'company'; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/clients/'.$this->client->hashed_id, $this->client->toArray()); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('company', $arr['data']['classification']); + } + + public function testValidation3Classification() + { + $this->client->classification = 'this_is_not_validated'; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/clients/'.$this->client->hashed_id, $this->client->toArray()); + + $response->assertStatus(422); + + } + + public function testVendorClassification() + { + $data = [ + 'name' => 'Personal Company', + 'classification' => 'individual' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/vendors', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('individual', $arr['data']['classification']); + } + + public function testVendorValidationClassification() + { + $data = [ + 'name' => 'Personal Company', + 'classification' => 'this_is_not_validated' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/vendors', $data); + + $response->assertStatus(422); + + } + + public function testVendorValidation2Classification() + { + $this->vendor->classification = 'company'; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/vendors/'.$this->vendor->hashed_id, $this->vendor->toArray()); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('company', $arr['data']['classification']); + } + + public function testVendorValidation3Classification() + { + $this->vendor->classification = 'this_is_not_validated'; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/vendors/'.$this->vendor->hashed_id, $this->vendor->toArray()); + + $response->assertStatus(422); + + } + + public function testCompanyClassification() + { + $settings = $this->company->settings; + $settings->classification = 'company'; + + $this->company->settings = $settings; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/companies/'.$this->company->hashed_id, $this->company->toArray()); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('company', $arr['data']['settings']['classification']); + } + + public function testCompanyValidationClassification() + { + $settings = $this->company->settings; + $settings->classification = 545454; + + $this->company->settings = $settings; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/companies/'.$this->company->hashed_id, $this->company->toArray()); + + $response->assertStatus(422); + + } + + public function testCompanyValidation2Classification() + { + $settings = $this->company->settings; + $settings->classification = null; + + $this->company->settings = $settings; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/companies/'.$this->company->hashed_id, $this->company->toArray()); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('', $arr['data']['settings']['classification']); + } +} \ No newline at end of file diff --git a/tests/MockUnitData.php b/tests/MockUnitData.php index 4ca402d23a6c..fcf7c3e1841f 100644 --- a/tests/MockUnitData.php +++ b/tests/MockUnitData.php @@ -11,15 +11,16 @@ namespace Tests; +use App\Models\User; +use App\Models\Client; +use App\Models\Vendor; +use App\Models\Account; +use App\Models\Company; +use App\Models\CompanyToken; +use App\Models\ClientContact; use App\DataMapper\CompanySettings; use App\DataMapper\DefaultSettings; use App\Factory\InvoiceItemFactory; -use App\Models\Account; -use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\CompanyToken; -use App\Models\User; /** * Class MockUnitData. @@ -34,6 +35,8 @@ trait MockUnitData public $client; + public $vendor; + public $faker; public $primary_contact; @@ -92,6 +95,11 @@ trait MockUnitData 'company_id' => $this->company->id, ]); + $this->vendor = Vendor::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + ]); + $this->primary_contact = ClientContact::factory()->create([ 'user_id' => $this->user->id, 'client_id' => $this->client->id, From 8ff3c91930d13af1073ec14b43137ae3dc36d6e1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 11 Sep 2023 11:22:10 +1000 Subject: [PATCH 02/34] Add inline logo option --- app/Http/Controllers/CompanyController.php | 15 +++++++++++++++ routes/api.php | 1 + tests/Feature/CompanyTest.php | 19 +++++++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index 016485535bc0..ede5c1b92d21 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -697,4 +697,19 @@ class CompanyController extends BaseController return $this->itemResponse($company->fresh()); } + + public function logo() + { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $company = $user->company(); + $logo = strlen($company->settings->company_logo) > 5 ? $company->settings->company_logo : 'https://pdf.invoicing.co/favicon-v2.png'; + $headers = ['Content-Disposition' => 'inline']; + + return response()->streamDownload(function () use ($logo){ + echo @file_get_contents($logo); + }, 'logo.png', $headers); + + } } diff --git a/routes/api.php b/routes/api.php index 3ce1fdc1737e..ae2521300da8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -177,6 +177,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('companies/purge_save_settings/{company}', [MigrationController::class, 'purgeCompanySaveSettings'])->middleware('password_protected'); Route::resource('companies', CompanyController::class); // name = (companies. index / create / show / update / destroy / edit + Route::post('companies/{company}/logo', [CompanyController::class, 'logo']); Route::put('companies/{company}/upload', [CompanyController::class, 'upload']); Route::post('companies/{company}/default', [CompanyController::class, 'default']); Route::post('companies/updateOriginTaxData/{company}', [CompanyController::class, 'updateOriginTaxData'])->middleware('throttle:3,1'); diff --git a/tests/Feature/CompanyTest.php b/tests/Feature/CompanyTest.php index 6d0fefbc2bf1..c3be4dfc2694 100644 --- a/tests/Feature/CompanyTest.php +++ b/tests/Feature/CompanyTest.php @@ -34,6 +34,8 @@ class CompanyTest extends TestCase use MockAccountData; use DatabaseTransactions; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -47,6 +49,19 @@ class CompanyTest extends TestCase $this->makeTestData(); } + + public function testCompanyLogoInline() + { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/companies/{$this->company->hashed_id}/logo"); + + $response->assertStatus(200); + $response->streamedContent(); + + } + public function testUpdateCompanyPropertyInvoiceTaskHours() { $company_update = [ @@ -56,9 +71,9 @@ class CompanyTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - ])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $company_update) - ->assertStatus(200); + ])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $company_update); + $response->assertStatus(200); $arr = $response->json(); From a2c9abbf945970cd41e6bd7ac2b1940915e42557 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 11 Sep 2023 12:01:05 +1000 Subject: [PATCH 03/34] ACH Status checks --- app/Console/Kernel.php | 38 ++++++++------- app/Jobs/Ninja/CheckACHStatus.php | 79 +++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 app/Jobs/Ninja/CheckACHStatus.php diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 9da20b1020c9..c66cdc40e5a9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -11,28 +11,29 @@ namespace App\Console; -use App\Jobs\Cron\AutoBillCron; -use App\Jobs\Cron\RecurringExpensesCron; -use App\Jobs\Cron\RecurringInvoicesCron; -use App\Jobs\Cron\SubscriptionCron; -use App\Jobs\Cron\UpdateCalculatedFields; -use App\Jobs\Invoice\InvoiceCheckLateWebhook; -use App\Jobs\Ninja\AdjustEmailQuota; -use App\Jobs\Ninja\BankTransactionSync; -use App\Jobs\Ninja\CompanySizeCheck; +use App\Utils\Ninja; +use App\Models\Account; use App\Jobs\Ninja\QueueSize; -use App\Jobs\Ninja\SystemMaintenance; -use App\Jobs\Ninja\TaskScheduler; -use App\Jobs\Quote\QuoteCheckExpired; -use App\Jobs\Subscription\CleanStaleInvoiceOrder; use App\Jobs\Util\DiskCleanup; use App\Jobs\Util\ReminderJob; -use App\Jobs\Util\SchedulerCheck; -use App\Jobs\Util\UpdateExchangeRates; +use App\Jobs\Cron\AutoBillCron; use App\Jobs\Util\VersionCheck; -use App\Models\Account; -use App\Utils\Ninja; +use App\Jobs\Ninja\TaskScheduler; +use App\Jobs\Util\SchedulerCheck; +use App\Jobs\Ninja\CheckACHStatus; +use App\Jobs\Cron\SubscriptionCron; +use App\Jobs\Ninja\AdjustEmailQuota; +use App\Jobs\Ninja\CompanySizeCheck; +use App\Jobs\Ninja\SystemMaintenance; +use App\Jobs\Quote\QuoteCheckExpired; +use App\Jobs\Util\UpdateExchangeRates; +use App\Jobs\Ninja\BankTransactionSync; +use App\Jobs\Cron\RecurringExpensesCron; +use App\Jobs\Cron\RecurringInvoicesCron; +use App\Jobs\Cron\UpdateCalculatedFields; use Illuminate\Console\Scheduling\Schedule; +use App\Jobs\Invoice\InvoiceCheckLateWebhook; +use App\Jobs\Subscription\CleanStaleInvoiceOrder; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel @@ -109,6 +110,9 @@ class Kernel extends ConsoleKernel /* Pulls in bank transactions from third party services */ $schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer(); + /* Checks ACH verification status and updates state to authorize when verified */ + $schedule->job(new CheckACHStatus)->everySixHours()->withoutOverlapping()->name('ach-status-job')->onOneServer(); + $schedule->command('ninja:check-data --database=db-ninja-01')->dailyAt('02:10')->withoutOverlapping()->name('check-data-db-1-job')->onOneServer(); $schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('02:20')->withoutOverlapping()->name('check-data-db-2-job')->onOneServer(); diff --git a/app/Jobs/Ninja/CheckACHStatus.php b/app/Jobs/Ninja/CheckACHStatus.php new file mode 100644 index 000000000000..a7f61b25520c --- /dev/null +++ b/app/Jobs/Ninja/CheckACHStatus.php @@ -0,0 +1,79 @@ +where('created_at', '>', now()->subMonths(2)) + ->where('gateway_type_id', 2) + ->whereHas('gateway', function ($q) { + $q->whereIn('gateway_key', ['d14dd26a37cecc30fdd65700bfb55b23','d14dd26a47cecc30fdd65700bfb67b34']); + }) + ->whereJsonContains('meta', ['state' => 'unauthorized']) + ->cursor() + ->each(function ($token) { + + try { + $stripe = $token->gateway->driver($token->client)->init(); + $pm = $stripe->getStripePaymentMethod($token->token); + + if($pm) { + + $meta = $token->meta; + $meta->state = 'authorized'; + $token->meta = $meta; + $token->save(); + + } + + } catch (\Exception $e) { + } + + }); + } + } +} \ No newline at end of file From d5555d1d39e2af4a5c673ee3a4f0f8eaa6ebb9ef Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 11 Sep 2023 15:44:02 +1000 Subject: [PATCH 04/34] Add recurring invoice `pseudo` activity --- app/Jobs/RecurringInvoice/SendRecurring.php | 25 +++++++++++-------- .../Invoice/CreateInvoiceActivity.php | 3 ++- app/Models/Activity.php | 3 +++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index ad6074309f80..3c98c00a4387 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -11,22 +11,24 @@ namespace App\Jobs\RecurringInvoice; -use App\DataMapper\Analytics\SendRecurringFailure; -use App\Factory\InvoiceInvitationFactory; -use App\Factory\RecurringInvoiceToInvoiceFactory; -use App\Jobs\Cron\AutoBill; -use App\Jobs\Entity\EmailEntity; +use Carbon\Carbon; +use App\Utils\Ninja; use App\Models\Invoice; +use App\Jobs\Cron\AutoBill; +use Illuminate\Bus\Queueable; +use App\Utils\Traits\MakesHash; +use App\Jobs\Entity\EmailEntity; use App\Models\RecurringInvoice; use App\Utils\Traits\GeneratesCounter; -use App\Utils\Traits\MakesHash; -use Carbon\Carbon; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Turbo124\Beacon\Facades\LightLogs; +use Illuminate\Queue\InteractsWithQueue; +use App\Events\Invoice\InvoiceWasCreated; +use App\Factory\InvoiceInvitationFactory; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use App\Factory\RecurringInvoiceToInvoiceFactory; +use App\DataMapper\Analytics\SendRecurringFailure; class SendRecurring implements ShouldQueue { @@ -105,6 +107,7 @@ class SendRecurring implements ShouldQueue $this->recurring_invoice->save(); event('eloquent.created: App\Models\Invoice', $invoice); + event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars())); //auto bill, BUT NOT DRAFTS!! if ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->client->getSetting('auto_email_invoice')) { diff --git a/app/Listeners/Invoice/CreateInvoiceActivity.php b/app/Listeners/Invoice/CreateInvoiceActivity.php index 42a5ccb34a19..f5baea05f0ea 100644 --- a/app/Listeners/Invoice/CreateInvoiceActivity.php +++ b/app/Listeners/Invoice/CreateInvoiceActivity.php @@ -52,7 +52,8 @@ class CreateInvoiceActivity implements ShouldQueue $fields->client_id = $event->invoice->client_id; $fields->company_id = $event->invoice->company_id; $fields->activity_type_id = Activity::CREATE_INVOICE; - + $fields->recurring_invoice_id = $event->invoice->recurring_id; + $this->activity_repo->save($fields, $event->invoice, $event->event_vars); } } diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 2677a149d5c5..1b9d89ce59d7 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -416,6 +416,9 @@ class Activity extends StaticModel if($this->vendor) $replacements['vendor'] = ['label' => $this?->vendor?->present()->name() ?? '', 'hashed_id' => $this->vendor->hashed_id ?? '']; + if($this->activity_type_id == 4 && $this->recurring_invoice) + $replacements['recurring_invoice'] = ['label' => $this?->recurring_invoice?->number ?? '', 'hashed_id' => $this->recurring_invoice->hashed_id ?? '']; + $replacements['activity_type_id'] = $this->activity_type_id; $replacements['id'] = $this->id; $replacements['hashed_id'] = $this->hashed_id; From eddc65b13f9dd2e7c2d015e90fdd88aad13035b7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 12 Sep 2023 07:30:02 +1000 Subject: [PATCH 05/34] Change post to get for logo inline --- routes/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index ae2521300da8..61e44f833902 100644 --- a/routes/api.php +++ b/routes/api.php @@ -177,7 +177,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('companies/purge_save_settings/{company}', [MigrationController::class, 'purgeCompanySaveSettings'])->middleware('password_protected'); Route::resource('companies', CompanyController::class); // name = (companies. index / create / show / update / destroy / edit - Route::post('companies/{company}/logo', [CompanyController::class, 'logo']); + Route::get('companies/{company}/logo', [CompanyController::class, 'logo']); Route::put('companies/{company}/upload', [CompanyController::class, 'upload']); Route::post('companies/{company}/default', [CompanyController::class, 'default']); Route::post('companies/updateOriginTaxData/{company}', [CompanyController::class, 'updateOriginTaxData'])->middleware('throttle:3,1'); From e739ad24fffbec7a6f0aa0f16c7089509e4ffd3d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 12 Sep 2023 07:55:02 +1000 Subject: [PATCH 06/34] Fixes for tests --- tests/Feature/QuoteTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Feature/QuoteTest.php b/tests/Feature/QuoteTest.php index 957286739fb0..e32f10f8824f 100644 --- a/tests/Feature/QuoteTest.php +++ b/tests/Feature/QuoteTest.php @@ -74,19 +74,19 @@ class QuoteTest extends TestCase 'line_items' =>[ [ 'type_id' => 2, - 'unit_cost' => 200, + 'cost' => 200, 'quantity' => 2, 'notes' => 'Test200', ], [ 'type_id' => 2, - 'unit_cost' => 100, + 'cost' => 100, 'quantity' => 1, 'notes' => 'Test100', ], [ 'type_id' => 1, - 'unit_cost' => 10, + 'cost' => 10, 'quantity' => 1, 'notes' => 'Test', ], From c1a9ee6d083ff39832c990e4ea6d45dee1f8563a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 12 Sep 2023 19:55:42 +1000 Subject: [PATCH 07/34] Fixes for project name --- app/Services/Quote/ConvertQuoteToProject.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Quote/ConvertQuoteToProject.php b/app/Services/Quote/ConvertQuoteToProject.php index 6b1b642cd16a..59227fcfc566 100644 --- a/app/Services/Quote/ConvertQuoteToProject.php +++ b/app/Services/Quote/ConvertQuoteToProject.php @@ -36,7 +36,7 @@ class ConvertQuoteToProject }); $project = ProjectFactory::create($this->quote->company_id, $this->quote->user_id); - $project->name = ctrans('texts.quote_number_short'). " " . $this->quote->number . "[{$this->quote->client->present()->name()}]"; + $project->name = ctrans('texts.quote_number_short'). " " . $this->quote->number . " [{$this->quote->client->present()->name()}]"; $project->client_id = $this->quote->client_id; $project->public_notes = $this->quote->public_notes; $project->private_notes = $this->quote->private_notes; From 9f384af12506a10a76509815bd776067d7e828ac Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 12 Sep 2023 21:19:30 +1000 Subject: [PATCH 08/34] Fixes for tests --- tests/Feature/QuoteTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/QuoteTest.php b/tests/Feature/QuoteTest.php index 57ebde27b3e6..35605558207c 100644 --- a/tests/Feature/QuoteTest.php +++ b/tests/Feature/QuoteTest.php @@ -171,7 +171,7 @@ class QuoteTest extends TestCase $project = Project::find($this->decodePrimaryKey($res['data'][0]['project_id'])); - $this->assertEquals($project->name, ctrans('texts.quote_number_short') . " " . $this->quote->number."[{$this->quote->client->present()->name()}]"); + $this->assertEquals($project->name, ctrans('texts.quote_number_short') . " " . $this->quote->number." [{$this->quote->client->present()->name()}]"); } public function testQuoteList() From caa5385e22d2366dc269865c2e03837fb2d29655 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 13 Sep 2023 07:26:22 +1000 Subject: [PATCH 09/34] Updates for searchcontroller --- app/Helpers/Invoice/InvoiceItemSumInclusive.php | 16 +++++++++++++--- app/Helpers/Invoice/InvoiceSumInclusive.php | 3 ++- app/Http/Controllers/SearchController.php | 4 ++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/Helpers/Invoice/InvoiceItemSumInclusive.php b/app/Helpers/Invoice/InvoiceItemSumInclusive.php index b2a4b40337e6..1057fe1bb2f2 100644 --- a/app/Helpers/Invoice/InvoiceItemSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceItemSumInclusive.php @@ -349,14 +349,16 @@ class InvoiceItemSumInclusive { $this->setGroupedTaxes(collect([])); - $item_tax = 0; foreach ($this->line_items as $this->item) { if ($this->sub_total == 0) { $amount = $this->item->line_total; } else { - $amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / $this->sub_total)); + $amount = ($this->sub_total > 0) ? $this->item->line_total - ($this->invoice->discount * ($this->item->line_total / $this->sub_total)) : 0; + // $amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / $this->sub_total)); } + + $item_tax = 0; $item_tax_rate1_total = $this->calcInclusiveLineTax($this->item->tax_rate1, $amount); @@ -381,9 +383,17 @@ class InvoiceItemSumInclusive if ($item_tax_rate3_total != 0) { $this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total); } + + $this->setTotalTaxes($this->getTotalTaxes() + $item_tax); + $this->item->gross_line_total = $this->getLineTotal() + $item_tax; + + $this->item->tax_amount = $item_tax; + } - $this->setTotalTaxes($item_tax); + return $this; + + // $this->setTotalTaxes($item_tax); } diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index e57237bf3d05..69410f54db9e 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -315,8 +315,9 @@ class InvoiceSumInclusive public function setTaxMap() { - if ($this->invoice->is_amount_discount == true) { + if ($this->invoice->is_amount_discount) { $this->invoice_items->calcTaxesWithAmountDiscount(); + $this->invoice->line_items = $this->invoice_items->getLineItems(); } $this->tax_map = collect(); diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index fba7c9529439..3b3c0f11ead8 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -86,7 +86,7 @@ class SearchController extends Controller 'name' => $invoice->client->present()->name() . ' - ' . $invoice->number, 'type' => '/invoice', 'id' => $invoice->hashed_id, - 'path' => "/clients/{$invoice->hashed_id}/edit", + 'path' => "/invoices/{$invoice->hashed_id}/edit", 'heading' => ctrans('texts.invoices') ]; }); @@ -104,7 +104,7 @@ class SearchController extends Controller 'custom_fields' => '/settings/user_details/custom_fields', 'preferences' => '/settings/user_details/preferences', 'company_details' => '/settings/company_details', - 'company_details,details' => '/settings/company_details/details', + 'company_details,details' => '/settings/company_details/', 'company_details,address' => '/settings/company_details/address', 'company_details,logo' => '/settings/company_details/logo', 'company_details,defaults' => '/settings/company_details/defaults', From 6e57f1959861853e0052a19b01d61e21c7e7a479 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 13 Sep 2023 09:42:00 +1000 Subject: [PATCH 10/34] listen for charge.refunded for stripe --- app/Models/Payment.php | 18 +++ .../Stripe/Jobs/ChargeRefunded.php | 147 ++++++++++++++++++ app/PaymentDrivers/StripePaymentDriver.php | 7 + 3 files changed, 172 insertions(+) create mode 100644 app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 3773e8237d99..6df4fbc0dbaf 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -361,6 +361,24 @@ class Payment extends BaseModel return new PaymentService($this); } + /** + * $data = [ + 'id' => $payment->id, + 'amount' => 10, + 'invoices' => [ + [ + 'invoice_id' => $invoice->id, + 'amount' => 10, + ], + ], + 'date' => '2020/12/12', + 'gateway_refund' => false, + 'email_receipt' => false, + ]; + * + * @param array $data + * @return self + */ public function refund(array $data) :self { return $this->service()->refundPayment($data); diff --git a/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php new file mode 100644 index 000000000000..d0393dd2e752 --- /dev/null +++ b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php @@ -0,0 +1,147 @@ +stripe_request = $stripe_request; + $this->company_key = $company_key; + $this->company_gateway_id = $company_gateway_id; + } + + public function handle() + { + MultiDB::findAndSetDbByCompanyKey($this->company_key); + nlog($this->stripe_request); + + $company = Company::query()->where('company_key', $this->company_key)->first(); + + $source = $this->stripe_request['object']; + $charge_id = $source['id']; + $amount_refunded = $source['amount_refunded'] ?? 0; + + $payment_hash_key = $source['metadata']['payment_hash'] ?? null; + + $company_gateway = CompanyGateway::query()->find($this->company_gateway_id); + $payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first(); + + $stripe_driver = $company_gateway->driver()->init(); + + $stripe_driver->payment_hash = $payment_hash; + + /** @var \App\Models\Payment $payment **/ + $payment = Payment::query() + ->withTrashed() + ->where('company_id', $company->id) + ->where('transaction_reference', $charge_id) + ->first(); + + //don't touch if already refunded + if(!$payment || in_array($payment->status_id, [Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])) { + return; + } + + $stripe_driver->client = $payment->client; + + $amount_refunded = $stripe_driver->convertFromStripeAmount($amount_refunded, $payment->client->currency()->precision, $payment->client->currency()); + + if ($payment->status_id == Payment::STATUS_PENDING) { + $payment->service()->deletePayment(); + $payment->status_id = Payment::STATUS_FAILED; + $payment->save(); + return; + } + + if($payment->status_id == Payment::STATUS_COMPLETED) { + + $invoice_collection = $payment->paymentables + ->where('paymentable_type','invoices') + ->map(function ($pivot){ + return [ + 'invoice_id' => $pivot->paymentable_id, + 'amount' => $pivot->amount - $pivot->refunded + ]; + }); + + if($invoice_collection->count() == 1 && $invoice_collection->first()['amount'] >= $amount_refunded) { + //If there is only one invoice- and we are refunding _less_ than the amount of the invoice, we can just refund the payment + + $invoice_collection = $payment->paymentables + ->where('paymentable_type', 'invoices') + ->map(function ($pivot) use ($amount_refunded){ + return [ + 'invoice_id' => $pivot->paymentable_id, + 'amount' => $amount_refunded + ]; + }); + + } + elseif($invoice_collection->sum('amount') != $amount_refunded) { + //too many edges cases at this point, return early + return; + } + + $invoices = $invoice_collection->toArray(); + + $data = [ + 'id' => $payment->id, + 'amount' => $amount_refunded, + 'invoices' => $invoices, + 'date' => now()->format('Y-m-d'), + 'gateway_refund' => false, + 'email_receipt' => false, + ]; + + nlog($data); + + $payment->refund($data); + + $payment->private_notes .= 'Refunded via Stripe'; + return; + } + + } + + public function middleware() + { + return [new WithoutOverlapping($this->company_gateway_id)]; + } +} diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index a80c09789dcd..de4f2ac93e14 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -39,6 +39,7 @@ use App\PaymentDrivers\Stripe\FPX; use App\PaymentDrivers\Stripe\GIROPAY; use App\PaymentDrivers\Stripe\iDeal; use App\PaymentDrivers\Stripe\ImportCustomers; +use App\PaymentDrivers\Stripe\Jobs\ChargeRefunded; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook; use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook; @@ -790,6 +791,12 @@ class StripePaymentDriver extends BaseDriver } elseif ($request->data['object']['status'] == "pending") { return response()->json([], 200); } + } elseif ($request->type === "charge.refunded") { + + ChargeRefunded::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10))); + + return response()->json([], 200); + } return response()->json([], 200); From ff204375f655368348d9e56247ac4f944d171be8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 13 Sep 2023 13:42:49 +1000 Subject: [PATCH 11/34] Updated lock --- composer.lock | 82 +++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/composer.lock b/composer.lock index ba232aa837fc..6bbda0be6137 100644 --- a/composer.lock +++ b/composer.lock @@ -525,16 +525,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.281.4", + "version": "3.281.5", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c37035bcfb67a9d54f91dae303b3fe8f98ea59f4" + "reference": "53cb798ca9b0b817dd2584f9406ee84d0214e6fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c37035bcfb67a9d54f91dae303b3fe8f98ea59f4", - "reference": "c37035bcfb67a9d54f91dae303b3fe8f98ea59f4", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/53cb798ca9b0b817dd2584f9406ee84d0214e6fc", + "reference": "53cb798ca9b0b817dd2584f9406ee84d0214e6fc", "shasum": "" }, "require": { @@ -614,9 +614,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.281.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.281.5" }, - "time": "2023-09-11T18:07:49+00:00" + "time": "2023-09-12T18:13:00+00:00" }, { "name": "bacon/bacon-qr-code", @@ -3441,16 +3441,16 @@ }, { "name": "horstoeko/zugferd", - "version": "v1.0.26", + "version": "v1.0.28", "source": { "type": "git", "url": "https://github.com/horstoeko/zugferd.git", - "reference": "2a7541a35f00499c206391273f30159dc2c7072a" + "reference": "be78b1b53a46e94a69b92dcff1e909180170583c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/2a7541a35f00499c206391273f30159dc2c7072a", - "reference": "2a7541a35f00499c206391273f30159dc2c7072a", + "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/be78b1b53a46e94a69b92dcff1e909180170583c", + "reference": "be78b1b53a46e94a69b92dcff1e909180170583c", "shasum": "" }, "require": { @@ -3508,9 +3508,9 @@ ], "support": { "issues": "https://github.com/horstoeko/zugferd/issues", - "source": "https://github.com/horstoeko/zugferd/tree/v1.0.26" + "source": "https://github.com/horstoeko/zugferd/tree/v1.0.28" }, - "time": "2023-08-18T03:05:43+00:00" + "time": "2023-09-12T14:54:01+00:00" }, { "name": "http-interop/http-factory-guzzle", @@ -4331,16 +4331,16 @@ }, { "name": "laravel/framework", - "version": "v10.22.0", + "version": "v10.23.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "9234388a895206d4e1df37342b61adc67e5c5d31" + "reference": "7d6a79ce8ff52a4d0d385ac290dcc048aa514ce7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/9234388a895206d4e1df37342b61adc67e5c5d31", - "reference": "9234388a895206d4e1df37342b61adc67e5c5d31", + "url": "https://api.github.com/repos/laravel/framework/zipball/7d6a79ce8ff52a4d0d385ac290dcc048aa514ce7", + "reference": "7d6a79ce8ff52a4d0d385ac290dcc048aa514ce7", "shasum": "" }, "require": { @@ -4527,20 +4527,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-09-05T13:20:01+00:00" + "time": "2023-09-12T15:55:31+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.6", + "version": "v0.1.7", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "b514c5620e1b3b61221b0024dc88def26d9654f4" + "reference": "554e7d855a22e87942753d68e23b327ad79b2070" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/b514c5620e1b3b61221b0024dc88def26d9654f4", - "reference": "b514c5620e1b3b61221b0024dc88def26d9654f4", + "url": "https://api.github.com/repos/laravel/prompts/zipball/554e7d855a22e87942753d68e23b327ad79b2070", + "reference": "554e7d855a22e87942753d68e23b327ad79b2070", "shasum": "" }, "require": { @@ -4573,9 +4573,9 @@ ], "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.6" + "source": "https://github.com/laravel/prompts/tree/v0.1.7" }, - "time": "2023-08-18T13:32:23+00:00" + "time": "2023-09-12T11:09:22+00:00" }, { "name": "laravel/serializable-closure", @@ -4700,16 +4700,16 @@ }, { "name": "laravel/socialite", - "version": "v5.9.0", + "version": "v5.9.1", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "14acfa3262875f180fba51efe3c7aaa089a9ef24" + "reference": "49ecc4c907ed88c1254bae991c6b2948945645c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/14acfa3262875f180fba51efe3c7aaa089a9ef24", - "reference": "14acfa3262875f180fba51efe3c7aaa089a9ef24", + "url": "https://api.github.com/repos/laravel/socialite/zipball/49ecc4c907ed88c1254bae991c6b2948945645c2", + "reference": "49ecc4c907ed88c1254bae991c6b2948945645c2", "shasum": "" }, "require": { @@ -4766,7 +4766,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2023-09-05T15:20:21+00:00" + "time": "2023-09-07T16:13:53+00:00" }, { "name": "laravel/tinker", @@ -15942,16 +15942,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.4", + "version": "10.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "cd59bb34756a16ca8253ce9b2909039c227fff71" + "reference": "1df504e42a88044c27a90136910f0b3fe9e91939" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cd59bb34756a16ca8253ce9b2909039c227fff71", - "reference": "cd59bb34756a16ca8253ce9b2909039c227fff71", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1df504e42a88044c27a90136910f0b3fe9e91939", + "reference": "1df504e42a88044c27a90136910f0b3fe9e91939", "shasum": "" }, "require": { @@ -16008,7 +16008,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.4" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.5" }, "funding": [ { @@ -16016,7 +16016,7 @@ "type": "github" } ], - "time": "2023-08-31T14:04:38+00:00" + "time": "2023-09-12T14:37:22+00:00" }, { "name": "phpunit/php-file-iterator", @@ -16263,16 +16263,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.3.3", + "version": "10.3.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "241ed4dd0db1c096984e62d414c4e1ac8d5dbff4" + "reference": "b8d59476f19115c9774b3b447f78131781c6c32b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/241ed4dd0db1c096984e62d414c4e1ac8d5dbff4", - "reference": "241ed4dd0db1c096984e62d414c4e1ac8d5dbff4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b8d59476f19115c9774b3b447f78131781c6c32b", + "reference": "b8d59476f19115c9774b3b447f78131781c6c32b", "shasum": "" }, "require": { @@ -16286,7 +16286,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.1", + "phpunit/php-code-coverage": "^10.1.5", "phpunit/php-file-iterator": "^4.0", "phpunit/php-invoker": "^4.0", "phpunit/php-text-template": "^3.0", @@ -16344,7 +16344,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.4" }, "funding": [ { @@ -16360,7 +16360,7 @@ "type": "tidelift" } ], - "time": "2023-09-05T04:34:51+00:00" + "time": "2023-09-12T14:42:28+00:00" }, { "name": "sebastian/cli-parser", From 0c0e0bff31f79cb57cdc5cbdb8ce28c806ce562f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 13 Sep 2023 14:31:06 +1000 Subject: [PATCH 12/34] WOrking on tax calculations --- app/Helpers/Invoice/InvoiceItemSum.php | 2 -- app/Helpers/Invoice/InvoiceItemSumInclusive.php | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index bc368e5f7fa5..74625c98416c 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -391,8 +391,6 @@ class InvoiceItemSum { $this->setGroupedTaxes(collect([])); - - foreach ($this->line_items as $key => $this->item) { if ($this->item->line_total == 0) { continue; diff --git a/app/Helpers/Invoice/InvoiceItemSumInclusive.php b/app/Helpers/Invoice/InvoiceItemSumInclusive.php index 1057fe1bb2f2..ad1a92600a08 100644 --- a/app/Helpers/Invoice/InvoiceItemSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceItemSumInclusive.php @@ -385,7 +385,7 @@ class InvoiceItemSumInclusive } $this->setTotalTaxes($this->getTotalTaxes() + $item_tax); - $this->item->gross_line_total = $this->getLineTotal() + $item_tax; + $this->item->gross_line_total = $this->getLineTotal(); $this->item->tax_amount = $item_tax; From faa70b842d1c09a949824543234f838d8b4b9334 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 12:00:36 +1000 Subject: [PATCH 13/34] Updates for composer.lock --- composer.lock | 60 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/composer.lock b/composer.lock index 6bbda0be6137..70cd0016ca7c 100644 --- a/composer.lock +++ b/composer.lock @@ -525,16 +525,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.281.5", + "version": "3.281.6", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "53cb798ca9b0b817dd2584f9406ee84d0214e6fc" + "reference": "b83c543a9ff07fc1d9f11766ee77fc7f4deed2b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/53cb798ca9b0b817dd2584f9406ee84d0214e6fc", - "reference": "53cb798ca9b0b817dd2584f9406ee84d0214e6fc", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b83c543a9ff07fc1d9f11766ee77fc7f4deed2b9", + "reference": "b83c543a9ff07fc1d9f11766ee77fc7f4deed2b9", "shasum": "" }, "require": { @@ -614,9 +614,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.281.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.281.6" }, - "time": "2023-09-12T18:13:00+00:00" + "time": "2023-09-13T18:07:28+00:00" }, { "name": "bacon/bacon-qr-code", @@ -2517,16 +2517,16 @@ }, { "name": "google/apiclient", - "version": "v2.15.0", + "version": "v2.15.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client.git", - "reference": "49787fa30b8d8313146a61efbf77ed1fede723c2" + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/49787fa30b8d8313146a61efbf77ed1fede723c2", - "reference": "49787fa30b8d8313146a61efbf77ed1fede723c2", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/7a95ed29e4b6c6859d2d22300c5455a92e2622ad", + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad", "shasum": "" }, "require": { @@ -2537,7 +2537,7 @@ "guzzlehttp/psr7": "^1.8.4||^2.2.1", "monolog/monolog": "^2.9||^3.0", "php": "^7.4|^8.0", - "phpseclib/phpseclib": "^3.0.2" + "phpseclib/phpseclib": "^3.0.19" }, "require-dev": { "cache/filesystem-adapter": "^1.1", @@ -2580,9 +2580,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client/issues", - "source": "https://github.com/googleapis/google-api-php-client/tree/v2.15.0" + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.15.1" }, - "time": "2023-05-18T13:51:33+00:00" + "time": "2023-09-13T21:46:39+00:00" }, { "name": "google/apiclient-services", @@ -4331,16 +4331,16 @@ }, { "name": "laravel/framework", - "version": "v10.23.0", + "version": "v10.23.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "7d6a79ce8ff52a4d0d385ac290dcc048aa514ce7" + "reference": "dbfd495557678759153e8d71cc2f6027686ca51e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/7d6a79ce8ff52a4d0d385ac290dcc048aa514ce7", - "reference": "7d6a79ce8ff52a4d0d385ac290dcc048aa514ce7", + "url": "https://api.github.com/repos/laravel/framework/zipball/dbfd495557678759153e8d71cc2f6027686ca51e", + "reference": "dbfd495557678759153e8d71cc2f6027686ca51e", "shasum": "" }, "require": { @@ -4440,7 +4440,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.4", + "orchestra/testbench-core": "^8.10", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", @@ -4527,7 +4527,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-09-12T15:55:31+00:00" + "time": "2023-09-13T14:51:46+00:00" }, { "name": "laravel/prompts", @@ -8271,16 +8271,16 @@ }, { "name": "predis/predis", - "version": "v2.2.1", + "version": "v2.2.2", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "5f2b410a74afaff296a87a494e4c5488cf9fab57" + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/5f2b410a74afaff296a87a494e4c5488cf9fab57", - "reference": "5f2b410a74afaff296a87a494e4c5488cf9fab57", + "url": "https://api.github.com/repos/predis/predis/zipball/b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", "shasum": "" }, "require": { @@ -8320,7 +8320,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.2.1" + "source": "https://github.com/predis/predis/tree/v2.2.2" }, "funding": [ { @@ -8328,7 +8328,7 @@ "type": "github" } ], - "time": "2023-08-15T23:01:46+00:00" + "time": "2023-09-13T16:42:03+00:00" }, { "name": "psr/cache", @@ -15880,16 +15880,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.33", + "version": "1.10.34", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1" + "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", - "reference": "03b1cf9f814ba0863c4e9affea49a4d1ed9a2ed1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7f806b6f1403e6914c778140e2ba07c293cb4901", + "reference": "7f806b6f1403e6914c778140e2ba07c293cb4901", "shasum": "" }, "require": { @@ -15938,7 +15938,7 @@ "type": "tidelift" } ], - "time": "2023-09-04T12:20:53+00:00" + "time": "2023-09-13T09:49:47+00:00" }, { "name": "phpunit/php-code-coverage", From 4d12cb5db8d04b080be497ec264f5dfd42d21232 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 14:17:52 +1000 Subject: [PATCH 14/34] Fixes for tests --- tests/Feature/ClientPortal/InvoicesTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index 11cb71a7b4f9..a5749138896e 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -30,6 +30,8 @@ class InvoicesTest extends TestCase use DatabaseTransactions; use AppSetup; + public $faker; + protected function setUp(): void { parent::setUp(); From d5b380d3023f55bba388494b5bd21d99ed93b67a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 14:35:43 +1000 Subject: [PATCH 15/34] Fixes for tests --- app/Export/CSV/BaseExport.php | 26 +++++++++++---------- app/Export/CSV/ExpenseExport.php | 4 ++++ tests/Feature/ClientPortal/CreditsTest.php | 6 +++++ tests/Feature/ClientPortal/InvoicesTest.php | 4 ++++ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 21ccedac0b14..21581c887557 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -377,7 +377,7 @@ class BaseExport protected array $expense_report_keys = [ 'amount' => 'expense.amount', 'category' => 'expense.category_id', - 'client' => 'expense.client_id', + // 'client' => 'expense.client_id', 'custom_value1' => 'expense.custom_value1', 'custom_value2' => 'expense.custom_value2', 'custom_value3' => 'expense.custom_value3', @@ -591,31 +591,33 @@ class BaseExport $manager->setSerializer(new ArraySerializer()); $transformed_client = $manager->createData($transformed_client)->toArray(); - if($column == 'name') + if(in_array($column, ['client.name', 'name'])) return $transformed_client['display_name']; - if($column == 'user_id') + if(in_array($column, ['client.user_id', 'user_id'])) return $entity->client->user->present()->name(); - if($column == 'country_id') + if(in_array($column, ['client.assigned_user_id', 'assigned_user_id'])) + return $entity->client->assigned_user->present()->name(); + + if(in_array($column, ['client.country_id', 'country_id'])) return $entity->client->country ? ctrans("texts.country_{$entity->client->country->name}") : ''; - if($column == 'shipping_country_id') + if(in_array($column, ['client.shipping_country_id', 'shipping_country_id'])) return $entity->client->shipping_country ? ctrans("texts.country_{$entity->client->shipping_country->name}") : ''; - if($column == 'size_id') + if(in_array($column, ['client.size_id', 'size_id'])) return $entity->client->size?->name ?? ''; - if($column == 'industry_id') + if(in_array($column, ['client.industry_id', 'industry_id'])) return $entity->client->industry?->name ?? ''; - if ($column == 'currency_id') { + if (in_array($column, ['client.currency_id', 'currency_id'])) return $entity->client->currency() ? $entity->client->currency()->code : $entity->company->currency()->code; - } - - if($column == 'client.payment_terms') { + + if(in_array($column, ['payment_terms', 'client.payment_terms'])) return $entity->client->getSetting('payment_terms'); - } + if(array_key_exists($column, $transformed_client)) return $transformed_client[$column]; diff --git a/app/Export/CSV/ExpenseExport.php b/app/Export/CSV/ExpenseExport.php index 32eaf9b6ca60..6de23398163f 100644 --- a/app/Export/CSV/ExpenseExport.php +++ b/app/Export/CSV/ExpenseExport.php @@ -159,6 +159,10 @@ class ExpenseExport extends BaseExport $entity['expense.assigned_user'] = $expense->assigned_user ? $expense->assigned_user->present()->name() : ''; } + if (in_array('expense.category_id', $this->input['report_keys'])) { + $entity['expense.category_id'] = $expense->category ? $expense->category->name : ''; + } + return $entity; } } diff --git a/tests/Feature/ClientPortal/CreditsTest.php b/tests/Feature/ClientPortal/CreditsTest.php index e555dcf8f0d6..b25cdcbf28ca 100644 --- a/tests/Feature/ClientPortal/CreditsTest.php +++ b/tests/Feature/ClientPortal/CreditsTest.php @@ -106,6 +106,8 @@ class CreditsTest extends TestCase ->assertDontSee('testing-number-01') ->assertSee('testing-number-02') ->assertSee('testing-number-03'); + + $user->forceDelete(); } public function testShowingCreditsWithNullDueDate() @@ -173,5 +175,9 @@ class CreditsTest extends TestCase ->assertSee('testing-number-01') ->assertSee('testing-number-02') ->assertSee('testing-number-03'); + + + $user->forceDelete(); + } } diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index a5749138896e..4a8020772c28 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -96,5 +96,9 @@ class InvoicesTest extends TestCase ->set('status', ['paid']) ->assertSee($paid->number) ->assertDontSee($unpaid->number); + + + $user->forceDelete(); + } } From cee315cf6d4bb7da0b36f6b952b6e188f9e57635 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 14:44:44 +1000 Subject: [PATCH 16/34] Fixes for report previews --- app/Export/CSV/BaseExport.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 21581c887557..bb7a6ac95651 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -915,13 +915,13 @@ class BaseExport $helper = new Helpers(); $header = []; - + nlog("header"); foreach ($this->input['report_keys'] as $value) { $key = array_search($value, $this->entity_keys); $original_key = $key; - // nlog("{$key} => {$value}"); + nlog("{$key} => {$value}"); $prefix = ''; if(!$key) { @@ -962,6 +962,9 @@ class BaseExport if(!$key) { $prefix = ctrans('texts.expense')." "; $key = array_search($value, $this->expense_report_keys); + + if(!$key && $value == 'expense.category') + $key = 'category'; } if(!$key) { @@ -988,6 +991,8 @@ class BaseExport $prefix = ''; } + nlog("key => {$key}"); + $key = str_replace('item.', '', $key); $key = str_replace('recurring_invoice.', '', $key); $key = str_replace('purchase_order.', '', $key); @@ -1040,7 +1045,7 @@ class BaseExport } } - // nlog($header); + nlog($header); return $header; } @@ -1094,8 +1099,6 @@ class BaseExport } - nlog($clean_row); - return $clean_row; } From 2558c4345f665252f1d8753298424b314f6e0bde Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 15:34:02 +1000 Subject: [PATCH 17/34] Adjustments for activity exports --- app/Export/CSV/ActivityExport.php | 35 +++++++++++++++++++++++++++---- app/Export/CSV/BaseExport.php | 14 ++++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/Export/CSV/ActivityExport.php b/app/Export/CSV/ActivityExport.php index 4ae02fb63e4e..7775d55dc879 100644 --- a/app/Export/CSV/ActivityExport.php +++ b/app/Export/CSV/ActivityExport.php @@ -57,9 +57,11 @@ class ActivityExport extends BaseExport return ['identifier' => $value, 'display_value' => $headerdisplay[$value]]; })->toArray(); - $report = $query->cursor() - ->map(function ($credit) { - return $this->buildActivityRow($credit); + + $report = $query->cursor() + ->map(function ($resource) { + $row = $this->buildActivityRow($resource); + return $this->processMetaData($row, $resource); })->toArray(); return array_merge(['columns' => $header], $report); @@ -70,6 +72,8 @@ class ActivityExport extends BaseExport return [ Carbon::parse($activity->created_at)->format($this->date_format), ctrans("texts.activity_{$activity->activity_type_id}",[ + 'payment_amount' => $activity->payment ? $activity->payment->amount : '', + 'adjustment' => $activity->payment ? $activity->payment->refunded : '', 'client' => $activity->client ? $activity->client->present()->name() : '', 'contact' => $activity->contact ? $activity->contact->present()->name() : '', 'quote' => $activity->quote ? $activity->quote->number : '', @@ -101,7 +105,7 @@ class ActivityExport extends BaseExport $this->date_format = DateFormat::find($this->company->settings->date_format_id)->format; - ksort($this->entity_keys); + // ksort($this->entity_keys); if (count($this->input['report_keys']) == 0) { $this->input['report_keys'] = array_values($this->entity_keys); @@ -146,4 +150,27 @@ class ActivityExport extends BaseExport { return $entity; } + + + public function processMetaData(array $row, $resource): array + { + + $clean_row = []; + + foreach (array_values($this->input['report_keys']) as $key => $value) { + + nlog("key: {$key}, value: {$value}"); + nlog($row); + $clean_row[$key]['entity'] = 'activity'; + $clean_row[$key]['id'] = $key; + $clean_row[$key]['hashed_id'] = null; + $clean_row[$key]['value'] = $row[$key]; + $clean_row[$key]['identifier'] = $value; + $clean_row[$key]['display_value'] = $row[$key]; + + } + + return $clean_row; + } + } diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index bb7a6ac95651..c58590d50e40 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -11,6 +11,7 @@ namespace App\Export\CSV; +use App\Models\Activity; use App\Models\Quote; use App\Utils\Number; use App\Models\Client; @@ -915,13 +916,13 @@ class BaseExport $helper = new Helpers(); $header = []; - nlog("header"); + // nlog("header"); foreach ($this->input['report_keys'] as $value) { $key = array_search($value, $this->entity_keys); $original_key = $key; - nlog("{$key} => {$value}"); + // nlog("{$key} => {$value}"); $prefix = ''; if(!$key) { @@ -991,7 +992,7 @@ class BaseExport $prefix = ''; } - nlog("key => {$key}"); + // nlog("key => {$key}"); $key = str_replace('item.', '', $key); $key = str_replace('recurring_invoice.', '', $key); @@ -1045,7 +1046,7 @@ class BaseExport } } - nlog($header); + // nlog($header); return $header; } @@ -1057,6 +1058,7 @@ class BaseExport $entity = ''; match ($class) { + Activity::class => $entity = 'activity', Invoice::class => $entity = 'invoice', RecurringInvoice::class => $entity = 'recurring_invoice', Quote::class => $entity = 'quote', @@ -1079,7 +1081,9 @@ class BaseExport $report_keys = explode(".", $value); $column_key = $value; - + nlog($value); + nlog($report_keys); + nlog($row); if($value == 'product_image') { $column_key = 'image'; $value = 'image'; From 11496e34eb9d4112713cc879ddbf170f8031e7f4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 15:35:39 +1000 Subject: [PATCH 18/34] Fixes for tests --- tests/Feature/ClientPortal/CreditsTest.php | 3 +-- tests/Feature/ClientPortal/InvoicesTest.php | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/Feature/ClientPortal/CreditsTest.php b/tests/Feature/ClientPortal/CreditsTest.php index b25cdcbf28ca..78b72481211a 100644 --- a/tests/Feature/ClientPortal/CreditsTest.php +++ b/tests/Feature/ClientPortal/CreditsTest.php @@ -176,8 +176,7 @@ class CreditsTest extends TestCase ->assertSee('testing-number-02') ->assertSee('testing-number-03'); - - $user->forceDelete(); + $account->delete(); } } diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index 4a8020772c28..d1dee684023f 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -64,21 +64,21 @@ class InvoicesTest extends TestCase 'company_id' => $company->id, ]); - $sent = Invoice::factory()->for($client)->create([ + $sent = Invoice::factory()->create([ 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'status_id' => Invoice::STATUS_SENT, ]); - $paid = Invoice::factory()->for($client)->create([ + $paid = Invoice::factory()->create([ 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'status_id' => Invoice::STATUS_PAID, ]); - $unpaid = Invoice::factory()->for($client)->create([ + $unpaid = Invoice::factory()->create([ 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, From a6945fea115369491d031bdd60ddec65bb68b2ae Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 16:00:41 +1000 Subject: [PATCH 19/34] Fixes for tests --- app/Export/CSV/BaseExport.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index c58590d50e40..db95dbb354b1 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -1026,10 +1026,13 @@ class BaseExport } } - elseif(count($parts) == 2 && stripos($parts[0], 'contact') !== false) { + elseif(count($parts) == 2 && (stripos($parts[0], 'vendor_contact') !== false || stripos($parts[0], 'contact') !== false)) { + $parts[0] = str_replace('vendor_contact', 'contact', $parts[0]); + $entity = "contact".substr($parts[1], -1); $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, $entity)) > 1 ? $helper->makeCustomField($this->company->custom_fields, $entity) : ctrans("texts.{$parts[1]}"); $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; + } elseif(count($parts) == 2 && in_array(substr($original_key, 0, -1), ['credit','quote','invoice','purchase_order','recurring_invoice','task'])){ $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1))) > 1 ? $helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1)) : ctrans("texts.{$parts[1]}"); @@ -1058,7 +1061,6 @@ class BaseExport $entity = ''; match ($class) { - Activity::class => $entity = 'activity', Invoice::class => $entity = 'invoice', RecurringInvoice::class => $entity = 'recurring_invoice', Quote::class => $entity = 'quote', @@ -1081,9 +1083,7 @@ class BaseExport $report_keys = explode(".", $value); $column_key = $value; - nlog($value); - nlog($report_keys); - nlog($row); + if($value == 'product_image') { $column_key = 'image'; $value = 'image'; From fb33fadae395d4991ba682149745f1323cbc6dda Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 16:04:15 +1000 Subject: [PATCH 20/34] Fixes for tests --- tests/Feature/ClientPortal/InvoicesTest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index d1dee684023f..f95a497b4856 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -68,6 +68,8 @@ class InvoicesTest extends TestCase 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, + 'number' => 'testing-number-02', + 'due_date' => now()->addMonth(), 'status_id' => Invoice::STATUS_SENT, ]); @@ -75,6 +77,7 @@ class InvoicesTest extends TestCase 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, + 'number' => 'testing-number-03', 'status_id' => Invoice::STATUS_PAID, ]); @@ -82,10 +85,16 @@ class InvoicesTest extends TestCase 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, + 'number' => 'testing-number-04', + 'due_date' => '', 'status_id' => Invoice::STATUS_UNPAID, ]); - $this->actingAs($client->contacts->first(), 'contact'); + $sent->load('client'); + $paid->load('client'); + $unpaid->load('client'); + + $this->actingAs($client->contacts()->first(), 'contact'); Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db]) ->assertSee($sent->number) @@ -97,7 +106,6 @@ class InvoicesTest extends TestCase ->assertSee($paid->number) ->assertDontSee($unpaid->number); - $user->forceDelete(); } From 1233cfc0c28a488e74e6b278610a0cc813ebc2b4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 16:10:54 +1000 Subject: [PATCH 21/34] Add check for zip api key --- app/Transformers/AccountTransformer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 7f5e914a3be6..15797cff2f01 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -91,6 +91,7 @@ class AccountTransformer extends EntityTransformer 'trial_days_left' => Ninja::isHosted() ? (int) $account->getTrialDays() : 0, 'account_sms_verified' => (bool) $account->account_sms_verified, 'has_iap_plan' => (bool)$account->inapp_transaction_id, + 'tax_api_enabled' => (bool) config('services.tax.zip_tax.key') ? true : false ]; } From b3ac20826b20384c59e2797c7a56f71a79cfdd44 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 16:12:59 +1000 Subject: [PATCH 22/34] Updated translations --- lang/en/texts.php | 2 ++ tests/Feature/ClientPortal/InvoicesTest.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index a7f79bf59196..90bc248ee1c1 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5165,6 +5165,8 @@ $LANG = array( 'charity' => 'Charity', 'government' => 'Government', 'in_stock_quantity' => 'Stock quantity', + 'vendor_contact' => 'Vendor Contact', + ); return $LANG; diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index f95a497b4856..e56d4b76a161 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -106,7 +106,7 @@ class InvoicesTest extends TestCase ->assertSee($paid->number) ->assertDontSee($unpaid->number); - $user->forceDelete(); + $account->delete(); } } From 4c46f9397b27650332134bfc2b174bf116e52709 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 16:38:59 +1000 Subject: [PATCH 23/34] Tests for tests --- app/Export/CSV/VendorExport.php | 2 +- app/Jobs/Report/PreviewReport.php | 4 +- tests/Feature/ClientPortal/InvoicesTest.php | 9 ++- .../Export/ReportCsvGenerationTest.php | 64 ++++++++++++++++++- 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/app/Export/CSV/VendorExport.php b/app/Export/CSV/VendorExport.php index 7f35f96e5425..74e9785b2f40 100644 --- a/app/Export/CSV/VendorExport.php +++ b/app/Export/CSV/VendorExport.php @@ -55,7 +55,7 @@ class VendorExport extends BaseExport if (count($this->input['report_keys']) == 0) { $this->input['report_keys'] = array_values($this->vendor_report_keys); } - + $query = Vendor::query()->with('contacts') ->withTrashed() ->where('company_id', $this->company->id) diff --git a/app/Jobs/Report/PreviewReport.php b/app/Jobs/Report/PreviewReport.php index ce07cbfc1c34..c52cd1a3e110 100644 --- a/app/Jobs/Report/PreviewReport.php +++ b/app/Jobs/Report/PreviewReport.php @@ -39,8 +39,8 @@ class PreviewReport implements ShouldQueue /** @var \App\Export\CSV\CreditExport $export */ $export = new $this->report_class($this->company, $this->request); $report = $export->returnJson(); - nlog($report); - nlog($this->report_class); + // nlog($report); + // nlog($this->report_class); // nlog($report); Cache::put($this->hash, $report, 60 * 60); } diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index e56d4b76a161..044fad145cf3 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -47,7 +47,7 @@ class InvoicesTest extends TestCase $user = User::factory()->create( ['account_id' => $account->id, 'email' => $this->faker->safeEmail()] ); - +echo "1"; $company = Company::factory()->create(['account_id' => $account->id]); $company->settings->language_id = '1'; $company->save(); @@ -89,24 +89,31 @@ class InvoicesTest extends TestCase 'due_date' => '', 'status_id' => Invoice::STATUS_UNPAID, ]); +echo "2"; $sent->load('client'); $paid->load('client'); $unpaid->load('client'); + echo "3"; + $this->actingAs($client->contacts()->first(), 'contact'); + echo "4"; Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db]) ->assertSee($sent->number) ->assertSee($paid->number) ->assertSee($unpaid->number); +echo "5"; Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db]) ->set('status', ['paid']) ->assertSee($paid->number) ->assertDontSee($unpaid->number); +echo "6"; $account->delete(); +echo "7"; } } diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index f7224d9aa43a..1fc580c81ec8 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -18,15 +18,17 @@ use App\Models\Credit; use League\Csv\Reader; use App\Models\Account; use App\Models\Company; +use App\Models\Expense; use App\Models\Invoice; use Tests\MockAccountData; use App\Models\CompanyToken; use App\Models\ClientContact; +use App\Export\CSV\TaskExport; use App\Utils\Traits\MakesHash; +use App\Export\CSV\VendorExport; use App\DataMapper\CompanySettings; use App\Factory\CompanyUserFactory; use App\Factory\InvoiceItemFactory; -use App\Models\Expense; use App\Services\Report\ARDetailReport; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -293,7 +295,7 @@ class ReportCsvGenerationTest extends TestCase ])->post('/api/v1/reports/vendors', $data); $csv = $response->streamedContent(); -nlog($csv); + $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Vendor Number')); $this->assertEquals('city', $this->getFirstValueByColumn($csv, 'Vendor City')); @@ -305,6 +307,28 @@ nlog($csv); $this->assertEquals('public_notes', $this->getFirstValueByColumn($csv, 'Vendor Public Notes')); $this->assertEquals('website', $this->getFirstValueByColumn($csv, 'Vendor Website')); + $data = [ + 'date_range' => 'all', + // 'end_date' => 'bail|required_if:date_range,custom|nullable|date', + // 'start_date' => 'bail|required_if:date_range,custom|nullable|date', + 'report_keys' => [], + 'send_email' => false, + // 'status' => 'sometimes|string|nullable|in:all,draft,sent,viewed,paid,unpaid,overdue', + ]; + + $export = new VendorExport($this->company, $data); + $data = $export->returnJson(); + + $this->assertNotNull($data); + + $this->assertEquals(0, $this->traverseJson($data, 'columns.0.identifier')); + $this->assertEquals('Vendor Name', $this->traverseJson($data, 'columns.9.display_value')); + $this->assertEquals('vendor', $this->traverseJson($data, '0.0.entity')); + $this->assertEquals('address1', $this->traverseJson($data, '0.0.id')); + $this->assertNull($this->traverseJson($data, '0.0.hashed_id')); + $this->assertEquals('address1', $this->traverseJson($data, '0.0.value')); + $this->assertEquals('vendor.address1', $this->traverseJson($data, '0.0.identifier')); + $this->assertEquals('address1', $this->traverseJson($data, '0.0.display_value')); } public function testVendorCustomColumnCsvGeneration() @@ -348,6 +372,22 @@ nlog($csv); $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Vendor Number')); $this->assertEquals('city', $this->getFirstValueByColumn($csv, 'Vendor City')); + + $export = new VendorExport($this->company, $data); + $data = $export->returnJson(); + + $this->assertNotNull($data); + + $this->assertEquals(0, $this->traverseJson($data, 'columns.0.identifier')); + $this->assertEquals('Vendor Name', $this->traverseJson($data, 'columns.0.display_value')); + $this->assertEquals('vendor', $this->traverseJson($data, '0.0.entity')); + $this->assertEquals('name', $this->traverseJson($data, '0.0.id')); + $this->assertNull($this->traverseJson($data, '0.0.hashed_id')); + $this->assertEquals('Vendor 1', $this->traverseJson($data, '0.0.value')); + $this->assertEquals('vendor.name', $this->traverseJson($data, '0.0.identifier')); + $this->assertEquals('Vendor 1', $this->traverseJson($data, '0.0.display_value')); + $this->assertEquals('number', $this->traverseJson($data, '0.2.id')); + } @@ -423,6 +463,19 @@ nlog($csv); $this->assertEquals('123456', $this->getFirstValueByColumn($csv, 'Invoice Invoice Number')); $this->assertEquals(1000, $this->getFirstValueByColumn($csv, 'Invoice Amount')); + $export = new TaskExport($this->company, $data); + $data = $export->returnJson(); + + $this->assertNotNull($data); + + $this->assertEquals(0, $this->traverseJson($data, 'columns.0.identifier')); + $this->assertEquals('Client Name', $this->traverseJson($data, 'columns.0.display_value')); + $this->assertEquals('client', $this->traverseJson($data, '0.0.entity')); + $this->assertEquals('name', $this->traverseJson($data, '0.0.id')); + $this->assertNotNull($this->traverseJson($data, '0.0.hashed_id')); + $this->assertEquals('bob', $this->traverseJson($data, '0.0.value')); + $this->assertEquals('client.name', $this->traverseJson($data, '0.0.identifier')); + $this->assertEquals('bob', $this->traverseJson($data, '0.0.display_value')); $data = [ 'date_range' => 'all', @@ -1364,6 +1417,13 @@ nlog($csv); } + private function traverseJson($array, $keys) + { + $value = data_get($array, $keys, false); + + return $value; + } + private function getFirstValueByColumn($csv, $column) { $reader = Reader::createFromString($csv); From ef3a06dbb06dee8de844ad5679e624b057d4d89e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 16:50:35 +1000 Subject: [PATCH 24/34] Tests for json export --- .../Export/ReportCsvGenerationTest.php | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 1fc580c81ec8..be93e9f74947 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -26,7 +26,9 @@ use App\Models\ClientContact; use App\Export\CSV\TaskExport; use App\Utils\Traits\MakesHash; use App\Export\CSV\VendorExport; +use App\Export\CSV\ProductExport; use App\DataMapper\CompanySettings; +use App\Export\CSV\PaymentExport; use App\Factory\CompanyUserFactory; use App\Factory\InvoiceItemFactory; use App\Services\Report\ARDetailReport; @@ -580,7 +582,7 @@ class ReportCsvGenerationTest extends TestCase ])->post('/api/v1/reports/products', $data); $csv = $response->streamedContent(); -// nlog($csv); + $this->assertEquals('product_key', $this->getFirstValueByColumn($csv, 'Product')); $this->assertEquals('notes', $this->getFirstValueByColumn($csv, 'Notes')); $this->assertEquals(100, $this->getFirstValueByColumn($csv, 'Cost')); @@ -588,8 +590,22 @@ class ReportCsvGenerationTest extends TestCase $this->assertEquals('Custom 1', $this->getFirstValueByColumn($csv, 'Custom Value 1')); $this->assertEquals('Custom 2', $this->getFirstValueByColumn($csv, 'Custom Value 2')); $this->assertEquals('Custom 3', $this->getFirstValueByColumn($csv, 'Custom Value 3')); - $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Custom Value 4')); - + $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Custom Value 4')); + + $export = new ProductExport($this->company, $data); + $data = $export->returnJson(); + + $this->assertNotNull($data); + + $this->assertEquals(0, $this->traverseJson($data, 'columns.0.identifier')); + $this->assertEquals('Custom Value 1', $this->traverseJson($data, 'columns.0.display_value')); + $this->assertEquals('custom_value1', $this->traverseJson($data, '0.0.entity')); + $this->assertEquals('custom_value1', $this->traverseJson($data, '0.0.id')); + $this->assertNull($this->traverseJson($data, '0.0.hashed_id')); + $this->assertEquals('Custom 1', $this->traverseJson($data, '0.0.value')); + $this->assertEquals('custom_value1', $this->traverseJson($data, '0.0.identifier')); + $this->assertEquals('Custom 1', $this->traverseJson($data, '0.0.display_value')); + } @@ -646,7 +662,26 @@ class ReportCsvGenerationTest extends TestCase $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals(0, $this->getFirstValueByColumn($csv, 'Client Balance')); $this->assertEquals(100, $this->getFirstValueByColumn($csv, 'Client Paid to Date')); - + + $export = new PaymentExport($this->company, $data); + $data = $export->returnJson(); + + $this->assertNotNull($data); + + $this->assertEquals(0, $this->traverseJson($data, 'columns.0.identifier')); + $this->assertEquals('Payment Date', $this->traverseJson($data, 'columns.0.display_value')); + $this->assertEquals(1, $this->traverseJson($data, 'columns.1.identifier')); + $this->assertEquals('Payment Amount', $this->traverseJson($data, 'columns.1.display_value')); + $this->assertEquals(2, $this->traverseJson($data, 'columns.2.identifier')); + $this->assertEquals('Invoice Invoice Number', $this->traverseJson($data, 'columns.2.display_value')); + $this->assertEquals(4, $this->traverseJson($data, 'columns.4.identifier')); + $this->assertEquals('Client Name', $this->traverseJson($data, 'columns.4.display_value')); + + + $this->assertEquals('payment', $this->traverseJson($data, '0.0.entity')); + $this->assertEquals('date', $this->traverseJson($data, '0.0.id')); + $this->assertNull($this->traverseJson($data, '0.0.hashed_id')); + $this->assertEquals('payment.date', $this->traverseJson($data, '0.0.identifier')); $data = [ From e928fca24829a5ad69cc4bc1d37b45826ad628af Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 17:17:24 +1000 Subject: [PATCH 25/34] Skip livewire tests --- tests/Feature/ClientPortal/InvoicesTest.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index 044fad145cf3..8b82c39069d3 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -40,14 +40,14 @@ class InvoicesTest extends TestCase $this->buildCache(true); } - public function testInvoiceTableFilters() + public function skipInvoiceTableFilters() { $account = Account::factory()->create(); $user = User::factory()->create( ['account_id' => $account->id, 'email' => $this->faker->safeEmail()] ); -echo "1"; + $company = Company::factory()->create(['account_id' => $account->id]); $company->settings->language_id = '1'; $company->save(); @@ -89,31 +89,24 @@ echo "1"; 'due_date' => '', 'status_id' => Invoice::STATUS_UNPAID, ]); -echo "2"; $sent->load('client'); $paid->load('client'); $unpaid->load('client'); - echo "3"; - $this->actingAs($client->contacts()->first(), 'contact'); - echo "4"; Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db]) ->assertSee($sent->number) ->assertSee($paid->number) ->assertSee($unpaid->number); -echo "5"; Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db]) ->set('status', ['paid']) ->assertSee($paid->number) ->assertDontSee($unpaid->number); -echo "6"; $account->delete(); -echo "7"; } } From a8e9467bb40c2ce1c9500fa5f4262d4edb38f162 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 17:56:19 +1000 Subject: [PATCH 26/34] Skip livewire tests --- tests/Feature/ClientPortal/InvoicesTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index 8b82c39069d3..c0ad82b5b474 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -38,9 +38,10 @@ class InvoicesTest extends TestCase $this->faker = Factory::create(); $this->buildCache(true); + $this->markTestSkipped(); } - public function skipInvoiceTableFilters() + public function testInvoiceTableFilters() { $account = Account::factory()->create(); From 5958e1a0732dc029e0bedffc11aae647e11a2ad2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 19:47:53 +1000 Subject: [PATCH 27/34] Skip livewire tests --- app/Http/Controllers/SearchController.php | 3 +-- tests/Feature/ClientPortal/CreditsTest.php | 1 + tests/Feature/ClientPortal/InvoicesTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 3b3c0f11ead8..8e7559894b1e 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -14,12 +14,11 @@ namespace App\Http\Controllers; use App\Models\User; use App\Models\Client; use App\Models\ClientContact; -use App\Http\Requests\Search\GenericSearchRequest; use App\Models\Invoice; class SearchController extends Controller { - public function __invoke(GenericSearchRequest $request) + public function __invoke() { /** @var \App\Models\User $user */ $user = auth()->user(); diff --git a/tests/Feature/ClientPortal/CreditsTest.php b/tests/Feature/ClientPortal/CreditsTest.php index 78b72481211a..b3c82a18c489 100644 --- a/tests/Feature/ClientPortal/CreditsTest.php +++ b/tests/Feature/ClientPortal/CreditsTest.php @@ -41,6 +41,7 @@ class CreditsTest extends TestCase $this->faker = Factory::create(); $this->buildCache(true); + $this->markTestSkipped(''); } public function testShowingOnlyCreditsWithDueDateLessOrEqualToNow() diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index c0ad82b5b474..c6927e2b9f0b 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -38,7 +38,7 @@ class InvoicesTest extends TestCase $this->faker = Factory::create(); $this->buildCache(true); - $this->markTestSkipped(); + $this->markTestSkipped(''); } public function testInvoiceTableFilters() From f9cb8409a00d2704753d669dc633168d18840edb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 20:27:21 +1000 Subject: [PATCH 28/34] Fixes for company logo tests --- tests/Feature/CompanyTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/CompanyTest.php b/tests/Feature/CompanyTest.php index c3be4dfc2694..6cfc427d9271 100644 --- a/tests/Feature/CompanyTest.php +++ b/tests/Feature/CompanyTest.php @@ -55,7 +55,7 @@ class CompanyTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - ])->postJson("/api/v1/companies/{$this->company->hashed_id}/logo"); + ])->get("/api/v1/companies/{$this->company->hashed_id}/logo"); $response->assertStatus(200); $response->streamedContent(); From 431ae35edf675e36a234e6edb20a38c868dce358 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 20:30:00 +1000 Subject: [PATCH 29/34] Reinit livewire tests --- tests/Feature/ClientPortal/CreditsTest.php | 2 +- tests/Feature/ClientPortal/InvoicesTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Feature/ClientPortal/CreditsTest.php b/tests/Feature/ClientPortal/CreditsTest.php index b3c82a18c489..ce401c2986bc 100644 --- a/tests/Feature/ClientPortal/CreditsTest.php +++ b/tests/Feature/ClientPortal/CreditsTest.php @@ -41,7 +41,7 @@ class CreditsTest extends TestCase $this->faker = Factory::create(); $this->buildCache(true); - $this->markTestSkipped(''); + } public function testShowingOnlyCreditsWithDueDateLessOrEqualToNow() diff --git a/tests/Feature/ClientPortal/InvoicesTest.php b/tests/Feature/ClientPortal/InvoicesTest.php index c6927e2b9f0b..e56d4b76a161 100644 --- a/tests/Feature/ClientPortal/InvoicesTest.php +++ b/tests/Feature/ClientPortal/InvoicesTest.php @@ -38,7 +38,6 @@ class InvoicesTest extends TestCase $this->faker = Factory::create(); $this->buildCache(true); - $this->markTestSkipped(''); } public function testInvoiceTableFilters() From a3d99d185cec79562dd6dd1bd28d689e26cac82e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 14 Sep 2023 21:12:41 +1000 Subject: [PATCH 30/34] Fixes for company logo tests --- tests/Unit/ClientSettingsTest.php | 120 ++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/Unit/ClientSettingsTest.php b/tests/Unit/ClientSettingsTest.php index 01e707a70506..c3625542827d 100644 --- a/tests/Unit/ClientSettingsTest.php +++ b/tests/Unit/ClientSettingsTest.php @@ -24,6 +24,8 @@ class ClientSettingsTest extends TestCase use MockAccountData; use DatabaseTransactions; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -33,6 +35,124 @@ class ClientSettingsTest extends TestCase $this->faker = \Faker\Factory::create(); } + + public function testClientValidSettingsWithBadProps() + { + $data = [ + 'name' => $this->faker->firstName(), + 'id_number' => 'Coolio', + 'settings' => [ + 'currency_id' => '43', + 'language_id' => '1', + 'website' => null, + 'address1' => null, + 'address2' => null, + 'city' => null, + 'state' => null, + 'postal_code' => null, + 'phone' => null, + 'email' => null, + 'vat_number' => null, + 'id_number' => null, + 'purchase_order_terms' => null, + 'purchase_order_footer' => null, + 'besr_id' => null, + 'qr_iban' => null, + 'name' => 'frank', + 'custom_value1' => null, + 'custom_value2' => null, + 'custom_value3' => null, + 'custom_value4' => null, + 'invoice_terms' => null, + 'quote_terms' =>null, + 'quote_footer' => null, + 'credit_terms' => null, + 'credit_footer' => null, + ], + ]; + + $response = false; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/clients/', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + nlog($arr); + + $this->assertEquals('frank', $arr['data']['settings']['name']); + + $client_id = $arr['data']['id']; + + $data = [ + 'name' => $this->faker->firstName(), + 'id_number' => 'Coolio', + 'settings' => [ + 'currency_id' => '43', + 'language_id' => '1', + 'name' => 'white', + ], + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/clients/'.$client_id, $data); + + $response->assertStatus(200); + + $arr = $response->json(); + nlog($arr); + $this->assertEquals('white', $arr['data']['settings']['name']); + + $data = [ + 'name' => $this->faker->firstName(), + 'id_number' => 'Coolio', + 'settings' => [ + 'currency_id' => '43', + 'language_id' => '1', + 'website' => null, + 'address1' => null, + 'besr_id' => null, + 'qr_iban' => null, + 'name' => 'white', + 'custom_value1' => null, + 'custom_value2' => null, + 'custom_value3' => null, + 'custom_value4' => null, + 'invoice_terms' => null, + 'quote_terms' =>null, + 'quote_footer' => null, + 'credit_terms' => null, + 'credit_footer' => null, + ], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson('/api/v1/clients/'.$client_id, $data); + + $response->assertStatus(200); + + $arr = $response->json(); + nlog($arr); + $this->assertEquals('white', $arr['data']['settings']['name']); + + // $this->assertEquals('1', $arr['data']['settings']['currency_id']); + // $this->assertEquals('1', $arr['data']['settings']['language_id']); + // $this->assertEquals('1', $arr['data']['settings']['payment_terms']); + // $this->assertEquals(10, $arr['data']['settings']['default_task_rate']); + // $this->assertEquals(true, $arr['data']['settings']['send_reminders']); + // $this->assertEquals('1', $arr['data']['settings']['valid_until']); + } + + + public function testClientBaseline() { $data = [ From df35545fef8634423daf06e1464b21ef6221573a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Sep 2023 07:28:26 +1000 Subject: [PATCH 31/34] Fixes for recurring expense generation of next_send_date --- app/Factory/RecurringExpenseFactory.php | 4 +++- .../UpdateRecurringExpenseRequest.php | 5 ++++- app/Jobs/Cron/RecurringExpensesCron.php | 5 +++++ .../Activity/CreatedExpenseActivity.php | 3 ++- tests/Unit/ClientSettingsTest.php | 17 +++++------------ 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/Factory/RecurringExpenseFactory.php b/app/Factory/RecurringExpenseFactory.php index cc92a45811e3..15d000334c17 100644 --- a/app/Factory/RecurringExpenseFactory.php +++ b/app/Factory/RecurringExpenseFactory.php @@ -34,7 +34,8 @@ class RecurringExpenseFactory $recurring_expense->tax_amount1 = 0; $recurring_expense->tax_amount2 = 0; $recurring_expense->tax_amount3 = 0; - $recurring_expense->date = null; + $recurring_expense->date = now()->format('Y-m-d'); + $recurring_expense->next_send_date = now()->format('Y-m-d'); $recurring_expense->payment_date = null; $recurring_expense->amount = 0; $recurring_expense->foreign_amount = 0; @@ -47,6 +48,7 @@ class RecurringExpenseFactory $recurring_expense->custom_value4 = ''; $recurring_expense->uses_inclusive_taxes = true; $recurring_expense->calculate_tax_by_amount = true; + $recurring_expense->remaining_cycles = -1; return $recurring_expense; } diff --git a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php index 76c61120971e..e45f66e80099 100644 --- a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php @@ -75,6 +75,9 @@ class UpdateRecurringExpenseRequest extends Request public function prepareForValidation() { + /** @var \App\Models\User $user*/ + $user = auth()->user(); + $input = $this->all(); $input = $this->decodePrimaryKeys($input); @@ -88,7 +91,7 @@ class UpdateRecurringExpenseRequest extends Request } if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) { - $input['currency_id'] = (string) auth()->user()->company()->settings->currency_id; + $input['currency_id'] = (string) $user->company()->settings->currency_id; } $this->replace($input); diff --git a/app/Jobs/Cron/RecurringExpensesCron.php b/app/Jobs/Cron/RecurringExpensesCron.php index 1b51f2fc70f4..55eee07e5bb5 100644 --- a/app/Jobs/Cron/RecurringExpensesCron.php +++ b/app/Jobs/Cron/RecurringExpensesCron.php @@ -11,12 +11,14 @@ namespace App\Jobs\Cron; +use App\Utils\Ninja; use App\Libraries\MultiDB; use Illuminate\Support\Carbon; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; use Illuminate\Support\Facades\Auth; use App\Utils\Traits\GeneratesCounter; +use App\Events\Expense\ExpenseWasCreated; use Illuminate\Foundation\Bus\Dispatchable; use App\Factory\RecurringExpenseToExpenseFactory; @@ -109,6 +111,9 @@ class RecurringExpensesCron $expense->number = $this->getNextExpenseNumber($expense); $expense->saveQuietly(); + event(new ExpenseWasCreated($expense, $expense->company, Ninja::eventVars(null))); + event('eloquent.created: App\Models\Expense', $expense); + $recurring_expense->next_send_date = $recurring_expense->nextSendDate(); $recurring_expense->next_send_date_client = $recurring_expense->next_send_date; $recurring_expense->last_sent_date = now(); diff --git a/app/Listeners/Activity/CreatedExpenseActivity.php b/app/Listeners/Activity/CreatedExpenseActivity.php index 9309db70e921..c6e46122cd5f 100644 --- a/app/Listeners/Activity/CreatedExpenseActivity.php +++ b/app/Listeners/Activity/CreatedExpenseActivity.php @@ -49,7 +49,8 @@ class CreatedExpenseActivity implements ShouldQueue $fields->user_id = $user_id; $fields->company_id = $event->expense->company_id; $fields->activity_type_id = Activity::CREATE_EXPENSE; - + $fields->recurring_expense_id = $event->expense->recurring_expense_id ?? null; + $this->activity_repo->save($fields, $event->expense, $event->event_vars); } } diff --git a/tests/Unit/ClientSettingsTest.php b/tests/Unit/ClientSettingsTest.php index c3625542827d..e0200d021628 100644 --- a/tests/Unit/ClientSettingsTest.php +++ b/tests/Unit/ClientSettingsTest.php @@ -81,7 +81,6 @@ class ClientSettingsTest extends TestCase $response->assertStatus(200); $arr = $response->json(); - nlog($arr); $this->assertEquals('frank', $arr['data']['settings']['name']); @@ -106,7 +105,7 @@ class ClientSettingsTest extends TestCase $response->assertStatus(200); $arr = $response->json(); - nlog($arr); + $this->assertEquals('white', $arr['data']['settings']['name']); $data = [ @@ -140,7 +139,6 @@ class ClientSettingsTest extends TestCase $response->assertStatus(200); $arr = $response->json(); - nlog($arr); $this->assertEquals('white', $arr['data']['settings']['name']); // $this->assertEquals('1', $arr['data']['settings']['currency_id']); @@ -169,7 +167,6 @@ class ClientSettingsTest extends TestCase ])->post('/api/v1/clients/', $data); } catch (ValidationException $e) { $message = json_decode($e->validator->getMessageBag(), 1); - nlog($message); } $response->assertStatus(200); @@ -203,7 +200,6 @@ class ClientSettingsTest extends TestCase ])->post('/api/v1/clients/', $data); } catch (ValidationException $e) { $message = json_decode($e->validator->getMessageBag(), 1); - nlog($message); } $response->assertStatus(200); @@ -235,17 +231,14 @@ class ClientSettingsTest extends TestCase $response = false; - try { + $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/clients/', $data); - } catch (ValidationException $e) { - $message = json_decode($e->validator->getMessageBag(), 1); - nlog($message); - } + ])->postJson('/api/v1/clients/', $data); + - $response->assertStatus(302); + $response->assertStatus(422); } public function testClientIllegalLanguage() From a16c12f8984ac0714a236b8be1435c96e5bd4289 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Sep 2023 07:52:11 +1000 Subject: [PATCH 32/34] Updated lock --- composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 70cd0016ca7c..49dc0b294acd 100644 --- a/composer.lock +++ b/composer.lock @@ -525,16 +525,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.281.6", + "version": "3.281.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "b83c543a9ff07fc1d9f11766ee77fc7f4deed2b9" + "reference": "926cea9a41a545ca9801ac304f2a9ffd23ac68c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b83c543a9ff07fc1d9f11766ee77fc7f4deed2b9", - "reference": "b83c543a9ff07fc1d9f11766ee77fc7f4deed2b9", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/926cea9a41a545ca9801ac304f2a9ffd23ac68c9", + "reference": "926cea9a41a545ca9801ac304f2a9ffd23ac68c9", "shasum": "" }, "require": { @@ -614,9 +614,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.281.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.281.7" }, - "time": "2023-09-13T18:07:28+00:00" + "time": "2023-09-14T18:05:11+00:00" }, { "name": "bacon/bacon-qr-code", @@ -14560,16 +14560,16 @@ }, { "name": "brianium/paratest", - "version": "v7.2.6", + "version": "v7.2.7", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "7f372b5bb59b4271adedc67d3129df29b84c4173" + "reference": "1526eb4fd195f65075456dee394d14742ae0a66c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/7f372b5bb59b4271adedc67d3129df29b84c4173", - "reference": "7f372b5bb59b4271adedc67d3129df29b84c4173", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/1526eb4fd195f65075456dee394d14742ae0a66c", + "reference": "1526eb4fd195f65075456dee394d14742ae0a66c", "shasum": "" }, "require": { @@ -14639,7 +14639,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.2.6" + "source": "https://github.com/paratestphp/paratest/tree/v7.2.7" }, "funding": [ { @@ -14651,7 +14651,7 @@ "type": "paypal" } ], - "time": "2023-08-29T07:47:39+00:00" + "time": "2023-09-14T14:10:09+00:00" }, { "name": "composer/class-map-generator", From bd8f5711d4207bcba22687e2dc9928d86a8b9e52 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Sep 2023 08:56:47 +1000 Subject: [PATCH 33/34] Change checks for applying payments --- .../Requests/Payment/UpdatePaymentRequest.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 1cd8d8b4c850..02b465638efe 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -30,18 +30,25 @@ class UpdatePaymentRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('edit', $this->payment); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('edit', $this->payment); } public function rules() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = [ 'invoices' => ['array', new PaymentAppliedValidAmount($this->all()), new ValidCreditsPresentRule($this->all())], 'invoices.*.invoice_id' => 'distinct', ]; if ($this->number) { - $rules['number'] = Rule::unique('payments')->where('company_id', auth()->user()->company()->id)->ignore($this->payment->id); + $rules['number'] = Rule::unique('payments')->where('company_id', $user->company()->id)->ignore($this->payment->id); } if ($this->file('documents') && is_array($this->file('documents'))) { @@ -75,7 +82,8 @@ class UpdatePaymentRequest extends Request if (isset($input['invoices']) && is_array($input['invoices']) !== false) { foreach ($input['invoices'] as $key => $value) { - if (array_key_exists('invoice_id', $input['invoices'][$key])) { + if(isset($input['invoices'][$key]['invoice_id'])){ + // if (array_key_exists('invoice_id', $input['invoices'][$key])) { $input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']); } } @@ -83,7 +91,8 @@ class UpdatePaymentRequest extends Request if (isset($input['credits']) && is_array($input['credits']) !== false) { foreach ($input['credits'] as $key => $value) { - if (array_key_exists('credits', $input['credits'][$key])) { + // if (array_key_exists('credits', $input['credits'][$key])) { + if (isset($input['credits'][$key]['credit_id'])) { $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']); } } From 4489ee583303955d095f05d41d82af65a748b0e6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 15 Sep 2023 13:31:33 +1000 Subject: [PATCH 34/34] v5.7.12 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 67fd02cd7eea..2fab05581196 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.11 \ No newline at end of file +5.7.12 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index 1c509fec85e7..fcb68333ee1a 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.11'), - 'app_tag' => env('APP_TAG','5.7.11'), + 'app_version' => env('APP_VERSION','5.7.12'), + 'app_tag' => env('APP_TAG','5.7.12'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''),