diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 84722eda5c6d..9f19c338aaec 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -11,37 +11,46 @@ namespace App\Http\Controllers; -use App\Events\Client\ClientWasCreated; -use App\Events\Client\ClientWasUpdated; +use App\Utils\Ninja; +use App\Models\Quote; +use App\Models\Client; +use App\Models\Credit; +use App\Models\Account; +use App\Models\Company; +use App\Models\Invoice; +use App\Models\Document; +use App\Models\SystemLog; +use Postmark\PostmarkClient; +use Illuminate\Http\Response; use App\Factory\ClientFactory; use App\Filters\ClientFilters; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\Uploadable; +use App\Utils\Traits\BulkOptions; +use App\Jobs\Client\UpdateTaxData; +use App\Utils\Traits\SavesDocuments; +use App\Repositories\ClientRepository; +use App\Events\Client\ClientWasCreated; +use App\Events\Client\ClientWasUpdated; +use App\Transformers\ClientTransformer; +use Illuminate\Support\Facades\Storage; +use App\Services\Template\TemplateAction; +use App\Jobs\PostMark\ProcessPostmarkWebhook; use App\Http\Requests\Client\BulkClientRequest; -use App\Http\Requests\Client\CreateClientRequest; -use App\Http\Requests\Client\DestroyClientRequest; use App\Http\Requests\Client\EditClientRequest; -use App\Http\Requests\Client\PurgeClientRequest; -use App\Http\Requests\Client\ReactivateClientEmailRequest; use App\Http\Requests\Client\ShowClientRequest; +use App\Http\Requests\Client\PurgeClientRequest; use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Client\CreateClientRequest; use App\Http\Requests\Client\UpdateClientRequest; use App\Http\Requests\Client\UploadClientRequest; -use App\Jobs\Client\UpdateTaxData; -use App\Jobs\PostMark\ProcessPostmarkWebhook; -use App\Models\Account; -use App\Models\Client; -use App\Models\Company; -use App\Models\SystemLog; -use App\Repositories\ClientRepository; -use App\Services\Template\TemplateAction; -use App\Transformers\ClientTransformer; -use App\Utils\Ninja; -use App\Utils\Traits\BulkOptions; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\SavesDocuments; -use App\Utils\Traits\Uploadable; -use Illuminate\Http\Response; -use Illuminate\Support\Facades\Storage; -use Postmark\PostmarkClient; +use App\Http\Requests\Client\DestroyClientRequest; +use App\Http\Requests\Client\ClientDocumentsRequest; +use App\Http\Requests\Client\ReactivateClientEmailRequest; +use App\Models\Expense; +use App\Models\Payment; +use App\Models\Task; +use App\Transformers\DocumentTransformer; /** * Class ClientController. @@ -402,4 +411,24 @@ class ClientController extends BaseController } } + + public function documents(ClientDocumentsRequest $request, Client $client) + { + + $this->entity_type = Document::class; + + $this->entity_transformer = DocumentTransformer::class; + + $documents = Document::query() + ->company() + ->whereHasMorph('documentable', [Invoice::class, Quote::class, Credit::class, Expense::class, Payment::class, Task::class], function ($query) use($client) { + $query->where('client_id', $client->id); + }) + ->orWhereHasMorph('documentable', [Client::class], function ($query) use ($client){ + $query->where('id', $client->id); + }); + + return $this->listResponse($documents); + + } } diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index edd51d5d6fb2..81ae40103380 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -260,7 +260,7 @@ class ImportController extends Controller } } - return $bestDelimiter; + return $bestDelimiter ?? ','; } } diff --git a/app/Http/Requests/Client/ClientDocumentsRequest.php b/app/Http/Requests/Client/ClientDocumentsRequest.php new file mode 100644 index 000000000000..bbcdf3cf0203 --- /dev/null +++ b/app/Http/Requests/Client/ClientDocumentsRequest.php @@ -0,0 +1,30 @@ +user(); + + return $user->can('view', $this->client); + } +} diff --git a/app/Http/Requests/Client/PurgeClientRequest.php b/app/Http/Requests/Client/PurgeClientRequest.php index 7af1ae59e820..68f4fe57cd8a 100644 --- a/app/Http/Requests/Client/PurgeClientRequest.php +++ b/app/Http/Requests/Client/PurgeClientRequest.php @@ -22,6 +22,9 @@ class PurgeClientRequest extends Request */ public function authorize(): bool { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin(); } } diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index db9d3807baba..56bda23e86af 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -152,7 +152,8 @@ class BaseImport } } - return $bestDelimiter; + + return $bestDelimiter ?? ','; } public function mapCSVHeaderToKeys($csvData) diff --git a/app/Jobs/Company/CompanyExport.php b/app/Jobs/Company/CompanyExport.php index 406ac09e9984..00b69d235bd7 100644 --- a/app/Jobs/Company/CompanyExport.php +++ b/app/Jobs/Company/CompanyExport.php @@ -111,13 +111,16 @@ $this->export_data = null; $this->export_data['users'] = $this->company->users()->withTrashed()->cursor()->map(function ($user) { $user->account_id = $this->encodePrimaryKey($user->account_id); + return $user; })->all(); + $x = $this->writer->collection('users'); $x->addItems($this->export_data['users']); $this->export_data = null; + $this->export_data['client_contacts'] = $this->company->client_contacts->map(function ($client_contact) { $client_contact = $this->transformArrayOfKeys($client_contact, ['company_id', 'user_id', 'client_id']); @@ -663,20 +666,9 @@ $this->writer->end(); private function zipAndSend() { - // $file_name = date('Y-m-d').'_'.str_replace([" ", "/"], ["_",""], $this->company->present()->name() . '_' . $this->company->company_key .'.zip'); $zip_path = \Illuminate\Support\Str::ascii(str_replace(".json", ".zip", $this->file_name)); - // $path = 'backups'; - // Storage::makeDirectory(storage_path('backups/')); - - // try { - // mkdir(storage_path('backups/')); - // } catch(\Exception $e) { - // nlog("could not create directory"); - // } - - // $zip_path = storage_path('backups/'.\Illuminate\Support\Str::ascii($file_name)); $zip = new \ZipArchive(); if ($zip->open($zip_path, \ZipArchive::CREATE) !== true) { @@ -686,7 +678,6 @@ $this->writer->end(); $zip->addFile($this->file_name); $zip->renameName($this->file_name, 'backup.json'); - // $zip->addFromString("backup.json", json_encode($this->export_data)); $zip->close(); Storage::disk(config('filesystems.default'))->put('backups/'.str_replace(".json", ".zip",$this->file_name), file_get_contents($zip_path)); @@ -695,6 +686,10 @@ $this->writer->end(); unlink($zip_path); } + if(file_exists($this->file_name)){ + unlink($this->file_name); + } + if(Ninja::isSelfHost()) { $storage_path = 'backups/'.str_replace(".json", ".zip",$this->file_name); } else { @@ -709,8 +704,6 @@ $this->writer->end(); $t = app('translator'); $t->replace(Ninja::transformTranslations($this->company->settings)); - // $company_reference = Company::find($this->company->id); - $nmo = new NinjaMailerObject(); $nmo->mailable = new DownloadBackup($url, $this->company->withoutRelations()); $nmo->to_user = $this->user; diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 4636fb40430e..0fe90c3e229d 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -312,7 +312,7 @@ class CompanyImport implements ShouldQueue } unlink($tmp_file); - unlink($this->file_location); + unlink(Storage::path($this->file_location)); } // diff --git a/routes/api.php b/routes/api.php index 8903843abe1f..904885c59174 100644 --- a/routes/api.php +++ b/routes/api.php @@ -168,6 +168,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('clients/{client}/updateTaxData', [ClientController::class, 'updateTaxData'])->name('clients.update_tax_data')->middleware('throttle:3,1'); Route::post('clients/{client}/{mergeable_client}/merge', [ClientController::class, 'merge'])->name('clients.merge')->middleware('password_protected'); Route::post('clients/bulk', [ClientController::class, 'bulk'])->name('clients.bulk'); + Route::post('clients/{client}/documents', [ClientController::class, 'documents'])->name('clients.documents'); Route::post('reactivate_email/{bounce_id}', [ClientController::class, 'reactivateEmail'])->name('clients.reactivate_email'); diff --git a/tests/Feature/ClientApiTest.php b/tests/Feature/ClientApiTest.php index 9cb18a2dc2e3..882fd3cdd459 100644 --- a/tests/Feature/ClientApiTest.php +++ b/tests/Feature/ClientApiTest.php @@ -59,6 +59,148 @@ class ClientApiTest extends TestCase Model::reguard(); } + public function testClientDocumentQuery() + { + + $d = \App\Models\Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $this->invoice->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients/{$this->client->hashed_id}/documents") + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(1, $arr['data']); + + $d = \App\Models\Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $this->client->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients/{$this->client->hashed_id}/documents") + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(2, $arr['data']); + + + $d = \App\Models\Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $this->client->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients/{$this->client->hashed_id}/documents") + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(3, $arr['data']); + + $d = \App\Models\Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $this->quote->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients/{$this->client->hashed_id}/documents") + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(4, $arr['data']); + + + + $d = \App\Models\Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + $this->credit->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients/{$this->client->hashed_id}/documents") + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(5, $arr['data']); + + + + $d = \App\Models\Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + ]); + + + $e = \App\Models\Expense::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'amount' => 100 + ]); + + + $e->documents()->save($d); + + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/clients/{$this->client->hashed_id}/documents") + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertCount(6, $arr['data']); + + +$d = \App\Models\Document::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, +]); + + +$t = \App\Models\Task::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, +]); + + +$t->documents()->save($d); + +$response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, +])->postJson("/api/v1/clients/{$this->client->hashed_id}/documents") +->assertStatus(200); + +$arr = $response->json(); + +$this->assertCount(7, $arr['data']); + + + + + } public function testCrossCompanyBulkActionsFail() {