From c35f9fbe04c237dc1288378bd87049b2870a7d0b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 08:29:04 +1000 Subject: [PATCH 1/9] recurring invoices --- app/DataMapper/ClientSettings.php | 3 - app/Factory/RecurringInvoiceFactory.php | 51 +++++ .../RecurringInvoiceController.php | 200 ++++++++++++++--- .../ActionRecurringInvoiceRequest.php | 21 ++ .../CreateRecurringInvoiceRequest.php | 21 ++ .../DestroyRecurringInvoiceRequest.php | 21 ++ .../EditRecurringInvoiceRequest.php | 40 ++++ .../ShowRecurringInvoiceRequest.php | 21 ++ .../StoreRecurringInvoiceRequest.php | 41 ++++ .../UpdateRecurringInvoiceRequest.php | 32 +++ app/Models/RecurringInvoice.php | 37 ++++ app/Policies/RecurringInvoicePolicy.php | 25 +++ app/Providers/AuthServiceProvider.php | 3 + .../RecurringInvoiceRepository.php | 38 ++++ .../RecurringInvoiceTransformer.php | 161 ++++++++++++++ .../factories/RecurringInvoiceFactory.php | 32 +++ .../2014_10_13_000000_create_users_table.php | 4 +- tests/Feature/RecurringInvoiceTest.php | 203 ++++++++++++++++++ 18 files changed, 925 insertions(+), 29 deletions(-) create mode 100644 app/Factory/RecurringInvoiceFactory.php create mode 100644 app/Http/Requests/RecurringInvoice/ActionRecurringInvoiceRequest.php create mode 100644 app/Http/Requests/RecurringInvoice/CreateRecurringInvoiceRequest.php create mode 100644 app/Http/Requests/RecurringInvoice/DestroyRecurringInvoiceRequest.php create mode 100644 app/Http/Requests/RecurringInvoice/EditRecurringInvoiceRequest.php create mode 100644 app/Http/Requests/RecurringInvoice/ShowRecurringInvoiceRequest.php create mode 100644 app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php create mode 100644 app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php create mode 100644 app/Policies/RecurringInvoicePolicy.php create mode 100644 app/Repositories/RecurringInvoiceRepository.php create mode 100644 app/Transformers/RecurringInvoiceTransformer.php create mode 100644 database/factories/RecurringInvoiceFactory.php create mode 100644 tests/Feature/RecurringInvoiceTest.php diff --git a/app/DataMapper/ClientSettings.php b/app/DataMapper/ClientSettings.php index 128a25b8c3ff..e5845a0c7ce0 100644 --- a/app/DataMapper/ClientSettings.php +++ b/app/DataMapper/ClientSettings.php @@ -59,9 +59,6 @@ class ClientSettings extends BaseSettings public $quote_number_prefix; public $quote_number_pattern; public $quote_number_counter; - - //public $client_number_prefix; - //public $client_number_pattern; public $credit_number_prefix; public $credit_number_pattern; diff --git a/app/Factory/RecurringInvoiceFactory.php b/app/Factory/RecurringInvoiceFactory.php new file mode 100644 index 000000000000..b656eda9af03 --- /dev/null +++ b/app/Factory/RecurringInvoiceFactory.php @@ -0,0 +1,51 @@ +status_id = RecurringInvoice::STATUS_PENDING; + $invoice->discount = 0; + $invoice->is_amount_discount = true; + $invoice->po_number = ''; + $invoice->footer = ''; + $invoice->terms = ''; + $invoice->public_notes = ''; + $invoice->private_notes = ''; + $invoice->invoice_date = null; + $invoice->due_date = null; + $invoice->partial_due_date = null; + $invoice->is_deleted = false; + $invoice->line_items = json_encode([]); + $invoice->settings = ClientSettings::buildClientSettings(new CompanySettings(CompanySettings::defaults()), new ClientSettings(ClientSettings::defaults())); //todo need to embed the settings here + $invoice->backup = json_encode([]); + $invoice->tax_name1 = ''; + $invoice->tax_rate1 = 0; + $invoice->tax_name2 = ''; + $invoice->tax_rate2 = 0; + $invoice->custom_value1 = 0; + $invoice->custom_value2 = 0; + $invoice->custom_value3 = 0; + $invoice->custom_value4 = 0; + $invoice->amount = 0; + $invoice->balance = 0; + $invoice->partial = 0; + $invoice->user_id = $user_id; + $invoice->company_id = $company_id; + $invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY; + $invoice->start_date = null; + $invoice->last_sent_date = null; + $invoice->next_send_date = null; + $invoice->remaining_cycles = 0; + + return $invoice; + } + +} diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index fe680cd059c4..8fc7c273c525 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -2,83 +2,235 @@ namespace App\Http\Controllers; +use App\Factory\CloneRecurringInvoiceFactory; +use App\Factory\CloneRecurringInvoiceToQuoteFactory; +use App\Factory\RecurringInvoiceFactory; +use App\Filters\RecurringInvoiceFilters; +use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\EditRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\ShowRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest; +use App\Http\Requests\RecurringInvoice\UpdateRecurringInvoiceRequest; +use App\Jobs\Entity\ActionEntity; +use App\Models\RecurringInvoice; +use App\Repositories\BaseRepository; +use App\Repositories\RecurringInvoiceRepository; +use App\Transformers\RecurringInvoiceTransformer; +use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; -class RecurringInvoiceController extends Controller +/** + * Class RecurringInvoiceController + * @package App\Http\Controllers\RecurringInvoiceController + */ + +class RecurringInvoiceController extends BaseController { + + use MakesHash; + + protected $entity_type = RecurringInvoice::class; + + protected $entity_transformer = RecurringInvoiceTransformer::class; + /** - * Display a listing of the resource. + * @var RecurringInvoiceRepository + */ + protected $recurring_invoice_repo; + + protected $base_repo; + + /** + * RecurringInvoiceController constructor. + * + * @param \App\Repositories\RecurringInvoiceRepository $recurring_invoice_repo The RecurringInvoice repo + */ + public function __construct(RecurringInvoiceRepository $recurring_invoice_repo) + { + + parent::__construct(); + + $this->recurring_invoice_repo = $recurring_invoice_repo; + + } + + /** + * Show the list of recurring_invoices + * + * @param \App\Filters\RecurringInvoiceFilters $filters The filters * * @return \Illuminate\Http\Response */ - public function index() + public function index(RecurringInvoiceFilters $filters) { - // + + $recurring_invoices = RecurringInvoice::filter($filters); + + return $this->listResponse($recurring_invoices); + } /** * Show the form for creating a new resource. * + * @param \App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest $request The request + * * @return \Illuminate\Http\Response */ - public function create() + public function create(CreateRecurringInvoiceRequest $request) { - // + + $recurring_invoice = RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($recurring_invoice); + } + /** * Store a newly created resource in storage. * - * @param \Illuminate\Http\Request $request + * @param \App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest $request The request + * * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(StoreRecurringInvoiceRequest $request) { - // + + $recurring_invoice = $this->RecurringInvoice_repo->save($request, RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id)); + + return $this->itemResponse($recurring_invoice); + } /** * Display the specified resource. * - * @param int $id + * @param \App\Http\Requests\RecurringInvoice\ShowRecurringInvoiceRequest $request The request + * @param \App\Models\RecurringInvoice $recurring_invoice The RecurringInvoice + * * @return \Illuminate\Http\Response */ - public function show($id) + public function show(ShowRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice) { - // + + return $this->itemResponse($recurring_invoice); + } /** * Show the form for editing the specified resource. * - * @param int $id + * @param \App\Http\Requests\RecurringInvoice\EditRecurringInvoiceRequest $request The request + * @param \App\Models\RecurringInvoice $recurring_invoice The RecurringInvoice + * * @return \Illuminate\Http\Response */ - public function edit($id) + public function edit(EditRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice) { - // - } + return $this->itemResponse($recurring_invoice); + + } + /** * Update the specified resource in storage. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \App\Http\Requests\RecurringInvoice\UpdateRecurringInvoiceRequest $request The request + * @param \App\Models\RecurringInvoice $recurring_invoice The RecurringInvoice + * * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(UpdateRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice) { - // + + $recurring_invoice = $this->RecurringInvoice_repo->save(request(), $recurring_invoice); + + return $this->itemResponse($recurring_invoice); + } /** * Remove the specified resource from storage. * - * @param int $id - * @return \Illuminate\Http\Response + * @param \App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest $request + * @param \App\Models\RecurringInvoice $recurring_invoice + * + * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy(DestroyRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice) { - // + + $recurring_invoice->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_invoices = RecurringInvoice::withTrashed()->find($ids); + + $recurring_invoices->each(function ($recurring_invoice, $key) use($action){ + + if(auth()->user()->can('edit', $recurring_invoice)) + $this->RecurringInvoice_repo->{$action}($recurring_invoice); + + }); + + //todo need to return the updated dataset + return $this->listResponse(RecurringInvoice::withTrashed()->whereIn('id', $ids)); + + } + + public function action(ActionRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice, $action) + { + + switch ($action) { + case 'clone_to_RecurringInvoice': + // $recurring_invoice = CloneRecurringInvoiceFactory::create($recurring_invoice, auth()->user()->id); + // return $this->itemResponse($recurring_invoice); + break; + case 'clone_to_quote': + // $quote = CloneRecurringInvoiceToQuoteFactory::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/RecurringInvoice/ActionRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/ActionRecurringInvoiceRequest.php new file mode 100644 index 000000000000..26ddad27aba2 --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/ActionRecurringInvoiceRequest.php @@ -0,0 +1,21 @@ +user()->can('edit', $this->recurring_invoice); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringInvoice/CreateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/CreateRecurringInvoiceRequest.php new file mode 100644 index 000000000000..2344cbad2051 --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/CreateRecurringInvoiceRequest.php @@ -0,0 +1,21 @@ +user()->can('create', RecurringInvoice::class); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringInvoice/DestroyRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/DestroyRecurringInvoiceRequest.php new file mode 100644 index 000000000000..3e21d60e502b --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/DestroyRecurringInvoiceRequest.php @@ -0,0 +1,21 @@ +user()->can('edit', $this->recurring_invoice); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringInvoice/EditRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/EditRecurringInvoiceRequest.php new file mode 100644 index 000000000000..5ed0432bcda4 --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/EditRecurringInvoiceRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->recurring_invoice); + } + + 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/RecurringInvoice/ShowRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/ShowRecurringInvoiceRequest.php new file mode 100644 index 000000000000..4d3c8138ec8d --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/ShowRecurringInvoiceRequest.php @@ -0,0 +1,21 @@ +user()->can('view', $this->recurring_invoice); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php new file mode 100644 index 000000000000..7be5d6bd89d4 --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -0,0 +1,41 @@ +user()->can('create', RecurringInvoice::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 RecurringInvoice request here, ie. RecurringInvoice_items + } + + public function messages() + { + + } + + +} + diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php new file mode 100644 index 000000000000..ec11b37cb677 --- /dev/null +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -0,0 +1,32 @@ +user()->can('edit', $this->recurring_invoice); + + } + + + 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 b84ce24c719b..443c1437fcfa 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -7,12 +7,39 @@ use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +/** + * Class for Recurring Invoices. + */ class RecurringInvoice extends BaseModel { use MakesHash; use SoftDeletes; use Filterable; + /** + * Invoice Statuses + */ + const STATUS_PENDING = 1; + const STATUS_ACTIVE = 2; + const STATUS_COMPLETED = 3; + const STATUS_CANCELLED = 4; + + /** + * Recurring intervals + */ + const FREQUENCY_WEEKLY = 1; + const FREQUENCY_TWO_WEEKS = 2; + const FREQUENCY_FOUR_WEEKS = 3; + const FREQUENCY_MONTHLY = 4; + const FREQUENCY_TWO_MONTHS = 5; + const FREQUENCY_THREE_MONTHS = 6; + const FREQUENCY_FOUR_MONTHS = 7; + const FREQUENCY_SIX_MONTHS = 8; + const FREQUENCY_ANNUALLY = 9; + const FREQUENCY_TWO_YEARS = 10; + + const RECURS_INDEFINITELY = 1; + protected $guarded = [ 'id', ]; @@ -21,11 +48,21 @@ class RecurringInvoice extends BaseModel 'settings' => '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); diff --git a/app/Policies/RecurringInvoicePolicy.php b/app/Policies/RecurringInvoicePolicy.php new file mode 100644 index 000000000000..ab255cce577e --- /dev/null +++ b/app/Policies/RecurringInvoicePolicy.php @@ -0,0 +1,25 @@ +isAdmin() || $user->hasPermission('create_recurring_invoice'); + } + +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index f48b7ba08831..91065a771b59 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -6,11 +6,13 @@ use App\Models\Client; use App\Models\Invoice; use App\Models\Product; use App\Models\Quote; +use App\Models\RecurringInvoice; use App\Models\User; use App\Policies\ClientPolicy; use App\Policies\InvoicePolicy; use App\Policies\ProductPolicy; use App\Policies\QuotePolicy; +use App\Policies\RecurringInvoicePolicy; use Auth; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Gate; @@ -26,6 +28,7 @@ class AuthServiceProvider extends ServiceProvider Client::class => ClientPolicy::class, Product::class => ProductPolicy::class, Invoice::class => InvoicePolicy::class, + RecurringInvoice::class => RecurringInvoicePolicy::class, Quote::class => QuotePolicy::class, User::class => UserPolicy::class, ]; diff --git a/app/Repositories/RecurringInvoiceRepository.php b/app/Repositories/RecurringInvoiceRepository.php new file mode 100644 index 000000000000..b103ce1f7036 --- /dev/null +++ b/app/Repositories/RecurringInvoiceRepository.php @@ -0,0 +1,38 @@ +fill($request->input()); + + $invoice->save(); + + + $invoice_calc = new InvoiceCalc($invoice, $invoice->settings); + + $invoice = $invoice_calc->build()->getInvoice(); + + //fire events here that cascading from the saving of an invoice + //ie. client balance update... + + return $invoice; + } + +} \ No newline at end of file diff --git a/app/Transformers/RecurringInvoiceTransformer.php b/app/Transformers/RecurringInvoiceTransformer.php new file mode 100644 index 000000000000..5795e8c10504 --- /dev/null +++ b/app/Transformers/RecurringInvoiceTransformer.php @@ -0,0 +1,161 @@ +serializer); + + return $this->includeCollection($invoice->invoice_items, $transformer, ENTITY_INVOICE_ITEM); + } + + public function includeInvitations(Invoice $invoice) + { + $transformer = new InvitationTransformer($this->account, $this->serializer); + + return $this->includeCollection($invoice->invitations, $transformer, ENTITY_INVITATION); + } + + public function includePayments(Invoice $invoice) + { + $transformer = new PaymentTransformer($this->account, $this->serializer, $invoice); + + return $this->includeCollection($invoice->payments, $transformer, ENTITY_PAYMENT); + } + + public function includeClient(Invoice $invoice) + { + $transformer = new ClientTransformer($this->account, $this->serializer); + + return $this->includeItem($invoice->client, $transformer, ENTITY_CLIENT); + } + + public function includeExpenses(Invoice $invoice) + { + $transformer = new ExpenseTransformer($this->account, $this->serializer); + + return $this->includeCollection($invoice->expenses, $transformer, ENTITY_EXPENSE); + } + + public function includeDocuments(Invoice $invoice) + { + $transformer = new DocumentTransformer($this->account, $this->serializer); + + $invoice->documents->each(function ($document) use ($invoice) { + $document->setRelation('invoice', $invoice); + }); + + return $this->includeCollection($invoice->documents, $transformer, ENTITY_DOCUMENT); + } +*/ + public function transform(RecurringInvoice $invoice) + { + return [ + 'id' => $this->encodePrimaryKey($invoice->id), + 'amount' => (float) $invoice->amount, + 'balance' => (float) $invoice->balance, + 'client_id' => (int) $invoice->client_id, + 'status_id' => (int) ($invoice->status_id ?: 1), + 'updated_at' => $invoice->updated_at, + 'archived_at' => $invoice->deleted_at, + 'discount' => (float) $invoice->discount, + 'po_number' => $invoice->po_number, + 'invoice_date' => $invoice->invoice_date ?: '', + 'due_date' => $invoice->due_date ?: '', + 'terms' => $invoice->terms ?: '', + 'public_notes' => $invoice->public_notes ?: '', + 'private_notes' => $invoice->private_notes ?: '', + 'is_deleted' => (bool) $invoice->is_deleted, + 'tax_name1' => $invoice->tax_name1 ? $invoice->tax_name1 : '', + 'tax_rate1' => (float) $invoice->tax_rate1, + 'tax_name2' => $invoice->tax_name2 ? $invoice->tax_name2 : '', + 'tax_rate2' => (float) $invoice->tax_rate2, + 'is_amount_discount' => (bool) ($invoice->is_amount_discount ?: false), + 'invoice_footer' => $invoice->invoice_footer ?: '', + 'partial' => (float) ($invoice->partial ?: 0.0), + 'partial_due_date' => $invoice->partial_due_date ?: '', + 'custom_value1' => (float) $invoice->custom_value1, + 'custom_value2' => (float) $invoice->custom_value2, + 'custom_taxes1' => (bool) $invoice->custom_taxes1, + 'custom_taxes2' => (bool) $invoice->custom_taxes2, + 'has_tasks' => (bool) $invoice->has_tasks, + 'has_expenses' => (bool) $invoice->has_expenses, + 'custom_text_value1' => $invoice->custom_text_value1 ?: '', + 'custom_text_value2' => $invoice->custom_text_value2 ?: '', + 'backup' => $invoice->backup ?: '', + 'settings' => $invoice->settings, + 'frequency_id' => (int) $invoice->frequency_id, + 'start_date' => $invoice->start_date, + 'last_sent_date' => $invoice->last_sent_date, + 'next_send_date' => $invoice->next_send_date, + 'remaining_cycles' => (int) $invoice->remaining_cycles, + ]; + } +} diff --git a/database/factories/RecurringInvoiceFactory.php b/database/factories/RecurringInvoiceFactory.php new file mode 100644 index 000000000000..cb735c181f8c --- /dev/null +++ b/database/factories/RecurringInvoiceFactory.php @@ -0,0 +1,32 @@ +define(App\Models\RecurringInvoice::class, function (Faker $faker) { + return [ + 'status_id' => App\Models\RecurringInvoice::STATUS_PENDING, + '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), + 'invoice_date' => $faker->date(), + 'due_date' => $faker->date(), + 'line_items' => false, + 'backup' => '', + 'frequency_id' => App\Models\RecurringInvoice::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 71145e854616..2c1bbab9f132 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -418,7 +418,8 @@ class CreateUsersTable extends Migration $t->unsignedInteger('user_id'); $t->unsignedInteger('company_id')->index(); - $t->string('invoice_number'); + $t->unsignedInteger('status_id')->index(); + $t->float('discount'); $t->boolean('is_amount_discount'); @@ -469,7 +470,6 @@ class CreateUsersTable extends Migration $t->timestamps(); $t->softDeletes(); - $t->unique(['company_id', 'invoice_number']); }); Schema::create('quotes', function ($t) { diff --git a/tests/Feature/RecurringInvoiceTest.php b/tests/Feature/RecurringInvoiceTest.php new file mode 100644 index 000000000000..8a3778e2383d --- /dev/null +++ b/tests/Feature/RecurringInvoiceTest.php @@ -0,0 +1,203 @@ +faker = \Faker\Factory::create(); + + Model::reguard(); + + + } + + public function testRecurringInvoiceList() + { + $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\RecurringInvoice::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_invoices'); + + $response->assertStatus(200); + + } + + public function testRecurringInvoiceRESTEndPoints() + { + $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\RecurringInvoice::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]); + + $RecurringInvoice = RecurringInvoice::where('user_id',$user->id)->first(); + $RecurringInvoice->settings = $client->getMergedSettings(); + $RecurringInvoice->save(); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->get('/api/v1/recurring_invoices/'.$this->encodePrimaryKey($RecurringInvoice->id)); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->get('/api/v1/recurring_invoices/'.$this->encodePrimaryKey($RecurringInvoice->id).'/edit'); + + $response->assertStatus(200); + + $RecurringInvoice_update = [ + 'status_id' => RecurringInvoice::STATUS_PAID + ]; + + $this->assertNotNull($RecurringInvoice); + $this->assertNotNull($RecurringInvoice->settings); + + $this->assertTrue(property_exists($RecurringInvoice->settings, 'custom_taxes1')); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->put('/api/v1/recurring_invoices/'.$this->encodePrimaryKey($RecurringInvoice->id), $RecurringInvoice_update) + ->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->delete('/api/v1/recurring_invoices/'.$this->encodePrimaryKey($RecurringInvoice->id)); + + $response->assertStatus(200); + + } + +} From 7985315335260a37f5a1a9cacf5f89ced0b3be3b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 08:33:32 +1000 Subject: [PATCH 2/9] Recurring Invoice Filters --- app/Filters/RecurringInvoiceFilters.php | 111 ++++++++++++++++++ .../RecurringInvoiceTransformer.php | 1 + 2 files changed, 112 insertions(+) create mode 100644 app/Filters/RecurringInvoiceFilters.php diff --git a/app/Filters/RecurringInvoiceFilters.php b/app/Filters/RecurringInvoiceFilters.php new file mode 100644 index 000000000000..b412dc7736c0 --- /dev/null +++ b/app/Filters/RecurringInvoiceFilters.php @@ -0,0 +1,111 @@ +builder; + + return $this->builder->where(function ($query) use ($filter) { + $query->where('recurring_invoices.custom_value1', 'like', '%'.$filter.'%') + ->orWhere('recurring_invoices.custom_value2', 'like' , '%'.$filter.'%') + ->orWhere('recurring_invoices.custom_value3', 'like' , '%'.$filter.'%') + ->orWhere('recurring_invoices.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/Transformers/RecurringInvoiceTransformer.php b/app/Transformers/RecurringInvoiceTransformer.php index 5795e8c10504..e8a668113e07 100644 --- a/app/Transformers/RecurringInvoiceTransformer.php +++ b/app/Transformers/RecurringInvoiceTransformer.php @@ -3,6 +3,7 @@ namespace App\Transformers; use App\Models\Invoice; +use App\Models\RecurringInvoice; use App\Utils\Traits\MakesHash; /** From 7b62c50f90c934bc954d963307728c66ec56747d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 11:30:08 +1000 Subject: [PATCH 3/9] Fix Recurring implicit binding --- app/Models/RecurringInvoice.php | 4 ++-- app/Providers/RouteServiceProvider.php | 4 ++++ tests/Feature/QuoteTest.php | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index 443c1437fcfa..cb2ad71021f7 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -49,8 +49,8 @@ class RecurringInvoice extends BaseModel ]; protected $with = [ - 'client', - 'company', + // 'client', + // 'company', ]; public function company() diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 7547c292e611..674ba882fe43 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -42,6 +42,10 @@ class RouteServiceProvider extends ServiceProvider return \App\Models\Invoice::withTrashed()->where('id', $this->decodePrimaryKey($value))->firstOrFail(); }); + Route::bind('recurring_invoice', function ($value) { + return \App\Models\RecurringInvoice::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/tests/Feature/QuoteTest.php b/tests/Feature/QuoteTest.php index 834dd56e018c..b8224d4dfc7c 100644 --- a/tests/Feature/QuoteTest.php +++ b/tests/Feature/QuoteTest.php @@ -160,7 +160,7 @@ class QuoteTest extends TestCase $quote = Quote::where('user_id',$user->id)->first(); $quote->settings = $client->getMergedSettings(); $quote->save(); - Log::error(print_r($quote,1)); + $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $token, From 5bc41b4d79d4fb0e46d6ec13d5620c5cf81dee71 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 16:11:43 +1000 Subject: [PATCH 4/9] payments --- app/Http/Controllers/PaymentController.php | 230 +++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 app/Http/Controllers/PaymentController.php diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php new file mode 100644 index 000000000000..4097d1e3e3d1 --- /dev/null +++ b/app/Http/Controllers/PaymentController.php @@ -0,0 +1,230 @@ +payment_repo = $payment_repo; + + } + + /** + * Show the list of Invoices + * + * @param \App\Filters\PaymentFilters $filters The filters + * + * @return \Illuminate\Http\Response + */ + public function index(PaymentFilters $filters) + { + + $payments = Invoice::filter($filters); + + return $this->listResponse($payments); + + } + + /** + * Show the form for creating a new resource. + * + * @param \App\Http\Requests\Payment\CreatePaymentRequest $request The request + * + * @return \Illuminate\Http\Response + */ + public function create(CreatePaymentRequest $request) + { + + $payment = PaymentFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($payment); + + } + + + /** + * Store a newly created resource in storage. + * + * @param \App\Http\Requests\Payment\StorePaymentRequest $request The request + * + * @return \Illuminate\Http\Response + */ + public function store(StorePaymentRequest $request) + { + + $payment = $this->payment_repo->save($request, PaymentFactory::create(auth()->user()->company()->id, auth()->user()->id)); + + return $this->itemResponse($payment); + + } + + /** + * Display the specified resource. + * + * @param \App\Http\Requests\Payment\ShowPaymentRequest $request The request + * @param \App\Models\Invoice $payment The invoice + * + * @return \Illuminate\Http\Response + */ + public function show(ShowPaymentRequest $request, Invoice $payment) + { + + return $this->itemResponse($payment); + + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Http\Requests\Payment\EditPaymentRequest $request The request + * @param \App\Models\Invoice $payment The invoice + * + * @return \Illuminate\Http\Response + */ + public function edit(EditPaymentRequest $request, Invoice $payment) + { + + return $this->itemResponse($payment); + + } + + /** + * Update the specified resource in storage. + * + * @param \App\Http\Requests\Payment\UpdatePaymentRequest $request The request + * @param \App\Models\Invoice $payment The invoice + * + * @return \Illuminate\Http\Response + */ + public function update(UpdatePaymentRequest $request, Invoice $payment) + { + + $payment = $this->payment_repo->save(request(), $payment); + + return $this->itemResponse($payment); + + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Http\Requests\Payment\DestroyPaymentRequest $request + * @param \App\Models\Invoice $payment + * + * @return \Illuminate\Http\Response + */ + public function destroy(DestroyPaymentRequest $request, Invoice $payment) + { + + $payment->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'); + + $payments = Payment::withTrashed()->find($ids); + + $payments->each(function ($payment, $key) use($action){ + + if(auth()->user()->can('edit', $payment)) + $this->payment_repo->{$action}($payment); + + }); + + //todo need to return the updated dataset + return $this->listResponse(Payment::withTrashed()->whereIn('id', $ids)); + + } + + public function action(ActionPaymentRequest $request, Invoice $payment, $action) + { + + switch ($action) { + case 'clone_to_invoice': + $payment = CloneInvoiceFactory::create($payment, auth()->user()->id); + return $this->itemResponse($payment); + break; + case 'clone_to_quote': + $quote = CloneInvoiceToQuoteFactory::create($payment, 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; + } + } + +} From 9f9f3439b43d48c299c4003139ce8a45c186f84b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 17:35:49 +1000 Subject: [PATCH 5/9] Fixes for recurring invoices test --- app/Factory/RecurringInvoiceFactory.php | 2 +- app/Http/Controllers/RecurringInvoiceController.php | 6 +++--- app/Models/RecurringInvoice.php | 10 ++++++---- tests/Feature/RecurringInvoiceTest.php | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/Factory/RecurringInvoiceFactory.php b/app/Factory/RecurringInvoiceFactory.php index b656eda9af03..7e6d2b7b7952 100644 --- a/app/Factory/RecurringInvoiceFactory.php +++ b/app/Factory/RecurringInvoiceFactory.php @@ -11,7 +11,7 @@ class RecurringInvoiceFactory public static function create(int $company_id, int $user_id) :RecurringInvoice { $invoice = new RecurringInvoice(); - $invoice->status_id = RecurringInvoice::STATUS_PENDING; + $invoice->status_id = RecurringInvoice::STATUS_DRAFT; $invoice->discount = 0; $invoice->is_amount_discount = true; $invoice->po_number = ''; diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index 8fc7c273c525..9cd0543af6c6 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -100,7 +100,7 @@ class RecurringInvoiceController extends BaseController public function store(StoreRecurringInvoiceRequest $request) { - $recurring_invoice = $this->RecurringInvoice_repo->save($request, RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id)); + $recurring_invoice = $this->recurring_invoice_repo->save($request, RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id)); return $this->itemResponse($recurring_invoice); @@ -147,7 +147,7 @@ class RecurringInvoiceController extends BaseController public function update(UpdateRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice) { - $recurring_invoice = $this->RecurringInvoice_repo->save(request(), $recurring_invoice); + $recurring_invoice = $this->recurring_invoice_repo->save(request(), $recurring_invoice); return $this->itemResponse($recurring_invoice); @@ -187,7 +187,7 @@ class RecurringInvoiceController extends BaseController $recurring_invoices->each(function ($recurring_invoice, $key) use($action){ if(auth()->user()->can('edit', $recurring_invoice)) - $this->RecurringInvoice_repo->{$action}($recurring_invoice); + $this->recurring_invoice_repo->{$action}($recurring_invoice); }); diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index cb2ad71021f7..f06cbf996ae2 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -19,10 +19,12 @@ class RecurringInvoice extends BaseModel /** * Invoice Statuses */ - const STATUS_PENDING = 1; - const STATUS_ACTIVE = 2; - const STATUS_COMPLETED = 3; - const STATUS_CANCELLED = 4; + const STATUS_DRAFT = 2; + const STATUS_ACTIVE = 3; + const STATUS_PENDING = -1; + const STATUS_COMPLETED = -2; + const STATUS_CANCELLED = -3; + /** * Recurring intervals diff --git a/tests/Feature/RecurringInvoiceTest.php b/tests/Feature/RecurringInvoiceTest.php index 8a3778e2383d..abc4eb0e51c2 100644 --- a/tests/Feature/RecurringInvoiceTest.php +++ b/tests/Feature/RecurringInvoiceTest.php @@ -177,7 +177,7 @@ class RecurringInvoiceTest extends TestCase $response->assertStatus(200); $RecurringInvoice_update = [ - 'status_id' => RecurringInvoice::STATUS_PAID + 'status_id' => RecurringInvoice::STATUS_DRAFT ]; $this->assertNotNull($RecurringInvoice); From ac05fc6ff6797fcce0a8d3a2271a020e2f0fd63d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 17:57:55 +1000 Subject: [PATCH 6/9] Working on Payments --- app/Factory/PaymentFactory.php | 35 +++ app/Filters/PaymentFilters.php | 111 ++++++++++ app/Http/Controllers/PaymentController.php | 7 +- .../Requests/Payment/ActionPaymentRequest.php | 21 ++ .../Requests/Payment/CreatePaymentRequest.php | 21 ++ .../Payment/DestroyPaymentRequest.php | 21 ++ .../Requests/Payment/EditPaymentRequest.php | 40 ++++ .../Requests/Payment/ShowPaymentRequest.php | 21 ++ .../Requests/Payment/StorePaymentRequest.php | 41 ++++ .../Requests/Payment/UpdatePaymentRequest.php | 31 +++ app/Models/Payment.php | 17 +- app/Policies/PaymentPolicy.php | 25 +++ app/Providers/AuthServiceProvider.php | 3 + database/factories/PaymentFactory.php | 17 ++ tests/Feature/PaymentTest.php | 202 ++++++++++++++++++ 15 files changed, 604 insertions(+), 9 deletions(-) create mode 100644 app/Factory/PaymentFactory.php create mode 100644 app/Filters/PaymentFilters.php create mode 100644 app/Http/Requests/Payment/ActionPaymentRequest.php create mode 100644 app/Http/Requests/Payment/CreatePaymentRequest.php create mode 100644 app/Http/Requests/Payment/DestroyPaymentRequest.php create mode 100644 app/Http/Requests/Payment/EditPaymentRequest.php create mode 100644 app/Http/Requests/Payment/ShowPaymentRequest.php create mode 100644 app/Http/Requests/Payment/StorePaymentRequest.php create mode 100644 app/Http/Requests/Payment/UpdatePaymentRequest.php create mode 100644 app/Policies/PaymentPolicy.php create mode 100644 database/factories/PaymentFactory.php create mode 100644 tests/Feature/PaymentTest.php diff --git a/app/Factory/PaymentFactory.php b/app/Factory/PaymentFactory.php new file mode 100644 index 000000000000..e626f4aa68e2 --- /dev/null +++ b/app/Factory/PaymentFactory.php @@ -0,0 +1,35 @@ +company_id = $company_id; + $payment->user_id = $user_id; + $payment->client_id = 0; + $payment->client_contact_id = null; + $payment->invitation_id = null; + $payment->account_gateway_id = null; + $payment->payment_type_id = null; + $payment->is_deleted = false; + $payment->amount = 0; + $payment->payment_date = null; + $payment->transaction_reference = null; + $payment->payer_id = null; + $payment->invoice_id = 0; + + return $payment; + } +} + + + diff --git a/app/Filters/PaymentFilters.php b/app/Filters/PaymentFilters.php new file mode 100644 index 000000000000..90902314259c --- /dev/null +++ b/app/Filters/PaymentFilters.php @@ -0,0 +1,111 @@ +builder; + + return $this->builder->where(function ($query) use ($filter) { + $query->where('payments.custom_value1', 'like', '%'.$filter.'%') + ->orWhere('payments.custom_value2', 'like' , '%'.$filter.'%') + ->orWhere('payments.custom_value3', 'like' , '%'.$filter.'%') + ->orWhere('payments.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 = 'payments'; + $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/PaymentController.php b/app/Http/Controllers/PaymentController.php index 4097d1e3e3d1..fd16e04cefe7 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; +use App\Filters\PaymentFilters; use App\Http\Requests\Payment\ActionPaymentRequest; use App\Http\Requests\Payment\CreatePaymentRequest; use App\Http\Requests\Payment\DestroyPaymentRequest; @@ -13,6 +14,7 @@ use App\Http\Requests\Payment\UpdatePaymentRequest; use App\Jobs\Entity\ActionEntity; use App\Models\Payment; use App\Repositories\BaseRepository; +use App\Transformers\PaymentTransformer; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; @@ -28,14 +30,13 @@ class PaymentController extends BaseController protected $entity_type = Payment::class; - protected $entity_transformer = Paymentransformer::class; + protected $entity_transformer = PaymentTransformer::class; /** * @var PaymentRepository */ protected $payment_repo; - protected $base_repo; /** * PaymentController constructor. @@ -61,7 +62,7 @@ class PaymentController extends BaseController public function index(PaymentFilters $filters) { - $payments = Invoice::filter($filters); + $payments = Payment::filter($filters); return $this->listResponse($payments); diff --git a/app/Http/Requests/Payment/ActionPaymentRequest.php b/app/Http/Requests/Payment/ActionPaymentRequest.php new file mode 100644 index 000000000000..d60bb3babdd4 --- /dev/null +++ b/app/Http/Requests/Payment/ActionPaymentRequest.php @@ -0,0 +1,21 @@ +user()->can('edit', $this->payment); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/Payment/CreatePaymentRequest.php b/app/Http/Requests/Payment/CreatePaymentRequest.php new file mode 100644 index 000000000000..0c3c30b74749 --- /dev/null +++ b/app/Http/Requests/Payment/CreatePaymentRequest.php @@ -0,0 +1,21 @@ +user()->can('create', Payment::class); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/Payment/DestroyPaymentRequest.php b/app/Http/Requests/Payment/DestroyPaymentRequest.php new file mode 100644 index 000000000000..34604ce5ea64 --- /dev/null +++ b/app/Http/Requests/Payment/DestroyPaymentRequest.php @@ -0,0 +1,21 @@ +user()->can('edit', $this->payment); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/Payment/EditPaymentRequest.php b/app/Http/Requests/Payment/EditPaymentRequest.php new file mode 100644 index 000000000000..a336386d81ab --- /dev/null +++ b/app/Http/Requests/Payment/EditPaymentRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->payment); + } + + 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/Payment/ShowPaymentRequest.php b/app/Http/Requests/Payment/ShowPaymentRequest.php new file mode 100644 index 000000000000..b9a5b998b8b2 --- /dev/null +++ b/app/Http/Requests/Payment/ShowPaymentRequest.php @@ -0,0 +1,21 @@ +user()->can('view', $this->payment); + } + +} \ No newline at end of file diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php new file mode 100644 index 000000000000..2a1336aad191 --- /dev/null +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -0,0 +1,41 @@ +user()->can('create', Payment::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 Payment request here, ie. Payment_items + } + + public function messages() + { + + } + + +} + diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php new file mode 100644 index 000000000000..96c5fe59712c --- /dev/null +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -0,0 +1,31 @@ +user()->can('edit', $this->payment); + + } + + + 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/Payment.php b/app/Models/Payment.php index dab643fe3357..b32b69f967f0 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -2,27 +2,32 @@ namespace App\Models; +use App\Models\Filterable; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; class Payment extends BaseModel { use MakesHash; + use Filterable; protected $guarded = [ 'id', ]; - protected $appends = ['payment_id']; - - public function getRouteKeyName() + public function client() { - return 'payment_id'; + return $this->belongsTo(Client::class); } - public function getPaymentIdAttribute() + public function company() { - return $this->encodePrimaryKey($this->id); + return $this->belongsTo(Company::class); + } + + public function user() + { + return $this->belongsTo(User::class); } public function documents() diff --git a/app/Policies/PaymentPolicy.php b/app/Policies/PaymentPolicy.php new file mode 100644 index 000000000000..986a60bac533 --- /dev/null +++ b/app/Policies/PaymentPolicy.php @@ -0,0 +1,25 @@ +isAdmin() || $user->hasPermission('create_payment'); + } + +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 91065a771b59..82ca77936732 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -4,12 +4,14 @@ namespace App\Providers; use App\Models\Client; use App\Models\Invoice; +use App\Models\Payment; use App\Models\Product; use App\Models\Quote; use App\Models\RecurringInvoice; use App\Models\User; use App\Policies\ClientPolicy; use App\Policies\InvoicePolicy; +use App\Policies\PaymentPolicy; use App\Policies\ProductPolicy; use App\Policies\QuotePolicy; use App\Policies\RecurringInvoicePolicy; @@ -28,6 +30,7 @@ class AuthServiceProvider extends ServiceProvider Client::class => ClientPolicy::class, Product::class => ProductPolicy::class, Invoice::class => InvoicePolicy::class, + Payment::class => PaymentPolicy::class, RecurringInvoice::class => RecurringInvoicePolicy::class, Quote::class => QuotePolicy::class, User::class => UserPolicy::class, diff --git a/database/factories/PaymentFactory.php b/database/factories/PaymentFactory.php new file mode 100644 index 000000000000..919a6473a10d --- /dev/null +++ b/database/factories/PaymentFactory.php @@ -0,0 +1,17 @@ +define(App\Models\Payment::class, function (Faker $faker) { + return [ + 'id_deleted' => false, + 'amount' => $faker->numberBetween(1,10), + 'payment_date' => $faker->date(), + 'transaction_reference' => $faker->text(10), + 'invoice_id' => $faker->numberBetween(1,10) + ]; +}); + + \ No newline at end of file diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php new file mode 100644 index 000000000000..3ce8ac6e4d88 --- /dev/null +++ b/tests/Feature/PaymentTest.php @@ -0,0 +1,202 @@ +faker = \Faker\Factory::create(); + + Model::reguard(); + + + } + + public function testPaymentList() + { + $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\Payment::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/payments'); + + $response->assertStatus(200); + + } + + public function testPaymentRESTEndPoints() + { + $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\Payment::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]); + + $Payment = Payment::where('user_id',$user->id)->first(); + $Payment->settings = $client->getMergedSettings(); + $Payment->save(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->get('/api/v1/payments/'.$this->encodePrimaryKey($Payment->id)); + + $response->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->get('/api/v1/payments/'.$this->encodePrimaryKey($Payment->id).'/edit'); + + $response->assertStatus(200); + + $Payment_update = [ + 'amount' => 10 + ]; + + $this->assertNotNull($Payment); + $this->assertNotNull($Payment->settings); + + $this->assertTrue(property_exists($Payment->settings, 'custom_taxes1')); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->put('/api/v1/payments/'.$this->encodePrimaryKey($Payment->id), $Payment_update) + ->assertStatus(200); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $token, + ])->delete('/api/v1/payments/'.$this->encodePrimaryKey($Payment->id)); + + $response->assertStatus(200); + + } + +} From 6660e881ef543cf4cc945640c0d90fce56de7b6a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 18:28:48 +1000 Subject: [PATCH 7/9] Fixes for Payment tests --- app/Http/Controllers/PaymentController.php | 12 +++++++----- database/factories/PaymentFactory.php | 2 +- .../2014_10_13_000000_create_users_table.php | 2 +- routes/api.php | 8 +++++--- tests/Feature/PaymentTest.php | 6 +----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index fd16e04cefe7..1e8866771492 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; +use App\Factory\PaymentFactory; use App\Filters\PaymentFilters; use App\Http\Requests\Payment\ActionPaymentRequest; use App\Http\Requests\Payment\CreatePaymentRequest; @@ -14,6 +15,7 @@ use App\Http\Requests\Payment\UpdatePaymentRequest; use App\Jobs\Entity\ActionEntity; use App\Models\Payment; use App\Repositories\BaseRepository; +use App\Repositories\PaymentRepository; use App\Transformers\PaymentTransformer; use App\Utils\Traits\MakesHash; use Illuminate\Http\Request; @@ -109,7 +111,7 @@ class PaymentController extends BaseController * * @return \Illuminate\Http\Response */ - public function show(ShowPaymentRequest $request, Invoice $payment) + public function show(ShowPaymentRequest $request, Payment $payment) { return $this->itemResponse($payment); @@ -124,7 +126,7 @@ class PaymentController extends BaseController * * @return \Illuminate\Http\Response */ - public function edit(EditPaymentRequest $request, Invoice $payment) + public function edit(EditPaymentRequest $request, Payment $payment) { return $this->itemResponse($payment); @@ -139,7 +141,7 @@ class PaymentController extends BaseController * * @return \Illuminate\Http\Response */ - public function update(UpdatePaymentRequest $request, Invoice $payment) + public function update(UpdatePaymentRequest $request, Payment $payment) { $payment = $this->payment_repo->save(request(), $payment); @@ -156,7 +158,7 @@ class PaymentController extends BaseController * * @return \Illuminate\Http\Response */ - public function destroy(DestroyPaymentRequest $request, Invoice $payment) + public function destroy(DestroyPaymentRequest $request, Payment $payment) { $payment->delete(); @@ -191,7 +193,7 @@ class PaymentController extends BaseController } - public function action(ActionPaymentRequest $request, Invoice $payment, $action) + public function action(ActionPaymentRequest $request, Payment $payment, $action) { switch ($action) { diff --git a/database/factories/PaymentFactory.php b/database/factories/PaymentFactory.php index 919a6473a10d..de107b6e2eb9 100644 --- a/database/factories/PaymentFactory.php +++ b/database/factories/PaymentFactory.php @@ -6,7 +6,7 @@ use Faker\Generator as Faker; $factory->define(App\Models\Payment::class, function (Faker $faker) { return [ - 'id_deleted' => false, + 'is_deleted' => false, 'amount' => $faker->numberBetween(1,10), 'payment_date' => $faker->date(), 'transaction_reference' => $faker->text(10), 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 2c1bbab9f132..984cdf54d0b4 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -624,7 +624,7 @@ class CreateUsersTable extends Migration $t->string('transaction_reference')->nullable(); $t->string('payer_id')->nullable(); - $t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); + //$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); $t->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); $t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $t->foreign('client_contact_id')->references('id')->on('client_contacts')->onDelete('cascade'); diff --git a/routes/api.php b/routes/api.php index 6109501d69e8..84bfd75bf191 100644 --- a/routes/api.php +++ b/routes/api.php @@ -51,14 +51,16 @@ Route::group(['middleware' => ['db','api_secret_check','token_auth'], 'prefix' = Route::post('recurring_invoices/bulk', 'RecurringInvoiceController@bulk')->name('recurring_invoices.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 + + Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk'); + /* Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit Route::post('tasks/bulk', 'TaskController@bulk')->name('tasks.bulk'); - Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit - - Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk'); Route::resource('credits', 'CreditController'); // name = (credits. index / create / show / update / destroy / edit diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index 3ce8ac6e4d88..32d8545962b8 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -157,9 +157,7 @@ class PaymentTest extends TestCase factory(\App\Models\Payment::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]); - $Payment = Payment::where('user_id',$user->id)->first(); - $Payment->settings = $client->getMergedSettings(); - $Payment->save(); + $Payment = Payment::all()->first(); $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), @@ -180,9 +178,7 @@ class PaymentTest extends TestCase ]; $this->assertNotNull($Payment); - $this->assertNotNull($Payment->settings); - $this->assertTrue(property_exists($Payment->settings, 'custom_taxes1')); $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), From 9538626b0390ea417dc4c162e91714c0ab40a58a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 18:32:30 +1000 Subject: [PATCH 8/9] Add Payment Repo --- app/Repositories/PaymentRepository.php | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/Repositories/PaymentRepository.php diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php new file mode 100644 index 000000000000..63004d332756 --- /dev/null +++ b/app/Repositories/PaymentRepository.php @@ -0,0 +1,29 @@ +fill($request->input()); + + $payment->save(); + + return $payment; + } + +} \ No newline at end of file From a823b9107e6f0efaa3987bc72cff4f0032ca44b9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 3 May 2019 18:34:27 +1000 Subject: [PATCH 9/9] Fixes for factory tests --- tests/Unit/FactoryCreationTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/FactoryCreationTest.php b/tests/Unit/FactoryCreationTest.php index 97a390b6ccf7..8a40cc9112ba 100644 --- a/tests/Unit/FactoryCreationTest.php +++ b/tests/Unit/FactoryCreationTest.php @@ -85,7 +85,7 @@ class FactoryCreationTest extends TestCase /** * @test - * @covers App|Factory\CloneInvoiceFactory + * @covers App\Factory\CloneInvoiceFactory */ public function testCloneInvoiceCreation() { @@ -114,7 +114,7 @@ class FactoryCreationTest extends TestCase /** * @test - * @covers App|Factory\ClientFactory + * @covers App\Factory\ClientFactory */ public function testClientCreate() { @@ -129,7 +129,7 @@ class FactoryCreationTest extends TestCase /** * @test - * @covers App|Factory\ClientContactFactory + * @covers App\Factory\ClientContactFactory */ public function testClientContactCreate() { @@ -166,7 +166,7 @@ class FactoryCreationTest extends TestCase /** * @test - * @covers App|Factory\UserFactory + * @covers App\Factory\UserFactory */ public function testUserCreate() {