Invoice functionnality
This commit is contained in:
parent
86aa480c04
commit
88e5379610
21 changed files with 1664 additions and 214 deletions
|
|
@ -2,9 +2,282 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\Invoice\TaskType;
|
||||
use App\Http\Requests\Invoice\InvoiceSetDateRequest;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Package;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest;
|
||||
use Kwaadpepper\CrudPolicies\Traits\CrudController;
|
||||
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* * Because update is denied when invoice is sent to the client
|
||||
* ForceUpdate On signed date
|
||||
* ForceUpdate On compta sent date
|
||||
*/
|
||||
class InvoiceController extends Controller
|
||||
{
|
||||
//
|
||||
use CrudController {
|
||||
index as crudIndex;
|
||||
show as crudShow;
|
||||
create as crudCreate;
|
||||
edit as crudEdit;
|
||||
}
|
||||
|
||||
/** @var \App\Models\Invoice */
|
||||
public static $modelClass = Invoice::class;
|
||||
|
||||
/**
|
||||
* Crud Layout
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $layout = 'layout.app';
|
||||
|
||||
/**
|
||||
* Crud index view
|
||||
*
|
||||
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function index(CrudIndexRequest $request)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
$view = $this->crudIndex($request);
|
||||
if ($request->ajax()) {
|
||||
return $view;
|
||||
}
|
||||
if (($models = $view->getData()['models'])->count() === 1) {
|
||||
return \redirect()->route('invoices.edit', $models->first());
|
||||
}
|
||||
return view('invoices.index', $view->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Index query middleware
|
||||
*
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
* @throws \ErrorException If account is missing in url.
|
||||
*/
|
||||
public function indexQuery(Builder $query): Builder
|
||||
{
|
||||
$query->orderBy('created_at', 'DESC');
|
||||
|
||||
return $query->orWhere(function (Builder $q) {
|
||||
$q->whereHas('site', function ($q) {
|
||||
$q->where('name', 'LIKE', '%' . \filter_var(request()->search, \FILTER_SANITIZE_STRING) . '%');
|
||||
})->orWhereHas('company', function ($q) {
|
||||
$q->where('name', 'LIKE', '%' . \filter_var(request()->search, \FILTER_SANITIZE_STRING) . '%');
|
||||
})->orWhereHas('companyInvoiced', function ($q) {
|
||||
$q->where('name', 'LIKE', '%' . \filter_var(request()->search, \FILTER_SANITIZE_STRING) . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show model action
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param mixed ...$params
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function show(Request $request, ...$params)
|
||||
{
|
||||
/** @var \Illuminate\View\View|\Illuminate\Http\JsonResponse */
|
||||
$view = call_user_func_array([$this, 'crudShow'], \array_merge([$request], $params));
|
||||
if ($view instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $view;
|
||||
}
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Package> */
|
||||
$packages = Package::get()->keyBy('id')->all();
|
||||
return view('invoices.show', \array_merge($view->getData(), ['packages' => $packages]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Crud create view
|
||||
*
|
||||
* @param \Kwaadpepper\CrudPolicies\Http\Requests\CrudIndexRequest $request
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function create(CrudIndexRequest $request)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
$view = $this->crudCreate($request);
|
||||
|
||||
$companies = Company::get();
|
||||
$packages = Package::get();
|
||||
$taskRowModel = Invoice::getTaskStructure();
|
||||
|
||||
return view('invoices.create', \array_merge($view->getData(), [
|
||||
'companies' => $companies,
|
||||
'packages' => $packages,
|
||||
'taskRowModel' => $taskRowModel,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Before store model middleware
|
||||
*
|
||||
* @param \App\Models\Invoice $model
|
||||
* @return void
|
||||
*/
|
||||
public function storeModel(Invoice &$model)
|
||||
{
|
||||
$this->validateTasks($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Before update model middleware
|
||||
*
|
||||
* @param \App\Models\Invoice $model
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If invoice is signed.
|
||||
*/
|
||||
public function updateModel(Invoice &$model)
|
||||
{
|
||||
if ($model->signed_date) {
|
||||
throw ValidationException::withMessages([
|
||||
'erreur' => trans('Il n\'est pas psossible de mettre a jour une facture qui est signée')
|
||||
]);
|
||||
}
|
||||
$this->validateTasks($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit model action
|
||||
*
|
||||
* @param mixed ...$params
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function edit(...$params)
|
||||
{
|
||||
/** @var \Illuminate\View\View */
|
||||
$view = call_user_func_array([$this, 'crudEdit'], $params);
|
||||
|
||||
$companies = Company::get();
|
||||
$packages = Package::get();
|
||||
$taskRowModel = Invoice::getTaskStructure();
|
||||
|
||||
return view('invoices.edit', \array_merge($view->getData(), [
|
||||
'companies' => $companies,
|
||||
'packages' => $packages,
|
||||
'taskRowModel' => $taskRowModel,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Tasks
|
||||
*
|
||||
* @param \App\Models\Invoice $invoice
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If tasks are not valid.
|
||||
*/
|
||||
private function validateTasks(Invoice $invoice): void
|
||||
{
|
||||
$tasks = \collect(\json_decode($invoice->tasks, true))
|
||||
->map(function ($task) {
|
||||
if (\is_array($task)) {
|
||||
return \array_intersect_key($task, Invoice::getTaskStructure());
|
||||
}
|
||||
});
|
||||
$invoice->tasks = Validator::make(['tasks' => $tasks->all()], [
|
||||
'tasks' => 'required|array|min:1',
|
||||
'tasks.*' => 'required|array',
|
||||
'tasks.*.days' => 'required|numeric|min:0.125',
|
||||
'tasks.*.amount' => 'required|numeric|min:0.125',
|
||||
'tasks.*.desc' => 'required|string|min:10',
|
||||
'tasks.*.type' => [
|
||||
'required', new EnumIsValidRule(TaskType::class)
|
||||
],
|
||||
'tasks.*.package' => 'required_if:tasks.*.type,' .
|
||||
TaskType::package()->value . '|nullable|exists:packages,id',
|
||||
], [], [
|
||||
'tasks' => trans('Tâches')
|
||||
])->validated()['tasks'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Download Invoice
|
||||
*
|
||||
* @param \App\Models\Invoice $invoice
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function download(Invoice $invoice): Response
|
||||
{
|
||||
$this->authorize($invoice, 'download');
|
||||
return $invoice->generateInvoice()
|
||||
->stream(Str::slug(trans('Facture :appname :date - ref :ref', [
|
||||
'appname' => config('app.name'),
|
||||
'date' => $invoice->created_at->isoFormat('YYYY-MM-DD'),
|
||||
'ref' => $invoice->ref,
|
||||
])) . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set signed date
|
||||
*
|
||||
* @param \App\Http\Requests\Invoice\InvoiceSetDateRequest $request
|
||||
* @param \App\Models\Invoice $invoice
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Validation\ValidationException If already signed.
|
||||
*/
|
||||
public function setSignedDate(InvoiceSetDateRequest $request, Invoice $invoice)
|
||||
{
|
||||
if ($invoice->signed_date) {
|
||||
throw ValidationException::withMessages([
|
||||
'erreur' => trans('La date de signature est déjà actée.')
|
||||
]);
|
||||
}
|
||||
$invoice->signed_date = $request->date;
|
||||
$invoice->saveOrFail();
|
||||
return \back()->with('success', trans('La date de signature à bien été enregistrée'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set client signed date
|
||||
*
|
||||
* @param \App\Http\Requests\Invoice\InvoiceSetDateRequest $request
|
||||
* @param \App\Models\Invoice $invoice
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Validation\ValidationException If already signed.
|
||||
*/
|
||||
public function setClientSentDate(InvoiceSetDateRequest $request, Invoice $invoice)
|
||||
{
|
||||
if ($invoice->sent_client_date) {
|
||||
throw ValidationException::withMessages([
|
||||
'erreur' => trans('La date de signature client est déjà actée.')
|
||||
]);
|
||||
}
|
||||
$invoice->sent_client_date = $request->date;
|
||||
$invoice->saveOrFail();
|
||||
return \back()->with('success', trans('La date de signature client à bien été enregistrée'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set compta signed date
|
||||
*
|
||||
* @param \App\Http\Requests\Invoice\InvoiceSetDateRequest $request
|
||||
* @param \App\Models\Invoice $invoice
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Validation\ValidationException If already signed.
|
||||
*/
|
||||
public function setComptaSentDate(InvoiceSetDateRequest $request, Invoice $invoice)
|
||||
{
|
||||
if ($invoice->sent_accounting_date) {
|
||||
throw ValidationException::withMessages([
|
||||
'erreur' => trans('La date de signature compta est déjà actée.')
|
||||
]);
|
||||
}
|
||||
$invoice->sent_accounting_date = $request->date;
|
||||
$invoice->saveOrFail();
|
||||
return \back()->with('success', trans('La date de signature compta à bien été enregistrée'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
33
app/Http/Requests/Invoice/InvoiceSetDateRequest.php
Normal file
33
app/Http/Requests/Invoice/InvoiceSetDateRequest.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Invoice;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class InvoiceSetDateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
/** @var \App\Models\User|null $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return auth()->check() && $user->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date'
|
||||
];
|
||||
}
|
||||
}
|
||||
64
app/Lib/CollectionHelper.php
Normal file
64
app/Lib/CollectionHelper.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CollectionHelper
|
||||
{
|
||||
/**
|
||||
* Paginate a collection
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @param integer $pageSize
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginate(Collection $results, int $pageSize): LengthAwarePaginator
|
||||
{
|
||||
$page = Paginator::resolveCurrentPage('page');
|
||||
|
||||
return self::paginateManually($results, $pageSize, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate a collection
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @param integer $pageSize
|
||||
* @param integer $page
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginateManually(Collection $results, int $pageSize, int $page = 1): LengthAwarePaginator
|
||||
{
|
||||
$total = $results->count();
|
||||
|
||||
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => 'page',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new length-aware paginator instance.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param integer $total
|
||||
* @param integer $perPage
|
||||
* @param integer $currentPage
|
||||
* @param array $options
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
protected static function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options)
|
||||
{
|
||||
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'perPage' => $perPage,
|
||||
'currentPage' => $currentPage,
|
||||
'options' => $options
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Invoice\TaskType;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
|
@ -9,6 +10,7 @@ use Illuminate\Support\Str;
|
|||
use Kwaadpepper\CrudPolicies\Enums\CrudAction;
|
||||
use Kwaadpepper\CrudPolicies\Enums\CrudType;
|
||||
use Kwaadpepper\CrudPolicies\Traits\IsCrudModel;
|
||||
use Meneses\LaravelMpdf\LaravelMpdf;
|
||||
|
||||
class Invoice extends Model
|
||||
{
|
||||
|
|
@ -48,7 +50,10 @@ class Invoice extends Model
|
|||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'tasks' => 'array'
|
||||
'tasks' => 'array',
|
||||
'signed_date' => 'date',
|
||||
'sent_client_date' => 'date',
|
||||
'sent_accounting_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -114,20 +119,18 @@ class Invoice extends Model
|
|||
'label' => trans('Montant total'),
|
||||
'type' => CrudType::float(),
|
||||
'nullable' => false,
|
||||
'readonly' => true,
|
||||
'actions' => [
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
'total_days' => [
|
||||
'label' => trans('Montant total jours'),
|
||||
'type' => CrudType::float(),
|
||||
'nullable' => false,
|
||||
'readonly' => true,
|
||||
'actions' => [
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
'comment' => [
|
||||
|
|
@ -154,33 +157,30 @@ class Invoice extends Model
|
|||
'label' => trans('Date de signature'),
|
||||
'type' => CrudType::datetime(),
|
||||
'nullable' => true,
|
||||
'readonly' => true,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
'sent_client_date' => [
|
||||
'label' => trans('Date d\'envoi client'),
|
||||
'type' => CrudType::datetime(),
|
||||
'nullable' => true,
|
||||
'readonly' => true,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
'sent_accounting_date' => [
|
||||
'label' => trans('Date d\'envoi compta'),
|
||||
'type' => CrudType::datetime(),
|
||||
'nullable' => true,
|
||||
'readonly' => true,
|
||||
'actions' => [
|
||||
CrudAction::viewAny(),
|
||||
CrudAction::view(),
|
||||
CrudAction::create(),
|
||||
CrudAction::update()
|
||||
]
|
||||
],
|
||||
'site_id' => [
|
||||
|
|
@ -209,7 +209,7 @@ class Invoice extends Model
|
|||
]
|
||||
],
|
||||
'invoiced_to_id' => [
|
||||
'label' => trans('models.classes.companies'),
|
||||
'label' => trans('Entité à facturer'),
|
||||
'type' => CrudType::belongsTo(),
|
||||
'belongsTo' => Company::class,
|
||||
'belongsToLabel' => 'entité',
|
||||
|
|
@ -249,39 +249,40 @@ class Invoice extends Model
|
|||
protected static function booted()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
$model->ref = \sprintf(
|
||||
$model->ref = \sprintf(
|
||||
'%s_%s_%s',
|
||||
Carbon::now()->isoFormat('Y-m'),
|
||||
Str::padLeft(Invoice::whereYear('created_at', Carbon::now()->isoFormat('Y'))
|
||||
->whereMonth('created_at', Carbon::now()->isoFormat('M'))
|
||||
->count() ?: 1, 4, '0'),
|
||||
Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 2)
|
||||
Carbon::now()->isoFormat('YYYY'),
|
||||
Str::padLeft((Invoice::whereYear('created_at', Carbon::now()->isoFormat('YYYY'))
|
||||
->whereMonth('created_at', Carbon::now()->isoFormat('MM'))
|
||||
->count() + 1) ?: 1, 4, '0'),
|
||||
Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 1)
|
||||
);
|
||||
$model->total_days = collect($model->tasks)
|
||||
/** @var null|float $previous */
|
||||
->reduce(function ($previous, array $task) {
|
||||
return \floatval($task['days']) + \floatval($previous);
|
||||
});
|
||||
$model->total = collect($model->tasks)
|
||||
/** @var null|float $previous */
|
||||
->reduce(function ($previous, array $task) {
|
||||
return \floatval($task['amount']) + \floatval($previous);
|
||||
});
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
$model->total_days = collect($model->tasks)
|
||||
/** @var null|float $previous */
|
||||
->reduce(function ($previous, array $task) {
|
||||
return \floatval($task['days']) + \floatval($previous);
|
||||
});
|
||||
$model->total = collect($model->tasks)
|
||||
/** @var null|float $previous */
|
||||
->reduce(function ($previous, array $task) {
|
||||
return \floatval($task['amount']) + \floatval($previous);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Get the tasks line structure skel
|
||||
*
|
||||
* @return array<string,string|float>
|
||||
*/
|
||||
public static function getTaskStructure(): array
|
||||
{
|
||||
return [
|
||||
// * string
|
||||
'desc' => '',
|
||||
// * usigned float
|
||||
'days' => '',
|
||||
// * Invoice\TaskType
|
||||
'type' => '',
|
||||
// * usigned float
|
||||
'amount' => '',
|
||||
];
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* BelongsTo Site
|
||||
|
|
@ -311,4 +312,79 @@ class Invoice extends Model
|
|||
{
|
||||
return $this->BelongsTo(Company::class, 'invoiced_to_id');
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Get the tasks line structure skel
|
||||
*
|
||||
* @return array<string,string|float>
|
||||
*/
|
||||
public static function getTaskStructure(): array
|
||||
{
|
||||
return [
|
||||
// * string
|
||||
'desc' => '',
|
||||
// * usigned float
|
||||
'days' => 0,
|
||||
// * Invoice\TaskType value
|
||||
'type' => TaskType::web()->value,
|
||||
// * usigned float
|
||||
'amount' => 0,
|
||||
// * package ID if task type package or null
|
||||
'package' => null
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Invoice
|
||||
*
|
||||
* @return LaravelMpdf
|
||||
*/
|
||||
public function generateInvoice(): LaravelMpdf
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Package> */
|
||||
$packages = Package::get()->keyBy('id')->all();
|
||||
|
||||
/** @var \Meneses\LaravelMpdf\LaravelMpdfWrapper */
|
||||
$mpdf = app('mpdf.wrapper');
|
||||
/** @var \Meneses\LaravelMpdf\LaravelMpdf */
|
||||
$pdf = $mpdf->loadView(
|
||||
'documents.invoice',
|
||||
$this->toArray(),
|
||||
[
|
||||
'site' => $this->site,
|
||||
'company' => $this->company,
|
||||
'companyInvoiced' => $this->companyInvoiced,
|
||||
'packages' => $packages,
|
||||
],
|
||||
[
|
||||
'mode' => '',
|
||||
'format' => 'A4',
|
||||
'default_font_size' => '12',
|
||||
'default_font' => 'sans-serif',
|
||||
'margin_left' => 0,
|
||||
'margin_right' => 0,
|
||||
'margin_top' => 0,
|
||||
'margin_bottom' => 0,
|
||||
'margin_header' => 0,
|
||||
'margin_footer' => 0,
|
||||
'orientation' => 'P',
|
||||
'title' => __('Facture Service Web :ref', ['ref' => $this->ref]),
|
||||
'author' => 'Equipe Web',
|
||||
'watermark' => '',
|
||||
'show_watermark' => false,
|
||||
'watermark_font' => 'sans-serif',
|
||||
'display_mode' => 'fullpage',
|
||||
'watermark_text_alpha' => 0.1,
|
||||
'custom_font_dir' => '',
|
||||
'custom_font_data' => [],
|
||||
'auto_language_detection' => false,
|
||||
'temp_dir' => rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR),
|
||||
'pdfa' => false,
|
||||
'pdfaauto' => false,
|
||||
]
|
||||
);
|
||||
return $pdf;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Policies;
|
|||
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserRoleRules as URR;
|
||||
use App\Policies\Rules\InvoiceStaticRules as ISR;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
|
||||
|
|
@ -29,9 +30,17 @@ class InvoicePolicy extends RootPolicy
|
|||
$this->policies = [
|
||||
'invoice' => [
|
||||
'viewAny' => URR::make('userIsAdmin', $user),
|
||||
'view' => URR::make('userIsAdmin', $user),
|
||||
'create' => URR::make('userIsAdmin', $user),
|
||||
'update' => URR::make('userIsAdmin', $user),
|
||||
'delete' => URR::make('userIsAdmin', $user)
|
||||
'update' => [
|
||||
URR::make('userIsAdmin', $user),
|
||||
ISR::make('invoiceHasNotBeenSentTotheClient', $user),
|
||||
],
|
||||
'delete' => URR::make('denyEveryOne'),
|
||||
'download' => [
|
||||
URR::make('userIsAdmin', $user),
|
||||
ISR::make('invoiceHasTasks', $user),
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
|||
35
app/Policies/Rules/InvoiceStaticRules.php
Normal file
35
app/Policies/Rules/InvoiceStaticRules.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies\Rules;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
|
||||
|
||||
/**
|
||||
* Invoice Policiy Static Rules
|
||||
* ! Rules has to return boolean value
|
||||
*/
|
||||
abstract class InvoiceStaticRules extends StaticRules
|
||||
{
|
||||
/**
|
||||
* Invoice has not been sent ot the client
|
||||
*
|
||||
* @param \App\Models\Invoice $model
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function invoiceHasNotBeenSentTotheClient(Invoice $model): bool
|
||||
{
|
||||
return \is_null($model->sent_client_date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice has some tasks
|
||||
*
|
||||
* @param \App\Models\Invoice $model
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function invoiceHasTasks(Invoice $model): bool
|
||||
{
|
||||
return (bool)\count($model->tasks);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,12 +10,13 @@
|
|||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0",
|
||||
"carlos-meneses/laravel-mpdf": "^2.1",
|
||||
"doctrine/dbal": "^3.1",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"fideloper/proxy": "^4.4",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"kwaadpepper/crud-policies": "^0.4.5",
|
||||
"kwaadpepper/crud-policies": "^0.4.7",
|
||||
"kwaadpepper/enum": "^2.1",
|
||||
"kwaadpepper/exception-handler": "^0.0.9",
|
||||
"laravel-lang/lang": "~8.0",
|
||||
|
|
|
|||
435
composer.lock
generated
435
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "47a64c62b7e388c80216db44f86f0d44",
|
||||
"content-hash": "d6431e8c4b000923f948206977031782",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
|
@ -122,6 +122,56 @@
|
|||
],
|
||||
"time": "2021-08-15T20:50:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "carlos-meneses/laravel-mpdf",
|
||||
"version": "2.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mccarlosen/laravel-mpdf.git",
|
||||
"reference": "810008e1a4b82fb6c8c74cdf32fd130195756027"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mccarlosen/laravel-mpdf/zipball/810008e1a4b82fb6c8c74cdf32fd130195756027",
|
||||
"reference": "810008e1a4b82fb6c8c74cdf32fd130195756027",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"mpdf/mpdf": "^8.0",
|
||||
"php": ">=7.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Meneses\\LaravelMpdf\\LaravelMpdfServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"PDF": "Meneses\\LaravelMpdf\\Facades\\LaravelMpdf"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Meneses\\LaravelMpdf\\": "src/LaravelMpdf"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Laravel Mpdf: Using Mpdf in Laravel 5 for generate Pdfs.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"mpdf",
|
||||
"pdf"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mccarlosen/laravel-mpdf/issues",
|
||||
"source": "https://github.com/mccarlosen/laravel-mpdf/tree/2.1.5"
|
||||
},
|
||||
"time": "2021-03-30T21:17:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
"version": "2.2.0",
|
||||
|
|
@ -1502,16 +1552,16 @@
|
|||
},
|
||||
{
|
||||
"name": "kwaadpepper/crud-policies",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Kwaadpepper/crud-policies.git",
|
||||
"reference": "8f83c66385409ce98955b1b3c65aa2da414acdc8"
|
||||
"reference": "89e35c6969da683b0b5ed440539b45b5109871b0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Kwaadpepper/crud-policies/zipball/8f83c66385409ce98955b1b3c65aa2da414acdc8",
|
||||
"reference": "8f83c66385409ce98955b1b3c65aa2da414acdc8",
|
||||
"url": "https://api.github.com/repos/Kwaadpepper/crud-policies/zipball/89e35c6969da683b0b5ed440539b45b5109871b0",
|
||||
"reference": "89e35c6969da683b0b5ed440539b45b5109871b0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1562,9 +1612,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Kwaadpepper/crud-policies/issues",
|
||||
"source": "https://github.com/Kwaadpepper/crud-policies/tree/0.4.6"
|
||||
"source": "https://github.com/Kwaadpepper/crud-policies/tree/0.4.7"
|
||||
},
|
||||
"time": "2022-06-17T08:48:10+00:00"
|
||||
"time": "2022-08-26T14:55:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "kwaadpepper/enum",
|
||||
|
|
@ -2690,6 +2740,142 @@
|
|||
],
|
||||
"time": "2022-07-24T11:55:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mpdf/mpdf",
|
||||
"version": "v8.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mpdf/mpdf.git",
|
||||
"reference": "a8a22f4874157e490d41b486053a20bec42e182c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mpdf/mpdf/zipball/a8a22f4874157e490d41b486053a20bec42e182c",
|
||||
"reference": "a8a22f4874157e490d41b486053a20bec42e182c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-gd": "*",
|
||||
"ext-mbstring": "*",
|
||||
"myclabs/deep-copy": "^1.7",
|
||||
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
|
||||
"php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0",
|
||||
"php-http/message-factory": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"psr/log": "^1.0 || ^2.0",
|
||||
"setasign/fpdi": "^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.0",
|
||||
"mpdf/qrcode": "^1.1.0",
|
||||
"squizlabs/php_codesniffer": "^3.5.0",
|
||||
"tracy/tracy": "^2.4",
|
||||
"yoast/phpunit-polyfills": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Needed for generation of some types of barcodes",
|
||||
"ext-xml": "Needed mainly for SVG manipulation",
|
||||
"ext-zlib": "Needed for compression of embedded resources, such as fonts"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mpdf\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matěj Humpál",
|
||||
"role": "Developer, maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Ian Back",
|
||||
"role": "Developer (retired)"
|
||||
}
|
||||
],
|
||||
"description": "PHP library generating PDF files from UTF-8 encoded HTML",
|
||||
"homepage": "https://mpdf.github.io",
|
||||
"keywords": [
|
||||
"pdf",
|
||||
"php",
|
||||
"utf-8"
|
||||
],
|
||||
"support": {
|
||||
"docs": "http://mpdf.github.io",
|
||||
"issues": "https://github.com/mpdf/mpdf/issues",
|
||||
"source": "https://github.com/mpdf/mpdf"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.me/mpdf",
|
||||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2022-08-15T08:15:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
|
||||
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/collections": "<1.6.8",
|
||||
"doctrine/common": "<2.13.3 || >=3,<3.2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/collections": "^1.6.8",
|
||||
"doctrine/common": "^2.13.3 || ^3.2.2",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/DeepCopy/deep_copy.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Create deep copies (clones) of your objects",
|
||||
"keywords": [
|
||||
"clone",
|
||||
"copy",
|
||||
"duplicate",
|
||||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-03T13:19:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "2.61.0",
|
||||
|
|
@ -2913,6 +3099,110 @@
|
|||
},
|
||||
"time": "2022-01-27T09:35:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v9.99.100",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">= 7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*",
|
||||
"vimeo/psalm": "^1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"polyfill",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"support": {
|
||||
"email": "info@paragonie.com",
|
||||
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||
"source": "https://github.com/paragonie/random_compat"
|
||||
},
|
||||
"time": "2020-10-15T08:29:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-http/message-factory",
|
||||
"version": "v1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-http/message-factory.git",
|
||||
"reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1",
|
||||
"reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Factory interfaces for PSR-7 HTTP Message",
|
||||
"homepage": "http://php-http.org",
|
||||
"keywords": [
|
||||
"factory",
|
||||
"http",
|
||||
"message",
|
||||
"stream",
|
||||
"uri"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-http/message-factory/issues",
|
||||
"source": "https://github.com/php-http/message-factory/tree/master"
|
||||
},
|
||||
"time": "2015-12-19T14:08:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.0",
|
||||
|
|
@ -3693,6 +3983,78 @@
|
|||
],
|
||||
"time": "2021-09-25T23:10:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "setasign/fpdi",
|
||||
"version": "v2.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Setasign/FPDI.git",
|
||||
"reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/6231e315f73e4f62d72b73f3d6d78ff0eed93c31",
|
||||
"reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-zlib": "*",
|
||||
"php": "^5.6 || ^7.0 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"setasign/tfpdf": "<1.31"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~5.7",
|
||||
"setasign/fpdf": "~1.8",
|
||||
"setasign/tfpdf": "1.31",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"tecnickcom/tcpdf": "~6.2"
|
||||
},
|
||||
"suggest": {
|
||||
"setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"setasign\\Fpdi\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jan Slabon",
|
||||
"email": "jan.slabon@setasign.com",
|
||||
"homepage": "https://www.setasign.com"
|
||||
},
|
||||
{
|
||||
"name": "Maximilian Kresse",
|
||||
"email": "maximilian.kresse@setasign.com",
|
||||
"homepage": "https://www.setasign.com"
|
||||
}
|
||||
],
|
||||
"description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.",
|
||||
"homepage": "https://www.setasign.com/fpdi",
|
||||
"keywords": [
|
||||
"fpdf",
|
||||
"fpdi",
|
||||
"pdf"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Setasign/FPDI/issues",
|
||||
"source": "https://github.com/Setasign/FPDI/tree/v2.3.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/setasign/fpdi",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-11T11:37:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v6.3.0",
|
||||
|
|
@ -6720,65 +7082,6 @@
|
|||
},
|
||||
"time": "2022-01-20T13:18:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
|
||||
"reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/collections": "<1.6.8",
|
||||
"doctrine/common": "<2.13.3 || >=3,<3.2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/collections": "^1.6.8",
|
||||
"doctrine/common": "^2.13.3 || ^3.2.2",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/DeepCopy/deep_copy.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"DeepCopy\\": "src/DeepCopy/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Create deep copies (clones) of your objects",
|
||||
"keywords": [
|
||||
"clone",
|
||||
"copy",
|
||||
"duplicate",
|
||||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-03T13:19:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nunomaduro/collision",
|
||||
"version": "v5.11.0",
|
||||
|
|
|
|||
|
|
@ -49,11 +49,14 @@ class InvoiceFactory extends Factory
|
|||
}
|
||||
$totalDays += $days;
|
||||
$total += $amount;
|
||||
$tasks[] = \array_merge(Invoice::getTaskStructure(), [
|
||||
/** @var \App\Enums\Invoice\TaskType */
|
||||
$taskType = \collect(TaskType::toArray())->random();
|
||||
$tasks[] = \array_merge(Invoice::getTaskStructure(), [
|
||||
'desc' => $this->faker->words(9, true),
|
||||
'days' => $days,
|
||||
'type' => \collect(TaskType::toArray())->random()->value,
|
||||
'type' => $taskType->value,
|
||||
'amount' => $amount,
|
||||
'package' => 1
|
||||
]);
|
||||
} //end for
|
||||
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ class InvoicingSeeder extends Seeder
|
|||
Invoice::factory(45)->make()->each(function (Invoice $model) {
|
||||
$model->ref = \sprintf(
|
||||
'%s_%s_%s',
|
||||
Carbon::now()->isoFormat('Y-m'),
|
||||
Str::padLeft(Invoice::whereYear('created_at', Carbon::now()->isoFormat('Y'))
|
||||
->whereMonth('created_at', Carbon::now()->isoFormat('M'))
|
||||
->count() ?: 1, 4, '0'),
|
||||
Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 2)
|
||||
Carbon::now()->isoFormat('YYYY-MM'),
|
||||
Str::padLeft((Invoice::whereYear('created_at', Carbon::now()->isoFormat('YYYY'))
|
||||
->whereMonth('created_at', Carbon::now()->isoFormat('MM'))
|
||||
->count() + 1) ?: 1, 4, '0'),
|
||||
Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 1)
|
||||
);
|
||||
$model->site()->associate(Site::query()->inRandomOrder()->first());
|
||||
$model->company()->associate(Company::query()->inRandomOrder()->first());
|
||||
|
|
|
|||
BIN
resources/assets/documents/invoice/logo_ldc.jpg
Normal file
BIN
resources/assets/documents/invoice/logo_ldc.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -15,6 +15,6 @@ return [
|
|||
'labels' => 'Labels',
|
||||
'packages' => 'Forfaits',
|
||||
'invoices' => 'Factures',
|
||||
'companies' => 'Compagnies',
|
||||
'companies' => 'Sociétés',
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -24,103 +24,110 @@
|
|||
<th width="100">
|
||||
{{ __("Jours") }}
|
||||
</th>
|
||||
<th width="100">
|
||||
<th width="140">
|
||||
{{ __("Montant") }}
|
||||
</th>
|
||||
<th />
|
||||
</thead>
|
||||
<tbody v-if="tasks.length">
|
||||
<tr
|
||||
v-for="(task, index) in tasks"
|
||||
:key="index"
|
||||
>
|
||||
<td>
|
||||
<textarea
|
||||
v-model="task.desc"
|
||||
rows="2"
|
||||
class="form-control"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
v-model="task.type"
|
||||
@change="updateAmount(index)"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="(typeEnum, index) in taskTypeEnums"
|
||||
:value="typeEnum.value"
|
||||
:key="index"
|
||||
<tbody>
|
||||
<template v-if="tasks.length">
|
||||
<tr
|
||||
v-for="(task, index) in tasks"
|
||||
:key="index"
|
||||
>
|
||||
<td>
|
||||
<textarea
|
||||
v-model="task.desc"
|
||||
rows="2"
|
||||
class="form-control"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
v-model="task.type"
|
||||
@change="updateAmount(index)"
|
||||
class="form-select"
|
||||
>
|
||||
{{ typeEnum.label }}
|
||||
</option>
|
||||
</select>
|
||||
<select
|
||||
v-if="task.type === getTaskTypeEnum('forfait').value"
|
||||
v-model="task.package"
|
||||
@change="updateAmount(index)"
|
||||
class="form-select"
|
||||
>
|
||||
<option
|
||||
v-for="(packageModel, index) in packages"
|
||||
:value="packageModel"
|
||||
:key="index"
|
||||
:selected="task.package === undefined && index === 0"
|
||||
<option
|
||||
v-for="(typeEnum, typeIndex) in taskTypeEnums"
|
||||
:value="typeEnum.value"
|
||||
:key="typeIndex"
|
||||
>
|
||||
{{ typeEnum.label }}
|
||||
</option>
|
||||
</select>
|
||||
<select
|
||||
v-if="task.type === getTaskTypeEnum('forfait').value"
|
||||
v-model="task.packageModel"
|
||||
@change="updateAmount(index)"
|
||||
class="form-select"
|
||||
>
|
||||
{{ packageModel.name }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
class="inputNumber"
|
||||
v-model="task.days"
|
||||
@change="updateAmount(index)"
|
||||
type="number"
|
||||
step="0.125"
|
||||
min="0"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
v-if="task.type === getTaskTypeEnum('libre').value"
|
||||
class="inputNumber"
|
||||
v-model="task.amount"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
<template v-else>
|
||||
{{ task.amount.toFixed(2) }}
|
||||
</template> €
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
class="btn-group"
|
||||
role="group"
|
||||
:aria-label="__('Ajouter ou enlever une ligne')"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
@click.prevent="addTasksRow(index)"
|
||||
<option
|
||||
v-for="(packageModel, packageIndex) in packages"
|
||||
:value="packageModel"
|
||||
:key="packageIndex"
|
||||
:selected="!task.packageModel && index === 0"
|
||||
>
|
||||
{{ packageModel.name }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
class="inputNumber form-control"
|
||||
v-model="task.days"
|
||||
@change="updateAmount(index)"
|
||||
type="number"
|
||||
step="0.125"
|
||||
min="0"
|
||||
>
|
||||
<font-awesome-icon icon="fa-solid fa-plus" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-danger"
|
||||
@click.prevent="removeTasksRow(index)"
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
v-if="task.type === getTaskTypeEnum('libre').value"
|
||||
class="input-group"
|
||||
>
|
||||
<font-awesome-icon icon="fa-solid fa-minus" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<input
|
||||
class="inputNumber form-control"
|
||||
v-model="task.amount"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
<span class="input-group-text">€</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
{{ task.amount.toFixed(2) }} €
|
||||
</template>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
class="btn-group"
|
||||
role="group"
|
||||
:aria-label="__('Ajouter ou enlever une ligne')"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
@click.prevent="addTasksRow(index)"
|
||||
>
|
||||
<font-awesome-icon icon="fa-solid fa-plus" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-danger"
|
||||
@click.prevent="removeTasksRow(index)"
|
||||
>
|
||||
<font-awesome-icon icon="fa-solid fa-minus" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr>
|
||||
<td
|
||||
colspan="4"
|
||||
colspan="5"
|
||||
class="text-end"
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
class="btn btn-sm btn-primary"
|
||||
@click.prevent="addTasksRow()"
|
||||
>
|
||||
<font-awesome-icon icon="fa-solid fa-plus" />
|
||||
|
|
@ -131,11 +138,23 @@
|
|||
<td colspan="2">
|
||||
{{ __("Total") }}
|
||||
</td>
|
||||
<td>{{ totalDays }}</td>
|
||||
<td>{{ Number.parseFloat(String(total)).toFixed(2) }} €</td>
|
||||
<td>
|
||||
<span class="mt-2">{{ totalDays }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="mt-2">
|
||||
{{ Number.parseFloat(String(total)).toFixed(2) }} €</span>
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<input
|
||||
class="d-none"
|
||||
name="tasks"
|
||||
type="text"
|
||||
:value="JSON.stringify(databaseTasks)"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -150,7 +169,8 @@ type TaskRow = {
|
|||
days: number;
|
||||
type: number;
|
||||
amount: number;
|
||||
package: LaravelModel | null;
|
||||
package: number | null;
|
||||
packageModel: LaravelModel | null;
|
||||
};
|
||||
|
||||
type TaskType = Enum & { price: number };
|
||||
|
|
@ -164,15 +184,25 @@ export default defineComponent({
|
|||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
|
||||
this.companies = data.companies;
|
||||
this.packages = data.packages;
|
||||
this.taskTypeEnums = data.taskTypeEnums;
|
||||
this.taskRowModel = data.taskRowModel;
|
||||
|
||||
const tasks = data.tasks;
|
||||
tasks.map((task: TaskRow) => {
|
||||
if (task.package) {
|
||||
task.packageModel =
|
||||
data.packages.find(
|
||||
(packageModel: LaravelModel) => packageModel.id === task.package
|
||||
) ?? null;
|
||||
}
|
||||
});
|
||||
this.tasks = tasks;
|
||||
|
||||
// * Use first enum value as default
|
||||
this.taskRowModel.type = this.getTaskTypeEnum("web").value as number;
|
||||
this.taskRowModel.package = this._packages[0];
|
||||
this.taskRowModel.packageModel = this.packages[0];
|
||||
},
|
||||
data(): {
|
||||
tasks: TaskRow[];
|
||||
|
|
@ -192,10 +222,20 @@ export default defineComponent({
|
|||
type: 1,
|
||||
amount: 0,
|
||||
package: null,
|
||||
packageModel: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed__tasks: {
|
||||
computed: {
|
||||
databaseTasks(): TaskRow[] {
|
||||
let tasks = [...this.tasks];
|
||||
tasks.map((task: TaskRow) => {
|
||||
task = Object.assign({}, task);
|
||||
task.package = task.packageModel ? task.package : null;
|
||||
task.packageModel = null;
|
||||
});
|
||||
return tasks;
|
||||
},
|
||||
total(): number {
|
||||
let total = 0;
|
||||
this.tasks.map((task) => (total += task.amount));
|
||||
|
|
@ -207,7 +247,14 @@ export default defineComponent({
|
|||
return total;
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
watch: {
|
||||
tasks(tasks: TaskRow[]) {
|
||||
tasks.map((task) => {
|
||||
task.package = task.packageModel?.id as number | null;
|
||||
});
|
||||
this.tasks = tasks;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Get the coresponding task type using value or label
|
||||
|
|
@ -246,10 +293,8 @@ export default defineComponent({
|
|||
task.amount = task.days * this.getTaskTypeEnum(task.type).price;
|
||||
break;
|
||||
case this.getTaskTypeEnum("forfait").value:
|
||||
if (!task.package) {
|
||||
throw new Error(`task ${task.desc} has undefined package`);
|
||||
}
|
||||
task.amount = task.days * (task.package.amount as number);
|
||||
task.amount =
|
||||
task.days * ((task.packageModel?.amount ?? 0) as number);
|
||||
break;
|
||||
case this.getTaskTypeEnum("libre").value:
|
||||
// Do nothing, free input
|
||||
|
|
|
|||
219
resources/views/documents/invoice.blade.php
Normal file
219
resources/views/documents/invoice.blade.php
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title>
|
||||
{{ __('Facture :appname - ref :ref', [
|
||||
'appname' => config('app.name'),
|
||||
'ref' => $ref,
|
||||
]) }}
|
||||
</title>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
|
||||
<style type="text/css">
|
||||
html,
|
||||
body {
|
||||
position: relative;
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Tahoma;
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#mainTable {
|
||||
margin: auto;
|
||||
padding-top: 10mm;
|
||||
height: 244mm;
|
||||
width: 188mm;
|
||||
}
|
||||
|
||||
table.bordered,
|
||||
table.bordered tr,
|
||||
table.bordered th,
|
||||
table.bordered td {
|
||||
border: 1px solid;
|
||||
border-color: #CCC;
|
||||
}
|
||||
.with-bg {
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
|
||||
@page {
|
||||
footer: page-footer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mainTable" cellpadding="0" cellspacing="0">
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td colspan="2" style="text-align: right;padding-bottom: 3mm;"><b>{{ Str::upper($lastname) }} {{ $firstname }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;padding-right: 3mm;"><b>{{ Str::singular(__('models.classes.companies')) }}</b></td>
|
||||
<td width="auto" style="white-space: nowrap;">{{ $company->name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;padding-right: 3mm;"><b>{{ __('Entité à facturer') }}</b></td>
|
||||
<td width="auto" style="white-space: nowrap;">{{ $companyInvoiced->name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td valign="middle" width="12mm" style="height: 23mm;">
|
||||
<img style="width: 12mm;" src="{{ resource_path('assets/documents/invoice/logo_ldc.jpg') }}" alt="{{ trans('Logo LDC') }}"/>
|
||||
</td>
|
||||
<td width="50%">
|
||||
<span style="font-size: 16px;font-weight: bold;">{{ __('SERVICE WEB MARKETING GROUPE') }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td width="25mm"><b>{{ __('Objet :') }}</b></td>
|
||||
<td>{{ $object }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="25mm"><b>{{ __('Site Internet :') }}</b></td>
|
||||
<td><u>{{ $site->name }}</u></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="25mm"><b>{{ __('Devis :') }}</b></td>
|
||||
<td><strong><i>{{ $ref }}</i></strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="25mm"><b>{{ __('Date :') }}</b></td>
|
||||
<td><i>{{ Carbon::parse($updated_at)->isoFormat('DD/MM/YYYY HH:mm') }}</i></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td height="26mm" style="text-align: center;">
|
||||
<span style="font-size: 16px;font-weight: bold;">{{ __('Prestation WEB') }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@php
|
||||
$tasks = collect($tasks);
|
||||
$columns = 2 + ( $show_days ? 1 : 0);
|
||||
$page = 1;
|
||||
$perPage = 8;
|
||||
$lastPage = App\Lib\CollectionHelper::paginateManually($tasks, $perPage)->lastPage();
|
||||
@endphp
|
||||
|
||||
@while($lastPage >= $page)
|
||||
@php $taskList = App\Lib\CollectionHelper::paginateManually($tasks, $perPage, $page); @endphp
|
||||
|
||||
@if($page !== 1)
|
||||
<table width="100%" style="height: 10mm;">
|
||||
<tr>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
@endif
|
||||
|
||||
<table width="100%" cellspacing="0" cellpadding="2" class="bordered" @if($page !== 1) style="margin-top: 30mm;" @endif>
|
||||
@if($page === 1)
|
||||
<tr>
|
||||
<td class="with-bg" style="padding-left: 2mm;"><strong>{{ __('Tâches') }}</strong></td>
|
||||
@if($show_days)
|
||||
<td width="22mm" class="with-bg" style="padding-left: 2mm;"><strong>{{ __('Jours') }}</strong></td>
|
||||
@endif
|
||||
<td width="22mm" class="with-bg" style="padding-left: 2mm;"><strong>{{ __('Montant') }}</strong></td>
|
||||
</tr>
|
||||
<tr style="display:none;"><td colspan="{{ $columns }}"></td></tr>
|
||||
@endif
|
||||
@foreach($taskList as $task)
|
||||
<tr>
|
||||
<td valign="top" style="padding: 3mm 2mm;">
|
||||
@if($task['package'] ?? 0)
|
||||
<b>{{ __('Forfait : :package', [
|
||||
'package' => $packages[$task['package']]->name ?? __('non existant')
|
||||
]) }}</b>
|
||||
<br>
|
||||
@endif
|
||||
{{ $task['desc'] }}
|
||||
</td>
|
||||
@if($show_days)
|
||||
<td style="text-align: center;">{{ number_format($task['days'], 2, ',', '') }}</td>
|
||||
@endif
|
||||
<td style="text-align: center;">{{ number_format($task['amount'], 2, ',', '') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@for ($i = $perPage - $taskList->count(); $i > 0; $i--)
|
||||
<tr>
|
||||
@for ($k = $columns; $k > 0; $k--)
|
||||
<td valign="top"> </td>
|
||||
@endfor
|
||||
</tr>
|
||||
@endfor
|
||||
</table>
|
||||
|
||||
@php $page++; @endphp
|
||||
@if($taskList->currentPage() < $taskList->lastPage())
|
||||
<pagebreak>
|
||||
@endif
|
||||
@endwhile
|
||||
|
||||
<table width="100%" cellspacing="0" cellpadding="2" class="bordered">
|
||||
<tr>
|
||||
<td style="padding-left: 2mm;"><span style="font-size: 14px;font-weight: bold;">{{ __('Total') }}</span></td>
|
||||
@if($show_days)
|
||||
<td width="22mm" align="center"><strong>{{ __(':total Jours', [
|
||||
'total' => number_format($total_days, 2, ',', '')
|
||||
]) }}</strong></td>
|
||||
@endif
|
||||
<td width="22mm" align="center"><strong>{{ __(':total €', [
|
||||
'total' => number_format($total, 2, ',', '')
|
||||
]) }}</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
@if($comment)
|
||||
<br>
|
||||
<div>
|
||||
<span style="font-size: 13px;font-weight: bold;">{{ __('Commentaire') }}</span>
|
||||
</div>
|
||||
<table width="100%" cellspacing="0" cellpadding="2" class="bordered">
|
||||
<tr>
|
||||
<td style="padding: 2mm;">
|
||||
{{ Str::limit($comment, 2100) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
||||
<htmlpagefooter name="page-footer">
|
||||
<table width="90%" cellspacing="0" cellpadding="2" style="margin: auto;margin-bottom: 10mm;">
|
||||
<tr>
|
||||
<td width="33%" style="border-top:double medium #CCCCCC;padding-top: 2mm;" valign="top">
|
||||
{{ __("LDC Web Service") }}<br/>
|
||||
</td>
|
||||
<td width="33%" style="text-align: center;border-top:double medium #CCCCCC;padding-top: 2mm;" valign="top">
|
||||
|
||||
</td>
|
||||
<td valign="bottom" width="34%" style="text-align: right;border-top:double medium #CCCCCC;padding-top: 2mm;">
|
||||
<span>{{ __('page') }} {PAGENO}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</htmlpagefooter>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
43
resources/views/invoices/create.blade.php
Normal file
43
resources/views/invoices/create.blade.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
@extends($viewLayout)
|
||||
|
||||
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.create')))
|
||||
@section('description', sprintf('%s %s', __('crud-policies::crud.create'), class_basename($modelClass)))
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
@include('crud-policies::crud.modules.flashMessage')
|
||||
<div class="card-header">
|
||||
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::create()])
|
||||
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::create()])
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ CrudController::getRoutePrefixed("$modelTable.store") }}" method="POST" @if($hasFile) enctype="multipart/form-data" @endif>
|
||||
@csrf
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col-3 border border-secondary py-1 px-1">{{ __('crud-policies::crud.property') }}</div>
|
||||
<div class="col-9 border border-secondary py-1 px-1">{{ __('crud-policies::crud.value') }}</div>
|
||||
</div>
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@php
|
||||
// create form cannot accept readonly field
|
||||
$prop['readonly'] = false;
|
||||
@endphp
|
||||
@if(in_array(CrudAction::create(), $prop['actions']))
|
||||
<div class="row">
|
||||
@include('crud-policies::crud.formfield')
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<button class="btn btn-info" type="submit">{{ __('crud-policies::crud.create') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@include('crud-policies::crud.modules.scriptConstants')
|
||||
@endsection
|
||||
114
resources/views/invoices/edit.blade.php
Normal file
114
resources/views/invoices/edit.blade.php
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
@extends($viewLayout)
|
||||
|
||||
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.edit')))
|
||||
@section('description', sprintf('%s %s', __('crud-policies::crud.edit'), trans(class_basename($modelClass))))
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
@include('crud-policies::crud.modules.flashMessage')
|
||||
<div class="card-header">
|
||||
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::update()])
|
||||
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::update()])
|
||||
<div class="row text-end">
|
||||
<div class="col">
|
||||
@if(!$model->signed_date)
|
||||
<form action="{{ route('invoices.sign.setSignedDate', $model) }}" method="POST">
|
||||
@csrf
|
||||
<div class="input-group">
|
||||
<input name="date" type="date" class="form-control" aria-label="{{ __('Signé par le service') }}" aria-describedby="signed_date">
|
||||
<button class="btn {{ !$model->signed_date ? 'btn-outline-warning' : 'btn-outline-secondary' }}" type="submit" id="signed_date">{{ __('Service Web') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<span class="d-block h5 fw-bold pt-2">{{ __('Signé le :date', [
|
||||
'date' => $model->signed_date->isoFormat('DD/MM/YYYY')
|
||||
]) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col">
|
||||
@if(!$model->sent_client_date)
|
||||
<form action="{{ route('invoices.sign.setClientSentDate', $model) }}" method="POST">
|
||||
@csrf
|
||||
<div class="input-group">
|
||||
<input name="date" type="date" class="form-control" aria-label="{{ __('Signé par le client') }}" aria-describedby="sent_client_date" @if($model->sent_client_date)readonly @endif>
|
||||
<button class="btn {{ !$model->sent_client_date ? 'btn-outline-success' : 'btn-outline-secondary' }}" type="submit" id="sent_client_date">{{ __('Client') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<span class="d-block h5 fw-bold pt-2">{{ __('Signé le :date (client)', [
|
||||
'date' => $model->sent_client_date->isoFormat('DD/MM/YYYY')
|
||||
]) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col">
|
||||
@if(!$model->sent_accounting_date)
|
||||
<form action="{{ route('invoices.sign.setComptaSentDate', $model) }}" method="POST">
|
||||
@csrf
|
||||
<div class="input-group">
|
||||
<input name="date" type="date" class="form-control" aria-label="{{ __('Signé par la compta') }}" aria-describedby="sent_accounting_date" @if($model->sent_accounting_date)readonly @endif>
|
||||
<button class="btn {{ !$model->sent_accounting_date ? 'btn-outline-info' : 'btn-outline-secondary' }}" type="submit" id="sent_accounting_date">{{ __('Compta') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<span class="d-block h5 fw-bold pt-2">{{ __('Signé le :date (compta)', [
|
||||
'date' => $model->sent_accounting_date->isoFormat('DD/MM/YYYY')
|
||||
]) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ CrudController::getRoutePrefixed("$modelTable.update", $model) }}" method="POST" @if($hasFile) enctype="multipart/form-data" @endif>
|
||||
@method('put')
|
||||
@csrf
|
||||
<div class="container text-center">
|
||||
<div class="row">
|
||||
<div class="col-3 border border-secondary py-1 px-1">{{ __('crud-policies::crud.property') }}</div>
|
||||
<div class="col-9 border border-secondary py-1 px-1">{{ __('crud-policies::crud.value') }}</div>
|
||||
</div>
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if(in_array(CrudAction::update(), $prop['actions']))
|
||||
@if($fieldName === 'tasks')
|
||||
<div class="row">
|
||||
<div class="col-3 border border-secondary py-1 px-1">
|
||||
<label for="total" class="form-label">{{ $prop['label'] ?? $fieldName }}</label>
|
||||
</div>
|
||||
<div class="col-9 border border-secondary py-1 px-1">
|
||||
@php
|
||||
$taskTypeEnums = collect(App\Enums\Invoice\TaskType::toArray())->map(function($enum) {
|
||||
return [
|
||||
'value' => $enum->value,
|
||||
'label' => $enum->label,
|
||||
'price' => $enum->price,
|
||||
];
|
||||
});
|
||||
$dataVue = [
|
||||
'tasks'=> old($fieldName) ? json_decode(old($fieldName), true): $model->{$fieldName} ?? [],
|
||||
'companies' => $companies,
|
||||
'packages' => $packages,
|
||||
'taskRowModel' => $taskRowModel,
|
||||
'taskTypeEnums' => $taskTypeEnums,
|
||||
];
|
||||
@endphp
|
||||
<div
|
||||
id="invoice-tasks"
|
||||
data-json='@json($dataVue)'></div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="row">
|
||||
@include('crud-policies::crud.formfield')
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<button class="btn btn-info" type="submit" @if($model->signed_date) disabled @endif>{{ __('crud-policies::crud.update') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@include('crud-policies::crud.modules.scriptConstants')
|
||||
@endsection
|
||||
130
resources/views/invoices/index.blade.php
Normal file
130
resources/views/invoices/index.blade.php
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
@extends($viewLayout)
|
||||
|
||||
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s',
|
||||
strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))),
|
||||
__('crud-policies::crud.viewAny')))
|
||||
|
||||
@section('description', sprintf('%s %s', __('crud-policies::crud.viewAny'),
|
||||
trans(Str::plural(class_basename($modelClass)))))
|
||||
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
@include('crud-policies::crud.modules.flashMessage')
|
||||
<div class="card-header">
|
||||
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::viewAny()])
|
||||
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::viewAny()])
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col">
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
<div class="col">
|
||||
<form action="{{ url()->current() }}">
|
||||
<div class="input-group mb-3">
|
||||
<input type="search" name="search" class="form-control" value="{{ request()->search }}"
|
||||
placeholder="Recherche.." />
|
||||
<input type="text" name="sort_col" class="d-none" value="{{ request()->sort_col }}" />
|
||||
<input type="text" name="sort_way" class="d-none" value="{{ request()->sort_way }}" />
|
||||
<a class="btn btn-danger" href="{{ url()->current() }}">{{ __('crud-policies::crud.reset')
|
||||
}}</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ __('crud-policies::crud.search') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table text-center">
|
||||
<thead class="thead-dark">
|
||||
@include('crud-policies::crud.modules.sorter')
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(count($models))
|
||||
|
||||
{{-- INIT QUERIES for OPTIMIZING --}}
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if($prop['type']->equals(CrudType::order()))
|
||||
@php ${"$modelTable$fieldName"} = $modelClass::max($fieldName); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsTo()))
|
||||
@php try { $models->load(Str::singular((new $prop['belongsTo']())->getTable())); } catch(\Exception $e)
|
||||
{} @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::belongsToMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@if($prop['type']->equals(CrudType::hasMany()))
|
||||
@php $models->load("$fieldName"); @endphp
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- DISPLAY MODELS --}}
|
||||
@foreach($models as $model)
|
||||
<tr>
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if(in_array(CrudAction::viewAny(), $prop['actions']))
|
||||
@if($fieldName === 'object')
|
||||
<td>{{ Str::limit($model->{$fieldName}, 25) }}</td>
|
||||
@else
|
||||
@switch($prop['type'])
|
||||
@case(CrudType::password())
|
||||
{{-- Ignore --}}
|
||||
@break
|
||||
@default
|
||||
<td>@include('crud-policies::crud.printfield', [
|
||||
'action' => CrudAction::viewAny(),
|
||||
'field' => $model->{$fieldName},
|
||||
'props' => $prop,
|
||||
"$modelTable$fieldName" => ${"$modelTable$fieldName"} ?? null
|
||||
])</td>
|
||||
@endswitch
|
||||
@endif
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{{-- MODEL ACTIONS --}}
|
||||
<td>
|
||||
<div class="btn-group" role="group"
|
||||
aria-label="{{ Str::plural(__('crud-policies::crud.action')) }}">
|
||||
@can('view', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.show", $model) }}"
|
||||
class="btn btn-sm btn-primary">{{ __('crud-policies::crud.view') }}</a>
|
||||
@endcan
|
||||
@can('update', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.edit", $model) }}"
|
||||
class="btn btn-sm btn-info">{{ __('crud-policies::crud.edit') }}</a>
|
||||
@endcan
|
||||
@can('download', $model)
|
||||
<a href="{{ CrudController::getRoutePrefixed("$modelTable.download", $model) }}"
|
||||
class="btn btn-sm btn-secondary">{{ __('Télécharger') }}</a>
|
||||
@endcan
|
||||
@if(config('crud-policies.deleteOnIndex') and Auth::user()->can('delete', $model))
|
||||
<form class="CrudConfirmDelete" action="{{ CrudController::getRoutePrefixed("
|
||||
$modelTable.destroy", $model) }}" method="POST">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">{{ __('crud-policies::crud.delete')
|
||||
}}</button>
|
||||
</form>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="{{ count($modelClass::getEditableProperties()) }}">
|
||||
{{ __('crud-policies::crud.nothingtoshow') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
{{ $models->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@include('crud-policies::crud.modules.scriptConstants')
|
||||
@endsection
|
||||
87
resources/views/invoices/show.blade.php
Normal file
87
resources/views/invoices/show.blade.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
@extends($viewLayout)
|
||||
|
||||
@section('title', sprintf('%s - %s', transFb(sprintf('models.classes.%s', strtolower(Str::plural(class_basename($modelClass)))), Str::plural(class_basename($modelClass))), __('crud-policies::crud.view')))
|
||||
@section('description', sprintf('%s %s', __('crud-policies::crud.view'), trans(class_basename($modelClass))))
|
||||
@section('metaIndex', 'noindex,nofollow')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
@include('crud-policies::crud.modules.flashMessage')
|
||||
<div class="card-header">
|
||||
@include('crud-policies::crud.modules.breadcrumb', ['action' => CrudAction::view()])
|
||||
@include('crud-policies::crud.modules.actionbar.bar', ['action' => CrudAction::view()])
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm table-bordered border-primary align-top table-striped text-center">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col" class="w-25">{{ __('crud-policies::crud.property') }}</th>
|
||||
<th scope="col" class="w-75">{{ __('crud-policies::crud.value') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($modelClass::getEditableProperties() as $fieldName => $prop)
|
||||
@if(in_array(CrudAction::view(), $prop['actions']))
|
||||
@switch($prop['type'])
|
||||
@case(CrudType::password())
|
||||
{{-- Ignore --}}
|
||||
@break
|
||||
@default
|
||||
@if($fieldName === 'tasks')
|
||||
<tr>
|
||||
<td>{{ $prop['label'] }}</td>
|
||||
<td>
|
||||
@php $tasks = collect($model->{$fieldName}); @endphp
|
||||
@if($tasks->count())
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ __('Commentaire') }}</th>
|
||||
<th>{{ __('Type') }}</th>
|
||||
<th>{{ __('Jours') }}</th>
|
||||
<th>{{ __('Montant') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($tasks as $task)
|
||||
<tr>
|
||||
<td>{{ $task['desc'] }}</td>
|
||||
<td>
|
||||
{{ App\Enums\Invoice\TaskType::make($task['type'])->label }}
|
||||
@if($task['package'] ?? 0)
|
||||
<br>
|
||||
{{ optional($packages[$task['package']] ?? null)->name }}
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ number_format($task['days'], 2, ',', '') }}</td>
|
||||
<td>{{ number_format($task['amount'], 2, ',', '') }} €</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@else
|
||||
<span class="h5">{{ __('Pas de tâches') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@else
|
||||
<tr>
|
||||
<td>{{ $prop['label'] }}</td>
|
||||
<td>@include('crud-policies::crud.printfield', [
|
||||
'action' => CrudAction::view(),
|
||||
'field' => $model->{$fieldName},
|
||||
'props' => $prop
|
||||
])</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endswitch
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@include('crud-policies::crud.modules.scriptConstants')
|
||||
@endsection
|
||||
|
|
@ -37,6 +37,9 @@
|
|||
@can('viewAny', App\Models\Project::class)
|
||||
<a class="dropdown-item @if(strpos(request()->route()->getName(), 'projects') === 0)active @endif" href="{{ route('projects.index') }}">{{ __('models.classes.projects') }}</a>
|
||||
@endcan
|
||||
@can('viewAny', App\Models\Invoice::class)
|
||||
<a class="dropdown-item @if(strpos(request()->route()->getName(), 'invoices') === 0)active @endif" href="{{ route('invoices.index') }}">{{ __('models.classes.invoices') }}</a>
|
||||
@endcan
|
||||
@can('viewAny', App\Models\Company::class)
|
||||
<a class="dropdown-item @if(strpos(request()->route()->getName(), 'companies') === 0)active @endif" href="{{ route('companies.index') }}">{{ __('models.classes.companies') }}</a>
|
||||
@endcan
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ $userPr = Auth::user()->sites ?? collect([]);
|
|||
</td>
|
||||
@can('assignLabels', App\Models\Ticket::class)
|
||||
<td>
|
||||
<span class="ticketIndexLabels">
|
||||
<span class="ticket-index-labels">
|
||||
@foreach ($ticket->labels as $label)
|
||||
<span class="badge label colorize" data-color="{{ $label->color }}">{{ $label->name }}</span>
|
||||
@endforeach
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use App\Http\Controllers\Credit\CreditLogController;
|
|||
use App\Http\Controllers\Credit\EstimationController;
|
||||
use App\Http\Controllers\FileController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\InvoiceController;
|
||||
use App\Http\Controllers\LabelController;
|
||||
use App\Http\Controllers\LogController;
|
||||
use App\Http\Controllers\MarkerController;
|
||||
|
|
@ -74,6 +75,7 @@ Route::middleware('auth:web')->group(function () {
|
|||
Route::resource('users', UserController::class);
|
||||
Route::resource('packages', PackageController::class)->except('show');
|
||||
Route::resource('companies', CompanyController::class)->except('show');
|
||||
Route::resource('invoices', InvoiceController::class);
|
||||
Route::resource('labels', LabelController::class)->except('show');
|
||||
Route::resource('brands', BrandController::class);
|
||||
Route::resource('sites', SiteController::class)
|
||||
|
|
@ -120,6 +122,16 @@ Route::middleware('auth:web')->group(function () {
|
|||
Route::resource('sites.logs', LogController::class);
|
||||
Route::resource('sites.files', FileController::class);
|
||||
|
||||
// * INVOICES
|
||||
Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download'])
|
||||
->name('invoices.download');
|
||||
Route::post('/invoices/{invoice}/sign/service', [InvoiceController::class, 'setSignedDate'])
|
||||
->name('invoices.sign.setSignedDate');
|
||||
Route::post('/invoices/{invoice}/sign/client', [InvoiceController::class, 'setClientSentDate'])
|
||||
->name('invoices.sign.setClientSentDate');
|
||||
Route::post('/invoices/{invoice}/sign/compta', [InvoiceController::class, 'setComptaSentDate'])
|
||||
->name('invoices.sign.setComptaSentDate');
|
||||
|
||||
// * Ticket
|
||||
Route::prefix('/ticket')->name('ticket.')->group(function () {
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue