diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index aa9bb7064d34..1b055e71ea78 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -962,6 +962,7 @@ class CompanySettings extends BaseSettings '$invoice.due_date', '$invoice.total', '$invoice.balance_due', + '$invoice.project', ], 'quote_details' => [ '$quote.number', @@ -969,6 +970,7 @@ class CompanySettings extends BaseSettings '$quote.date', '$quote.valid_until', '$quote.total', + '$quote.project', ], 'credit_details' => [ '$credit.number', diff --git a/app/Http/Controllers/ActivityController.php b/app/Http/Controllers/ActivityController.php index 93445d7caf09..0abd534023b4 100644 --- a/app/Http/Controllers/ActivityController.php +++ b/app/Http/Controllers/ActivityController.php @@ -11,19 +11,31 @@ namespace App\Http\Controllers; -use App\Http\Requests\Activity\DownloadHistoricalEntityRequest; -use App\Http\Requests\Activity\ShowActivityRequest; -use App\Models\Activity; -use App\Transformers\ActivityTransformer; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\Pdf\PageNumbering; -use App\Utils\Traits\Pdf\PdfMaker; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Storage; use stdClass; +use App\Utils\Ninja; +use App\Models\Client; +use App\Models\Invoice; +use App\Models\Activity; +use Illuminate\Http\Request; +use App\Utils\Traits\MakesHash; +use App\Utils\PhantomJS\Phantom; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\Traits\Pdf\PdfMaker; +use App\Utils\Traits\Pdf\PageNumbering; +use Illuminate\Support\Facades\Storage; +use App\Transformers\ActivityTransformer; +use App\Http\Requests\Activity\StoreNoteRequest; +use App\Http\Requests\Activity\ShowActivityRequest; +use App\Http\Requests\Activity\DownloadHistoricalEntityRequest; +use App\Models\Credit; +use App\Models\Expense; +use App\Models\Payment; +use App\Models\PurchaseOrder; +use App\Models\Quote; +use App\Models\RecurringExpense; +use App\Models\RecurringInvoice; +use App\Models\Task; +use App\Models\Vendor; class ActivityController extends BaseController { @@ -177,4 +189,88 @@ class ActivityController extends BaseController echo $pdf; }, $filename, ['Content-Type' => 'application/pdf']); } + + public function note(StoreNoteRequest $request) + { + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $entity = $request->getEntity(); + + $activity = new Activity(); + $activity->account_id = $user->account_id; + $activity->company_id = $user->company()->id; + $activity->notes = $request->notes; + $activity->user_id = $user->id; + $activity->ip = $request->ip(); + + switch (get_class($entity)) { + case Invoice::class: + $activity->invoice_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + $activity->vendor_id = $entity->vendor_id; + break; + case Credit::class: + $activity->credit_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + $activity->vendor_id = $entity->vendor_id; + $activity->invoice_id = $entity->invoice_id; + break; + case Client::class: + $activity->client_id = $entity->id; + break; + case Quote::class: + $activity->quote_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + $activity->vendor_id = $entity->vendor_id; + break; + case RecurringInvoice::class: + $activity->recurring_invoice_id = $entity->id; + $activity->client_id = $entity->client_id; + break; + case Expense::class: + $activity->expense_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + $activity->vendor_id = $entity->vendor_id; + break; + case RecurringExpense::class: + $activity->recurring_expense_id = $entity->id; + $activity->expense_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + $activity->vendor_id = $entity->vendor_id; + break; + case Vendor::class: + $activity->vendor_id = $entity->id; + break; + case PurchaseOrder::class: + $activity->purchase_order_id = $entity->id; + $activity->expense_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + $activity->vendor_id = $entity->vendor_id; + case Task::class: + $activity->task_id = $entity->id; + $activity->expense_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + $activity->vendor_id = $entity->vendor_id; + case Payment::class: + $activity->payment_id = $entity->id; + $activity->expense_id = $entity->id; + $activity->client_id = $entity->client_id; + $activity->project_id = $entity->project_id; + default: + # code... + break; + } + + $activity->save(); + + return $this->itemResponse($activity); + } } diff --git a/app/Jobs/User/VerifyPhone.php b/app/Jobs/User/VerifyPhone.php index 54aa7565289b..58281a07ec77 100644 --- a/app/Jobs/User/VerifyPhone.php +++ b/app/Jobs/User/VerifyPhone.php @@ -56,7 +56,7 @@ class VerifyPhone implements ShouldQueue $twilio = new \Twilio\Rest\Client($sid, $token); - $country = $this->user->account?->companies()?->first()?->country(); + $country = $this->user->account?->companies()?->first()?->country(); //@phpstan-ignore-line if (!$country || strlen($this->user->phone) < 2) { return; @@ -73,7 +73,7 @@ class VerifyPhone implements ShouldQueue return; } - if ($phone_number && strlen($phone_number->phoneNumber) > 1) { + if ($phone_number && strlen($phone_number->phoneNumber) > 1) { //@phpstan-ignore-line $this->user->phone = $phone_number->phoneNumber; $this->user->verified_phone_number = true; $this->user->save(); diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 97e561b41c97..37e235c7fee4 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -261,7 +261,8 @@ class Activity extends StaticModel public const EMAIL_STATEMENT = 140; - + public const USER_NOTE = 141; + protected $casts = [ 'is_system' => 'boolean', 'updated_at' => 'timestamp', diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 073d7d55b882..f85f3909be52 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -222,6 +222,7 @@ class HtmlEngine if ($this->entity->project) { $data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project')]; $data['$invoice.project'] = &$data['$project.name']; + $data['$quote.project'] = &$data['$project.name']; } $data['$status_logo'] = ['value' => '
' . ctrans('texts.paid') .'
', 'label' => '']; @@ -276,8 +277,10 @@ class HtmlEngine $data['$credit.custom4'] = &$data['$quote.custom4']; if ($this->entity->project) { - $data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project_name')]; + $data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project_name')]; $data['$invoice.project'] = &$data['$project.name']; + $data['$quote.project'] = &$data['$project.name']; + } if ($this->entity->vendor) { diff --git a/lang/en/texts.php b/lang/en/texts.php index 94f85a222dfa..5de0e456cf28 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2364,7 +2364,7 @@ $lang = array( 'currency_gold_troy_ounce' => 'Gold Troy Ounce', 'currency_nicaraguan_córdoba' => 'Nicaraguan Córdoba', 'currency_malagasy_ariary' => 'Malagasy ariary', - "currency_tongan_pa_anga" => "Tongan Pa'anga", + "currency_tongan_paanga" => "Tongan Pa'anga", 'review_app_help' => 'We hope you\'re enjoying using the app.
If you\'d consider :link we\'d greatly appreciate it!', 'writing_a_review' => 'writing a review', @@ -5269,6 +5269,7 @@ $lang = array( 'merge_e_invoice_to_pdf' => 'Merge E-Invoice and PDF', 'task_assigned_subject' => 'New task assignment [Task :task] [ :date ]', 'task_assigned_body' => 'You have been assigned task :task

Description: :description

Client: :client', + 'activity_141' => 'User :user entered note: :notes', ); return $lang; \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 9befaf9a07f8..556e3ab6bc8b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -155,6 +155,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::get('activities', [ActivityController::class, 'index']); Route::post('activities/entity', [ActivityController::class, 'entityActivity']); + Route::post('activities/notes', [ActivityController::class, 'note']); Route::get('activities/download_entity/{activity}', [ActivityController::class, 'downloadHistoricalEntity']); Route::post('charts/totals', [ChartController::class, 'totals'])->name('chart.totals'); diff --git a/tests/Feature/ActivityApiTest.php b/tests/Feature/ActivityApiTest.php index 9b81136bb2d2..8b381a0dd821 100644 --- a/tests/Feature/ActivityApiTest.php +++ b/tests/Feature/ActivityApiTest.php @@ -40,6 +40,250 @@ class ActivityApiTest extends TestCase } + public function testActivityInvoiceNotes() + { + $data = [ + 'entity' => 'invoices', + 'entity_id' => $this->invoice->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + public function testActivityCreditNotes() + { + $data = [ + 'entity' => 'credits', + 'entity_id' => $this->credit->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + public function testActivityQuoteNotes() + { + $data = [ + 'entity' => 'quotes', + 'entity_id' => $this->quote->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + + public function testActivityClientNotes() + { + $data = [ + 'entity' => 'clients', + 'entity_id' => $this->client->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + + public function testActivityRecurringInvoiceNotes() + { + $data = [ + 'entity' => 'recurring_invoices', + 'entity_id' => $this->recurring_invoice->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + + public function testActivityExpenseNotes() + { + $data = [ + 'entity' => 'expenses', + 'entity_id' => $this->expense->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + public function testActivityRecurringExpenseNotes() + { + $data = [ + 'entity' => 'recurring_expenses', + 'entity_id' => $this->recurring_expense->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + + public function testActivityVendorNotes() + { + $data = [ + 'entity' => 'vendors', + 'entity_id' => $this->vendor->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + public function testActivityPurchaseOrderNotes() + { + $data = [ + 'entity' => 'purchase_orders', + 'entity_id' => $this->purchase_order->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + public function testActivityTaskNotes() + { + $data = [ + 'entity' => 'tasks', + 'entity_id' => $this->task->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + public function testActivityProjectNotes() + { + $data = [ + 'entity' => 'projects', + 'entity_id' => $this->project->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + + public function testActivityPaymentNotes() + { + $data = [ + 'entity' => 'payments', + 'entity_id' => $this->payment->hashed_id, + 'notes' => 'These are notes' + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/activities/notes', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('These are notes', $arr['data']['notes']); + } + public function testActivityEntity() {