From 4a55e261176b498b0b5f2433d551add4aa2dac1d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 26 Jun 2017 07:16:29 +0300 Subject: [PATCH] Working on recurring expenses --- app/Constants.php | 1 + app/Http/Controllers/ExpenseController.php | 1 + .../RecurringExpenseController.php | 164 +++++++++ .../CreateRecurringExpenseRequest.php | 28 ++ app/Http/Requests/ExpenseRequest.php | 2 +- app/Http/Requests/RecurringExpenseRequest.php | 8 + .../UpdateRecurringExpenseRequest.php | 28 ++ app/Http/routes.php | 8 + app/Models/RecurringExpense.php | 148 +++++++++ .../Datatables/RecurringExpenseDatatable.php | 112 +++++++ .../RecurringExpenseRepository.php | 141 ++++++++ app/Policies/RecurringExpensePolicy.php | 23 ++ app/Providers/AuthServiceProvider.php | 1 + app/Services/RecurringExpenseService.php | 82 +++++ .../2017_06_19_111515_update_dark_mode.php | 15 +- resources/lang/en/texts.php | 16 + resources/views/expenses/edit.blade.php | 313 ++++++++++-------- 17 files changed, 944 insertions(+), 147 deletions(-) create mode 100644 app/Http/Controllers/RecurringExpenseController.php create mode 100644 app/Http/Requests/CreateRecurringExpenseRequest.php create mode 100644 app/Http/Requests/RecurringExpenseRequest.php create mode 100644 app/Http/Requests/UpdateRecurringExpenseRequest.php create mode 100644 app/Models/RecurringExpense.php create mode 100644 app/Ninja/Datatables/RecurringExpenseDatatable.php create mode 100644 app/Ninja/Repositories/RecurringExpenseRepository.php create mode 100644 app/Policies/RecurringExpensePolicy.php create mode 100644 app/Services/RecurringExpenseService.php diff --git a/app/Constants.php b/app/Constants.php index 1c3035bc4d..eaaf19675f 100644 --- a/app/Constants.php +++ b/app/Constants.php @@ -37,6 +37,7 @@ if (! defined('APP_NAME')) { define('ENTITY_BANK_SUBACCOUNT', 'bank_subaccount'); define('ENTITY_EXPENSE_CATEGORY', 'expense_category'); define('ENTITY_PROJECT', 'project'); + define('ENTITY_RECURRING_EXPENSE', 'recurring_expense'); define('INVOICE_TYPE_STANDARD', 1); define('INVOICE_TYPE_QUOTE', 2); diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php index 80316b2330..0d7e068b89 100644 --- a/app/Http/Controllers/ExpenseController.php +++ b/app/Http/Controllers/ExpenseController.php @@ -266,6 +266,7 @@ class ExpenseController extends BaseController 'customLabel2' => Auth::user()->account->custom_vendor_label2, 'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(), 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('name')->get(), + 'isRecurring' => false, ]; } diff --git a/app/Http/Controllers/RecurringExpenseController.php b/app/Http/Controllers/RecurringExpenseController.php new file mode 100644 index 0000000000..0f20af6e28 --- /dev/null +++ b/app/Http/Controllers/RecurringExpenseController.php @@ -0,0 +1,164 @@ +recurringExpenseRepo = $recurringExpenseRepo; + $this->recurringExpenseService = $recurringExpenseService; + } + + /** + * Display a listing of the resource. + * + * @return Response + */ + public function index() + { + return View::make('list_wrapper', [ + 'entityType' => ENTITY_RECURRING_EXPENSE, + 'datatable' => new RecurringExpenseDatatable(), + 'title' => trans('texts.recurring_expenses'), + ]); + } + + public function getDatatable($expensePublicId = null) + { + $search = Input::get('sSearch'); + $userId = Auth::user()->filterId(); + + return $this->recurringExpenseService->getDatatable($search, $userId); + } + + public function create(RecurringExpenseRequest $request) + { + if ($request->vendor_id != 0) { + $vendor = Vendor::scope($request->vendor_id)->with('vendor_contacts')->firstOrFail(); + } else { + $vendor = null; + } + + $data = [ + 'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $request->vendor_id, + 'expense' => null, + 'method' => 'POST', + 'url' => 'recurring_expenses', + 'title' => trans('texts.new_expense'), + 'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(), + 'vendor' => $vendor, + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => $request->client_id, + 'categoryPublicId' => $request->category_id, + ]; + + $data = array_merge($data, self::getViewModel()); + + return View::make('expenses.edit', $data); + } + + public function edit(RecurringExpenseRequest $request) + { + $expense = $request->entity(); + + $actions = []; + if (! $expense->trashed()) { + $actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_expense')]; + $actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans('texts.delete_expense')]; + } else { + $actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_expense')]; + } + + $data = [ + 'vendor' => null, + 'expense' => $expense, + 'entity' => $expense, + 'method' => 'PUT', + 'url' => 'recurring_expenses/'.$expense->public_id, + 'title' => 'Edit Expense', + 'actions' => $actions, + 'vendors' => Vendor::scope()->with('vendor_contacts')->orderBy('name')->get(), + 'vendorPublicId' => $expense->vendor ? $expense->vendor->public_id : null, + 'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), + 'clientPublicId' => $expense->client ? $expense->client->public_id : null, + 'categoryPublicId' => $expense->expense_category ? $expense->expense_category->public_id : null, + ]; + + $data = array_merge($data, self::getViewModel()); + + return View::make('expenses.edit', $data); + } + + private static function getViewModel() + { + return [ + 'data' => Input::old('data'), + 'account' => Auth::user()->account, + 'sizes' => Cache::get('sizes'), + 'paymentTerms' => Cache::get('paymentTerms'), + 'industries' => Cache::get('industries'), + 'currencies' => Cache::get('currencies'), + 'languages' => Cache::get('languages'), + 'countries' => Cache::get('countries'), + 'customLabel1' => Auth::user()->account->custom_vendor_label1, + 'customLabel2' => Auth::user()->account->custom_vendor_label2, + 'categories' => ExpenseCategory::whereAccountId(Auth::user()->account_id)->withArchived()->orderBy('name')->get(), + 'taxRates' => TaxRate::scope()->whereIsInclusive(false)->orderBy('name')->get(), + 'isRecurring' => true, + ]; + } + + public function store(CreateRecurringExpenseRequest $request) + { + $recurringExpense = $this->recurringExpenseService->save($request->input()); + + Session::flash('message', trans('texts.created_recurring_expense')); + + return redirect()->to($recurringExpense->getRoute()); + } + + public function update(UpdateRecurringExpenseRequest $request) + { + $recurringExpense = $this->recurringExpenseService->save($request->input(), $request->entity()); + + Session::flash('message', trans('texts.updated_recurring_expense')); + + return redirect()->to($recurringExpense->getRoute()); + } + + public function bulk() + { + $action = Input::get('action'); + $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); + $count = $this->recurringExpenseService->bulk($ids, $action); + + if ($count > 0) { + $field = $count == 1 ? "{$action}d_recurring_expense" : "{$action}d_recurring_expenses"; + $message = trans("texts.$field", ['count' => $count]); + Session::flash('message', $message); + } + + return redirect()->to('/recurring_expenses'); + } +} diff --git a/app/Http/Requests/CreateRecurringExpenseRequest.php b/app/Http/Requests/CreateRecurringExpenseRequest.php new file mode 100644 index 0000000000..afb5bb9414 --- /dev/null +++ b/app/Http/Requests/CreateRecurringExpenseRequest.php @@ -0,0 +1,28 @@ +user()->can('create', ENTITY_RECURRING_EXPENSE); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'amount' => 'numeric', + ]; + } +} diff --git a/app/Http/Requests/ExpenseRequest.php b/app/Http/Requests/ExpenseRequest.php index 17388a6f3a..056cf1c308 100644 --- a/app/Http/Requests/ExpenseRequest.php +++ b/app/Http/Requests/ExpenseRequest.php @@ -14,7 +14,7 @@ class ExpenseRequest extends EntityRequest $expense = parent::entity(); // eager load the documents - if ($expense && ! $expense->relationLoaded('documents')) { + if ($expense && method_exists($expense, 'documents') && ! $expense->relationLoaded('documents')) { $expense->load('documents'); } diff --git a/app/Http/Requests/RecurringExpenseRequest.php b/app/Http/Requests/RecurringExpenseRequest.php new file mode 100644 index 0000000000..3088002f6b --- /dev/null +++ b/app/Http/Requests/RecurringExpenseRequest.php @@ -0,0 +1,8 @@ +user()->can('edit', $this->entity()); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'amount' => 'numeric', + ]; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index c0aa67eaa7..2af1e65f2d 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -179,6 +179,14 @@ Route::group(['middleware' => ['lookup:user', 'auth:user']], function () { Route::post('invoices/bulk', 'InvoiceController@bulk'); Route::post('recurring_invoices/bulk', 'InvoiceController@bulk'); + Route::get('recurring_expenses', 'RecurringExpenseController@index'); + Route::get('api/recurring_expenses', 'RecurringExpenseController@getDatatable'); + Route::get('recurring_expenses/create/{vendor_id?}/{client_id?}/{category_id?}', 'RecurringExpenseController@create'); + Route::post('recurring_expenses', 'RecurringExpenseController@store'); + Route::put('recurring_expenses/{recurring_expenses}', 'RecurringExpenseController@update'); + Route::get('recurring_expenses/{recurring_expenses}/edit', 'RecurringExpenseController@edit'); + Route::post('recurring_expenses/bulk', 'RecurringExpenseController@bulk'); + Route::get('documents/{documents}/{filename?}', 'DocumentController@get'); Route::get('documents/js/{documents}/{filename}', 'DocumentController@getVFSJS'); Route::get('documents/preview/{documents}/{filename?}', 'DocumentController@getPreview'); diff --git a/app/Models/RecurringExpense.php b/app/Models/RecurringExpense.php new file mode 100644 index 0000000000..2068bedb2f --- /dev/null +++ b/app/Models/RecurringExpense.php @@ -0,0 +1,148 @@ +belongsTo('App\Models\ExpenseCategory')->withTrashed(); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function account() + { + return $this->belongsTo('App\Models\Account'); + } + + /** + * @return mixed + */ + public function user() + { + return $this->belongsTo('App\Models\User')->withTrashed(); + } + + /** + * @return mixed + */ + public function vendor() + { + return $this->belongsTo('App\Models\Vendor')->withTrashed(); + } + + /** + * @return mixed + */ + public function client() + { + return $this->belongsTo('App\Models\Client')->withTrashed(); + } + + /** + * @return mixed + */ + public function getName() + { + if ($this->public_notes) { + return Utils::truncateString($this->public_notes, 16); + } else { + return '#' . $this->public_id; + } + } + + /** + * @return mixed + */ + public function getDisplayName() + { + return $this->getName(); + } + + /** + * @return string + */ + public function getRoute() + { + return "/recurring_expenses/{$this->public_id}/edit"; + } + + /** + * @return mixed + */ + public function getEntityType() + { + return ENTITY_RECURRING_EXPENSE; + } + + public function amountWithTax() + { + return Utils::calculateTaxes($this->amount, $this->tax_rate1, $this->tax_rate2); + } +} + +RecurringExpense::creating(function ($expense) { + $expense->setNullValues(); +}); + +RecurringExpense::created(function ($expense) { + //event(new ExpenseWasCreated($expense)); +}); + +RecurringExpense::updating(function ($expense) { + $expense->setNullValues(); +}); + +RecurringExpense::updated(function ($expense) { + //event(new ExpenseWasUpdated($expense)); +}); + +RecurringExpense::deleting(function ($expense) { + $expense->setNullValues(); +}); diff --git a/app/Ninja/Datatables/RecurringExpenseDatatable.php b/app/Ninja/Datatables/RecurringExpenseDatatable.php new file mode 100644 index 0000000000..497fbc0396 --- /dev/null +++ b/app/Ninja/Datatables/RecurringExpenseDatatable.php @@ -0,0 +1,112 @@ +vendor_public_id) { + if (! Auth::user()->can('viewByOwner', [ENTITY_VENDOR, $model->vendor_user_id])) { + return $model->vendor_name; + } + + return link_to("vendors/{$model->vendor_public_id}", $model->vendor_name)->toHtml(); + } else { + return ''; + } + }, + ! $this->hideClient, + ], + [ + 'client_name', + function ($model) { + if ($model->client_public_id) { + if (! Auth::user()->can('viewByOwner', [ENTITY_CLIENT, $model->client_user_id])) { + return Utils::getClientDisplayName($model); + } + + return link_to("clients/{$model->client_public_id}", Utils::getClientDisplayName($model))->toHtml(); + } else { + return ''; + } + }, + ! $this->hideClient, + ], +/* + [ + 'expense_date', + function ($model) { + if (! Auth::user()->can('viewByOwner', [ENTITY_EXPENSE, $model->user_id])) { + return Utils::fromSqlDate($model->expense_date_sql); + } + + return link_to("expenses/{$model->public_id}/edit", Utils::fromSqlDate($model->expense_date_sql))->toHtml(); + }, + ], +*/ + [ + 'amount', + function ($model) { + $amount = Utils::calculateTaxes($model->amount, $model->tax_rate1, $model->tax_rate2); + $str = Utils::formatMoney($amount, $model->expense_currency_id); + + /* + // show both the amount and the converted amount + if ($model->exchange_rate != 1) { + $converted = round($amount * $model->exchange_rate, 2); + $str .= ' | ' . Utils::formatMoney($converted, $model->invoice_currency_id); + } + */ + + return $str; + }, + ], + [ + 'category', + function ($model) { + $category = $model->category != null ? substr($model->category, 0, 100) : ''; + if (! Auth::user()->can('editByOwner', [ENTITY_EXPENSE_CATEGORY, $model->category_user_id])) { + return $category; + } + + return $model->category_public_id ? link_to("expense_categories/{$model->category_public_id}/edit", $category)->toHtml() : ''; + }, + ], + [ + 'public_notes', + function ($model) { + return $model->public_notes != null ? substr($model->public_notes, 0, 100) : ''; + }, + ], + ]; + } + + public function actions() + { + return [ + [ + trans('texts.edit_recurring_expense'), + function ($model) { + return URL::to("recurring_expenses/{$model->public_id}/edit"); + }, + function ($model) { + return Auth::user()->can('editByOwner', [ENTITY_RECURRING_EXPENSE, $model->user_id]); + }, + ], + ]; + } + +} diff --git a/app/Ninja/Repositories/RecurringExpenseRepository.php b/app/Ninja/Repositories/RecurringExpenseRepository.php new file mode 100644 index 0000000000..4fd5faf2e5 --- /dev/null +++ b/app/Ninja/Repositories/RecurringExpenseRepository.php @@ -0,0 +1,141 @@ +with('user') + ->withTrashed() + ->where('is_deleted', '=', false) + ->get(); + } + + public function find($filter = null) + { + $accountid = \Auth::user()->account_id; + $query = DB::table('recurring_expenses') + ->join('accounts', 'accounts.id', '=', 'recurring_expenses.account_id') + ->leftjoin('clients', 'clients.id', '=', 'recurring_expenses.client_id') + ->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id') + ->leftjoin('vendors', 'vendors.id', '=', 'recurring_expenses.vendor_id') + ->leftJoin('expense_categories', 'recurring_expenses.expense_category_id', '=', 'expense_categories.id') + ->where('recurring_expenses.account_id', '=', $accountid) + ->where('contacts.deleted_at', '=', null) + ->where('vendors.deleted_at', '=', null) + ->where('clients.deleted_at', '=', null) + ->where(function ($query) { // handle when client isn't set + $query->where('contacts.is_primary', '=', true) + ->orWhere('contacts.is_primary', '=', null); + }) + ->select( + 'recurring_expenses.account_id', + 'recurring_expenses.amount', + 'recurring_expenses.deleted_at', + 'recurring_expenses.id', + 'recurring_expenses.is_deleted', + 'recurring_expenses.private_notes', + 'recurring_expenses.public_id', + 'recurring_expenses.public_notes', + 'recurring_expenses.should_be_invoiced', + 'recurring_expenses.vendor_id', + 'recurring_expenses.expense_currency_id', + 'recurring_expenses.invoice_currency_id', + 'recurring_expenses.user_id', + 'recurring_expenses.tax_rate1', + 'recurring_expenses.tax_rate2', + 'expense_categories.name as category', + 'expense_categories.user_id as category_user_id', + 'expense_categories.public_id as category_public_id', + 'vendors.name as vendor_name', + 'vendors.public_id as vendor_public_id', + 'vendors.user_id as vendor_user_id', + DB::raw("COALESCE(NULLIF(clients.name,''), NULLIF(CONCAT(contacts.first_name, ' ', contacts.last_name),''), NULLIF(contacts.email,'')) client_name"), + 'clients.public_id as client_public_id', + 'clients.user_id as client_user_id', + 'contacts.first_name', + 'contacts.email', + 'contacts.last_name', + 'clients.country_id as client_country_id' + ); + + $this->applyFilters($query, ENTITY_RECURRING_EXPENSE); + + if ($filter) { + $query->where(function ($query) use ($filter) { + $query->where('recurring_expenses.public_notes', 'like', '%'.$filter.'%') + ->orWhere('clients.name', 'like', '%'.$filter.'%') + ->orWhere('vendors.name', 'like', '%'.$filter.'%') + ->orWhere('expense_categories.name', 'like', '%'.$filter.'%'); + ; + }); + } + + return $query; + } + + public function save($input, $expense = null) + { + $publicId = isset($input['public_id']) ? $input['public_id'] : false; + + if ($expense) { + // do nothing + } elseif ($publicId) { + $expense = RecurringExpense::scope($publicId)->firstOrFail(); + if (Utils::isNinjaDev()) { + \Log::warning('Entity not set in expense repo save'); + } + } else { + $expense = RecurringExpense::createNew(); + } + + if ($expense->is_deleted) { + return $expense; + } + + // First auto fill + $expense->fill($input); + + /* + if (isset($input['expense_date'])) { + $expense->expense_date = Utils::toSqlDate($input['expense_date']); + } + if (isset($input['payment_date'])) { + $expense->payment_date = Utils::toSqlDate($input['payment_date']); + } + + $expense->should_be_invoiced = isset($input['should_be_invoiced']) && floatval($input['should_be_invoiced']) || $expense->client_id ? true : false; + + if (! $expense->expense_currency_id) { + $expense->expense_currency_id = \Auth::user()->account->getCurrencyId(); + } + if (! $expense->invoice_currency_id) { + $expense->invoice_currency_id = \Auth::user()->account->getCurrencyId(); + } + + $rate = isset($input['exchange_rate']) ? Utils::parseFloat($input['exchange_rate']) : 1; + $expense->exchange_rate = round($rate, 4); + if (isset($input['amount'])) { + $expense->amount = round(Utils::parseFloat($input['amount']), 2); + } + */ + + $expense->save(); + + return $expense; + } +} diff --git a/app/Policies/RecurringExpensePolicy.php b/app/Policies/RecurringExpensePolicy.php new file mode 100644 index 0000000000..7c46370ce9 --- /dev/null +++ b/app/Policies/RecurringExpensePolicy.php @@ -0,0 +1,23 @@ +hasFeature(FEATURE_EXPENSES); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 34688793c8..28b56c1206 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -18,6 +18,7 @@ class AuthServiceProvider extends ServiceProvider \App\Models\Credit::class => \App\Policies\CreditPolicy::class, \App\Models\Document::class => \App\Policies\DocumentPolicy::class, \App\Models\Expense::class => \App\Policies\ExpensePolicy::class, + \App\Models\RecurringExpense::class => \App\Policies\RecurringExpensePolicy::class, \App\Models\ExpenseCategory::class => \App\Policies\ExpenseCategoryPolicy::class, \App\Models\Invoice::class => \App\Policies\InvoicePolicy::class, \App\Models\Payment::class => \App\Policies\PaymentPolicy::class, diff --git a/app/Services/RecurringExpenseService.php b/app/Services/RecurringExpenseService.php new file mode 100644 index 0000000000..8ee8265b8c --- /dev/null +++ b/app/Services/RecurringExpenseService.php @@ -0,0 +1,82 @@ +recurringExpenseRepo = $recurringExpenseRepo; + $this->datatableService = $datatableService; + } + + /** + * @return CreditRepository + */ + protected function getRepo() + { + return $this->recurringExpenseRepo; + } + + /** + * @param $data + * @param mixed $recurringExpense + * + * @return mixed|null + */ + public function save($data, $recurringExpense = false) + { + if (isset($data['client_id']) && $data['client_id']) { + $data['client_id'] = Client::getPrivateId($data['client_id']); + } + + if (isset($data['vendor_id']) && $data['vendor_id']) { + $data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']); + } + + return $this->recurringExpenseRepo->save($data, $recurringExpense); + } + + /** + * @param $clientPublicId + * @param $search + * @param mixed $userId + * + * @return \Illuminate\Http\JsonResponse + */ + public function getDatatable($search, $userId) + { + $query = $this->recurringExpenseRepo->find($search); + + if (! Utils::hasPermission('view_all')) { + $query->where('recurring_expenses.user_id', '=', Auth::user()->id); + } + + return $this->datatableService->createDatatable(new RecurringExpenseDatatable(), $query); + } +} diff --git a/database/migrations/2017_06_19_111515_update_dark_mode.php b/database/migrations/2017_06_19_111515_update_dark_mode.php index 4b970faed2..25764f72c2 100644 --- a/database/migrations/2017_06_19_111515_update_dark_mode.php +++ b/database/migrations/2017_06_19_111515_update_dark_mode.php @@ -38,7 +38,7 @@ class UpdateDarkMode extends Migration $table->decimal('amount', 13, 2); $table->text('private_notes'); $table->text('public_notes'); - $table->unsignedInteger('invoice_currency_id')->nullable(false); + $table->unsignedInteger('invoice_currency_id')->nullable()->index(); $table->unsignedInteger('expense_currency_id')->nullable()->index(); $table->boolean('should_be_invoiced')->default(true); $table->unsignedInteger('expense_category_id')->nullable()->index(); @@ -47,6 +47,11 @@ class UpdateDarkMode extends Migration $table->string('tax_name2')->nullable(); $table->decimal('tax_rate2', 13, 3); + $table->unsignedInteger('frequency_id'); + $table->date('start_date')->nullable(); + $table->date('end_date')->nullable(); + $table->timestamp('last_created_date')->nullable(); + // Relations $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); @@ -59,6 +64,10 @@ class UpdateDarkMode extends Migration $table->unique(['account_id', 'public_id']); }); + Schema::table('expenses', function ($table) { + $table->unsignedInteger('recurring_expense_id')->nullable(); + }); + } /** @@ -69,5 +78,9 @@ class UpdateDarkMode extends Migration public function down() { Schema::drop('recurring_expenses'); + + Schema::table('expenses', function ($table) { + $table->dropColumn('recurring_expense_id'); + }); } } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 4bf768490d..a3a68789d1 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -2272,6 +2272,22 @@ $LANG = array( 'update_payment_details' => 'Update payment details', 'updated_payment_details' => 'Successfully updated payment details', 'update_credit_card' => 'Update Credit Card', + 'recurring_expenses' => 'Recurring Expenses', + 'recurring_expense' => 'Recurring Expense', + 'new_recurring_expense' => 'New Recurring Expense', + 'edit_recurring_expense' => 'Edit Recurring Expense', + 'archive_recurring_expense' => 'Archive Recurring Expense', + 'list_recurring_expense' => 'List Recurring Expenses', + 'updated_recurring_expense' => 'Successfully updated recurring expense', + 'created_recurring_expense' => 'Successfully created recurring expense', + 'archived_recurring_expense' => 'Successfully archived recurring expense', + 'archived_recurring_expense' => 'Successfully archived :count recurring expenses', + 'restore_recurring_expense' => 'Restore Recurring Expense', + 'restored_recurring_expense' => 'Successfully restored recurring expense', + 'delete_recurring_expense' => 'Delete Recurring Expense', + 'deleted_recurring_expense' => 'Successfully deleted project', + 'deleted_recurring_expense' => 'Successfully deleted :count projects', + ); diff --git a/resources/views/expenses/edit.blade.php b/resources/views/expenses/edit.blade.php index f919f75d7e..309308ec59 100644 --- a/resources/views/expenses/edit.blade.php +++ b/resources/views/expenses/edit.blade.php @@ -76,74 +76,6 @@ ->addGroupClass('client-select') !!} @endif - @if (!$expense || ($expense && !$expense->invoice_id)) - {!! Former::checkbox('should_be_invoiced') - ->text(trans('texts.mark_billable')) - ->data_bind('checked: should_be_invoiced()') - ->label(' ') - ->value(1) !!} - @endif - - @if (! $expense || ! $expense->transaction_id) - - @if (! $expense || ! $expense->isPaid()) - {!! Former::checkbox('mark_paid') - ->data_bind('checked: mark_paid') - ->text(trans('texts.mark_expense_paid')) - ->label(' ') - ->value(1) !!} - @endif - -
- {!! Former::select('payment_type_id') - ->addOption('','') - ->fromQuery($paymentTypes, 'name', 'id') - ->addGroupClass('payment-type-select') !!} - - {!! Former::text('payment_date') - ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT)) - ->addGroupClass('payment_date') - ->append('') !!} - - {!! Former::text('transaction_reference') !!} -
- @endif - - @if (!$expense || ($expense && ! $expense->isExchanged())) - {!! Former::checkbox('convert_currency') - ->text(trans('texts.convert_currency')) - ->data_bind('checked: convert_currency') - ->label(' ') - ->value(1) !!} - @endif - - -
-
- - {!! Former::select('invoice_currency_id')->addOption('','') - ->label(trans('texts.invoice_currency')) - ->data_placeholder(Utils::getFromCache($account->getCurrencyId(), 'currencies')->name) - ->data_bind('combobox: invoice_currency_id, disable: true') - ->fromQuery($currencies, 'name', 'id') !!} - - - {!! Former::plaintext('') - ->value('') - ->style('min-height:46px') - ->label(trans('texts.invoice_currency')) !!} - - - {!! Former::text('exchange_rate') - ->data_bind("value: exchange_rate, enable: enableExchangeRate, valueUpdate: 'afterkeydown'") !!} - - {!! Former::text('invoice_amount') - ->addGroupClass('converted-amount') - ->data_bind("value: convertedAmount, enable: enableExchangeRate") - ->append('') !!} -
- - @if (count($taxRates)) @if (!$expense || ($expense && (!$expense->tax_name1 && !$expense->tax_name2))) {!! Former::checkbox('apply_taxes') @@ -159,22 +91,110 @@ @include('partials.tax_rates') - @if ($account->hasFeature(FEATURE_DOCUMENTS)) - {!! Former::checkbox('invoice_documents') - ->text(trans('texts.add_documents_to_invoice')) - ->onchange('onInvoiceDocumentsChange()') - ->data_bind('checked: invoice_documents') + @if (!$expense || ($expense && !$expense->invoice_id)) + {!! Former::checkbox('should_be_invoiced') + ->text(trans('texts.mark_billable')) + ->data_bind('checked: should_be_invoiced()') ->label(' ') ->value(1) !!} @endif + @if ($isRecurring) + + {!! Former::select('frequency_id') + ->label('frequency') + ->options(\App\Models\Frequency::selectOptions()) + ->data_bind("value: frequency_id") !!} + {!! Former::text('start_date') + ->data_bind("datePicker: start_date, valueUpdate: 'afterkeydown'") + ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT)) + ->appendIcon('calendar') + ->addGroupClass('start_date') !!} + {!! Former::text('end_date') + ->data_bind("datePicker: end_date, valueUpdate: 'afterkeydown'") + ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT)) + ->appendIcon('calendar') + ->addGroupClass('end_date') !!} + + @else + @if ((! $expense || ! $expense->transaction_id)) + + @if (! $expense || ! $expense->isPaid()) + {!! Former::checkbox('mark_paid') + ->data_bind('checked: mark_paid') + ->text(trans('texts.mark_expense_paid')) + ->label(' ') + ->value(1) !!} + @endif + +
+ {!! Former::select('payment_type_id') + ->addOption('','') + ->fromQuery($paymentTypes, 'name', 'id') + ->addGroupClass('payment-type-select') !!} + + {!! Former::text('payment_date') + ->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT)) + ->addGroupClass('payment_date') + ->append('') !!} + + {!! Former::text('transaction_reference') !!} +
+ @endif + + @if (!$expense || ($expense && ! $expense->isExchanged())) + {!! Former::checkbox('convert_currency') + ->text(trans('texts.convert_currency')) + ->data_bind('checked: convert_currency') + ->label(' ') + ->value(1) !!} + @endif + + +
+
+ + {!! Former::select('invoice_currency_id')->addOption('','') + ->label(trans('texts.invoice_currency')) + ->data_placeholder(Utils::getFromCache($account->getCurrencyId(), 'currencies')->name) + ->data_bind('combobox: invoice_currency_id, disable: true') + ->fromQuery($currencies, 'name', 'id') !!} + + + {!! Former::plaintext('') + ->value('') + ->style('min-height:46px') + ->label(trans('texts.invoice_currency')) !!} + + + {!! Former::text('exchange_rate') + ->data_bind("value: exchange_rate, enable: enableExchangeRate, valueUpdate: 'afterkeydown'") !!} + + {!! Former::text('invoice_amount') + ->addGroupClass('converted-amount') + ->data_bind("value: convertedAmount, enable: enableExchangeRate") + ->append('') !!} +
+ + @if ($account->hasFeature(FEATURE_DOCUMENTS)) + {!! Former::checkbox('invoice_documents') + ->text(trans('texts.add_documents_to_invoice')) + ->onchange('onInvoiceDocumentsChange()') + ->data_bind('checked: invoice_documents') + ->label(' ') + ->value(1) !!} + @endif + + @endif + +
{!! Former::textarea('public_notes')->rows(6) !!} {!! Former::textarea('private_notes')->rows(6) !!} - @if ($account->hasFeature(FEATURE_DOCUMENTS)) + @if (! $isRecurring && $account->hasFeature(FEATURE_DOCUMENTS))