Merge pull request #8515 from turbo124/v5-develop

v5.5.119
This commit is contained in:
David Bomba 2023-05-22 07:35:32 +10:00 committed by GitHub
commit 35e527c4fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 533 additions and 668 deletions

View File

@ -1 +1 @@
5.5.118 5.5.119

View File

@ -17,7 +17,6 @@ use App\Utils\Ninja;
use Sentry\State\Scope; use Sentry\State\Scope;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Sentry\Laravel\Integration; use Sentry\Laravel\Integration;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\ConnectException;
@ -35,20 +34,21 @@ use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
use InvalidArgumentException;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
/** /**
* A list of the exception types that are not reported. * A list of the exception types that are not reported.
* *
* @var array * @var array<int, class-string<Throwable>>
*/ */
protected $dontReport = [ protected $dontReport = [
PDOException::class, // PDOException::class,
MaxAttemptsExceededException::class, MaxAttemptsExceededException::class,
CommandNotFoundException::class, CommandNotFoundException::class,
ValidationException::class, ValidationException::class,
ModelNotFoundException::class, // ModelNotFoundException::class,
NotFoundHttpException::class, NotFoundHttpException::class,
]; ];

View File

@ -40,7 +40,7 @@ class RecurringExpenseToExpenseFactory
$expense->tax_name3 = $recurring_expense->tax_name3; $expense->tax_name3 = $recurring_expense->tax_name3;
$expense->tax_rate3 = $recurring_expense->tax_rate3; $expense->tax_rate3 = $recurring_expense->tax_rate3;
$expense->date = now()->format('Y-m-d'); $expense->date = now()->format('Y-m-d');
$expense->payment_date = $recurring_expense->payment_date; $expense->payment_date = $recurring_expense->payment_date ?: now()->format('Y-m-d');
$expense->amount = $recurring_expense->amount; $expense->amount = $recurring_expense->amount;
$expense->foreign_amount = $recurring_expense->foreign_amount ?: 0; $expense->foreign_amount = $recurring_expense->foreign_amount ?: 0;

View File

@ -22,45 +22,6 @@ class ChartController extends BaseController
} }
/** /**
* @OA\Post(
* path="/api/v1/charts/totals",
* operationId="getChartTotals",
* tags={"charts"},
* summary="Get chart data",
* description="Get chart data",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Parameter(
* name="rows",
* in="query",
* description="The number of activities to return",
* example="50",
* required=false,
* @OA\Schema(
* type="number",
* format="integer",
* ),
* ),
* @OA\Response(
* response=200,
* description="json dataset of chart data",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param ShowChartRequest $request * @param ShowChartRequest $request
*/ */
public function totals(ShowChartRequest $request) public function totals(ShowChartRequest $request)
@ -81,4 +42,28 @@ class ChartController extends BaseController
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200); return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200);
} }
/**
* @param ShowChartRequest $request
*/
public function totalsV2(ShowChartRequest $request)
{
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
$cs = new ChartService($user->company(), $user, $user->isAdmin());
return response()->json($cs->totals($request->input('start_date'), $request->input('end_date')), 200);
}
public function chart_summaryV2(ShowChartRequest $request)
{
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
$cs = new ChartService($user->company(), $user, $user->isAdmin());
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200);
}
} }

View File

@ -146,7 +146,10 @@ class DocumentController extends BaseController
*/ */
public function update(UpdateDocumentRequest $request, Document $document) public function update(UpdateDocumentRequest $request, Document $document)
{ {
return $this->itemResponse($document); $document->fill($request->all());
$document->save();
return $this->itemResponse($document->fresh());
} }
/** /**

View File

@ -37,28 +37,6 @@ class TaskSchedulerController extends BaseController
parent::__construct(); parent::__construct();
} }
/**
* @OA\GET(
* path="/api/v1/task_schedulers/",
* operationId="getTaskSchedulers",
* tags={"task_schedulers"},
* summary="Task Scheduler Index",
* description="Get all schedulers with associated jobs",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function index(SchedulerFilters $filters) public function index(SchedulerFilters $filters)
{ {
$schedulers = Scheduler::filter($filters); $schedulers = Scheduler::filter($filters);
@ -66,44 +44,6 @@ class TaskSchedulerController extends BaseController
return $this->listResponse($schedulers); return $this->listResponse($schedulers);
} }
/**
* Show the form for creating a new resource.
*
* @param CreateSchedulerRequest $request The request
*
* @return Response
*
*
* @OA\Get(
* path="/api/v1/invoices/task_schedulers",
* operationId="getTaskScheduler",
* tags={"task_schedulers"},
* summary="Gets a new blank scheduler object",
* description="Returns a blank object with default values",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="A blank scheduler object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskSchedulerSchema"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function create(CreateSchedulerRequest $request) public function create(CreateSchedulerRequest $request)
{ {
$scheduler = SchedulerFactory::create(auth()->user()->company()->id, auth()->user()->id); $scheduler = SchedulerFactory::create(auth()->user()->company()->id, auth()->user()->id);
@ -111,39 +51,6 @@ class TaskSchedulerController extends BaseController
return $this->itemResponse($scheduler); return $this->itemResponse($scheduler);
} }
/**
* @OA\Post(
* path="/api/v1/task_schedulers/",
* operationId="createTaskScheduler",
* tags={"task_schedulers"},
* summary="Create task scheduler with job ",
* description="Create task scheduler with a job (action(job) request should be sent via request also. Example: We want client report to be job which will be run
* multiple times, we should send the same parameters in the request as we would send if we wanted to get report, see example",
* @OA\Parameter(ref="#/components/parameters/X-API-SECRET"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/TaskSchedulerSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function store(StoreSchedulerRequest $request) public function store(StoreSchedulerRequest $request)
{ {
$scheduler = $this->scheduler_repository->save($request->all(), SchedulerFactory::create(auth()->user()->company()->id, auth()->user()->id)); $scheduler = $this->scheduler_repository->save($request->all(), SchedulerFactory::create(auth()->user()->company()->id, auth()->user()->id));
@ -151,86 +58,12 @@ class TaskSchedulerController extends BaseController
return $this->itemResponse($scheduler); return $this->itemResponse($scheduler);
} }
/**
* @OA\GET(
* path="/api/v1/task_schedulers/{id}",
* operationId="showTaskScheduler",
* tags={"task_schedulers"},
* summary="Show given scheduler",
* description="Get scheduler with associated job",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Scheduler Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function show(ShowSchedulerRequest $request, Scheduler $scheduler) public function show(ShowSchedulerRequest $request, Scheduler $scheduler)
{ {
return $this->itemResponse($scheduler); return $this->itemResponse($scheduler);
} }
/**
* @OA\PUT(
* path="/api/v1/task_schedulers/{id}",
* operationId="updateTaskScheduler",
* tags={"task_schedulers"},
* summary="Update task scheduler ",
* description="Update task scheduler",
* @OA\Parameter(ref="#/components/parameters/X-API-SECRET"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Scheduler Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ), * @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/TaskSchedulerSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function update(UpdateSchedulerRequest $request, Scheduler $scheduler) public function update(UpdateSchedulerRequest $request, Scheduler $scheduler)
{ {
$this->scheduler_repository->save($request->all(), $scheduler); $this->scheduler_repository->save($request->all(), $scheduler);
@ -238,39 +71,6 @@ class TaskSchedulerController extends BaseController
return $this->itemResponse($scheduler); return $this->itemResponse($scheduler);
} }
/**
* @OA\DELETE(
* path="/api/v1/task_schedulers/{id}",
* operationId="destroyTaskScheduler",
* tags={"task_schedulers"},
* summary="Destroy Task Scheduler",
* description="Destroy task scheduler and its associated job",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Scheduler Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function destroy(DestroySchedulerRequest $request, Scheduler $scheduler) public function destroy(DestroySchedulerRequest $request, Scheduler $scheduler)
{ {
$this->scheduler_repository->delete($scheduler); $this->scheduler_repository->delete($scheduler);
@ -278,57 +78,6 @@ class TaskSchedulerController extends BaseController
return $this->itemResponse($scheduler->fresh()); return $this->itemResponse($scheduler->fresh());
} }
/**
* Perform bulk actions on the list view.
*
* @return Response
*
*
* @OA\Post(
* path="/api/v1/task_schedulers/bulk",
* operationId="bulkTaskSchedulerActions",
* tags={"task_schedulers"},
* summary="Performs bulk actions on an array of task_schedulers",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\RequestBody(
* description="array of ids",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="integer",
* description="Array of hashed IDs to be bulk 'actioned",
* example="[0,1,2,3]",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="The TaskSchedule response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskSchedulerSchema"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function bulk() public function bulk()
{ {
$action = request()->input('action'); $action = request()->input('action');

View File

@ -1,4 +1,13 @@
<?php <?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers; namespace App\Http\Controllers;
@ -10,12 +19,10 @@ use App\Http\Requests\TaskStatus\DestroyTaskStatusRequest;
use App\Http\Requests\TaskStatus\ShowTaskStatusRequest; use App\Http\Requests\TaskStatus\ShowTaskStatusRequest;
use App\Http\Requests\TaskStatus\StoreTaskStatusRequest; use App\Http\Requests\TaskStatus\StoreTaskStatusRequest;
use App\Http\Requests\TaskStatus\UpdateTaskStatusRequest; use App\Http\Requests\TaskStatus\UpdateTaskStatusRequest;
use App\Models\Task;
use App\Models\TaskStatus; use App\Models\TaskStatus;
use App\Repositories\TaskStatusRepository; use App\Repositories\TaskStatusRepository;
use App\Transformers\TaskStatusTransformer; use App\Transformers\TaskStatusTransformer;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
class TaskStatusController extends BaseController class TaskStatusController extends BaseController
@ -44,36 +51,10 @@ class TaskStatusController extends BaseController
} }
/** /**
* @OA\Get( * index
* path="/api/v1/task_statuses", *
* operationId="getTaskStatuses", * @param TaskStatusFilters $filters
* tags={"task_status"}, * @return Response
* summary="Gets a list of task statuses",
* description="Lists task statuses",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Response(
* response=200,
* description="A list of task statuses",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/ */
public function index(TaskStatusFilters $filters) public function index(TaskStatusFilters $filters)
{ {
@ -82,44 +63,12 @@ class TaskStatusController extends BaseController
return $this->listResponse($task_status); return $this->listResponse($task_status);
} }
/** /**
* Show the form for creating a new resource. * create
*
* @param CreateTaskStatusRequest $request The request
* *
* @param CreateTaskStatusRequest $request
* @return Response * @return Response
*
*
*
* @OA\Get(
* path="/api/v1/task_statuses/create",
* operationId="getTaskStatussCreate",
* tags={"task_status"},
* summary="Gets a new blank TaskStatus object",
* description="Returns a blank object with default values",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="A blank TaskStatus object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/ */
public function create(CreateTaskStatusRequest $request) public function create(CreateTaskStatusRequest $request)
{ {
@ -132,49 +81,11 @@ class TaskStatusController extends BaseController
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
* @param StoreTaskStatusRequest $request The request * @param StoreTaskStatusRequest $request The request
*
* @return Response * @return Response
* *
* */
*
* @OA\Post(
* path="/api/v1/task_statuses",
* operationId="storeTaskStatus",
* tags={"task_status"},
* summary="Adds a TaskStatus",
* description="Adds a TaskStatusto the system",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\RequestBody(
* description="The task_status request",
* required=true,
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=200,
* description="Returns the saved TaskStatus object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function store(StoreTaskStatusRequest $request) public function store(StoreTaskStatusRequest $request)
{ {
// nlog($request->all());
$task_status = TaskStatusFactory::create(auth()->user()->company()->id, auth()->user()->id); $task_status = TaskStatusFactory::create(auth()->user()->company()->id, auth()->user()->id);
$task_status->fill($request->all()); $task_status->fill($request->all());
@ -185,46 +96,6 @@ class TaskStatusController extends BaseController
} }
/** /**
* @OA\Get(
* path="/api/v1/task_statuses/{id}",
* operationId="showTaskStatus",
* tags={"task_status"},
* summary="Shows a TaskStatus Term",
* description="Displays an TaskStatusby id",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The TaskStatusHashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the TaskStatusobject",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param ShowTaskStatusRequest $request * @param ShowTaskStatusRequest $request
* @param TaskStatus $task_status * @param TaskStatus $task_status
* @return Response|mixed * @return Response|mixed
@ -235,46 +106,6 @@ class TaskStatusController extends BaseController
} }
/** /**
* @OA\Get(
* path="/api/v1/task_statuses/{id}/edit",
* operationId="editTaskStatuss",
* tags={"task_status"},
* summary="Shows an TaskStatusfor editting",
* description="Displays an TaskStatusby id",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The TaskStatusHashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the TaskStatus object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param EditTaskStatusRequest $request * @param EditTaskStatusRequest $request
* @param TaskStatus $payment * @param TaskStatus $payment
* @return Response|mixed * @return Response|mixed
@ -289,56 +120,18 @@ class TaskStatusController extends BaseController
* *
* @param UpdateTaskStatusRequest $request The request * @param UpdateTaskStatusRequest $request The request
* @param TaskStatus $task_status The payment term * @param TaskStatus $task_status The payment term
*
* @return Response * @return Response
*
*
* @OA\Put(
* path="/api/v1/task_statuses/{id}",
* operationId="updateTaskStatus",
* tags={"task_status"},
* summary="Updates a TaskStatus Term",
* description="Handles the updating of an TaskStatus Termby id",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The TaskStatusHashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the TaskStatusobject",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/ */
public function update(UpdateTaskStatusRequest $request, TaskStatus $task_status) public function update(UpdateTaskStatusRequest $request, TaskStatus $task_status)
{ {
$task_status->fill($request->all()); $task_status->fill($request->all());
$reorder = $task_status->isDirty('status_order');
$task_status->save(); $task_status->save();
if ($reorder)
$this->task_status_repo->reorder($task_status);
return $this->itemResponse($task_status->fresh()); return $this->itemResponse($task_status->fresh());
} }
@ -347,50 +140,9 @@ class TaskStatusController extends BaseController
* *
* @param DestroyTaskStatusRequest $request * @param DestroyTaskStatusRequest $request
* @param TaskStatus $task_status * @param TaskStatus $task_status
* * @return Response
* @return Response
*
* *
* @throws \Exception * @throws \Exception
* @OA\Delete(
* path="/api/v1/task_statuses/{id}",
* operationId="deleteTaskStatus",
* tags={"task_statuss"},
* summary="Deletes a TaskStatus Term",
* description="Handles the deletion of an TaskStatus by id",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The TaskStatusHashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns a HTTP status",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/ */
public function destroy(DestroyTaskStatusRequest $request, TaskStatus $task_status) public function destroy(DestroyTaskStatusRequest $request, TaskStatus $task_status)
{ {
@ -401,54 +153,8 @@ class TaskStatusController extends BaseController
/** /**
* Perform bulk actions on the list view. * Perform bulk actions on the list view.
* * @param ActionTaskStatusRequest $request
* @return Collection * @return Response
*
*
* @OA\Post(
* path="/api/v1/task_statuses/bulk",
* operationId="bulkTaskStatuss",
* tags={"task_status"},
* summary="Performs bulk actions on an array of task statuses",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\RequestBody(
* description="TaskStatus Ter,s",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="integer",
* description="Array of hashed IDs to be bulk 'actioned",
* example="[0,1,2,3]",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="The TaskStatus Terms response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/TaskStatus"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/ */
public function bulk(ActionTaskStatusRequest $request) public function bulk(ActionTaskStatusRequest $request)
{ {

View File

@ -63,7 +63,7 @@ class UpdateCompanyRequest extends Request
if (Ninja::isHosted()) { if (Ninja::isHosted()) {
$rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain($this->all())]; $rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain($this->all())];
} else { } else {
$rules['subdomain'] = 'nullable|alpha_num'; $rules['subdomain'] = 'nullable|regex:/^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/';
} }
} }

View File

@ -30,7 +30,9 @@ class UpdateDocumentRequest extends Request
public function rules() public function rules()
{ {
return []; return [
'name' => 'sometimes|alpha_num'
];
} }
public function prepareForValidation() public function prepareForValidation()

View File

@ -60,7 +60,5 @@ class StoreSchedulerRequest extends Request
$this->merge(['next_run_client' => $input['next_run']]); $this->merge(['next_run_client' => $input['next_run']]);
} }
$this->replace($input);
} }
} }

View File

@ -57,6 +57,5 @@ class UpdateSchedulerRequest extends Request
$this->merge(['next_run_client' => $input['next_run']]); $this->merge(['next_run_client' => $input['next_run']]);
} }
$this->replace($input);
} }
} }

View File

@ -33,11 +33,6 @@ class UpdateTaskStatusRequest extends Request
{ {
$rules = []; $rules = [];
// 26/10/2021 we disable this as it prevent updating existing task status meta data where the same name already exists
// if ($this->input('name')) {
// $rules['name'] = Rule::unique('task_statuses')->where('company_id', auth()->user()->company()->id)->ignore($this->task_status->id);
// }
return $rules; return $rules;
} }

View File

@ -98,15 +98,10 @@ class RecurringExpensesCron
} }
} }
private function getRecurringExpenses()
{
//extracting this back to the if/else block to test duplicate crons
}
private function generateExpense(RecurringExpense $recurring_expense) private function generateExpense(RecurringExpense $recurring_expense)
{ {
$expense = RecurringExpenseToExpenseFactory::create($recurring_expense); $expense = RecurringExpenseToExpenseFactory::create($recurring_expense);
$expense->save(); $expense->saveQuietly();
$expense->number = $this->getNextExpenseNumber($expense); $expense->number = $this->getNextExpenseNumber($expense);
$expense->save(); $expense->save();

View File

@ -11,13 +11,14 @@
namespace App\Jobs\Invoice; namespace App\Jobs\Invoice;
use App\Libraries\MultiDB;
use App\Models\Invoice; use App\Models\Invoice;
use App\Libraries\MultiDB;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class CheckGatewayFee implements ShouldQueue class CheckGatewayFee implements ShouldQueue
{ {
@ -40,6 +41,8 @@ class CheckGatewayFee implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
nlog("Checking Gateway Fees for Invoice Id = {$this->invoice_id}");
MultiDB::setDb($this->db); MultiDB::setDb($this->db);
$i = Invoice::withTrashed()->find($this->invoice_id); $i = Invoice::withTrashed()->find($this->invoice_id);
@ -52,4 +55,9 @@ class CheckGatewayFee implements ShouldQueue
$i->service()->removeUnpaidGatewayFees(); $i->service()->removeUnpaidGatewayFees();
} }
} }
public function middleware()
{
return [(new WithoutOverlapping($this->invoice_id.$this->db))];
}
} }

View File

@ -72,7 +72,9 @@ class NinjaMailerJob implements ShouldQueue
public function backoff() public function backoff()
{ {
return [5, 10, 30, 240]; // return [5, 10, 30, 240];
return [rand(5, 10), rand(30, 40), rand(60, 79), rand(160, 400)];
} }
public function handle() public function handle()

View File

@ -63,7 +63,9 @@ class WebhookSingle implements ShouldQueue
public function backoff() public function backoff()
{ {
return [15, 35, 65, 185, 3605]; // return [15, 35, 65, 185, 3605];
return [rand(10, 15), rand(30, 40), rand(60, 79), rand(160, 200), rand(3000, 5000)];
} }
/** /**
@ -170,9 +172,9 @@ class WebhookSingle implements ShouldQueue
} }
if ($e->getResponse()->getStatusCode() >= 500) { if ($e->getResponse()->getStatusCode() >= 500) {
nlog("endpoint returned a 500, failing"); nlog("{$subscription->target_url} returned a 500, failing");
$message = "The was a problem when connecting to {$subscription->target_url} => status code ". $e->getResponse()->getStatusCode(). " no retry attempted."; $message = "There was a problem when connecting to {$subscription->target_url} => status code ". $e->getResponse()->getStatusCode(). " no retry attempted.";
(new SystemLogger( (new SystemLogger(
['message' => $message], ['message' => $message],

View File

@ -11,16 +11,17 @@
namespace App\Listeners\User; namespace App\Listeners\User;
use App\Models\SystemLog;
use App\Libraries\MultiDB;
use App\Jobs\Util\SystemLogger;
use App\Mail\User\UserLoggedIn;
use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Util\SystemLogger; use Illuminate\Support\Facades\Cache;
use App\Libraries\MultiDB; use Illuminate\Queue\SerializesModels;
use App\Mail\User\UserLoggedIn;
use App\Models\SystemLog;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Broadcasting\InteractsWithSockets;
class UpdateUserLastLogin implements ShouldQueue class UpdateUserLastLogin implements ShouldQueue
{ {
@ -51,8 +52,10 @@ class UpdateUserLastLogin implements ShouldQueue
$event_vars = $event->event_vars; $event_vars = $event->event_vars;
$ip = array_key_exists('ip', $event->event_vars) ? $event->event_vars['ip'] : 'IP address not resolved'; $ip = array_key_exists('ip', $event->event_vars) ? $event->event_vars['ip'] : 'IP address not resolved';
$key = "user_logged_in_{$user->id}{$event->company->db}";
if ($user->ip != $ip) {
if ($user->ip != $ip && is_null(Cache::get($key))) {
$nmo = new NinjaMailerObject; $nmo = new NinjaMailerObject;
$nmo->mailable = new UserLoggedIn($user, $user->account->companies->first(), $ip); $nmo->mailable = new UserLoggedIn($user, $user->account->companies->first(), $ip);
$nmo->company = $user->account->companies->first(); $nmo->company = $user->account->companies->first();
@ -64,6 +67,7 @@ class UpdateUserLastLogin implements ShouldQueue
$user->save(); $user->save();
} }
Cache::put($key, true, 60 * 24);
$arr = json_encode(['ip' => $ip]); $arr = json_encode(['ip' => $ip]);
SystemLogger::dispatch( SystemLogger::dispatch(

View File

@ -250,6 +250,7 @@ class Client extends BaseModel implements HasLocalePreference
'phone', 'phone',
'number', 'number',
'routing_id', 'routing_id',
'is_tax_exempt',
]; ];
protected $with = [ protected $with = [

View File

@ -153,6 +153,9 @@ class Scheduler extends BaseModel
$offset = $this->company->timezone_offset(); $offset = $this->company->timezone_offset();
switch ($this->frequency_id) { switch ($this->frequency_id) {
case 0: //used only for email entities
$next_run = now()->startOfDay();
break;
case RecurringInvoice::FREQUENCY_DAILY: case RecurringInvoice::FREQUENCY_DAILY:
$next_run = now()->startOfDay()->addDay(); $next_run = now()->startOfDay()->addDay();
break; break;

View File

@ -63,7 +63,7 @@ class TaskStatus extends BaseModel
public $timestamps = true; public $timestamps = true;
/** /**
* @var array * @var array<string>
*/ */
protected $fillable = [ protected $fillable = [
'name', 'name',

View File

@ -25,10 +25,14 @@ class SchedulerRepository extends BaseRepository
*/ */
public function save(array $data, Scheduler $scheduler): Scheduler public function save(array $data, Scheduler $scheduler): Scheduler
{ {
$scheduler->fill($data); $scheduler->fill($data);
$scheduler->save(); $scheduler->save();
return $scheduler; /** 18-5-2023 set client specific send times. */
$scheduler->calculateNextRun();
return $scheduler->fresh();
} }
} }

View File

@ -53,4 +53,18 @@ class TaskStatusRepository extends BaseRepository
return $task_status; return $task_status;
} }
public function reorder(TaskStatus $task_status)
{
TaskStatus::query()
->where('company_id', $task_status->company_id)
->orderByRaw('ISNULL(status_order), status_order ASC')
->orderBy('updated_at', 'DESC')
->cursor()
->each(function ($task_status, $index) {
$task_status->update(['status_order' => $index+1]);
});
}
} }

View File

@ -0,0 +1,169 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Chart;
use Illuminate\Support\Facades\DB;
/**
* Class ChartQueries.
*/
trait ChartQueriesLegacy
{
/**
* Expenses
*/
public function getExpenseQuery($start_date, $end_date)
{
return DB::select(DB::raw('
SELECT sum(expenses.amount) as amount,
IFNULL(expenses.currency_id, :company_currency) as currency_id
FROM expenses
WHERE expenses.is_deleted = 0
AND expenses.company_id = :company_id
AND (expenses.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
'), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $start_date, 'end_date' => $end_date]);
}
public function getExpenseChartQuery($start_date, $end_date, $currency_id)
{
return DB::select(DB::raw('
SELECT
sum(expenses.amount) as total,
expenses.date,
IFNULL(expenses.currency_id, :company_currency) AS currency_id
FROM expenses
WHERE (expenses.date BETWEEN :start_date AND :end_date)
AND expenses.company_id = :company_id
AND expenses.is_deleted = 0
GROUP BY expenses.date
HAVING currency_id = :currency_id
'), [
'company_currency' => $this->company->settings->currency_id,
'currency_id' => $currency_id,
'company_id' => $this->company->id,
'start_date' => $start_date,
'end_date' => $end_date,
]);
}
/**
* Payments
*/
public function getPaymentQuery($start_date, $end_date)
{
return DB::select(DB::raw('
SELECT sum(payments.amount) as amount,
IFNULL(payments.currency_id, :company_currency) as currency_id
FROM payments
WHERE payments.is_deleted = 0
AND payments.company_id = :company_id
AND (payments.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
'), [
'company_currency' => $this->company->settings->currency_id,
'company_id' => $this->company->id,
'start_date' => $start_date,
'end_date' => $end_date,
]);
}
public function getPaymentChartQuery($start_date, $end_date, $currency_id)
{
return DB::select(DB::raw('
SELECT
sum(payments.amount - payments.refunded) as total,
payments.date,
IFNULL(payments.currency_id, :company_currency) AS currency_id
FROM payments
WHERE payments.status_id IN (4,5,6)
AND (payments.date BETWEEN :start_date AND :end_date)
AND payments.company_id = :company_id
AND payments.is_deleted = 0
GROUP BY payments.date
HAVING currency_id = :currency_id
'), [
'company_currency' => $this->company->settings->currency_id,
'currency_id' => $currency_id,
'company_id' => $this->company->id,
'start_date' => $start_date,
'end_date' => $end_date,
]);
}
/**
* Invoices
*/
public function getOutstandingQuery($start_date, $end_date)
{
return DB::select(DB::raw("
SELECT
sum(invoices.balance) as amount,
IFNULL(JSON_EXTRACT( settings, '$.currency_id' ), :company_currency) AS currency_id
FROM clients
JOIN invoices
on invoices.client_id = clients.id
WHERE invoices.status_id IN (2,3)
AND invoices.company_id = :company_id
AND invoices.balance > 0
AND clients.is_deleted = 0
AND invoices.is_deleted = 0
AND (invoices.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
"), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $start_date, 'end_date' => $end_date]);
}
public function getRevenueQuery($start_date, $end_date)
{
return DB::select(DB::raw("
SELECT
sum(invoices.paid_to_date) as paid_to_date,
IFNULL(JSON_EXTRACT( settings, '$.currency_id' ), :company_currency) AS currency_id
FROM clients
JOIN invoices
on invoices.client_id = clients.id
WHERE invoices.status_id IN (3,4)
AND invoices.company_id = :company_id
AND invoices.amount > 0
AND clients.is_deleted = 0
AND invoices.is_deleted = 0
AND (invoices.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
"), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $start_date, 'end_date' => $end_date]);
}
public function getInvoiceChartQuery($start_date, $end_date, $currency_id)
{
return DB::select(DB::raw("
SELECT
sum(invoices.amount) as total,
invoices.date,
IFNULL(CAST(JSON_EXTRACT( settings, '$.currency_id' ) AS SIGNED), :company_currency) AS currency_id
FROM clients
JOIN invoices
on invoices.client_id = clients.id
WHERE invoices.status_id IN (2,3,4)
AND (invoices.date BETWEEN :start_date AND :end_date)
AND invoices.company_id = :company_id
AND clients.is_deleted = 0
AND invoices.is_deleted = 0
GROUP BY invoices.date
HAVING currency_id = :currency_id
"), [
'company_currency' => (int) $this->company->settings->currency_id,
'currency_id' => $currency_id,
'company_id' => $this->company->id,
'start_date' => $start_date,
'end_date' => $end_date,
]);
}
}

View File

@ -0,0 +1,162 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Chart;
use App\Models\Client;
use App\Models\Company;
use App\Models\Expense;
use Illuminate\Support\Facades\Cache;
class ChartServiceLegacy
{
use ChartQueriesLegacy;
public Company $company;
public function __construct(Company $company)
{
$this->company = $company;
}
/**
* Returns an array of currencies that have
* transacted with a company
*/
public function getCurrencyCodes() :array
{
/* Get all the distinct client currencies */
$currencies = Client::withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->distinct()
->pluck('settings->currency_id as id');
/* Push the company currency on also */
$currencies->push((int) $this->company->settings->currency_id);
/* Add our expense currencies*/
$expense_currencies = Expense::withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->distinct()
->pluck('currency_id as id');
/* Merge and filter by unique */
$currencies = $currencies->merge($expense_currencies)->unique();
$cache_currencies = Cache::get('currencies');
$filtered_currencies = $cache_currencies->whereIn('id', $currencies)->all();
$final_currencies = [];
foreach ($filtered_currencies as $c_currency) {
$final_currencies[$c_currency['id']] = $c_currency['code'];
}
return $final_currencies;
}
/* Chart Data */
public function chart_summary($start_date, $end_date) :array
{
$currencies = $this->getCurrencyCodes();
$data = [];
foreach ($currencies as $key => $value) {
$data[$key]['invoices'] = $this->getInvoiceChartQuery($start_date, $end_date, $key);
$data[$key]['payments'] = $this->getPaymentChartQuery($start_date, $end_date, $key);
$data[$key]['expenses'] = $this->getExpenseChartQuery($start_date, $end_date, $key);
}
return $data;
}
/* Chart Data */
/* Totals */
public function totals($start_date, $end_date) :array
{
$data = [];
$data['currencies'] = $this->getCurrencyCodes();
foreach ($data['currencies'] as $key => $value) {
$revenue = $this->getRevenue($start_date, $end_date);
$outstanding = $this->getOutstanding($start_date, $end_date);
$expenses = $this->getExpenses($start_date, $end_date);
$data[$key]['revenue'] = count($revenue) > 0 ? $revenue[array_search($key, array_column($revenue, 'currency_id'))] : new \stdClass;
$data[$key]['outstanding'] = count($outstanding) > 0 ? $outstanding[array_search($key, array_column($outstanding, 'currency_id'))] : new \stdClass;
$data[$key]['expenses'] = count($expenses) > 0 ? $expenses[array_search($key, array_column($expenses, 'currency_id'))] : new \stdClass;
}
return $data;
}
public function getRevenue($start_date, $end_date) :array
{
$revenue = $this->getRevenueQuery($start_date, $end_date);
$revenue = $this->addCurrencyCodes($revenue);
return $revenue;
}
public function getOutstanding($start_date, $end_date) :array
{
$outstanding = $this->getOutstandingQuery($start_date, $end_date);
$outstanding = $this->addCurrencyCodes($outstanding);
return $outstanding;
}
public function getExpenses($start_date, $end_date) :array
{
$expenses = $this->getExpenseQuery($start_date, $end_date);
$expenses = $this->addCurrencyCodes($expenses);
return $expenses;
}
/* Totals */
/* Helpers */
private function addCurrencyCodes($data_set) :array
{
$currencies = Cache::get('currencies');
foreach ($data_set as $key => $value) {
$data_set[$key]->currency_id = str_replace('"', '', $value->currency_id);
$data_set[$key]->code = $this->getCode($currencies, $data_set[$key]->currency_id);
}
return $data_set;
}
private function getCode($currencies, $currency_id) :string
{
$currency_id = str_replace('"', '', $currency_id);
$currency = $currencies->filter(function ($item) use ($currency_id) {
return $item->id == $currency_id;
})->first();
if ($currency) {
return $currency->code;
}
return '';
}
}

View File

@ -75,7 +75,8 @@ class Email implements ShouldQueue
*/ */
public function backoff() public function backoff()
{ {
return [10, 30, 60, 240]; // return [10, 30, 60, 240];
return [rand(10, 20), rand(30, 45), rand(60, 79), rand(160, 400)];
} }
public function handle() public function handle()

View File

@ -26,13 +26,13 @@ class Design extends BaseDesign
{ {
use MakesInvoiceValues, DesignHelpers, MakesDates; use MakesInvoiceValues, DesignHelpers, MakesDates;
/** @var App\Models\Invoice || @var App\Models\Quote */ /** @var \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\RecurringInvoice */
public $entity; public $entity;
/** @var App\Models\Client */ /** @var \App\Models\Client */
public $client; public $client;
/** @var App\Models\Vendor */ /** @var \App\Models\Vendor */
public $vendor; public $vendor;
/** Global state of the design, @var array */ /** Global state of the design, @var array */

View File

@ -100,8 +100,6 @@ class ARDetailReport extends BaseExport
$query = $this->filterByClients($query); $query = $this->filterByClients($query);
$this->csv->insertOne($this->buildHeader());
$query->cursor() $query->cursor()
->each(function ($invoice) { ->each(function ($invoice) {
$this->csv->insertOne($this->buildRow($invoice)); $this->csv->insertOne($this->buildRow($invoice));

View File

@ -122,7 +122,10 @@ class ARSummaryReport extends BaseExport
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_deleted', 0) ->where('is_deleted', 0)
->where('due_date', '<', now()->startOfDay()) ->where(function ($query){
$query->where('due_date', '<', now()->startOfDay())
->orWhereNull('due_date');
})
->sum('balance'); ->sum('balance');
return Number::formatMoney($amount, $this->client); return Number::formatMoney($amount, $this->client);

View File

@ -21,8 +21,6 @@ class SchedulerService
use MakesHash; use MakesHash;
use MakesDates; use MakesDates;
private string $method;
public function __construct(public Scheduler $scheduler) public function __construct(public Scheduler $scheduler)
{ {
} }

View File

@ -56,7 +56,7 @@ class AccountTransformer extends EntityTransformer
'key' => (string) $account->key, 'key' => (string) $account->key,
'default_url' => config('ninja.app_url'), 'default_url' => config('ninja.app_url'),
'plan' => $account->getPlan(), 'plan' => $account->getPlan(),
'plan_term' => (string) $account->plan_terms, 'plan_term' => (string) $account->plan_term,
'plan_started' => (string) $account->plan_started, 'plan_started' => (string) $account->plan_started,
'plan_paid' => (string) $account->plan_paid, 'plan_paid' => (string) $account->plan_paid,
'plan_expires' => (string) $account->plan_expires, 'plan_expires' => (string) $account->plan_expires,
@ -90,6 +90,8 @@ class AccountTransformer extends EntityTransformer
'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap, 'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap,
'trial_days_left' => Ninja::isHosted() ? (int) $account->getTrialDays() : 0, 'trial_days_left' => Ninja::isHosted() ? (int) $account->getTrialDays() : 0,
'account_sms_verified' => (bool) $account->account_sms_verified, 'account_sms_verified' => (bool) $account->account_sms_verified,
'has_iap_plan' => (bool)$account->inapp_transaction_id,
]; ];
} }

View File

@ -27,7 +27,6 @@ class SchedulerTransformer extends EntityTransformer
'next_run' => $scheduler->next_run_client->format('Y-m-d'), 'next_run' => $scheduler->next_run_client->format('Y-m-d'),
'template' => (string) $scheduler->template, 'template' => (string) $scheduler->template,
'is_paused' => (bool) $scheduler->is_paused, 'is_paused' => (bool) $scheduler->is_paused,
'is_deleted' => (bool) $scheduler->is_deleted,
'parameters'=> (array) $scheduler->parameters, 'parameters'=> (array) $scheduler->parameters,
'is_deleted' => (bool) $scheduler->is_deleted, 'is_deleted' => (bool) $scheduler->is_deleted,
'updated_at' => (int) $scheduler->updated_at, 'updated_at' => (int) $scheduler->updated_at,

View File

@ -1,8 +1,17 @@
<?php <?php
use Imdhemy\Purchases\Events\AppStore\Refund;
use Imdhemy\Purchases\Events\AppStore\DidRenew; use Imdhemy\Purchases\Events\AppStore\DidRenew;
use App\Listeners\Subscription\AppStoreRenewSubscription; use Imdhemy\Purchases\Events\AppStore\DidFailToRenew;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPaused;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionExpired;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed; use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRevoked;
use Imdhemy\Purchases\Events\AppStore\DidChangeRenewalStatus;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionCanceled;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPurchased;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRecovered;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRestarted;
return [ return [
/* /*
@ -99,7 +108,16 @@ return [
DidRenew::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleAutoRenew::class) ? [\Modules\Admin\Listeners\Subscription\AppleAutoRenew::class] : [], DidRenew::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleAutoRenew::class) ? [\Modules\Admin\Listeners\Subscription\AppleAutoRenew::class] : [],
SubscriptionRenewed::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleAutoRenew::class) ? [\Modules\Admin\Listeners\Subscription\GoogleAutoRenew::class] : [], SubscriptionRenewed::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleAutoRenew::class) ? [\Modules\Admin\Listeners\Subscription\GoogleAutoRenew::class] : [],
DidChangeRenewalStatus::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleChangeRenewalStaus::class) ? [\Modules\Admin\Listeners\Subscription\GoogleChangeRenewalStaus::class] : [],
DidFailToRenew::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleFailedToRenew::class) ? [\Modules\Admin\Listeners\Subscription\GoogleFailedToRenew::class] : [],
Refund::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleRefund::class) ? [\Modules\Admin\Listeners\Subscription\AppleRefund::class] : [],
SubscriptionRecovered::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRecovered::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRecovered::class] : [],
SubscriptionCanceled::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionCanceled::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionCanceled::class] : [],
SubscriptionPurchased::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPurchased::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPurchased::class] : [],
SubscriptionRestarted::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRestarted::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRestarted::class] : [],
SubscriptionPaused::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPaused::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPaused::class] : [],
SubscriptionRevoked::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRevoked::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRevoked::class] : [],
SubscriptionExpired::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionExpired::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionExpired::class] : [],
], ],
/* /*

View File

@ -15,8 +15,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.118', 'app_version' => '5.5.119',
'app_tag' => '5.5.118', 'app_tag' => '5.5.119',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -5087,6 +5087,13 @@ $LANG = array(
'light_dark_mode' => 'Light/Dark Mode', 'light_dark_mode' => 'Light/Dark Mode',
'activities' => 'Activities', 'activities' => 'Activities',
'recent_transactions' => "Here are your company's most recent transactions:", 'recent_transactions' => "Here are your company's most recent transactions:",
'country_Palestine' => "Palestine",
'country_Taiwan' => 'Taiwan',
'duties' => 'Duties',
'order_number' => 'Order Number',
'order_id' => 'Order',
'total_invoices_outstanding' => 'Total Invoices Outstanding',
'recent_activity' => 'Recent Activity',
); );

View File

@ -142,6 +142,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('charts/totals', [ChartController::class, 'totals'])->name('chart.totals'); Route::post('charts/totals', [ChartController::class, 'totals'])->name('chart.totals');
Route::post('charts/chart_summary', [ChartController::class, 'chart_summary'])->name('chart.chart_summary'); Route::post('charts/chart_summary', [ChartController::class, 'chart_summary'])->name('chart.chart_summary');
Route::post('charts/totals_v2', [ChartController::class, 'totalsV2'])->name('chart.totals_v2');
Route::post('charts/chart_summary_v2', [ChartController::class, 'chart_summaryV2'])->name('chart.chart_summary_v2');
Route::post('claim_license', [LicenseController::class, 'index'])->name('license.index'); Route::post('claim_license', [LicenseController::class, 'index'])->name('license.index');
Route::resource('clients', ClientController::class); // name = (clients. index / create / show / update / destroy / edit Route::resource('clients', ClientController::class); // name = (clients. index / create / show / update / destroy / edit

View File

@ -11,6 +11,7 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Models\TaskStatus;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
@ -28,6 +29,8 @@ class TaskStatusApiTest extends TestCase
use DatabaseTransactions; use DatabaseTransactions;
use MockAccountData; use MockAccountData;
public $faker;
protected function setUp() :void protected function setUp() :void
{ {
parent::setUp(); parent::setUp();
@ -41,6 +44,38 @@ class TaskStatusApiTest extends TestCase
Model::reguard(); Model::reguard();
} }
public function testSorting()
{
TaskStatus::factory()->count(5)->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id
]);
$t = TaskStatus::where('company_id', '=', $this->company->id)->orderBy('id', 'desc');
$this->assertEquals(10, $t->count());
$task_status = $t->first();
$id = $task_status->id;
nlog("setting {$id} to index 1");
$data = [
'status_order' => 1,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/task_statuses/'.$task_status->hashed_id, $data);
$t = TaskStatus::where('company_id', '=', $this->company->id)->orderBy('status_order', 'asc')->first();
$this->assertEquals($id, $t->id);
}
public function testTaskStatusGetFilter() public function testTaskStatusGetFilter()
{ {
$response = $this->withHeaders([ $response = $this->withHeaders([