mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Create 'Credits' module (#3263)
* Create 'Credits' module * Various fixes on Credit module * Fix MarkCreditPaid factory
This commit is contained in:
parent
956d4ba12e
commit
0f661495db
29
app/Events/Credit/CreditWasCreated.php
Normal file
29
app/Events/Credit/CreditWasCreated.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Credit;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasCreated
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
public $company;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Credit $credit
|
||||
*/
|
||||
public function __construct(Credit $credit, Company $company)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
$this->company = $company;
|
||||
}
|
||||
}
|
25
app/Events/Credit/CreditWasEmailed.php
Normal file
25
app/Events/Credit/CreditWasEmailed.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Credit;
|
||||
|
||||
use App\Models\Credit;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasEmailed
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Credit $credit)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
}
|
||||
}
|
24
app/Events/Credit/CreditWasEmailedAndFailed.php
Normal file
24
app/Events/Credit/CreditWasEmailedAndFailed.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Credit;
|
||||
|
||||
use App\Models\Credit;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasEmailedAndFailed
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
public $errors;
|
||||
|
||||
public function __construct(Credit $credit, array $errors)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
|
||||
$this->errors = $errors;
|
||||
}
|
||||
}
|
24
app/Events/Credit/CreditWasUpdated.php
Normal file
24
app/Events/Credit/CreditWasUpdated.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Credit;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasUpdated
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
public $company;
|
||||
|
||||
public function __construct(Credit $credit, Company $company)
|
||||
{
|
||||
$this->invoice = $credit;
|
||||
$this->company = $company;
|
||||
}
|
||||
}
|
58
app/Factory/CloneCreditToQuoteFactory.php
Normal file
58
app/Factory/CloneCreditToQuoteFactory.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\Credit;
|
||||
use App\Models\Quote;
|
||||
|
||||
class CloneCreditToQuoteFactory
|
||||
{
|
||||
public static function create(Credit $credit, $user_id) : ?Quote
|
||||
{
|
||||
$quote = new Quote();
|
||||
$quote->client_id = $credit->client_id;
|
||||
$quote->user_id = $user_id;
|
||||
$quote->company_id = $credit->company_id;
|
||||
$quote->discount = $credit->discount;
|
||||
$quote->is_amount_discount = $credit->is_amount_discount;
|
||||
$quote->po_number = $credit->po_number;
|
||||
$quote->is_deleted = false;
|
||||
$quote->backup = null;
|
||||
$quote->footer = $credit->footer;
|
||||
$quote->public_notes = $credit->public_notes;
|
||||
$quote->private_notes = $credit->private_notes;
|
||||
$quote->terms = $credit->terms;
|
||||
$quote->tax_name1 = $credit->tax_name1;
|
||||
$quote->tax_rate1 = $credit->tax_rate1;
|
||||
$quote->tax_name2 = $credit->tax_name2;
|
||||
$quote->tax_rate2 = $credit->tax_rate2;
|
||||
$quote->custom_value1 = $credit->custom_value1;
|
||||
$quote->custom_value2 = $credit->custom_value2;
|
||||
$quote->custom_value3 = $credit->custom_value3;
|
||||
$quote->custom_value4 = $credit->custom_value4;
|
||||
$quote->amount = $credit->amount;
|
||||
$quote->balance = $credit->balance;
|
||||
$quote->partial = $credit->partial;
|
||||
$quote->partial_due_date = $credit->partial_due_date;
|
||||
$quote->last_viewed = $credit->last_viewed;
|
||||
|
||||
$quote->status_id = Quote::STATUS_DRAFT;
|
||||
$quote->number = '';
|
||||
$quote->date = null;
|
||||
$quote->due_date = null;
|
||||
$quote->partial_due_date = null;
|
||||
$quote->balance = $credit->amount;
|
||||
$quote->line_items = $credit->line_items;
|
||||
|
||||
return $quote;
|
||||
}
|
||||
}
|
192
app/Filters/CreditFilters.php
Normal file
192
app/Filters/CreditFilters.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CreditFilters extends QueryFilters
|
||||
{
|
||||
/**
|
||||
* Filter based on client status
|
||||
*
|
||||
* Statuses we need to handle
|
||||
* //todo ?partials as a status?
|
||||
* - all
|
||||
* - paid
|
||||
* - unpaid
|
||||
* - overdue
|
||||
* - reversed
|
||||
*
|
||||
* @param string credit_status The credit status as seen by the client
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*
|
||||
*/
|
||||
|
||||
public function credit_status(string $value = '') :Builder
|
||||
{
|
||||
if (strlen($value) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$status_parameters = explode(",", $value);
|
||||
|
||||
if (in_array('all', $status_parameters)) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if (in_array('draft', $status_parameters)) {
|
||||
$this->builder->where('status_id', Credit::STATUS_DRAFT);
|
||||
}
|
||||
|
||||
if (in_array('partial', $status_parameters)) {
|
||||
$this->builder->where('status_id', Credit::STAUTS_PARTIAL);
|
||||
}
|
||||
|
||||
if(in_array('applied', $status_parameters)) {
|
||||
$this->builder->where('status_id', Credit::STATUS_APPLIED);
|
||||
}
|
||||
|
||||
//->where('due_date', '>', Carbon::now())
|
||||
//->orWhere('partial_due_date', '>', Carbon::now());
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter based on search text
|
||||
*
|
||||
* @param string query filter
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
* @deprecated
|
||||
*
|
||||
*/
|
||||
public function filter(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('credits.number', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.number', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.date', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.amount', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.balance', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.custom_value1', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.custom_value2', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.custom_value3', 'like', '%'.$filter.'%')
|
||||
->orWhere('credits.custom_value4', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the list based on the status
|
||||
* archived, active, deleted - legacy from V1
|
||||
*
|
||||
* @param string filter
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function status(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$table = 'credits';
|
||||
$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
|
||||
*
|
||||
* We need to ensure we are using the correct company ID
|
||||
* as we could be hitting this from either the client or company auth guard
|
||||
*
|
||||
* @param $company_id The company Id
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function entityFilter()
|
||||
{
|
||||
if (auth('contact')->user()) {
|
||||
return $this->contactViewFilter();
|
||||
} else {
|
||||
return $this->builder->company();
|
||||
}
|
||||
|
||||
// return $this->builder->whereCompanyId(auth()->user()->company()->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need additional filters when showing credits for the
|
||||
* client portal. Need to automatically exclude drafts and cancelled credits
|
||||
*
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
private function contactViewFilter() : Builder
|
||||
{
|
||||
return $this->builder
|
||||
->whereCompanyId(auth('contact')->user()->company->id)
|
||||
->whereNotIn('status_id', [Credit::STATUS_DRAFT]);
|
||||
}
|
||||
}
|
188
app/Http/Controllers/CreditController.php
Normal file
188
app/Http/Controllers/CreditController.php
Normal file
@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\Credit\CreditWasCreated;
|
||||
use App\Events\Credit\CreditWasUpdated;
|
||||
use App\Factory\CloneCreditFactory;
|
||||
use App\Factory\CloneCreditToQuoteFactory;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Filters\CreditFilters;
|
||||
use App\Http\Requests\Credit\ActionCreditRequest;
|
||||
use App\Http\Requests\Credit\CreateCreditRequest;
|
||||
use App\Http\Requests\Credit\DestroyCreditRequest;
|
||||
use App\Http\Requests\Credit\EditCreditRequest;
|
||||
use App\Http\Requests\Credit\ShowCreditRequest;
|
||||
use App\Http\Requests\Credit\StoreCreditRequest;
|
||||
use App\Http\Requests\Credit\UpdateCreditRequest;
|
||||
use App\Jobs\Credit\StoreCredit;
|
||||
use App\Jobs\Invoice\EmailCredit;
|
||||
use App\Jobs\Invoice\MarkInvoicePaid;
|
||||
use App\Models\Credit;
|
||||
use App\Repositories\CreditRepository;
|
||||
use App\Transformers\CreditTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class CreditController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = Credit::class;
|
||||
|
||||
protected $entity_transformer = CreditTransformer::class;
|
||||
|
||||
protected $credit_repository;
|
||||
|
||||
public function __construct(CreditRepository $credit_repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->credit_repository = $credit_repository;
|
||||
}
|
||||
|
||||
public function index(CreditFilters $filters)
|
||||
{
|
||||
$credits = Credit::filter($filters);
|
||||
|
||||
return $this->listResponse($credits);
|
||||
}
|
||||
|
||||
public function create(CreateCreditRequest $request)
|
||||
{
|
||||
$credit = CreditFactory::create(auth()->user()->company()->id, auth()->user()->id);
|
||||
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
|
||||
public function store(StoreCreditRequest $request)
|
||||
{
|
||||
$credit = $this->credit_repository->save($request->all(), CreditFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
$credit = StoreCredit::dispatchNow($credit, $request->all(), $credit->company);
|
||||
|
||||
event(new CreditWasCreated($credit, $credit->company));
|
||||
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
|
||||
public function show(ShowCreditRequest $request, Credit $credit)
|
||||
{
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
|
||||
public function edit(EditCreditRequest $request, Credit $credit)
|
||||
{
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
|
||||
public function update(UpdateCreditRequest $request, Credit $credit)
|
||||
{
|
||||
if($request->entityIsDeleted($credit))
|
||||
return $request->disallowUpdate();
|
||||
|
||||
$credit = $this->credit_repo->save($request->all(), $credit);
|
||||
|
||||
event(new CreditWasUpdated($credit, $credit->company));
|
||||
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
|
||||
public function destroy(DestroyCreditRequest $request, Credit $credit)
|
||||
{
|
||||
$credit->delete();
|
||||
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = request()->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
||||
$credits = Credit::withTrashed()->whereIn('id', $this->transformKeys($ids));
|
||||
|
||||
if (!$credits) {
|
||||
return response()->json(['message' => 'No Credits Found']);
|
||||
}
|
||||
|
||||
$credits->each(function ($credit, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $credit)) {
|
||||
$this->performAction($credit, $action, true);
|
||||
}
|
||||
});
|
||||
|
||||
return $this->listResponse(Credit::withTrashed()->whereIn('id', $this->transformKeys($ids)));
|
||||
}
|
||||
|
||||
public function action(ActionCreditRequest $request, Credit $credit, $action)
|
||||
{
|
||||
return $this->performAction($credit, $action);
|
||||
}
|
||||
|
||||
private function performAction(Credit $credit, $action, $bulk = false)
|
||||
{
|
||||
/*If we are using bulk actions, we don't want to return anything */
|
||||
switch ($action) {
|
||||
case 'clone_to_credit':
|
||||
$credit = CloneCreditFactory::create($credit, auth()->user()->id);
|
||||
return $this->itemResponse($credit);
|
||||
break;
|
||||
case 'clone_to_quote':
|
||||
$quote = CloneCreditToQuoteFactory::create($credit, 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':
|
||||
if ($credit->balance < 0 || $credit->status_id == Credit::STATUS_PAID || $credit->is_deleted === true) {
|
||||
return $this->errorResponse(['message' => 'Credit cannot be marked as paid'], 400);
|
||||
}
|
||||
|
||||
$credit = MarkInvoicePaid::dispatchNow($credit, $credit->company);
|
||||
|
||||
if (!$bulk) {
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
break;
|
||||
case 'mark_sent':
|
||||
$credit->markSent();
|
||||
|
||||
if (!$bulk) {
|
||||
return $this->itemResponse($credit);
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
return response()->download(public_path($credit->pdf_file_path()));
|
||||
break;
|
||||
case 'archive':
|
||||
$this->credit_repo->archive($credit);
|
||||
|
||||
if (!$bulk) {
|
||||
return $this->listResponse($credit);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
$this->credit_repo->delete($credit);
|
||||
|
||||
if (!$bulk) {
|
||||
return $this->listResponse($credit);
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
EmailCredit::dispatch($credit, $credit->company);
|
||||
if (!$bulk) {
|
||||
return response()->json(['message'=>'email sent'], 200);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return response()->json(['message' => "The requested action `{$action}` is not available."], 400);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
30
app/Http/Requests/Credit/ActionCreditRequest.php
Normal file
30
app/Http/Requests/Credit/ActionCreditRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ActionCreditRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
31
app/Http/Requests/Credit/CreateCreditRequest.php
Normal file
31
app/Http/Requests/Credit/CreateCreditRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use App\Models\Credit;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateCreditRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', Credit::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/Credit/DestroyCreditRequest.php
Normal file
30
app/Http/Requests/Credit/DestroyCreditRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class DestroyCreditRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/Credit/EditCreditRequest.php
Normal file
30
app/Http/Requests/Credit/EditCreditRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class EditCreditRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->user()->can('edit', $this->credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/Credit/ShowCreditRequest.php
Normal file
30
app/Http/Requests/Credit/ShowCreditRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ShowCreditRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('view', $this->credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
31
app/Http/Requests/Credit/StoreCreditRequest.php
Normal file
31
app/Http/Requests/Credit/StoreCreditRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use App\Models\Credit;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreCreditRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->user()->can('create', Credit::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
52
app/Http/Requests/Credit/UpdateCreditRequest.php
Normal file
52
app/Http/Requests/Credit/UpdateCreditRequest.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Credit;
|
||||
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateCreditRequest extends FormRequest
|
||||
{
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
use ChecksEntityStatus;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->credit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
|
||||
//'client_id' => 'required|integer',
|
||||
//'invoice_type_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (isset($input['client_id'])) {
|
||||
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||
}
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
102
app/Jobs/Credit/EmailCredit.php
Normal file
102
app/Jobs/Credit/EmailCredit.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Events\Credit\CreditWasEmailed;
|
||||
use App\Events\Credit\CreditWasEmailedAndFailed;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\TemplateEmail;
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use App\Models\SystemLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class EmailCredit implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
public $message_array = [];
|
||||
|
||||
private $company;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Credit $credit, Company $company)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/*Jobs are not multi-db aware, need to set! */
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
//todo - change runtime config of mail driver if necessary
|
||||
|
||||
$template_style = $this->credit->client->getSetting('email_style');
|
||||
|
||||
$this->credit->invitations->each(function ($invitation) use ($template_style) {
|
||||
if ($invitation->contact->send_invoice && $invitation->contact->email) {
|
||||
$message_array = $this->credit->getEmailData('', $invitation->contact);
|
||||
$message_array['title'] = &$message_array['subject'];
|
||||
$message_array['footer'] = "Sent to ".$invitation->contact->present()->name();
|
||||
|
||||
//change the runtime config of the mail provider here:
|
||||
|
||||
//send message
|
||||
Mail::to($invitation->contact->email, $invitation->contact->present()->name())
|
||||
->send(new TemplateEmail($message_array, $template_style, $invitation->contact->user, $invitation->contact->client));
|
||||
|
||||
if (count(Mail::failures()) > 0) {
|
||||
event(new CreditWasEmailedAndFailed($this->credit, Mail::failures()));
|
||||
|
||||
return $this->logMailError($errors);
|
||||
}
|
||||
|
||||
//fire any events
|
||||
event(new CreditWasEmailed($this->credit));
|
||||
|
||||
//sleep(5);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function logMailError($errors)
|
||||
{
|
||||
SystemLogger::dispatch(
|
||||
$errors,
|
||||
SystemLog::CATEGORY_MAIL,
|
||||
SystemLog::EVENT_MAIL_SEND,
|
||||
SystemLog::TYPE_FAILURE,
|
||||
$this->credit->client
|
||||
);
|
||||
}
|
||||
}
|
85
app/Jobs/Credit/MarkCreditPaid.php
Normal file
85
app/Jobs/Credit/MarkCreditPaid.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Jobs\Client\UpdateClientBalance;
|
||||
use App\Jobs\Client\UpdateClientPaidToDate;
|
||||
use App\Jobs\Company\UpdateCompanyLedgerWithPayment;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MarkCreditPaid implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
private $company;
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Credit $credit, Company $company)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
if($this->credit->status_id == Credit::STATUS_DRAFT)
|
||||
$this->credit->markSent();
|
||||
|
||||
/* Create Payment */
|
||||
$payment = PaymentFactory::create($this->credit->company_id, $this->credit->user_id);
|
||||
|
||||
$payment->amount = $this->credit->balance;
|
||||
$payment->status_id = Credit::STATUS_COMPLETED;
|
||||
$payment->client_id = $this->credit->client_id;
|
||||
$payment->transaction_reference = ctrans('texts.manual_entry');
|
||||
/* Create a payment relationship to the invoice entity */
|
||||
$payment->save();
|
||||
|
||||
$payment->credits()->attach($this->credit->id, [
|
||||
'amount' => $payment->amount
|
||||
]);
|
||||
|
||||
$this->credit->updateBalance($payment->amount*-1);
|
||||
|
||||
/* Update Credit balance */
|
||||
event(new PaymentWasCreated($payment, $payment->company));
|
||||
|
||||
// UpdateCompanyLedgerWithPayment::dispatchNow($payment, ($payment->amount*-1), $this->company);
|
||||
// UpdateClientBalance::dispatchNow($payment->client, $payment->amount*-1, $this->company);
|
||||
// UpdateClientPaidToDate::dispatchNow($payment->client, $payment->amount, $this->company);
|
||||
|
||||
return $this->credit;
|
||||
}
|
||||
}
|
57
app/Jobs/Credit/StoreCredit.php
Normal file
57
app/Jobs/Credit/StoreCredit.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Credit;
|
||||
|
||||
use App\Jobs\Payment\PaymentNotification;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\Credit;
|
||||
use App\Repositories\CreditRepository;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class StoreCredit implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
protected $data;
|
||||
|
||||
private $company;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Credit $credit, array $data, Company $company)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
|
||||
$this->data = $data;
|
||||
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(CreditRepository $credit_repository): ?Credit
|
||||
{
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
$payment = false;
|
||||
|
||||
if ($payment) {
|
||||
PaymentNotification::dispatch($payment, $payment->company);
|
||||
}
|
||||
|
||||
return $this->credit;
|
||||
}
|
||||
}
|
34
database/factories/CloneCreditFactory.php
Normal file
34
database/factories/CloneCreditFactory.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\Credit;
|
||||
|
||||
class CloneCreditFactory
|
||||
{
|
||||
public static function create(Credit $credit, $user_id) : ?Credit
|
||||
{
|
||||
$clone_credit = $credit->replicate();
|
||||
$clone_credit->status_id = credit::STATUS_DRAFT;
|
||||
$clone_credit->number = null;
|
||||
$clone_credit->date = null;
|
||||
$clone_credit->due_date = null;
|
||||
$clone_credit->partial_due_date = null;
|
||||
$clone_credit->user_id = $user_id;
|
||||
$clone_credit->balance = $credit->amount;
|
||||
$clone_credit->line_items = $credit->line_items;
|
||||
$clone_credit->backup = null;
|
||||
|
||||
return $clone_credit;
|
||||
}
|
||||
}
|
@ -41,11 +41,17 @@ Route::group(['middleware' => ['api_db', 'api_secret_check', 'token_auth', 'loca
|
||||
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
|
||||
|
||||
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit
|
||||
|
||||
|
||||
Route::get('invoices/{invoice}/{action}', 'InvoiceController@action')->name('invoices.action');
|
||||
|
||||
|
||||
Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk');
|
||||
|
||||
Route::resource('credits', 'CreditsController'); // name = (credits. index / create / show / update / destroy / edit
|
||||
|
||||
Route::get('credits/{credit}/{action}', 'CreditController@action')->name('credits.action');
|
||||
|
||||
Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk');
|
||||
|
||||
Route::resource('products', 'ProductController'); // name = (products. index / create / show / update / destroy / edit
|
||||
|
||||
Route::post('products/bulk', 'ProductController@bulk')->name('products.bulk');
|
||||
|
Loading…
x
Reference in New Issue
Block a user