diff --git a/app/Factory/RecurringQuoteFactory.php b/app/Factory/RecurringQuoteFactory.php new file mode 100644 index 000000000000..dd8d757570b8 --- /dev/null +++ b/app/Factory/RecurringQuoteFactory.php @@ -0,0 +1,51 @@ +status_id = RecurringQuote::STATUS_DRAFT; + $quote->discount = 0; + $quote->is_amount_discount = true; + $quote->po_number = ''; + $quote->footer = ''; + $quote->terms = ''; + $quote->public_notes = ''; + $quote->private_notes = ''; + $quote->quote_date = null; + $quote->valid_until = null; + $quote->partial_due_date = null; + $quote->is_deleted = false; + $quote->line_items = json_encode([]); + $quote->settings = ClientSettings::buildClientSettings(new CompanySettings(CompanySettings::defaults()), new ClientSettings(ClientSettings::defaults())); //todo need to embed the settings here + $quote->backup = json_encode([]); + $quote->tax_name1 = ''; + $quote->tax_rate1 = 0; + $quote->tax_name2 = ''; + $quote->tax_rate2 = 0; + $quote->custom_value1 = 0; + $quote->custom_value2 = 0; + $quote->custom_value3 = 0; + $quote->custom_value4 = 0; + $quote->amount = 0; + $quote->balance = 0; + $quote->partial = 0; + $quote->user_id = $user_id; + $quote->company_id = $company_id; + $quote->frequency_id = RecurringQuote::FREQUENCY_MONTHLY; + $quote->start_date = null; + $quote->last_sent_date = null; + $quote->next_send_date = null; + $quote->remaining_cycles = 0; + + return $quote; + } + +} diff --git a/app/Filters/RecurringQuoteFilters.php b/app/Filters/RecurringQuoteFilters.php new file mode 100644 index 000000000000..b3fd2d7b07bf --- /dev/null +++ b/app/Filters/RecurringQuoteFilters.php @@ -0,0 +1,111 @@ +builder; + + return $this->builder->where(function ($query) use ($filter) { + $query->where('recurring_quotes.custom_value1', 'like', '%'.$filter.'%') + ->orWhere('recurring_quotes.custom_value2', 'like' , '%'.$filter.'%') + ->orWhere('recurring_quotes.custom_value3', 'like' , '%'.$filter.'%') + ->orWhere('recurring_quotes.custom_value4', 'like' , '%'.$filter.'%'); + }); + } + + /** + * Filters the list based on the status + * archived, active, deleted + * + * @param string filter + * @return Illuminate\Database\Query\Builder + */ + public function status(string $filter = '') : Builder + { + if(strlen($filter) == 0) + return $this->builder; + + $table = 'recurring_'; + $filters = explode(',', $filter); + + return $this->builder->where(function ($query) use ($filters, $table) { + $query->whereNull($table . '.id'); + + if (in_array(parent::STATUS_ACTIVE, $filters)) { + $query->orWhereNull($table . '.deleted_at'); + } + + if (in_array(parent::STATUS_ARCHIVED, $filters)) { + $query->orWhere(function ($query) use ($table) { + $query->whereNotNull($table . '.deleted_at'); + + if (! in_array($table, ['users'])) { + $query->where($table . '.is_deleted', '=', 0); + } + }); + } + + if (in_array(parent::STATUS_DELETED, $filters)) { + $query->orWhere($table . '.is_deleted', '=', 1); + } + }); + } + + /** + * Sorts the list based on $sort + * + * @param string sort formatted as column|asc + * @return Illuminate\Database\Query\Builder + */ + public function sort(string $sort) : Builder + { + $sort_col = explode("|", $sort); + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + } + + /** + * Returns the base query + * + * @param int company_id + * @return Illuminate\Database\Query\Builder + * @deprecated + */ + public function baseQuery(int $company_id, User $user) : Builder + { + + } + + /** + * Filters the query by the users company ID + * + * @param $company_id The company Id + * @return Illuminate\Database\Query\Builder + */ + public function entityFilter() + { + + return $this->builder->whereCompanyId(auth()->user()->company()->id); + + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/RecurringQuoteController.php b/app/Http/Controllers/RecurringQuoteController.php new file mode 100644 index 000000000000..e83380bb1f2c --- /dev/null +++ b/app/Http/Controllers/RecurringQuoteController.php @@ -0,0 +1,236 @@ +recurring_quote_repo = $recurring_quote_repo; + + } + + /** + * Show the list of recurring_invoices + * + * @param \App\Filters\RecurringQuoteFilters $filters The filters + * + * @return \Illuminate\Http\Response + */ + public function index(RecurringQuoteFilters $filters) + { + + $recurring_quotes = RecurringQuote::filter($filters); + + return $this->listResponse($recurring_quotes); + + } + + /** + * Show the form for creating a new resource. + * + * @param \App\Http\Requests\RecurringQuote\CreateRecurringQuoteRequest $request The request + * + * @return \Illuminate\Http\Response + */ + public function create(CreateRecurringQuoteRequest $request) + { + + $recurring_quote = RecurringQuoteFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($recurring_quote); + + } + + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\RecurringQuote\StoreRecurringQuoteRequest $request The request + * + * @return \Illuminate\Http\Response + */ + public function store(StoreRecurringQuoteRequest $request) + { + + $recurring_quote = $this->recurring_quote_repo->save($request, RecurringQuoteFactory::create(auth()->user()->company()->id, auth()->user()->id)); + + return $this->itemResponse($recurring_quote); + + } + + /** + * Display the specified resource. + * + * @param \App\Http\Requests\RecurringQuote\ShowRecurringQuoteRequest $request The request + * @param \App\Models\RecurringQuote $recurring_quote The RecurringQuote + * + * @return \Illuminate\Http\Response + */ + public function show(ShowRecurringQuoteRequest $request, RecurringQuote $recurring_quote) + { + + return $this->itemResponse($recurring_quote); + + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Http\Requests\RecurringQuote\EditRecurringQuoteRequest $request The request + * @param \App\Models\RecurringQuote $recurring_quote The RecurringQuote + * + * @return \Illuminate\Http\Response + */ + public function edit(EditRecurringQuoteRequest $request, RecurringQuote $recurring_quote) + { + + return $this->itemResponse($recurring_quote); + + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\RecurringQuote\UpdateRecurringQuoteRequest $request The request + * @param \App\Models\RecurringQuote $recurring_quote The RecurringQuote + * + * @return \Illuminate\Http\Response + */ + public function update(UpdateRecurringQuoteRequest $request, RecurringQuote $recurring_quote) + { + + $recurring_quote = $this->recurring_quote_repo->save(request(), $recurring_quote); + + return $this->itemResponse($recurring_quote); + + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Http\Requests\RecurringQuote\DestroyRecurringQuoteRequest $request + * @param \App\Models\RecurringQuote $recurring_quote + * + * @return \Illuminate\Http\Response + */ + public function destroy(DestroyRecurringQuoteRequest $request, RecurringQuote $recurring_quote) + { + + $recurring_quote->delete(); + + return response()->json([], 200); + + } + + /** + * Perform bulk actions on the list view + * + * @return Collection + */ + public function bulk() + { + + $action = request()->input('action'); + + $ids = request()->input('ids'); + + $recurring_quotes = RecurringQuote::withTrashed()->find($ids); + + $recurring_quotes->each(function ($recurring_quote, $key) use($action){ + + if(auth()->user()->can('edit', $recurring_quote)) + $this->recurring_quote_repo->{$action}($recurring_quote); + + }); + + //todo need to return the updated dataset + return $this->listResponse(RecurringQuote::withTrashed()->whereIn('id', $ids)); + + } + + public function action(ActionRecurringQuoteRequest $request, RecurringQuote $recurring_quote, $action) + { + + switch ($action) { + case 'clone_to_RecurringQuote': + // $recurring_invoice = CloneRecurringQuoteFactory::create($recurring_invoice, auth()->user()->id); + // return $this->itemResponse($recurring_invoice); + break; + case 'clone_to_quote': + // $quote = CloneRecurringQuoteToQuoteFactory::create($recurring_invoice, auth()->user()->id); + // todo build the quote transformer and return response here + break; + case 'history': + # code... + break; + case 'delivery_note': + # code... + break; + case 'mark_paid': + # code... + break; + case 'archive': + # code... + break; + case 'delete': + # code... + break; + case 'email': + //dispatch email to queue + break; + + default: + # code... + break; + } + } + +} diff --git a/app/Http/Requests/RecurringQuote/ActionRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/ActionRecurringQuoteRequest.php new file mode 100644 index 000000000000..0285d21caf03 --- /dev/null +++ b/app/Http/Requests/RecurringQuote/ActionRecurringQuoteRequest.php @@ -0,0 +1,21 @@ +user()->can('edit', $this->recurring_quote); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringQuote/CreateRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/CreateRecurringQuoteRequest.php new file mode 100644 index 000000000000..9407ff3f2caa --- /dev/null +++ b/app/Http/Requests/RecurringQuote/CreateRecurringQuoteRequest.php @@ -0,0 +1,21 @@ +user()->can('create', RecurringQuote::class); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringQuote/DestroyRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/DestroyRecurringQuoteRequest.php new file mode 100644 index 000000000000..8921cd13f61e --- /dev/null +++ b/app/Http/Requests/RecurringQuote/DestroyRecurringQuoteRequest.php @@ -0,0 +1,21 @@ +user()->can('edit', $this->recurring_quote); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringQuote/EditRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/EditRecurringQuoteRequest.php new file mode 100644 index 000000000000..38ad1b7ed8fd --- /dev/null +++ b/app/Http/Requests/RecurringQuote/EditRecurringQuoteRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->recurring_quote); + } + + public function rules() + { + $rules = []; + + return $rules; + } + + + public function sanitize() + { + $input = $this->all(); + + //$input['id'] = $this->encodePrimaryKey($input['id']); + + //$this->replace($input); + + return $this->all(); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringQuote/ShowRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/ShowRecurringQuoteRequest.php new file mode 100644 index 000000000000..4a072db2ac48 --- /dev/null +++ b/app/Http/Requests/RecurringQuote/ShowRecurringQuoteRequest.php @@ -0,0 +1,21 @@ +user()->can('view', $this->recurring_quote); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php new file mode 100644 index 000000000000..edf378b07b9e --- /dev/null +++ b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php @@ -0,0 +1,41 @@ +user()->can('create', RecurringQuote::class); + } + + public function rules() + { + return [ + 'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', + ]; + } + + + public function sanitize() + { + //do post processing of RecurringQuote request here, ie. RecurringQuote_items + } + + public function messages() + { + + } + + +} + diff --git a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php new file mode 100644 index 000000000000..5545e3aa866d --- /dev/null +++ b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php @@ -0,0 +1,32 @@ +user()->can('edit', $this->recurring_quote); + + } + + + public function rules() + { + return [ + 'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', + ]; + } + +} \ No newline at end of file diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index f06cbf996ae2..c29c26dcff01 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -40,7 +40,7 @@ class RecurringInvoice extends BaseModel const FREQUENCY_ANNUALLY = 9; const FREQUENCY_TWO_YEARS = 10; - const RECURS_INDEFINITELY = 1; + const RECURS_INDEFINITELY = -1; protected $guarded = [ 'id', diff --git a/app/Models/RecurringQuote.php b/app/Models/RecurringQuote.php new file mode 100644 index 000000000000..084b8b44346d --- /dev/null +++ b/app/Models/RecurringQuote.php @@ -0,0 +1,77 @@ + 'object' + ]; + + protected $with = [ + // 'client', + // 'company', + ]; + + public function company() + { + return $this->belongsTo(Company::class); + } + + public function client() + { + return $this->belongsTo(Client::class); + } + + public function user() + { + return $this->belongsTo(User::class); + } + + public function invitations() + { + $this->morphMany(RecurringQuoteInvitation::class); + } +} diff --git a/app/Policies/RecurringQuotePolicy.php b/app/Policies/RecurringQuotePolicy.php new file mode 100644 index 000000000000..da00c70a4f39 --- /dev/null +++ b/app/Policies/RecurringQuotePolicy.php @@ -0,0 +1,25 @@ +isAdmin() || $user->hasPermission('create_recurring_quote'); + } + +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 82ca77936732..304324df028d 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -8,6 +8,7 @@ use App\Models\Payment; use App\Models\Product; use App\Models\Quote; use App\Models\RecurringInvoice; +use App\Models\RecurringQuote; use App\Models\User; use App\Policies\ClientPolicy; use App\Policies\InvoicePolicy; @@ -15,6 +16,7 @@ use App\Policies\PaymentPolicy; use App\Policies\ProductPolicy; use App\Policies\QuotePolicy; use App\Policies\RecurringInvoicePolicy; +use App\Policies\RecurringQuotePolicy; use Auth; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Gate; @@ -32,6 +34,7 @@ class AuthServiceProvider extends ServiceProvider Invoice::class => InvoicePolicy::class, Payment::class => PaymentPolicy::class, RecurringInvoice::class => RecurringInvoicePolicy::class, + RecurringQuote::class => RecurringQuotePolicy::class, Quote::class => QuotePolicy::class, User::class => UserPolicy::class, ]; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 674ba882fe43..ed58c418e176 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -46,6 +46,10 @@ class RouteServiceProvider extends ServiceProvider return \App\Models\RecurringInvoice::withTrashed()->where('id', $this->decodePrimaryKey($value))->firstOrFail(); }); + Route::bind('recurring_quote', function ($value) { + return \App\Models\RecurringQuote::withTrashed()->where('id', $this->decodePrimaryKey($value))->firstOrFail(); + }); + Route::bind('quote', function ($value) { return \App\Models\Quote::withTrashed()->where('id', $this->decodePrimaryKey($value))->firstOrFail(); }); diff --git a/app/Repositories/RecurringQuoteRepository.php b/app/Repositories/RecurringQuoteRepository.php new file mode 100644 index 000000000000..42614cbedd29 --- /dev/null +++ b/app/Repositories/RecurringQuoteRepository.php @@ -0,0 +1,38 @@ +fill($request->input()); + + $quote->save(); + + + $quote_calc = new InvoiceCalc($quote, $quote->settings); + + $quote = $quote_calc->build()->getInvoice(); + + //fire events here that cascading from the saving of an Quote + //ie. client balance update... + + return $quote; + } + +} \ No newline at end of file diff --git a/app/Transformers/RecurringQuoteTransformer.php b/app/Transformers/RecurringQuoteTransformer.php new file mode 100644 index 000000000000..4690efd014ff --- /dev/null +++ b/app/Transformers/RecurringQuoteTransformer.php @@ -0,0 +1,162 @@ +serializer); + + return $this->includeCollection($quote->invoice_items, $transformer, ENTITY_INVOICE_ITEM); + } + + public function includeInvitations(Invoice $quote) + { + $transformer = new InvitationTransformer($this->account, $this->serializer); + + return $this->includeCollection($quote->invitations, $transformer, ENTITY_INVITATION); + } + + public function includePayments(Invoice $quote) + { + $transformer = new PaymentTransformer($this->account, $this->serializer, $quote); + + return $this->includeCollection($quote->payments, $transformer, ENTITY_PAYMENT); + } + + public function includeClient(Invoice $quote) + { + $transformer = new ClientTransformer($this->account, $this->serializer); + + return $this->includeItem($quote->client, $transformer, ENTITY_CLIENT); + } + + public function includeExpenses(Invoice $quote) + { + $transformer = new ExpenseTransformer($this->account, $this->serializer); + + return $this->includeCollection($quote->expenses, $transformer, ENTITY_EXPENSE); + } + + public function includeDocuments(Invoice $quote) + { + $transformer = new DocumentTransformer($this->account, $this->serializer); + + $quote->documents->each(function ($document) use ($quote) { + $document->setRelation('invoice', $quote); + }); + + return $this->includeCollection($quote->documents, $transformer, ENTITY_DOCUMENT); + } +*/ + public function transform(RecurringQuote $quote) + { + return [ + 'id' => $this->encodePrimaryKey($quote->id), + 'amount' => (float) $quote->amount, + 'balance' => (float) $quote->balance, + 'client_id' => (int) $quote->client_id, + 'status_id' => (int) ($quote->status_id ?: 1), + 'updated_at' => $quote->updated_at, + 'archived_at' => $quote->deleted_at, + 'discount' => (float) $quote->discount, + 'po_number' => $quote->po_number, + 'quote_date' => $quote->quote_date ?: '', + 'valid_until' => $quote->valid_until ?: '', + 'terms' => $quote->terms ?: '', + 'public_notes' => $quote->public_notes ?: '', + 'private_notes' => $quote->private_notes ?: '', + 'is_deleted' => (bool) $quote->is_deleted, + 'tax_name1' => $quote->tax_name1 ? $quote->tax_name1 : '', + 'tax_rate1' => (float) $quote->tax_rate1, + 'tax_name2' => $quote->tax_name2 ? $quote->tax_name2 : '', + 'tax_rate2' => (float) $quote->tax_rate2, + 'is_amount_discount' => (bool) ($quote->is_amount_discount ?: false), + 'quote_footer' => $quote->quote_footer ?: '', + 'partial' => (float) ($quote->partial ?: 0.0), + 'partial_due_date' => $quote->partial_due_date ?: '', + 'custom_value1' => (float) $quote->custom_value1, + 'custom_value2' => (float) $quote->custom_value2, + 'custom_taxes1' => (bool) $quote->custom_taxes1, + 'custom_taxes2' => (bool) $quote->custom_taxes2, + 'has_tasks' => (bool) $quote->has_tasks, + 'has_expenses' => (bool) $quote->has_expenses, + 'custom_text_value1' => $quote->custom_text_value1 ?: '', + 'custom_text_value2' => $quote->custom_text_value2 ?: '', + 'backup' => $quote->backup ?: '', + 'settings' => $quote->settings, + 'frequency_id' => (int) $quote->frequency_id, + 'start_date' => $quote->start_date, + 'last_sent_date' => $quote->last_sent_date, + 'next_send_date' => $quote->next_send_date, + 'remaining_cycles' => (int) $quote->remaining_cycles, + ]; + } +} diff --git a/database/factories/RecurringQuoteFactory.php b/database/factories/RecurringQuoteFactory.php new file mode 100644 index 000000000000..5394ddccd6b6 --- /dev/null +++ b/database/factories/RecurringQuoteFactory.php @@ -0,0 +1,33 @@ +define(App\Models\RecurringQuote::class, function (Faker $faker) { + return [ + 'status_id' => App\Models\RecurringQuote::STATUS_DRAFT, + 'quote_number' => $faker->text(256), + 'discount' => $faker->numberBetween(1,10), + 'is_amount_discount' => $faker->boolean(), + 'tax_name1' => 'GST', + 'tax_rate1' => 10, + 'tax_name2' => 'VAT', + 'tax_rate2' => 17.5, + 'custom_value1' => $faker->numberBetween(1,4), + 'custom_value2' => $faker->numberBetween(1,4), + 'custom_value3' => $faker->numberBetween(1,4), + 'custom_value4' => $faker->numberBetween(1,4), + 'is_deleted' => false, + 'po_number' => $faker->text(10), + 'quote_date' => $faker->date(), + 'valid_until' => $faker->date(), + 'line_items' => false, + 'backup' => '', + 'frequency_id' => App\Models\RecurringQuote::FREQUENCY_MONTHLY, + 'start_date' => $faker->date(), + 'last_sent_date' => $faker->date(), + 'next_send_date' => $faker->date(), + 'remaining_cycles' => $faker->numberBetween(1,10), + ]; +}); \ No newline at end of file diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index 984cdf54d0b4..404a1ad772e1 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -472,6 +472,64 @@ class CreateUsersTable extends Migration }); + Schema::create('recurring_quotes', function ($t) { + $t->increments('id'); + $t->unsignedInteger('client_id')->index(); + $t->unsignedInteger('user_id'); + $t->unsignedInteger('company_id')->index(); + + $t->unsignedInteger('status_id')->index(); + + $t->float('discount'); + $t->boolean('is_amount_discount'); + $t->string('quote_number'); + + $t->string('po_number'); + $t->date('quote_date')->nullable(); + $t->date('valid_until')->nullable(); + + $t->boolean('is_deleted')->default(false); + + $t->text('line_items')->nullable(); + $t->text('settings')->nullable(); + $t->text('backup')->nullable(); + + $t->text('footer')->nullable(); + $t->text('public_notes')->nullable(); + $t->text('private_notes')->nullable(); + $t->text('terms')->nullable(); + + $t->string('tax_name1'); + $t->decimal('tax_rate1', 13, 3); + + $t->string('tax_name2'); + $t->decimal('tax_rate2', 13, 3); + + $t->string('custom_value1')->nullable(); + $t->string('custom_value2')->nullable(); + $t->string('custom_value3')->nullable(); + $t->string('custom_value4')->nullable(); + + $t->decimal('amount', 13, 2); + $t->decimal('balance', 13, 2); + + $t->datetime('last_viewed')->nullable(); + + $t->unsignedInteger('frequency_id'); + $t->date('start_date')->nullable(); + $t->date('last_sent_date')->nullable(); + $t->date('next_send_date')->nullable(); + $t->unsignedInteger('remaining_cycles')->nullable(); + + $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); + $t->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + + $t->timestamps(); + $t->softDeletes(); + + }); + Schema::create('quotes', function ($t) { $t->increments('id'); $t->unsignedInteger('client_id')->index(); diff --git a/routes/api.php b/routes/api.php index 84bfd75bf191..e111ef6ff9e9 100644 --- a/routes/api.php +++ b/routes/api.php @@ -50,6 +50,10 @@ Route::group(['middleware' => ['db','api_secret_check','token_auth'], 'prefix' = Route::post('recurring_invoices/bulk', 'RecurringInvoiceController@bulk')->name('recurring_invoices.bulk'); + Route::resource('recurring_quotes', 'RecurringQuoteController'); // name = (recurring_invoices. index / create / show / update / destroy / edit + + Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk'); + Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit diff --git a/tests/Feature/RecurringQuoteTest.php b/tests/Feature/RecurringQuoteTest.php new file mode 100644 index 000000000000..a9e46c4b3858 --- /dev/null +++ b/tests/Feature/RecurringQuoteTest.php @@ -0,0 +1,203 @@ +faker = \Faker\Factory::create(); + + Model::reguard(); + + + } + + public function testRecurringQuoteList() + { + $data = [ + 'first_name' => $this->faker->firstName, + 'last_name' => $this->faker->lastName, + 'email' => $this->faker->unique()->safeEmail, + 'password' => 'ALongAndBrilliantPassword123', + '_token' => csrf_token(), + 'privacy_policy' => 1, + 'terms_of_service' => 1 + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + ])->post('/api/v1/signup', $data); + + $acc = $response->json(); + + $account = Account::find($this->decodePrimaryKey($acc['data']['id'])); + + $company_token = $account->default_company->tokens()->first(); + $token = $company_token->token; + $company = $company_token->company; + + $user = $company_token->user; + + $this->assertNotNull($company_token); + $this->assertNotNull($token); + $this->assertNotNull($user); + $this->assertNotNull($company); + $this->assertNotNull($user->tokens->first()->company); + + factory(\App\Models\Client::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){ + + factory(\App\Models\ClientContact::class,1)->create([ + 'user_id' => $user->id, + 'client_id' => $c->id, + 'company_id' => $company->id, + 'is_primary' => 1 + ]); + + factory(\App\Models\ClientContact::class,1)->create([ + 'user_id' => $user->id, + 'client_id' => $c->id, + 'company_id' => $company->id + ]); + + }); + $client = Client::all()->first(); + + factory(\App\Models\RecurringQuote::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->get('/api/v1/recurring_quotes'); + + $response->assertStatus(200); + + } + + public function testRecurringQuoteRESTEndPoints() + { + $data = [ + 'first_name' => $this->faker->firstName, + 'last_name' => $this->faker->lastName, + 'email' => $this->faker->unique()->safeEmail, + 'password' => 'ALongAndBrilliantPassword123', + '_token' => csrf_token(), + 'privacy_policy' => 1, + 'terms_of_service' => 1 + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + ])->post('/api/v1/signup', $data); + + $acc = $response->json(); + + $account = Account::find($this->decodePrimaryKey($acc['data']['id'])); + + $company_token = $account->default_company->tokens()->first(); + $token = $company_token->token; + $company = $company_token->company; + + $user = $company_token->user; + + $this->assertNotNull($company_token); + $this->assertNotNull($token); + $this->assertNotNull($user); + $this->assertNotNull($company); + $this->assertNotNull($user->tokens->first()->company); + + factory(\App\Models\Client::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){ + + factory(\App\Models\ClientContact::class,1)->create([ + 'user_id' => $user->id, + 'client_id' => $c->id, + 'company_id' => $company->id, + 'is_primary' => 1 + ]); + + factory(\App\Models\ClientContact::class,1)->create([ + 'user_id' => $user->id, + 'client_id' => $c->id, + 'company_id' => $company->id + ]); + + }); + $client = Client::all()->first(); + + factory(\App\Models\RecurringQuote::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]); + + $RecurringQuote = RecurringQuote::where('user_id',$user->id)->first(); + $RecurringQuote->settings = $client->getMergedSettings(); + $RecurringQuote->save(); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->get('/api/v1/recurring_quotes/'.$this->encodePrimaryKey($RecurringQuote->id)); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->get('/api/v1/recurring_quotes/'.$this->encodePrimaryKey($RecurringQuote->id).'/edit'); + + $response->assertStatus(200); + + $RecurringQuote_update = [ + 'status_id' => RecurringQuote::STATUS_DRAFT + ]; + + $this->assertNotNull($RecurringQuote); + $this->assertNotNull($RecurringQuote->settings); + + $this->assertTrue(property_exists($RecurringQuote->settings, 'custom_taxes1')); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->put('/api/v1/recurring_quotes/'.$this->encodePrimaryKey($RecurringQuote->id), $RecurringQuote_update) + ->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->delete('/api/v1/recurring_quotes/'.$this->encodePrimaryKey($RecurringQuote->id)); + + $response->assertStatus(200); + + } + +}