Sync 51 Transactions PDF + multiple fixes
This commit is contained in:
parent
59ce99524a
commit
e7e5ce5cfe
29 changed files with 1154 additions and 112 deletions
|
|
@ -57,6 +57,8 @@ URL : /bo
|
|||
|
||||
Les pages statiques sont disponible a tout moment: `$StaticPageGlobal`
|
||||
|
||||
# Fonts
|
||||
- Robot fonts are used in PDF generation documents.
|
||||
|
||||
# STRIPE
|
||||
php artisan cashier:webhook
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
namespace App\Auth;
|
||||
|
||||
use App\Models\Customer;
|
||||
use App\Models\Customer\Customer;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Auth\EloquentUserProvider;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
|
||||
use Illuminate\Contracts\Auth\UserProvider as UserProviderContract;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\UserProvider;
|
||||
|
||||
/**
|
||||
* Overload EloquentUserProvider to handle
|
||||
* Soft deleted Customers
|
||||
*/
|
||||
class ReactivateCustomerProvider extends EloquentUserProvider implements UserProviderContract
|
||||
class ReactivateCustomerProvider extends EloquentUserProvider implements UserProvider
|
||||
{
|
||||
/**
|
||||
* Retrieve a customer by the given credentials.
|
||||
|
|
@ -46,15 +46,15 @@ class ReactivateCustomerProvider extends EloquentUserProvider implements UserPro
|
|||
/**
|
||||
* Validate a customer against the given credentials.
|
||||
*
|
||||
* @param UserContract $customer
|
||||
* @param array $credentials
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $customer
|
||||
* @param array $credentials
|
||||
* @return boolean
|
||||
* @throws \ErrorException If model provided is not a Customer or connection to server fails.
|
||||
*/
|
||||
public function validateCredentials(UserContract $customer, array $credentials)
|
||||
public function validateCredentials(Authenticatable $customer, array $credentials)
|
||||
{
|
||||
/** @var \App\Models\Customer $customer */
|
||||
if (\get_class($customer) !== Customer::class) {
|
||||
/** @var \App\Models\Customer\Customer $customer */
|
||||
if ($customer instanceof Customer) {
|
||||
throw new \ErrorException('Cannot authenticate any thing else than ' . Customer::class);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,28 +23,6 @@ class Handler extends ExceptionHandler
|
|||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Report or log an exception.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*
|
||||
* @phpcs:ignore Squiz.Commenting.FunctionComment.EmptyThrows
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function report(\Throwable $e)
|
||||
{
|
||||
// phpcs:enable
|
||||
if (get_class($e) == RedirectTo::class) {
|
||||
abort(redirect($e->url));
|
||||
}
|
||||
|
||||
if ($this->shouldntReport($e)) {
|
||||
return;
|
||||
}
|
||||
parent::report($e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -89,11 +89,10 @@ class OrderController extends Controller
|
|||
*
|
||||
* @param \App\Models\Shop\Order $order
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function show(Order $order): View
|
||||
{
|
||||
// phpcs:enable
|
||||
$order->load('transactions');
|
||||
return view('back::pages.orders.show', compact('order'));
|
||||
}
|
||||
|
||||
|
|
@ -213,6 +212,7 @@ class OrderController extends Controller
|
|||
$this->authorize('prepare', $order);
|
||||
return DB::transaction(function () use ($request, $order) {
|
||||
// * Stockage de la préparation de la commande.
|
||||
// * FIXME: Same code here as in Models/Shop/Order::getPartialRefundedBasket.
|
||||
// ! Ici on veux réutiliser le cart calculator.
|
||||
$cart = ToolboxHelper::createVirtualCart(\collect($order->basket)
|
||||
->map(function (object $basketItem) {
|
||||
|
|
@ -227,11 +227,11 @@ class OrderController extends Controller
|
|||
|
||||
// * Update virtual basket with actual content
|
||||
\collect($order->basket)->each(function (object $basketItem) use ($request, $cart) {
|
||||
$newQty = \intval($request->validated()['items'][$basketItem->id]);
|
||||
/** @var \Gloudemans\Shoppingcart\CartItem $cartItem */
|
||||
$cartItem = $cart->search(function (CartItem $cartItem) use ($basketItem) {
|
||||
return $cartItem->id === $basketItem->id;
|
||||
})->first();
|
||||
$newQty = \intval($request->validated()['items'][$basketItem->id]);
|
||||
// * Update Qty or remove item from the cart.
|
||||
if (!$newQty) {
|
||||
$cart->remove($cartItem->rowId);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\Order\TransactionType;
|
||||
use App\Models\Payment\Transaction;
|
||||
use App\Models\Shop\DeliveryMode;
|
||||
use App\Models\Shop\Order;
|
||||
use App\Models\Shop\PaymentMode;
|
||||
use Gloudemans\Shoppingcart\Cart;
|
||||
use Gloudemans\Shoppingcart\CartItem;
|
||||
|
|
@ -14,6 +17,8 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
|
|
@ -30,7 +35,7 @@ class Controller extends BaseController
|
|||
{
|
||||
$this->middleware(function (Request $request, callable $next) {
|
||||
$reponse = $next($request);
|
||||
if (\request()->is('bo*') and !\request()->ajax()) {
|
||||
if (\request()->routeIs('bo.*') and !\request()->ajax()) {
|
||||
if (!DeliveryMode::getCached()->where('status', true)->count()) {
|
||||
Session::flash('bo.warning', trans('Aucun mode de livraison n\'est disponible'));
|
||||
}
|
||||
|
|
@ -175,4 +180,40 @@ class Controller extends BaseController
|
|||
});
|
||||
return $basket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and download transaction invoice
|
||||
*
|
||||
* ! Appliable for both Fo and Bo controllers
|
||||
*
|
||||
* @param \App\Models\Shop\Order $order
|
||||
* @param \App\Models\Payment\Transaction $transaction
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function downloadTransactionInvoice(Order $order, Transaction $transaction): Response
|
||||
{
|
||||
$this->authorize('download', $transaction);
|
||||
$title = trans('Commande du :date - :orderRef - :transRef', [
|
||||
'date' => $transaction->created_at->isoFormat('DD\/MM\/YYYY'),
|
||||
'orderRef' => $order->reference,
|
||||
'transRef' => $transaction->reference,
|
||||
]);
|
||||
$sluggedTitle = Str::slug(\implode(' ', [
|
||||
config('app.name'),
|
||||
$transaction->type->equals(TransactionType::debit()) ? trans('Facture') : trans('Avoir'),
|
||||
$transaction->created_at->isoFormat('YYYY-MM-DD'),
|
||||
]));
|
||||
$transaction->generateInvoice($title);
|
||||
return \response()->streamDownload(function () use ($transaction, $title) {
|
||||
echo $transaction->generateInvoice($title)->output();
|
||||
}, "{$sluggedTitle}.pdf", [
|
||||
'Cache-Control' => 'public, must-revalidate, max-age=0',
|
||||
'Content-Description' => 'File Transfer',
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
|
||||
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
|
||||
// For glory.
|
||||
'X-Generator' => 'mPDF'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ class CustomerController extends Controller
|
|||
$customer = auth('frontend')->user();
|
||||
|
||||
$orders = $customer->orders()->orderBy('created_at', 'DESC')->paginate();
|
||||
return view('theme::pages.customer.my-orders', compact('orders'));
|
||||
return view('theme::pages.customer.orders', compact('orders'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -228,7 +228,7 @@ class CustomerController extends Controller
|
|||
public function orderDetail(Order $order)
|
||||
{
|
||||
$order->load('timeslot.shop');
|
||||
return view('theme::pages.customer.order-detail', compact('order'));
|
||||
return view('theme::pages.customer.orders.show', compact('order'));
|
||||
}
|
||||
|
||||
// * --- END Backoffice ---
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class StoreProductRequest extends UpdateMiniCrudRequest
|
|||
public function validateResolved()
|
||||
{
|
||||
// * Minicrud Hack to allow storage, apply only on store action
|
||||
if (\get_class($this) === self::class) {
|
||||
if (\get_class($this) instanceof self) {
|
||||
if (!\array_key_exists(\strval($this->type), Product::fetchTypesInputs())) {
|
||||
throw ValidationException::withMessages([
|
||||
'type' => trans('type de produit invalide')
|
||||
|
|
@ -141,7 +141,7 @@ class StoreProductRequest extends UpdateMiniCrudRequest
|
|||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes()
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => trans('nom'),
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
}
|
||||
} catch (InvalidXmlException $e) {
|
||||
$message = $e->getMessage();
|
||||
if (get_class($e) === QueryException::class) {
|
||||
if ($e instanceof QueryException) {
|
||||
$message = implode(' ', $e->errorInfo);
|
||||
}
|
||||
$this->log(
|
||||
|
|
|
|||
64
app/Lib/Helpers/CollectionHelper.php
Normal file
64
app/Lib/Helpers/CollectionHelper.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
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\Payment;
|
||||
|
||||
use App\Enums\GeneralConfigType;
|
||||
use App\Enums\Order\PaymentModeEnum;
|
||||
use App\Enums\Order\PaymentStatus;
|
||||
use App\Enums\Order\TransactionStatus;
|
||||
|
|
@ -10,13 +11,16 @@ use App\Enums\Order\ValidationStatus;
|
|||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Lib\Payments\PaymentAdapter;
|
||||
use App\Models\Shop\Order;
|
||||
use App\Models\Site\GeneralConfig;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
use Mccarlosen\LaravelMpdf\LaravelMpdf;
|
||||
use Money\Currency;
|
||||
use Money\Money;
|
||||
use Mpdf\Config\FontVariables;
|
||||
|
||||
/**
|
||||
* Transaction
|
||||
|
|
@ -86,6 +90,16 @@ class Transaction extends Model
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRouteKeyName()
|
||||
{
|
||||
return 'reference';
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
|
|
@ -256,6 +270,120 @@ class Transaction extends Model
|
|||
$this->attributes['foreign_refund_id'] = $foreign_refund_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Invoice
|
||||
*
|
||||
* @param string $title
|
||||
* @return \Mccarlosen\LaravelMpdf\LaravelMpdf
|
||||
*/
|
||||
public function generateInvoice(string $title): LaravelMpdf
|
||||
{
|
||||
/** @var \App\Models\GeneralConfig */
|
||||
$config = GeneralConfig::getCached()->where('type', GeneralConfigType::global())->firstOrFail();
|
||||
|
||||
$additionalData = $config->data;
|
||||
|
||||
/** @var \App\Models\Shop\Order */
|
||||
$order = $this->order;
|
||||
|
||||
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdfWrapper */
|
||||
$mpdf = app('laravel-mpdf');
|
||||
|
||||
$defaultFontConfig = (new FontVariables())->getDefaults();
|
||||
$fontData = $defaultFontConfig['fontdata'];
|
||||
|
||||
$isCompleteRefunded = false;
|
||||
$isRefunded = false;
|
||||
if ($this->type->equals(TransactionType::credit())) {
|
||||
$zeroMoney = new Money(0, app('phpMoneyCurrency'));
|
||||
/** */
|
||||
$isCompleteRefunded = (new Money($order->total_ttc, app('phpMoneyCurrency')))
|
||||
->subtract($this->amount)->lessThanOrEqual($zeroMoney) or
|
||||
\optional($order->transactions()->orderBy('created_at')->where('type', TransactionType::credit())
|
||||
->latest()->pluck('id')->last())->id === $this->id;
|
||||
$isRefunded = $isCompleteRefunded ?: $this->type->equals(
|
||||
TransactionType::credit()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* * Because of Stripe reusing transactions, therefore use prefix.
|
||||
*/
|
||||
$transactionReferenceSuffix = $order->payment_status->equals(PaymentStatus::refunded()) ? '_1' : '';
|
||||
|
||||
// * Transaction is Completely Refund.
|
||||
$invoiceBasketItems = $isCompleteRefunded ? \collect($order->basket) : null;
|
||||
// * Transaction is Refund Partial.
|
||||
$invoiceBasketItems = $invoiceBasketItems ?? ($isRefunded ? $order->getPartialRefundedBasket() : null);
|
||||
// * Transaction is Completed.
|
||||
$invoiceBasketItems = $invoiceBasketItems ?? \collect($order->basket);
|
||||
|
||||
/**
|
||||
* 7.5pt 10px
|
||||
* 9pt 12px
|
||||
* 10.5pt 14px
|
||||
* 12pt 16px
|
||||
* 13.5pt 18px
|
||||
*/
|
||||
/** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */
|
||||
$pdf = $mpdf->loadView(
|
||||
'back::documents.order-invoice',
|
||||
$this->toArray(),
|
||||
[
|
||||
'title' => $title,
|
||||
'billing' => !empty($order->billing->firstname) ? $order->billing : $order->shipping,
|
||||
'shipping' => $order->shipping,
|
||||
'logo' => \public_path(resize($additionalData->logo->value ?? '', 'BoutiqueLogo')),
|
||||
'introLines' => \collect($additionalData)->map(function (object $input, string $inputName) {
|
||||
return Str::substr($inputName, 0, -1) === 'invoice_line_' ? $input->value : null;
|
||||
})->filter()->all(),
|
||||
'orderReference' => $order->reference,
|
||||
'transactionReference' => $this->reference . $transactionReferenceSuffix,
|
||||
'date' => $this->updated_at,
|
||||
'transactionType' => $this->type,
|
||||
'basket' => $invoiceBasketItems,
|
||||
'totalTtc' => $invoiceBasketItems->sum('subtotal'),
|
||||
'totalTaxes' => $invoiceBasketItems->sum('tax')
|
||||
],
|
||||
[
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
// * 10.5pt = 14px
|
||||
'default_font_size' => 10.5,
|
||||
'default_font' => 'roboto',
|
||||
'margin_left' => 5,
|
||||
'margin_right' => 5,
|
||||
'margin_top' => 5,
|
||||
'margin_bottom' => 5,
|
||||
'margin_header' => 0,
|
||||
'margin_footer' => 5,
|
||||
'orientation' => 'P',
|
||||
'title' => $title,
|
||||
'author' => \config('app.name'),
|
||||
'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,
|
||||
'fontdata' => $fontData + [
|
||||
'roboto' => [
|
||||
'R' => \resource_path('assets/fonts/bo/roboto/RobotoMono-Regular.ttf'),
|
||||
'B' => \resource_path('assets/fonts/bo/roboto/RobotoMono-Bold.ttf'),
|
||||
'I' => \resource_path('assets/fonts/bo/roboto/RobotoMono-Italic.ttf'),
|
||||
'BI' => \resource_path('assets/fonts/bo/roboto/RobotoMono-BoldItalic.ttf')
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
// * GETTER
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ use App\Models\Payment\Transaction;
|
|||
use App\Notifications\Order\OrderValidationStatusChanged;
|
||||
use App\Notifications\Order\OrderDeliveryStatusChanged;
|
||||
use Gloudemans\Shoppingcart\Cart;
|
||||
use Gloudemans\Shoppingcart\CartItem;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
|
@ -166,6 +168,16 @@ class Order extends Model
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRouteKeyName()
|
||||
{
|
||||
return 'reference';
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
|
|
@ -451,4 +463,52 @@ class Order extends Model
|
|||
$this->total_ht = \intval($this->total_ttc - $basket->tax());
|
||||
$this->total_weight = \intval($basket->weight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate partial refunded items basket
|
||||
*
|
||||
* ! (diff de basket - shipped basket)
|
||||
*
|
||||
* @return \Illuminate\Support\Collection<\Gloudemans\Shoppingcart\CartItem>
|
||||
*/
|
||||
public function getPartialRefundedBasket(): Collection
|
||||
{
|
||||
$shippedBastetItems = collect($this->shipped_basket);
|
||||
// * FIXME: Same code here as in Bo/OrderController::prepareSubmit.
|
||||
// ! Ici on veux réutiliser le cart calculator (diff de basket - shipped basket).
|
||||
$cart = ToolboxHelper::createVirtualCart(\collect($this->basket)
|
||||
->map(function (object $basketItem) use ($shippedBastetItems) {
|
||||
if (!$shippedBastetItems->contains('id', $basketItem->id)) {
|
||||
return null;
|
||||
}
|
||||
$cartItem = ToolboxHelper::basketItemObjectToCartItem($basketItem);
|
||||
$cartItem->associate(Product::query()->where('id', $cartItem->id)->first());
|
||||
return $cartItem;
|
||||
})->filter());
|
||||
|
||||
// * Update virtual basket with actual content
|
||||
collect($this->shipped_basket)->each(function (object $basketItem) use ($cart) {
|
||||
/** @var \Gloudemans\Shoppingcart\CartItem $cartItem */
|
||||
$cartItem = $cart->search(function (CartItem $cartItem) use ($basketItem) {
|
||||
return $cartItem->id === $basketItem->id;
|
||||
})->first();
|
||||
$newQty = max($cartItem->qty - $basketItem->qty, 0);
|
||||
// * Update Qty or remove item from the cart.
|
||||
if (!$newQty) {
|
||||
$cart->remove($cartItem->rowId);
|
||||
return;
|
||||
}
|
||||
$cartItem->setQuantity($newQty);
|
||||
// ! https://github.com/bumbummen99/LaravelShoppingcart/pull/181
|
||||
// * Note Lib is is actually refactoring for laravel 9 php8 only
|
||||
// * https://github.com/bumbummen99/LaravelShoppingcart/pull/145
|
||||
// ! These lines are required to prevent amount bug.
|
||||
$cartItem->setTaxRate($basketItem->options->tax_rate / 100);
|
||||
$cartItem->setDiscountRate($basketItem->options->discount_rate);
|
||||
});
|
||||
|
||||
$virtualOrder = new self();
|
||||
$virtualOrder->assignBasket($cart);
|
||||
return collect($virtualOrder->basket);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@ use Illuminate\Database\Eloquent\Model;
|
|||
use Illuminate\Support\Facades\Cache;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
/**
|
||||
* GeneralConfig
|
||||
*
|
||||
* @property \App\Enums\GeneralConfigType $type The general type.
|
||||
* @property object $data The general config data array.
|
||||
*
|
||||
* @package App\Models
|
||||
*
|
||||
*/
|
||||
class GeneralConfig extends Model
|
||||
{
|
||||
use CastsEnums;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class OrderCreated extends Notification
|
|||
\trans(
|
||||
'Vous pouvez également consulter l\'avancement de votre commande dans votre espace sur la boutique.'
|
||||
),
|
||||
\route('account.order-detail', ['reference' => $this->order->reference])
|
||||
\route('account.orders.show', ['reference' => $this->order->reference])
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
45
app/Policies/TransactionPolicy.php
Normal file
45
app/Policies/TransactionPolicy.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Order\TransactionStatus;
|
||||
use App\Models\Customer\Customer;
|
||||
use App\Models\Payment\Transaction;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class TransactionPolicy extends Policy
|
||||
{
|
||||
/**
|
||||
* Can a user order things ?
|
||||
*
|
||||
* ! Only owner customer or a Bo user
|
||||
* ! + a validated transaction.
|
||||
*
|
||||
* @param \App\Models\Customer\Customer|\App\Models\User|null $user
|
||||
* @param \App\Models\Payment\Transaction|null $transaction
|
||||
*
|
||||
* @return \Illuminate\Auth\Access\Response
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
*/
|
||||
public function download(
|
||||
$user = null,
|
||||
Transaction $transaction = null
|
||||
): Response {
|
||||
if (!$transaction or !$transaction->status->equals(TransactionStatus::validated())) {
|
||||
return Response::deny();
|
||||
}
|
||||
if ($user and $user instanceof User) {
|
||||
return Response::allow();
|
||||
}
|
||||
if (
|
||||
$user and $user instanceof Customer and
|
||||
$transaction->order->customer and
|
||||
!\is_null($user->hashcode) and
|
||||
$transaction->order->customer->hashcode === $user->hashcode
|
||||
) {
|
||||
return Response::allow();
|
||||
}
|
||||
return Response::deny();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,10 +15,13 @@ use App\Models\Merchandise\Product;
|
|||
use App\Models\Merchandise\Range;
|
||||
use App\Models\Site\StaticPage;
|
||||
use App\Models\Merchandise\Tag;
|
||||
use App\Models\Payment\Transaction;
|
||||
use BadMethodCallException;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Route as RoutingRoute;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Kwaadpepper\Enum\Exceptions\EnumNotRoutableException;
|
||||
|
|
@ -81,6 +84,20 @@ class RouteServiceProvider extends ServiceProvider
|
|||
Route::bind('reference', function ($value) {
|
||||
return Order::query()->where('reference', $value)->firstOrFail();
|
||||
});
|
||||
Route::bind('transactionReference', function ($value, RoutingRoute $previousRoute) {
|
||||
$previousOrder = $previousRoute->parameter('reference');
|
||||
if (
|
||||
!$previousRoute->hasParameter('reference') or
|
||||
!\is_object($previousOrder) or
|
||||
!($previousOrder instanceof Order)
|
||||
) {
|
||||
\abort(404);
|
||||
}
|
||||
return Transaction::query()->where('reference', $value)
|
||||
->whereHas('order', function (Builder $q) use ($previousOrder) {
|
||||
return $q->where('id', $previousOrder->id);
|
||||
})->firstOrFail();
|
||||
});
|
||||
|
||||
Route::bind('static_page_front', function ($value) {
|
||||
return StaticPage::where('slug', $value)->firstOrFail();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"ext-intl": "*",
|
||||
"axlon/laravel-postal-code-validation": "^3.3",
|
||||
"bumbummen99/shoppingcart": "^4.0",
|
||||
"carlos-meneses/laravel-mpdf": "^2.1",
|
||||
"diglactic/laravel-breadcrumbs": "^6.1",
|
||||
"fideloper/proxy": "^4.4",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
|
|
|
|||
378
composer.lock
generated
378
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": "cd11a30c4de1fb87def9102b9a4e214f",
|
||||
"content-hash": "2b6013b98e0676dfa66fe4b2ea394475",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm89/stack-cors",
|
||||
|
|
@ -260,6 +260,62 @@
|
|||
},
|
||||
"time": "2022-05-15T15:32:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "carlos-meneses/laravel-mpdf",
|
||||
"version": "2.1.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mccarlosen/laravel-mpdf.git",
|
||||
"reference": "cb52e5c17df9caa340eaed8566cfb1e3c7b37c33"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mccarlosen/laravel-mpdf/zipball/cb52e5c17df9caa340eaed8566cfb1e3c7b37c33",
|
||||
"reference": "cb52e5c17df9caa340eaed8566cfb1e3c7b37c33",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"mpdf/mpdf": "^8.0",
|
||||
"php": "^5.6|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Mccarlosen\\LaravelMpdf\\LaravelMpdfServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"PDF": "Mccarlosen\\LaravelMpdf\\Facades\\LaravelMpdf"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mccarlosen\\LaravelMpdf\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Laravel Mpdf: Using Mpdf in Laravel to 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.11"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.me/mccarlosen",
|
||||
"type": "custom"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-28T02:20:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "clue/stream-filter",
|
||||
"version": "v1.6.0",
|
||||
|
|
@ -3283,6 +3339,142 @@
|
|||
],
|
||||
"time": "2023-02-06T13:44:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mpdf/mpdf",
|
||||
"version": "v8.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mpdf/mpdf.git",
|
||||
"reference": "add590e93b7502efafd9839a68cff99f3497b318"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mpdf/mpdf/zipball/add590e93b7502efafd9839a68cff99f3497b318",
|
||||
"reference": "add590e93b7502efafd9839a68cff99f3497b318",
|
||||
"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 || ~8.2.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.5",
|
||||
"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-12-15T11:24:39+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.66.0",
|
||||
|
|
@ -3629,6 +3821,56 @@
|
|||
},
|
||||
"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": "phenx/php-font-lib",
|
||||
"version": "0.5.4",
|
||||
|
|
@ -5403,6 +5645,78 @@
|
|||
],
|
||||
"time": "2023-02-01T10:56:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "setasign/fpdi",
|
||||
"version": "v2.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Setasign/FPDI.git",
|
||||
"reference": "bccc892d5fa1f48c43f8ba7db5ed4ba6f30c8c05"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/bccc892d5fa1f48c43f8ba7db5ed4ba6f30c8c05",
|
||||
"reference": "bccc892d5fa1f48c43f8ba7db5ed4ba6f30c8c05",
|
||||
"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.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/setasign/fpdi",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-02-09T10:38:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/browsershot",
|
||||
"version": "3.40.2",
|
||||
|
|
@ -9938,65 +10252,6 @@
|
|||
},
|
||||
"time": "2022-09-07T15:32:08+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",
|
||||
|
|
@ -11852,7 +12107,8 @@
|
|||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^7.3|^8.0"
|
||||
"php": "^7.3|^8.0",
|
||||
"ext-intl": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@ return [
|
|||
'keepRatio' => true,
|
||||
'trim' => true
|
||||
],
|
||||
'foBoutiqueLogo' => [
|
||||
'height' => 100,
|
||||
'width' => 100,
|
||||
'format' => 'png',
|
||||
'inCanvas' => true,
|
||||
'resize' => true,
|
||||
'keepRatio' => true,
|
||||
'trim' => true
|
||||
],
|
||||
'foPaymentButtonLogo' => [
|
||||
'height' => 25,
|
||||
'width' => 25,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\GeneralConfigType;
|
||||
use App\Enums\InputType;
|
||||
use App\Models\Site\GeneralConfig;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddInvoiceHeaderToGeneralConfigs extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Error If general config is missing.
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
/** @var App\Models\Site\GeneralConfig|null */
|
||||
$generalConfig = GeneralConfig::query()->where('type', GeneralConfigType::global())->first();
|
||||
if (!$generalConfig) {
|
||||
throw new \Error('missing \'global\' general config');
|
||||
}
|
||||
$data = $generalConfig->data;
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
if (empty($data->{"invoice_line_{$i}"})) {
|
||||
$data->{"invoice_line_{$i}"} = [
|
||||
'attribute' => "Facture ligne {$i}",
|
||||
'label' => "Facture ligne {$i}",
|
||||
'inputType' => InputType::string(),
|
||||
'value' => '',
|
||||
'required' => false,
|
||||
'validate' => 'nullable|string|min:0|max:70',
|
||||
'helper' => "Le texte qui sera affiché sur la facture en entête l.{$i}",
|
||||
];
|
||||
}
|
||||
}
|
||||
$generalConfig->data = $data;
|
||||
$generalConfig->saveOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
/** @var App\Models\Site\GeneralConfig|null */
|
||||
$generalConfig = GeneralConfig::query()->where('type', GeneralConfigType::global())->first();
|
||||
if (!$generalConfig) {
|
||||
return;
|
||||
}
|
||||
$data = $generalConfig->data;
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
if (!empty($data->{"invoice_line_{$i}"})) {
|
||||
unset($data->{"invoice_line_{$i}"});
|
||||
}
|
||||
}
|
||||
$generalConfig->data = $data;
|
||||
$generalConfig->saveOrFail();
|
||||
}
|
||||
}
|
||||
BIN
resources/assets/fonts/bo/roboto/RobotoMono-Bold.ttf
Normal file
BIN
resources/assets/fonts/bo/roboto/RobotoMono-Bold.ttf
Normal file
Binary file not shown.
BIN
resources/assets/fonts/bo/roboto/RobotoMono-BoldItalic.ttf
Normal file
BIN
resources/assets/fonts/bo/roboto/RobotoMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
resources/assets/fonts/bo/roboto/RobotoMono-Italic.ttf
Normal file
BIN
resources/assets/fonts/bo/roboto/RobotoMono-Italic.ttf
Normal file
Binary file not shown.
BIN
resources/assets/fonts/bo/roboto/RobotoMono-Regular.ttf
Normal file
BIN
resources/assets/fonts/bo/roboto/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
Subproject commit ab2545ed02b9a1f1eea6809c876e752856eadf34
|
||||
Subproject commit 33947ec0111809cefa8f29440708b77a0d01f550
|
||||
358
resources/views/documents/order-invoice.blade.php
Normal file
358
resources/views/documents/order-invoice.blade.php
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
<!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>{{ $title }}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
|
||||
<style type="text/css">
|
||||
html,
|
||||
body {
|
||||
/** page margins : 5mm */
|
||||
position: relative;
|
||||
width: 200mm;
|
||||
height: 287mm;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#main-table {
|
||||
margin: auto;
|
||||
height: 274mm;
|
||||
width: 100%;
|
||||
/** Test Mise en page */
|
||||
/* border-color: red;
|
||||
border-width: 1px;
|
||||
border-style: solid; */
|
||||
}
|
||||
|
||||
/* 1pt=0,352778mm, 1pt=1.3333333333333333px font-size=10.5pt=13,9999999965px=3,704169mm*/
|
||||
|
||||
/* LOGO + INTRO LINES */
|
||||
#top-row {
|
||||
height: 132.28px; /* 35mm */
|
||||
}
|
||||
|
||||
#addresses-row {
|
||||
height: 170.078px; /* 45mm */
|
||||
}
|
||||
|
||||
#billing-box {
|
||||
float: right;
|
||||
width: 321.259px; /* 85mm */
|
||||
margin-right: 56.69px; /* 15mm */
|
||||
padding-top: 3rem;
|
||||
/** Test Mise en page */
|
||||
/* background-color: rgb(253, 184, 255); */
|
||||
}
|
||||
|
||||
#shipping-box {
|
||||
height: 6rem; /* 7 lines */
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
text-align: center;
|
||||
/** Test Mise en page */
|
||||
/* background-color: rgb(215, 255, 135); */
|
||||
}
|
||||
|
||||
#logo-container {
|
||||
/** Test Mise en page */
|
||||
/* border: 1px solid blue; */
|
||||
}
|
||||
#intro-lines-container {
|
||||
/** Test Mise en page */
|
||||
/* border: 1px solid purple; */
|
||||
}
|
||||
.logo-container-pages {
|
||||
/** Test Mise en page */
|
||||
/* border: 1px solid blue; */
|
||||
}
|
||||
.intro-lines-container-pages {
|
||||
/** Test Mise en page */
|
||||
/* border: 1px solid purple; */
|
||||
}
|
||||
#intro-lines {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.font-typed {
|
||||
font-family: serif;
|
||||
}
|
||||
.font-upper {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.font-spaced {
|
||||
letter-spacing: 6px;
|
||||
}
|
||||
.font-amount {
|
||||
font-family: serif;
|
||||
text-transform: uppercase;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
margin: auto;
|
||||
/** Test Mise en page */
|
||||
/* border-color: red;
|
||||
border-width: 1px;
|
||||
border-style: solid; */
|
||||
}
|
||||
.page-footer td {
|
||||
padding-top: 2mm;
|
||||
border-top:double medium #CCCCCC;
|
||||
}
|
||||
|
||||
td.bold-cell,
|
||||
th.bold-cell {
|
||||
padding: 1mm;
|
||||
border: 1px solid #333333;
|
||||
background-color: #dadada;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
td.bordered-cell,
|
||||
th.bordered-cell {
|
||||
border: 1px solid #333333;
|
||||
}
|
||||
|
||||
table.bordered,
|
||||
table.bordered thead,
|
||||
table.bordered body,
|
||||
table.bordered td,
|
||||
table.bordered th {
|
||||
border: 1px solid;
|
||||
border-color: #333333;
|
||||
}
|
||||
table.bordered td,
|
||||
table.bordered th {
|
||||
padding-left: 1mm;
|
||||
padding-right: 1mm;
|
||||
}
|
||||
table.bordered td {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
border-color: #333333;
|
||||
}
|
||||
|
||||
@page {
|
||||
footer: page-footer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@php
|
||||
$customerIsRefunded = !$transactionType->equals(\App\Enums\Order\TransactionType::debit());
|
||||
$amountPrefix = $customerIsRefunded ? '- ' : '';
|
||||
@endphp
|
||||
|
||||
<body>
|
||||
<div id="main-table" cellpadding="0" cellspacing="0">
|
||||
|
||||
{{-- LOGO and INTROLINES FROM GENERAL CONFIGS --}}
|
||||
<div id="top-row">
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
{{-- LOGO --}}
|
||||
<td id="logo-container" valign="middle" width="25%" style="text-align: center;height: 30mm;">
|
||||
<img style="width: 26mm;" src="{{ $logo }}" alt="{{ config('app.name') }}"/>
|
||||
</td>
|
||||
{{-- INTROLINES --}}
|
||||
<td id="intro-lines-container" width="75%" style="text-align: center;">
|
||||
<h1>{{ config('app.name') }}</h1>
|
||||
<p id="intro-lines">
|
||||
@foreach ($introLines as $introLine)
|
||||
<span>{{ $introLine }}</span>
|
||||
@if(!$loop->last)<br />@endif
|
||||
@endforeach
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- SHIPPING AND BILLING --}}
|
||||
<div id="billing-box" class="font-typed font-upper">
|
||||
<strong>{{ Str::limit(Str::transliterate($billing->lastname) . ' '. Str::transliterate($billing->firstname), 37, '.') }}</strong><br/>
|
||||
{{ Str::limit(Str::transliterate($billing->line1), 37, '.') }}<br/>
|
||||
@if(!empty($billing->line2)){{ Str::limit(Str::transliterate($billing->line2), 37, '.') }}<br/>@endif
|
||||
{!! Str::limit(Str::transliterate(clean($billing->postal_code)) . ' '. Str::transliterate(clean($billing->city)), 37, '.') !!}
|
||||
</div>
|
||||
|
||||
<div id="addresses-row">
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
{{-- TYPE --}}
|
||||
<td width="80%" class="bold-cell font-spaced">
|
||||
<h2>@if(!$customerIsRefunded){{
|
||||
Str::upper(__('Facture'))
|
||||
}}@else{{
|
||||
Str::upper(__('Avoir'))
|
||||
}}@endif</h2>
|
||||
</td>
|
||||
{{-- BILLING --}}
|
||||
<td rowspan="4" valign="middle"> </td>
|
||||
</tr>
|
||||
<tr><td> </td></tr>
|
||||
<tr><td class="bold-cell"><h3>{{ Str::upper(__('Adresse de livraison')) }}</h3></td></tr>
|
||||
{{-- SHIPPING --}}
|
||||
<tr><td id="shipping-box" class="bordered-cell font-typed font-upper">
|
||||
<strong>{{ Str::transliterate($shipping->lastname) }} {{ Str::transliterate($shipping->firstname) }}</strong><br/>
|
||||
{{ Str::transliterate($shipping->line1) }}<br/>
|
||||
@if(!empty($shipping->line2)){{ Str::transliterate($shipping->line2) }}<br/>@endif
|
||||
{{ Str::transliterate($shipping->postal_code) }} {{ Str::transliterate($shipping->city) }}
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- DATE AND REFERENCES --}}
|
||||
<table class="bordered" width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="bold-cell">{{ __('Date') }}</td>
|
||||
<td class="font-typed font-upper" style="text-align: center;">{{
|
||||
Carbon\Carbon::parse($created_at)->isoFormat('DD\/MM\/YYYY')
|
||||
}}</td>
|
||||
<td class="bold-cell">{{ __('N° Commande') }}</td>
|
||||
<td class="font-typed font-upper" style="text-align: center;">{{ $orderReference }}</td>
|
||||
<td class="bold-cell">{{ __('N° Facture') }}</td>
|
||||
<td class="font-typed font-upper" style="text-align: center;">{{ $transactionReference }}</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@php
|
||||
$basket = collect($basket);
|
||||
$hasTaxes = !empty($totalTaxes) ? true : false;
|
||||
$columns = 4 + ($hasTaxes ? 1 : 0);
|
||||
$page = 1;
|
||||
$perPage = 15;
|
||||
$perPageAfterFirstPage = 22;
|
||||
$lastPage = \App\Lib\Helpers\CollectionHelper::paginateManually(collect($basket), $perPage)->lastPage();
|
||||
@endphp
|
||||
@while($lastPage >= $page)
|
||||
@php $basketPagedList = \App\Lib\Helpers\CollectionHelper::paginateManually(collect($basket), $perPage, $page); @endphp
|
||||
@if($page !== 1)
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
{{-- LOGO --}}
|
||||
<td class="logo-container-pages" valign="middle" width="25%" style="text-align: center;height: 15mm;">
|
||||
<img style="width: 16mm;" src="{{ $logo }}" alt="{{ config('app.name') }}"/>
|
||||
</td>
|
||||
{{-- APPNAME --}}
|
||||
<td class="intro-lines-container-pages" width="75%" style="text-align: center;">
|
||||
<h2>{{ config('app.name') }}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@endif
|
||||
<table class="bordered" width="100%" cellspacing="0" cellpadding="2" @if($page !== 1) style="margin-top: 5mm;" @endif>
|
||||
{{-- TABLE HEADER --}}
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%" class="bold-cell font-upper"><strong>{{ __('Désignation') }}</strong></th>
|
||||
<th class="bold-cell font-upper"><strong>{{ __('Qte') }}</strong></th>
|
||||
<th class="bold-cell font-upper"><strong>{{ __('Prix U. TTC') }}</strong></th>
|
||||
@if($hasTaxes)
|
||||
<th class="bold-cell font-upper"><strong>{{ __('Tva') }}</strong></th>
|
||||
@endif
|
||||
<th class="bold-cell font-upper"><strong>{{ __('S.Tot TTC') }}</strong></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($basketPagedList as $basketItem)
|
||||
<tr>
|
||||
<td valign="top" class="font-typed">
|
||||
<strong>{{ Str::limit($basketItem->name, 45, '.') }}</strong><br />
|
||||
<i>{{ $basketItem->options->reference }}</i>
|
||||
</td>
|
||||
<td valign="top" class="font-amount">{{ $amountPrefix }}{{ $basketItem->qty }}</td>
|
||||
<td valign="top" class="font-amount">{{ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($basketItem->options->price_ttc, '') }}</td>
|
||||
@if($hasTaxes)
|
||||
<td valign="top" class="font-amount">{{ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($basketItem->tax, '') }}</td>
|
||||
@endif
|
||||
<td valign="top" class="font-amount">{{ $amountPrefix }}{{ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($basketItem->subtotal, '') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@for ($i = $perPage - $basketPagedList->count(); $i > 0; $i--)
|
||||
<tr>
|
||||
@for ($k = $columns; $k > 0; $k--)
|
||||
<td valign="top"> <br> </td>
|
||||
@endfor
|
||||
</tr>
|
||||
@endfor
|
||||
</tbody>
|
||||
</table>
|
||||
@php $page++; $perPage = $perPageAfterFirstPage; $lastPage = \App\Lib\Helpers\CollectionHelper::paginateManually(collect($basket), $perPage)->lastPage(); @endphp
|
||||
@if($basketPagedList->currentPage() < $basketPagedList->lastPage())
|
||||
<pagebreak>
|
||||
@endif
|
||||
@endwhile
|
||||
|
||||
<table width="100%" cellspacing="0" cellpadding="2">
|
||||
<tr>
|
||||
<td style="width:50%;"><strong> </strong></td>
|
||||
<td>
|
||||
<table width="100%" cellspacing="0" cellpadding="2">
|
||||
@if($hasTaxes)
|
||||
<tr>
|
||||
<td align="right"><u>{{ __('Tva') }}</u></td>
|
||||
<td align="right" class="font-amount">{{
|
||||
\App\Lib\Helpers\ToolboxHelper::fmtCurrency($totalTaxes)
|
||||
}}</td>
|
||||
</tr>
|
||||
@endif
|
||||
<tr>
|
||||
<td align="right"><strong>{{ __('Total TTC') }}</strong></td>
|
||||
<td align="right" class="font-amount"><strong>{{ $amountPrefix }}{{
|
||||
\App\Lib\Helpers\ToolboxHelper::fmtCurrency($totalTtc)
|
||||
}}</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@if(!$hasTaxes)
|
||||
<table width="100%" style="height: 10mm;">
|
||||
<tr>
|
||||
<td style="text-align:justify;">
|
||||
{{ __('TVA non applicable, article 293B du code général des impôts') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
||||
<htmlpagefooter name="page-footer">
|
||||
<table width="100%" cellspacing="0" cellpadding="2" class="page-footer">
|
||||
<tr>
|
||||
<td width="16%" style="text-align: right;" valign="top">
|
||||
{{ __('N° Commande') }} :<br/>
|
||||
{{ __('N° Facture') }} :
|
||||
</td>
|
||||
<td width="22%">
|
||||
<span>{{ $orderReference }}</span><br/>
|
||||
<span>{{ $transactionReference }}</span>
|
||||
</td>
|
||||
<td width="45%" style="text-align: right;"> </td>
|
||||
<td width="7%" style="text-align: right;">
|
||||
<span>{{ __('date') }} :</span>
|
||||
<span>{{ __('page') }} :</span>
|
||||
</td>
|
||||
<td valign="bottom" width="10%" style="text-align: left;">
|
||||
<span>{{ Carbon\Carbon::parse($created_at)->isoFormat('DD\/MM\/YY') }}</span>
|
||||
<span>{PAGENO} / {nbpg}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</htmlpagefooter>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -3,8 +3,6 @@
|
|||
<div class="col">
|
||||
<div class="h4">{{ __('Transactions') }}</div>
|
||||
@foreach ($order->transactions as $transaction)
|
||||
{{-- FIXME: Invoice download here --}}
|
||||
{{-- <a class="btn btn-sm btn-info" href="{{ route('bo.orders.invoice', $order) }}" target="_blank" title="{{ __('Télécharger la facture') }}">{{ __('Facture') }}</a> --}}
|
||||
<ol class="list-group list-group-numbered">
|
||||
<li class="list-group-item d-flex flex-column flex-md-row justify-content-between align-items-start">
|
||||
<div class="ms-2 me-auto mb-3">
|
||||
|
|
@ -40,6 +38,11 @@
|
|||
<span class="h5 font-imprima">{{ __('Montant :amount', [
|
||||
'amount' => app('phpMoneyFormatter')->format($transaction->amount)
|
||||
]) }}</span>
|
||||
@can('download', $transaction)
|
||||
<a href="{{ route(request()->routeIs('bo.*') ? 'bo.orders.invoice' : 'account.orders.invoice', ['reference' => $order, 'transactionReference' => $transaction]) }}" class="btn btn-primary" target="_blank" role="button">{{
|
||||
__('Télécharger la facture')
|
||||
}}</a>
|
||||
@endcan
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
|
|
|||
|
|
@ -83,13 +83,13 @@ Breadcrumbs::for('account.dashboard', function ($trail) {
|
|||
$trail->parent('home');
|
||||
$trail->push(__('Mon compte'), route('account.dashboard'));
|
||||
});
|
||||
Breadcrumbs::for('account.my-orders', function ($trail) {
|
||||
Breadcrumbs::for('account.orders', function ($trail) {
|
||||
$trail->parent('account.dashboard');
|
||||
$trail->push(__('Mes commandes'), route('account.my-orders'));
|
||||
$trail->push(__('Mes commandes'), route('account.orders'));
|
||||
});
|
||||
Breadcrumbs::for('account.order-detail', function ($trail, $reference) {
|
||||
$trail->parent('account.my-orders');
|
||||
$trail->push(__('Détail d\'une commande'), route('account.order-detail', ['reference', $reference]));
|
||||
Breadcrumbs::for('account.orders.show', function ($trail, $reference) {
|
||||
$trail->parent('account.orders');
|
||||
$trail->push(__('Détail d\'une commande'), route('account.orders.show', ['reference', $reference]));
|
||||
});
|
||||
Breadcrumbs::for('account.email', function ($trail) {
|
||||
$trail->parent('account.dashboard');
|
||||
|
|
|
|||
|
|
@ -154,5 +154,9 @@ Route::prefix('bo')->name('bo.')->group(function () {
|
|||
->name('orders.refund-submit');
|
||||
Route::post('/orders/{order}/refund-submit', [OrderController::class, 'cancelRefundSubmit'])
|
||||
->name('orders.cancel-refund-submit');
|
||||
Route::get(
|
||||
'/commandes/{reference}/transaction/{transactionReference}/invoice',
|
||||
[OrderController::class, 'downloadTransactionInvoice']
|
||||
)->name('orders.invoice');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -35,8 +35,12 @@ Route::middleware(['auth:frontend', 'verified'])->group(function () {
|
|||
Route::post('/profil', [CustomerController::class, 'updateInfos'])->name('update-infos');
|
||||
|
||||
// * Orders
|
||||
Route::get('/commandes', [CustomerController::class, 'myOrders'])->name('my-orders');
|
||||
Route::get('/commandes/{reference}', [CustomerController::class, 'orderDetail'])->name('order-detail');
|
||||
Route::get('/commandes', [CustomerController::class, 'myOrders'])->name('orders');
|
||||
Route::get('/commandes/{reference}', [CustomerController::class, 'orderDetail'])->name('orders.show');
|
||||
Route::get(
|
||||
'/commandes/{reference}/transaction/{transactionReference}/invoice',
|
||||
[CustomerController::class, 'downloadTransactionInvoice']
|
||||
)->middleware('throttle:2,1')->name('orders.invoice');
|
||||
|
||||
// * Security
|
||||
Route::get('/securite/identification/email', [CustomerController::class, 'email'])->name('email');
|
||||
|
|
@ -87,6 +91,7 @@ Route::get('/produits', [ProductController::class, 'products'])->name('products.
|
|||
Route::get('/produit/{productSlug}', [ProductController::class, 'product'])
|
||||
->middleware('redirect-slugs')->name('products.show');
|
||||
Route::get('/produits/card/{productSlug}', [ProductController::class, 'ajaxProductsCard'])->name('products.card');
|
||||
|
||||
// * - Tags
|
||||
Route::get('/produits/special', [TagController::class, 'tags'])->name('tags.index');
|
||||
Route::get('/produits/{tagSlug}', [TagController::class, 'tag'])->name('tags.show');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue