1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-17 16:42:48 +01:00

Track changes to expenses in activities

This commit is contained in:
Hillel Coren 2016-10-20 23:17:37 +03:00
parent 040fb76a48
commit 83b66c880b
18 changed files with 367 additions and 58 deletions

View File

@ -0,0 +1,28 @@
<?php namespace App\Events;
use App\Models\Task;
use Illuminate\Queue\SerializesModels;
/**
* Class TaskWasArchived
*/
class TaskWasArchived extends Event
{
use SerializesModels;
/**
* @var Task
*/
public $task;
/**
* Create a new event instance.
*
* @param Task $task
*/
public function __construct(Task $task)
{
$this->task = $task;
}
}

View File

@ -0,0 +1,28 @@
<?php namespace App\Events;
use App\Models\Task;
use Illuminate\Queue\SerializesModels;
/**
* Class TaskWasDeleted
*/
class TaskWasDeleted extends Event
{
use SerializesModels;
/**
* @var Task
*/
public $task;
/**
* Create a new event instance.
*
* @param Task $task
*/
public function __construct(Task $task)
{
$this->task = $task;
}
}

View File

@ -0,0 +1,29 @@
<?php namespace App\Events;
use App\Models\Task;
use Illuminate\Queue\SerializesModels;
/**
* Class TaskWasRestored
*/
class TaskWasRestored extends Event
{
use SerializesModels;
/**
* @var Task
*/
public $task;
/**
* Create a new event instance.
*
* @param Task $task
*/
public function __construct(Task $task)
{
$this->task = $task;
}
}

View File

@ -294,7 +294,7 @@ class TaskController extends BaseController
return Redirect::to("invoices/{$invoiceId}/edit")->with('tasks', $data);
}
} else {
$count = $this->taskRepo->bulk($ids, $action);
$count = $this->taskService->bulk($ids, $action);
$message = Utils::pluralize($action.'d_task', $count);
Session::flash('message', $message);

View File

@ -433,56 +433,50 @@ if (!defined('CONTACT_EMAIL')) {
define('ACTIVITY_TYPE_CREATE_CLIENT', 1);
define('ACTIVITY_TYPE_ARCHIVE_CLIENT', 2);
define('ACTIVITY_TYPE_DELETE_CLIENT', 3);
define('ACTIVITY_TYPE_CREATE_INVOICE', 4);
define('ACTIVITY_TYPE_UPDATE_INVOICE', 5);
define('ACTIVITY_TYPE_EMAIL_INVOICE', 6);
define('ACTIVITY_TYPE_VIEW_INVOICE', 7);
define('ACTIVITY_TYPE_ARCHIVE_INVOICE', 8);
define('ACTIVITY_TYPE_DELETE_INVOICE', 9);
define('ACTIVITY_TYPE_CREATE_PAYMENT', 10);
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
define('ACTIVITY_TYPE_ARCHIVE_CREDIT', 16);
define('ACTIVITY_TYPE_DELETE_CREDIT', 17);
define('ACTIVITY_TYPE_CREATE_QUOTE', 18);
define('ACTIVITY_TYPE_UPDATE_QUOTE', 19);
define('ACTIVITY_TYPE_EMAIL_QUOTE', 20);
define('ACTIVITY_TYPE_VIEW_QUOTE', 21);
define('ACTIVITY_TYPE_ARCHIVE_QUOTE', 22);
define('ACTIVITY_TYPE_DELETE_QUOTE', 23);
define('ACTIVITY_TYPE_RESTORE_QUOTE', 24);
define('ACTIVITY_TYPE_RESTORE_INVOICE', 25);
define('ACTIVITY_TYPE_RESTORE_CLIENT', 26);
define('ACTIVITY_TYPE_RESTORE_PAYMENT', 27);
define('ACTIVITY_TYPE_RESTORE_CREDIT', 28);
define('ACTIVITY_TYPE_APPROVE_QUOTE', 29);
// Vendors
define('ACTIVITY_TYPE_CREATE_VENDOR', 30);
define('ACTIVITY_TYPE_ARCHIVE_VENDOR', 31);
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);
// tasks
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
define('ACTIVITY_TYPE_CREATE_TASK', 42);
define('ACTIVITY_TYPE_UPDATE_TASK', 43);
define('ACTIVITY_TYPE_ARCHIVE_TASK', 44);
define('ACTIVITY_TYPE_DELETE_TASK', 45);
define('ACTIVITY_TYPE_RESTORE_TASK', 46);
define('ACTIVITY_TYPE_UPDATE_EXPENSE', 47);
define('DEFAULT_INVOICE_NUMBER', '0001');
define('RECENTLY_VIEWED_LIMIT', 20);

View File

@ -24,6 +24,8 @@ class HistoryUtils
ACTIVITY_TYPE_CREATE_CLIENT,
ACTIVITY_TYPE_CREATE_TASK,
ACTIVITY_TYPE_UPDATE_TASK,
ACTIVITY_TYPE_CREATE_EXPENSE,
ACTIVITY_TYPE_UPDATE_EXPENSE,
ACTIVITY_TYPE_CREATE_INVOICE,
ACTIVITY_TYPE_UPDATE_INVOICE,
ACTIVITY_TYPE_EMAIL_INVOICE,
@ -35,7 +37,7 @@ class HistoryUtils
];
$activities = Activity::scope()
->with(['client.contacts', 'invoice', 'task'])
->with(['client.contacts', 'invoice', 'task', 'expense'])
->whereIn('user_id', $userIds)
->whereIn('activity_type_id', $activityTypes)
->orderBy('id', 'asc')
@ -52,6 +54,12 @@ class HistoryUtils
continue;
}
$entity->setRelation('client', $activity->client);
} else if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_EXPENSE || $activity->activity_type_id == ACTIVITY_TYPE_UPDATE_EXPENSE) {
$entity = $activity->expense;
if ( ! $entity) {
continue;
}
$entity->setRelation('client', $activity->client);
} else {
$entity = $activity->invoice;
if ( ! $entity) {

View File

@ -1,7 +1,5 @@
<?php namespace App\Listeners;
use App\Events\TaskWasCreated;
use App\Events\TaskWasUpdated;
use App\Models\Invoice;
use App\Events\ClientWasCreated;
use App\Events\ClientWasDeleted;
@ -33,6 +31,16 @@ use App\Events\CreditWasCreated;
use App\Events\CreditWasDeleted;
use App\Events\CreditWasArchived;
use App\Events\CreditWasRestored;
use App\Events\TaskWasCreated;
use App\Events\TaskWasUpdated;
use App\Events\TaskWasArchived;
use App\Events\TaskWasRestored;
use App\Events\TaskWasDeleted;
use App\Events\ExpenseWasCreated;
use App\Events\ExpenseWasUpdated;
use App\Events\ExpenseWasArchived;
use App\Events\ExpenseWasRestored;
use App\Events\ExpenseWasDeleted;
use App\Ninja\Repositories\ActivityRepository;
/**
@ -496,4 +504,71 @@ class ActivityListener
ACTIVITY_TYPE_UPDATE_TASK
);
}
public function archivedTask(TaskWasArchived $event)
{
$this->activityRepo->create(
$event->task,
ACTIVITY_TYPE_ARCHIVE_TASK
);
}
public function deletedTask(TaskWasDeleted $event)
{
$this->activityRepo->create(
$event->task,
ACTIVITY_TYPE_DELETE_TASK
);
}
public function restoredTask(TaskWasRestored $event)
{
$this->activityRepo->create(
$event->task,
ACTIVITY_TYPE_RESTORE_TASK
);
}
public function createdExpense(ExpenseWasCreated $event)
{
$this->activityRepo->create(
$event->expense,
ACTIVITY_TYPE_CREATE_EXPENSE
);
}
public function updatedExpense(ExpenseWasUpdated $event)
{
$this->activityRepo->create(
$event->expense,
ACTIVITY_TYPE_UPDATE_EXPENSE
);
}
public function archivedExpense(ExpenseWasArchived $event)
{
$this->activityRepo->create(
$event->expense,
ACTIVITY_TYPE_ARCHIVE_EXPENSE
);
}
public function deletedExpense(ExpenseWasDeleted $event)
{
$this->activityRepo->create(
$event->expense,
ACTIVITY_TYPE_DELETE_EXPENSE
);
}
public function restoredExpense(ExpenseWasRestored $event)
{
$this->activityRepo->create(
$event->expense,
ACTIVITY_TYPE_RESTORE_EXPENSE
);
}
}

View File

@ -83,6 +83,11 @@ class Activity extends Eloquent
return $this->belongsTo('App\Models\Task')->withTrashed();
}
public function expense()
{
return $this->belongsTo('App\Models\Expense')->withTrashed();
}
public function key()
{
return sprintf('%s-%s-%s', $this->activity_type_id, $this->client_id, $this->created_at->timestamp);
@ -101,9 +106,8 @@ class Activity extends Eloquent
$contactId = $this->contact_id;
$payment = $this->payment;
$credit = $this->credit;
$expense = $this->expense;
$isSystem = $this->is_system;
/** @var Task $task */
$task = $this->task;
$data = [
@ -117,6 +121,7 @@ class Activity extends Eloquent
'adjustment' => $this->adjustment ? $account->formatMoney($this->adjustment, $this) : null,
'credit' => $credit ? $account->formatMoney($credit->amount, $client) : null,
'task' => $task ? link_to($task->getRoute(), substr($task->description, 0, 30).'...') : null,
'expense' => $expense ? link_to($expense->getRoute(), substr($expense->public_notes, 0, 30).'...') : null,
];
return trans("texts.activity_{$activityTypeId}", $data);

View File

@ -27,7 +27,9 @@ class ActivityDatatable extends EntityDatatable
'payment' => $model->payment ?: '',
'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '',
'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null,
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null,
'task' => $model->task_public_id ? link_to('/tasks/' . $model->task_public_id, substr($model->task_description, 0, 30).'...') : null,
'expense' => $model->expense_public_id ? link_to('/expenses/' . $model->expense_public_id, substr($model->expense_public_notes, 0, 30).'...') : null,
];
return trans("texts.activity_{$model->activity_type_id}", $data);

View File

@ -74,6 +74,8 @@ class ActivityRepository
->leftJoin('invoices', 'invoices.id', '=', 'activities.invoice_id')
->leftJoin('payments', 'payments.id', '=', 'activities.payment_id')
->leftJoin('credits', 'credits.id', '=', 'activities.credit_id')
->leftJoin('tasks', 'tasks.id', '=', 'activities.task_id')
->leftJoin('expenses', 'expenses.id', '=', 'activities.expense_id')
->where('clients.id', '=', $clientId)
->where('contacts.is_primary', '=', 1)
->whereNull('contacts.deleted_at')
@ -102,7 +104,11 @@ class ActivityRepository
'contacts.email as email',
'payments.transaction_reference as payment',
'payments.amount as payment_amount',
'credits.amount as credit'
'credits.amount as credit',
'tasks.description as task_description',
'tasks.public_id as task_public_id',
'expenses.public_notes as expense_public_notes',
'expenses.public_id as expense_public_id'
);
}

View File

@ -8,7 +8,7 @@ class BaseRepository
/**
* @return null
*/
public function getClassName()
public function getClassName()
{
return null;
}
@ -40,7 +40,7 @@ class BaseRepository
if ($entity->trashed()) {
return;
}
$entity->delete();
$className = $this->getEventClass($entity, 'Archived');
@ -83,7 +83,7 @@ class BaseRepository
if ($entity->is_deleted) {
return;
}
$entity->is_deleted = true;
$entity->save();

View File

@ -252,7 +252,7 @@ class DashboardRepository
}
return $activities->orderBy('activities.created_at', 'desc')
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account', 'task')
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account', 'task', 'expense')
->take(50)
->get();
}

View File

@ -5,8 +5,13 @@ use Session;
use App\Models\Client;
use App\Models\Task;
class TaskRepository
class TaskRepository extends BaseRepository
{
public function getClassName()
{
return 'App\Models\Task';
}
public function find($clientPublicId = null, $filter = null)
{
$query = \DB::table('tasks')
@ -112,26 +117,4 @@ class TaskRepository
return $task;
}
public function bulk($ids, $action)
{
$tasks = Task::withTrashed()->scope($ids)->get();
foreach ($tasks as $task) {
if ($action == 'restore') {
$task->restore();
$task->is_deleted = false;
$task->save();
} else {
if ($action == 'delete') {
$task->is_deleted = true;
$task->save();
}
$task->delete();
}
}
return count($tasks);
}
}

View File

@ -157,9 +157,35 @@ class EventServiceProvider extends ServiceProvider {
'App\Events\TaskWasCreated' => [
'App\Listeners\ActivityListener@createdTask',
],
'App\Events\TaskWasUpdated' => [
'App\Events\TaskWasUpdated' => [
'App\Listeners\ActivityListener@updatedTask',
],
'App\Events\TaskWasRestored' => [
'App\Listeners\ActivityListener@restoredTask',
],
'App\Events\TaskWasArchived' => [
'App\Listeners\ActivityListener@archivedTask',
],
'App\Events\TaskWasDeleted' => [
'App\Listeners\ActivityListener@deletedTask',
],
// Expense events
'App\Events\ExpenseWasCreated' => [
'App\Listeners\ActivityListener@createdExpense',
],
'App\Events\ExpenseWasUpdated' => [
'App\Listeners\ActivityListener@updatedExpense',
],
'App\Events\ExpenseWasRestored' => [
'App\Listeners\ActivityListener@restoredExpense',
],
'App\Events\ExpenseWasArchived' => [
'App\Listeners\ActivityListener@archivedExpense',
],
'App\Events\ExpenseWasDeleted' => [
'App\Listeners\ActivityListener@deletedExpense',
],
// Update events
\Codedge\Updater\Events\UpdateAvailable::class => [

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddExpenseToActivities extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('activities', function($table)
{
$table->unsignedInteger('expense_id')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('activities', function($table)
{
$table->dropColumn('expense_id');
});
}
}

View File

@ -767,16 +767,20 @@ $LANG = array(
'activity_27' => ':user restored payment :payment',
'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_30' => ':user created vendor :vendor',
'activity_31' => ':user archived vendor :vendor',
'activity_32' => ':user deleted vendor :vendor',
'activity_33' => ':user restored vendor :vendor',
'activity_34' => ':user created expense :expense',
'activity_35' => ':user created :vendor',
'activity_36' => ':user created :vendor',
'activity_37' => ':user created :vendor',
'activity_35' => ':user archived expense :expense',
'activity_36' => ':user deleted expense :expense',
'activity_37' => ':user restored expense :expense',
'activity_42' => ':user created task ":task"',
'activity_43' => ':user updated task ":task"',
'activity_44' => ':user archived task ":task"',
'activity_45' => ':user deleted task ":task"',
'activity_46' => ':user restored task ":task"',
'activity_47' => ':user updated expense ":expense"',
'payment' => 'Payment',
'system' => 'System',
'signature' => 'Email Signature',

View File

@ -56,7 +56,7 @@
countryId = account.country_id;
}
if ( ! decorator) {
if (account && ! decorator) {
decorator = account.show_currency_code ? 'code' : 'symbol';
}

View File

@ -0,0 +1,88 @@
{{ trans('texts.powered_by') }}
{{-- Per our license, please do not remove or modify this section. --}}
{!! link_to('https://www.invoiceninja.com/?utm_source=powered_by', 'InvoiceNinja.com', ['target' => '_blank', 'title' => 'invoiceninja.com']) !!} -
{!! link_to(RELEASES_URL, 'v' . NINJA_VERSION, ['target' => '_blank', 'title' => trans('texts.trello_roadmap')]) !!} |
@if (Auth::user()->account->hasFeature(FEATURE_WHITE_LABEL))
{{ trans('texts.white_labeled') }}
@else
<a href="#" onclick="showWhiteLabelModal()">{{ trans('texts.white_label_link') }}</a>
<div class="modal fade" id="whiteLabelModal" tabindex="-1" role="dialog" aria-labelledby="whiteLabelModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{{ trans('texts.white_label_header') }}</h4>
</div>
<div class="panel-body">
<p>{{ trans('texts.white_label_text', ['price' => WHITE_LABEL_PRICE])}}</p>
<div class="row">
<div class="col-md-6">
<h4>{{ trans('texts.before') }}</h4>
<img src="{{ BLANK_IMAGE }}" data-src="{{ asset('images/pro_plan/white_label_before.png') }}" width="100%" alt="before">
</div>
<div class="col-md-6">
<h4>{{ trans('texts.after') }}</h4>
<img src="{{ BLANK_IMAGE }}" data-src="{{ asset('images/pro_plan/white_label_after.png') }}" width="100%" alt="after">
</div>
</div><br/>
<p>{!! trans('texts.reseller_text', ['email' => HTML::mailto('contact@invoiceninja.com')]) !!}</p>
</div>
<div class="modal-footer" id="signUpFooter" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.close') }} </button>
<button type="button" class="btn btn-primary" onclick="buyProduct('{{ WHITE_LABEL_AFFILIATE_KEY }}', '{{ PRODUCT_WHITE_LABEL }}')">{{ trans('texts.buy_license') }} </button>
<button type="button" class="btn btn-primary" onclick="showApplyLicense()">{{ trans('texts.apply_license') }} </button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="whiteLabelLicenseModal" tabindex="-1" role="dialog" aria-labelledby="whiteLabelLicenseModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{{ trans('texts.white_label_header') }}</h4>
</div>
<div class="panel-body">
{!! Former::open()->rules(['white_label_license_key' => 'required|min:24|max:24']) !!}
{!! Former::input('white_label_license_key') !!}
{!! Former::close() !!}
</div>
<div class="modal-footer" id="signUpFooter" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.close') }} </button>
<button type="button" class="btn btn-success" onclick="applyLicense()">{{ trans('texts.submit') }} </button>
</div>
</div>
</div>
</div>
@endif
<script type="text/javascript">
function showWhiteLabelModal() {
loadImages('#whiteLabelModal');
$('#whiteLabelModal').modal('show');
}
function buyProduct(affiliateKey, productId) {
window.open('{{ Utils::isNinjaDev() ? '' : NINJA_APP_URL }}/license?affiliate_key=' + affiliateKey + '&product_id=' + productId + '&return_url=' + window.location);
}
function showApplyLicense() {
$('#whiteLabelModal').modal('hide');
$('#whiteLabelLicenseModal').modal('show');
}
function applyLicense() {
var license = $('#white_label_license_key').val();
window.location = "{{ url('') }}/dashboard?license_key=" + license + "&product_id={{ PRODUCT_WHITE_LABEL }}";
}
</script>