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()
{