Create 'Credits' module (#3263)

* Create 'Credits' module

* Various fixes on Credit module

* Fix MarkCreditPaid factory
This commit is contained in:
Benjamin Beganović 2020-01-30 02:27:22 +01:00 committed by GitHub
parent 956d4ba12e
commit 0f661495db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1060 additions and 2 deletions

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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]);
}
}

View 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;
}
}
}

View 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 [
//
];
}
}

View 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 [
//
];
}
}

View 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 [
//
];
}
}

View 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 [
//
];
}
}

View 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 [
//
];
}
}

View 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 [
//
];
}
}

View 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);
}
}

View 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
);
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -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');