From 48ce3f64e649fc55127722140ff60e09d4340230 Mon Sep 17 00:00:00 2001 From: steenrabol Date: Wed, 6 Jan 2016 20:52:09 +0100 Subject: [PATCH] expenses --- app/Events/ExpenseWasArchived.php | 23 +++ app/Events/ExpenseWasCreated.php | 22 +++ app/Events/ExpenseWasDeleted.php | 23 +++ app/Events/ExpenseWasRestored.php | 23 +++ app/Http/Controllers/ExpenseController.php | 131 ++++++++++++++++++ app/Http/Requests/CreateExpenseRequest.php | 29 ++++ app/Http/Requests/UpdateExpenseRequest.php | 28 ++++ app/Http/Requests/UpdatePaymentRequest.php | 8 +- app/Http/routes.php | 23 ++- app/Listeners/ExpenseListener.php | 16 +++ app/Listeners/SubscriptionListener.php | 7 + app/Models/Expense.php | 79 +++++++++++ app/Models/ExpenseAcitvity.php | 56 ++++++++ app/Ninja/Presenters/ExpensePresenter.php | 17 +++ app/Ninja/Repositories/ExpenseRepository.php | 94 +++++++++++++ .../Transformers/VendorContactTransformer.php | 24 ++++ app/Ninja/Transformers/VendorTransformer.php | 91 ++++++++++++ app/Providers/EventServiceProvider.php | 15 ++ app/Services/ExpenseService.php | 84 +++++++++++ ...016_01_06_155001_create_expenses_table.php | 56 ++++++++ public/js/built.js | 11 ++ resources/lang/en/texts.php | 12 ++ 22 files changed, 868 insertions(+), 4 deletions(-) create mode 100644 app/Events/ExpenseWasArchived.php create mode 100644 app/Events/ExpenseWasCreated.php create mode 100644 app/Events/ExpenseWasDeleted.php create mode 100644 app/Events/ExpenseWasRestored.php create mode 100644 app/Http/Controllers/ExpenseController.php create mode 100644 app/Http/Requests/CreateExpenseRequest.php create mode 100644 app/Http/Requests/UpdateExpenseRequest.php create mode 100644 app/Listeners/ExpenseListener.php create mode 100644 app/Models/Expense.php create mode 100644 app/Models/ExpenseAcitvity.php create mode 100644 app/Ninja/Presenters/ExpensePresenter.php create mode 100644 app/Ninja/Repositories/ExpenseRepository.php create mode 100644 app/Ninja/Transformers/VendorContactTransformer.php create mode 100644 app/Ninja/Transformers/VendorTransformer.php create mode 100644 app/Services/ExpenseService.php create mode 100644 database/migrations/2016_01_06_155001_create_expenses_table.php diff --git a/app/Events/ExpenseWasArchived.php b/app/Events/ExpenseWasArchived.php new file mode 100644 index 000000000000..7ff42b7b97e2 --- /dev/null +++ b/app/Events/ExpenseWasArchived.php @@ -0,0 +1,23 @@ +expense = $expense; + } + +} diff --git a/app/Events/ExpenseWasCreated.php b/app/Events/ExpenseWasCreated.php new file mode 100644 index 000000000000..efeb308d9bc9 --- /dev/null +++ b/app/Events/ExpenseWasCreated.php @@ -0,0 +1,22 @@ +expense = $expense; + } +} diff --git a/app/Events/ExpenseWasDeleted.php b/app/Events/ExpenseWasDeleted.php new file mode 100644 index 000000000000..a058cf1666e1 --- /dev/null +++ b/app/Events/ExpenseWasDeleted.php @@ -0,0 +1,23 @@ +expense = $expense; + } + +} diff --git a/app/Events/ExpenseWasRestored.php b/app/Events/ExpenseWasRestored.php new file mode 100644 index 000000000000..ca308e4ca0f8 --- /dev/null +++ b/app/Events/ExpenseWasRestored.php @@ -0,0 +1,23 @@ +expense = $expense; + } + +} diff --git a/app/Http/Controllers/ExpenseController.php b/app/Http/Controllers/ExpenseController.php new file mode 100644 index 000000000000..c441a62dd0f6 --- /dev/null +++ b/app/Http/Controllers/ExpenseController.php @@ -0,0 +1,131 @@ +expenseRepo = $expenseRepo; + $this->expenseService = $expenseService; + } + + /** + * Display a listing of the resource. + * + * @return Response + */ + public function index() + { + return View::make('list', array( + 'entityType' => ENTITY_EXPENSE, + 'title' => trans('texts.expenses'), + 'sortCol' => '4', + 'columns' => Utils::trans([ + 'checkbox', + 'vendor', + 'expense_amount', + 'expense_balance', + 'expense_date', + 'private_notes', + '' + ]), + )); + } + + public function getDatatable($vendorPublicId = null) + { + return $this->expenseService->getDatatable($vendorPublicId, Input::get('sSearch')); + } + + public function create($vendorPublicId = 0) + { + $vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail(); + $data = array( + 'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $vendorPublicId, + 'expense' => null, + 'method' => 'POST', + 'url' => 'expenses', + 'title' => trans('texts.new_expense'), + 'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), + ); + + $data = array_merge($data, self::getViewModel()); + + return View::make('expenses.edit', $data); + } + + public function edit($publicId) + { + $expense = Expense::scope($publicId)->firstOrFail(); + $expense->expense_date = Utils::fromSqlDate($expense->expense_date); + + $data = array( + 'vendor' => null, + 'expense' => $expense, + 'method' => 'PUT', + 'url' => 'expenses/'.$publicId, + 'title' => 'Edit Expense', + 'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(), ); + + return View::make('expense.edit', $data); + } + + public function store(CreateExpenseRequest $request) + { + $expense = $this->expenseRepo->save($request->input()); + + Session::flash('message', trans('texts.created_expense')); + + return redirect()->to('expenses'); + } + + public function bulk() + { + $action = Input::get('action'); + $ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids'); + $count = $this->expenseService->bulk($ids, $action); + + if ($count > 0) { + $message = Utils::pluralize($action.'d_expense', $count); + Session::flash('message', $message); + } + + return Redirect::to('expenses'); + } + + 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, + ]; + } + +} diff --git a/app/Http/Requests/CreateExpenseRequest.php b/app/Http/Requests/CreateExpenseRequest.php new file mode 100644 index 000000000000..0a0c71dd8208 --- /dev/null +++ b/app/Http/Requests/CreateExpenseRequest.php @@ -0,0 +1,29 @@ + 'required|positive', + ]; + } +} diff --git a/app/Http/Requests/UpdateExpenseRequest.php b/app/Http/Requests/UpdateExpenseRequest.php new file mode 100644 index 000000000000..83b192280849 --- /dev/null +++ b/app/Http/Requests/UpdateExpenseRequest.php @@ -0,0 +1,28 @@ + 'required|positive', + ]; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 3f7ed3a84969..f4bdc8865d9c 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -188,6 +188,21 @@ Route::group(['middleware' => 'auth'], function() { Route::get('api/vendor', array('as'=>'api.vendors', 'uses'=>'VendorController@getDatatable')); Route::get('api/vendoractivities/{vendor_id?}', array('as'=>'api.vendoractivities', 'uses'=>'VendorActivityController@getDatatable')); Route::post('vendors/bulk', 'VendorController@bulk'); + + // Expense + Route::get('expenses/{id}/edit', function() { + return View::make('header'); + }); + + Route::resource('expenses', 'ExpenseController'); + Route::get('expenses/create/{vendor_id?}', 'ExpenseController@create'); + Route::get('api/expenses/{vendor_id?}', array('as'=>'api.expenses', 'uses'=>'ExpenseController@getDatatable')); + + //Route::get('api/expenseactivities/{vendor_id?}', array('as'=>'api.expenseactivities', 'uses'=>'ExpenseActivityController@getDatatable')); + //Route::post('vendors/bulk', 'VendorController@bulk'); + + + Route::post('expenses/bulk', 'ExpenseController@bulk'); }); @@ -252,6 +267,7 @@ if (!defined('CONTACT_EMAIL')) { define('ENV_STAGING', 'staging'); define('RECENTLY_VIEWED', 'RECENTLY_VIEWED'); + define('ENTITY_CLIENT', 'client'); define('ENTITY_CONTACT', 'contact'); define('ENTITY_INVOICE', 'invoice'); @@ -346,6 +362,12 @@ if (!defined('CONTACT_EMAIL')) { define('ACTIVITY_TYPE_DELETE_VENDOR', 32); define('ACTIVITY_TYPE_RESTORE_VENDOR', 33); + // expenses + define('ACTIVITY_TYPE_CREATE_EXPENSE', 34); + define('ACTIVITY_TYPE_ARCHIVE_EXPENSE', 35); + define('ACTIVITY_TYPE_DELETE_EXPENSE', 36); + define('ACTIVITY_TYPE_RESTORE_EXPENSE', 37); + define('DEFAULT_INVOICE_NUMBER', '0001'); define('RECENTLY_VIEWED_LIMIT', 8); define('LOGGED_ERROR_LIMIT', 100); @@ -379,7 +401,6 @@ if (!defined('CONTACT_EMAIL')) { define('MAX_NUM_VENDORS_PRO', 20000); define('MAX_NUM_VENDORS_LEGACY', 500); - define('INVOICE_STATUS_DRAFT', 1); define('INVOICE_STATUS_SENT', 2); define('INVOICE_STATUS_VIEWED', 3); diff --git a/app/Listeners/ExpenseListener.php b/app/Listeners/ExpenseListener.php new file mode 100644 index 000000000000..8d02f00ff4db --- /dev/null +++ b/app/Listeners/ExpenseListener.php @@ -0,0 +1,16 @@ +expenseRepo = $expenseRepo; + } +} diff --git a/app/Listeners/SubscriptionListener.php b/app/Listeners/SubscriptionListener.php index 60643a25e6dd..fab5a2c57493 100644 --- a/app/Listeners/SubscriptionListener.php +++ b/app/Listeners/SubscriptionListener.php @@ -10,6 +10,7 @@ use App\Events\CreditWasCreated; use App\Events\PaymentWasCreated; use App\Events\VendorWasCreated; +use App\Events\ExpenseWasCreated; class SubscriptionListener { @@ -51,4 +52,10 @@ class SubscriptionListener { $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_VENDOR, $event->vendor); } + + public function createdExpense(ExpenseWasCreated $event) + { + $this->checkSubscriptions(ACTIVITY_TYPE_CREATE_EXPENSE, $event->expense); + } + } diff --git a/app/Models/Expense.php b/app/Models/Expense.php new file mode 100644 index 000000000000..c1522fa1bda4 --- /dev/null +++ b/app/Models/Expense.php @@ -0,0 +1,79 @@ +belongsTo('App\Models\Account'); + } + + public function user() + { + return $this->belongsTo('App\Models\User'); + } + + public function vendor() + { + return $this->belongsTo('App\Models\Vendor')->withTrashed(); + } + + public function getName() + { + return ''; + } + + public function getEntityType() + { + return ENTITY_EXPENSE; + } + + public function apply($amount) + { + if ($amount > $this->balance) { + $applied = $this->balance; + $this->balance = 0; + } else { + $applied = $amount; + $this->balance = $this->balance - $amount; + } + + $this->save(); + + return $applied; + } +} + +Expense::creating(function ($expense) { + $expense->setNullValues(); +}); + +Expense::created(function ($expense) { + event(new ExpenseWasCreated($expense)); +}); + +Expense::updating(function ($expense) { + $expense->setNullValues(); +}); + +Expense::updated(function ($expense) { + event(new ExpenseWasUpdated($expense)); +}); + + + diff --git a/app/Models/ExpenseAcitvity.php b/app/Models/ExpenseAcitvity.php new file mode 100644 index 000000000000..5a4166141668 --- /dev/null +++ b/app/Models/ExpenseAcitvity.php @@ -0,0 +1,56 @@ +whereAccountId(Auth::user()->account_id); + } + + public function account() + { + return $this->belongsTo('App\Models\Account'); + } + + public function user() + { + return $this->belongsTo('App\Models\User')->withTrashed(); + } + + public function vendor() + { + return $this->belongsTo('App\Models\Vendor')->withTrashed(); + } + + public function getMessage() + { + $activityTypeId = $this->activity_type_id; + $account = $this->account; + $vendor = $this->vendor; + $user = $this->user; + $contactId = $this->contact_id; + $isSystem = $this->is_system; + + if($vendor) { + $route = $vendor->getRoute(); + + $data = [ + 'vendor' => link_to($route, $vendor->getDisplayName()), + 'user' => $isSystem ? '' . trans('texts.system') . '' : $user->getDisplayName(), + 'vendorcontact' => $contactId ? $vendor->getDisplayName() : $user->getDisplayName(), + ]; + } else { + return trans("texts.invalid_activity"); + } + return trans("texts.activity_{$activityTypeId}", $data); + } +} diff --git a/app/Ninja/Presenters/ExpensePresenter.php b/app/Ninja/Presenters/ExpensePresenter.php new file mode 100644 index 000000000000..72da1cfef2f1 --- /dev/null +++ b/app/Ninja/Presenters/ExpensePresenter.php @@ -0,0 +1,17 @@ +entity->vendor ? $this->entity->vendor->getDisplayName() : ''; + } + + public function expense_date() + { + return Utils::fromSqlDate($this->entity->expense_date); + } +} \ No newline at end of file diff --git a/app/Ninja/Repositories/ExpenseRepository.php b/app/Ninja/Repositories/ExpenseRepository.php new file mode 100644 index 000000000000..f585b740436b --- /dev/null +++ b/app/Ninja/Repositories/ExpenseRepository.php @@ -0,0 +1,94 @@ +join('accounts', 'accounts.id', '=', 'expenses.account_id') + ->join('vendors', 'vendors.id', '=', 'expenses.vendor_id') + ->join('vendor_contacts', 'vendor_contacts.vendor_id', '=', 'vendors.id') + ->where('vendors.account_id', '=', \Auth::user()->account_id) + ->where('vendors.deleted_at', '=', null) + ->where('vendor_contacts.deleted_at', '=', null) + ->where('vendor_contacts.is_primary', '=', true) + ->select( + DB::raw('COALESCE(vendors.currency_id, accounts.currency_id) currency_id'), + DB::raw('COALESCE(vendors.country_id, accounts.country_id) country_id'), + 'expenses.public_id', + 'vendors.name as vendor_name', + 'vendors.public_id as vendor_public_id', + 'expenses.amount', + 'expenses.balance', + 'expenses.expense_date', + 'vendor_contacts.first_name', + 'vendor_contacts.last_name', + 'vendor_contacts.email', + 'expenses.private_notes', + 'expenses.deleted_at', + 'expenses.is_deleted' + ); + + if ($vendorPublicId) { + $query->where('vendors.public_id', '=', $vendorPublicId); + } + + if (!\Session::get('show_trash:expense')) { + $query->where('expenses.deleted_at', '=', null); + } + + if ($filter) { + $query->where(function ($query) use ($filter) { + $query->where('vendors.name', 'like', '%'.$filter.'%'); + }); + } + + return $query; + } + + public function save($input) + { + $publicId = isset($input['public_id']) ? $input['public_id'] : false; + + if ($publicId) { + $expense = Expense::scope($publicId)->firstOrFail(); + } else { + $expense = Expense::createNew(); + } + + // First auto fille + $expense->fill($input); + + // We can have an expense without a vendor + if(isset($input['vendor'])) { + $expense->vendor_id = Vendor::getPrivateId($input['vendor']); + } + + $expense->expense_date = Utils::toSqlDate($input['expense_date']); + $expense->amount = Utils::parseFloat($input['amount']); + + if(isset($input['amountcur'])) + $expense->amountcur = Utils::parseFloat($input['amountcur']); + + $expense->balance = Utils::parseFloat($input['amount']); + $expense->private_notes = trim($input['private_notes']); + + if(isset($input['exchange_rate'])) + $expense->exchange_rate = Utils::parseFloat($input['exchange_rate']); + + $expense->save(); + + return $expense; + } +} diff --git a/app/Ninja/Transformers/VendorContactTransformer.php b/app/Ninja/Transformers/VendorContactTransformer.php new file mode 100644 index 000000000000..9a34ac740289 --- /dev/null +++ b/app/Ninja/Transformers/VendorContactTransformer.php @@ -0,0 +1,24 @@ + (int) $contact->public_id, + 'first_name' => $contact->first_name, + 'last_name' => $contact->last_name, + 'email' => $contact->email, + 'updated_at' => $this->getTimestamp($contact->updated_at), + 'archived_at' => $this->getTimestamp($contact->deleted_at), + 'is_primary' => (bool) $contact->is_primary, + 'phone' => $contact->phone, + 'last_login' => $contact->last_login, + 'account_key' => $this->account->account_key, + ]; + } +} \ No newline at end of file diff --git a/app/Ninja/Transformers/VendorTransformer.php b/app/Ninja/Transformers/VendorTransformer.php new file mode 100644 index 000000000000..3ef965a15b97 --- /dev/null +++ b/app/Ninja/Transformers/VendorTransformer.php @@ -0,0 +1,91 @@ +account, $this->serializer); + return $this->includeCollection($vendor->contacts, $transformer, ENTITY_CONTACT); + } + + public function includeInvoices(Vendor $vendor) + { + $transformer = new InvoiceTransformer($this->account, $this->serializer); + return $this->includeCollection($vendor->invoices, $transformer, ENTITY_INVOICE); + } + + public function transform(Vendor $vendor) + { + return [ + 'id' => (int) $vendor->public_id, + 'name' => $vendor->name, + 'balance' => (float) $vendor->balance, + 'paid_to_date' => (float) $vendor->paid_to_date, + 'user_id' => (int) $vendor->user->public_id + 1, + 'account_key' => $this->account->account_key, + 'updated_at' => $this->getTimestamp($vendor->updated_at), + 'archived_at' => $this->getTimestamp($vendor->deleted_at), + 'address1' => $vendor->address1, + 'address2' => $vendor->address2, + 'city' => $vendor->city, + 'state' => $vendor->state, + 'postal_code' => $vendor->postal_code, + 'country_id' => (int) $vendor->country_id, + 'work_phone' => $vendor->work_phone, + 'private_notes' => $vendor->private_notes, + 'last_login' => $vendor->last_login, + 'website' => $vendor->website, + 'industry_id' => (int) $vendor->industry_id, + 'size_id' => (int) $vendor->size_id, + 'is_deleted' => (bool) $vendor->is_deleted, + 'payment_terms' => (int) $vendor->payment_terms, + 'vat_number' => $vendor->vat_number, + 'id_number' => $vendor->id_number, + 'language_id' => (int) $vendor->language_id, + 'currency_id' => (int) $vendor->currency_id + ]; + } +} \ No newline at end of file diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index def84e1fdddd..dbdfe9b7395b 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -151,6 +151,21 @@ class EventServiceProvider extends ServiceProvider { 'App\Events\VendorWasRestored' => [ 'App\Listeners\VendorActivityListener@restoredVendor', ], + + // Expense events + 'App\Events\ExpenseWasCreated' => [ + 'App\Listeners\ExpenseActivityListener@createdExpense', + 'App\Listeners\SubscriptionListener@createdExpense', + ], + 'App\Events\ExpenseWasArchived' => [ + 'App\Listeners\ExpenseActivityListener@archivedExpense', + ], + 'App\Events\ExpenseWasDeleted' => [ + 'App\Listeners\ExpenseActivityListener@deletedExpense', + ], + 'App\Events\ExpenseWasRestored' => [ + 'App\Listeners\ExpenseActivityListener@restoredExpense', + ], ]; diff --git a/app/Services/ExpenseService.php b/app/Services/ExpenseService.php new file mode 100644 index 000000000000..fb6333622315 --- /dev/null +++ b/app/Services/ExpenseService.php @@ -0,0 +1,84 @@ +expenseRepo = $expenseRepo; + $this->datatableService = $datatableService; + } + + protected function getRepo() + { + return $this->expenseRepo; + } + + public function save($data) + { + return $this->expenseRepo->save($data); + } + + public function getDatatable($vendorPublicId, $search) + { + $query = $this->expenseRepo->find($vendorPublicId, $search); + + return $this->createDatatable(ENTITY_CREDIT, $query, !$vendorPublicId); + } + + protected function getDatatableColumns($entityType, $hideClient) + { + return [ + [ + 'vendor_name', + function ($model) { + return $model->vendor_public_id ? link_to("vendors/{$model->vendor_public_id}", Utils::getVendorDisplayName($model)) : ''; + }, + ! $hideClient + ], + [ + 'amount', + function ($model) { + return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id) . ''; + } + ], + [ + 'balance', + function ($model) { + return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); + } + ], + [ + 'expense_date', + function ($model) { + return Utils::fromSqlDate($model->expense_date); + } + ], + [ + 'private_notes', + function ($model) { + return $model->private_notes; + } + ] + ]; + } + + protected function getDatatableActions($entityType) + { + return [ + [ + trans('texts.apply_expense'), + function ($model) { + return URL::to("espense/create/{$model->vendor_public_id}") . '?paymentTypeId=1'; + } + ] + ]; + } +} \ No newline at end of file diff --git a/database/migrations/2016_01_06_155001_create_expenses_table.php b/database/migrations/2016_01_06_155001_create_expenses_table.php new file mode 100644 index 000000000000..12dc669203f4 --- /dev/null +++ b/database/migrations/2016_01_06_155001_create_expenses_table.php @@ -0,0 +1,56 @@ +increments('id'); + $table->timestamps(); + + $table->unsignedInteger('account_id')->index(); + $table->unsignedInteger('vendor_id')->nullable(); + $table->unsignedInteger('user_id'); + + $table->softDeletes(); + + $table->boolean('is_deleted')->default(false); + $table->decimal('amount', 13, 2); + $table->decimal('amountcur', 13, 2); + $table->decimal('exchange_rate', 13, 2); + $table->decimal('balance', 13, 2); + $table->date('expense_date')->nullable(); + $table->string('expense_number')->nullable(); + $table->text('private_notes'); + $table->integer('currency_id',false, true)->nullable(); + $table->boolean('is_invoiced')->default(false); + $table->boolean('should_be_invoiced')->default(true); + + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');; + + $table->unsignedInteger('public_id')->index(); + $table->unique( array('account_id','public_id') ); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('expenses'); + } +} diff --git a/public/js/built.js b/public/js/built.js index 6c00fbe7cbbe..b3186d0b7aef 100644 --- a/public/js/built.js +++ b/public/js/built.js @@ -30298,6 +30298,17 @@ function getClientDisplayName(client) return ''; } +function getVendorDisplayName(vendor) +{ + var contact = vendor.contacts ? vendor.vendorcontacts[0] : false; + if (vendor.name) { + return vendor.name; + } else if (contact) { + return getContactDisplayName(contact); + } + return ''; +} + function populateInvoiceComboboxes(clientId, invoiceId) { var clientMap = {}; var invoiceMap = {}; diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index dbed40c5fa3c..9687bf330869 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -893,6 +893,13 @@ return array( 'activity_28' => ':user restored :credit credit', 'activity_29' => ':contact approved quote :quote', 'activity_30' => ':user created :vendor', + 'activity_31' => ':user created :vendor', + 'activity_32' => ':user created :vendor', + 'activity_33' => ':user created :vendor', + 'activity_34' => ':user created :vendor', + 'activity_35' => ':user created :vendor', + 'activity_36' => ':user created :vendor', + 'activity_37' => ':user created :vendor', 'payment' => 'Payment', 'system' => 'System', @@ -1018,4 +1025,9 @@ return array( 'archive_vendor' => 'Archive vendor', 'delete_vendor' => 'Delete vendor', 'view_vendor' => 'View vendor', + + // Expenses + 'expense_amount' => 'Expense amount', + 'expense_balance' => 'Expense balance', + 'expense_date' => 'Expense date', );