diff --git a/README.md b/README.md
index 4f62070..6b5d5f4 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/app/Auth/ReactivateCustomerProvider.php b/app/Auth/ReactivateCustomerProvider.php
index 285f212..68c25bd 100644
--- a/app/Auth/ReactivateCustomerProvider.php
+++ b/app/Auth/ReactivateCustomerProvider.php
@@ -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);
}
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index 16a2882..d714e25 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -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.
*
diff --git a/app/Http/Controllers/Bo/OrderController.php b/app/Http/Controllers/Bo/OrderController.php
index b8e9da5..6f30919 100644
--- a/app/Http/Controllers/Bo/OrderController.php
+++ b/app/Http/Controllers/Bo/OrderController.php
@@ -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);
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index f8f1929..39f7e7e 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -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'
+ ]);
+ }
}
diff --git a/app/Http/Controllers/Fo/CustomerController.php b/app/Http/Controllers/Fo/CustomerController.php
index bb19882..39ab7c6 100644
--- a/app/Http/Controllers/Fo/CustomerController.php
+++ b/app/Http/Controllers/Fo/CustomerController.php
@@ -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 ---
diff --git a/app/Http/Requests/Bo/Products/StoreProductRequest.php b/app/Http/Requests/Bo/Products/StoreProductRequest.php
index 2142626..16eb78b 100644
--- a/app/Http/Requests/Bo/Products/StoreProductRequest.php
+++ b/app/Http/Requests/Bo/Products/StoreProductRequest.php
@@ -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'),
diff --git a/app/Jobs/XmlJobs.php b/app/Jobs/XmlJobs.php
index 33f155b..41298fe 100644
--- a/app/Jobs/XmlJobs.php
+++ b/app/Jobs/XmlJobs.php
@@ -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(
diff --git a/app/Lib/Helpers/CollectionHelper.php b/app/Lib/Helpers/CollectionHelper.php
new file mode 100644
index 0000000..b9e9ecd
--- /dev/null
+++ b/app/Lib/Helpers/CollectionHelper.php
@@ -0,0 +1,64 @@
+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
+ ]);
+ }
+}
diff --git a/app/Models/Payment/Transaction.php b/app/Models/Payment/Transaction.php
index 0017fb4..88c14eb 100644
--- a/app/Models/Payment/Transaction.php
+++ b/app/Models/Payment/Transaction.php
@@ -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
/**
diff --git a/app/Models/Shop/Order.php b/app/Models/Shop/Order.php
index e0ab08b..2be0ed6 100644
--- a/app/Models/Shop/Order.php
+++ b/app/Models/Shop/Order.php
@@ -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);
+ }
}
diff --git a/app/Models/Site/GeneralConfig.php b/app/Models/Site/GeneralConfig.php
index f4f4dec..a35c202 100644
--- a/app/Models/Site/GeneralConfig.php
+++ b/app/Models/Site/GeneralConfig.php
@@ -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;
diff --git a/app/Notifications/Order/OrderCreated.php b/app/Notifications/Order/OrderCreated.php
index 91e6ac0..8c6a533 100644
--- a/app/Notifications/Order/OrderCreated.php
+++ b/app/Notifications/Order/OrderCreated.php
@@ -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])
);
}
diff --git a/app/Policies/TransactionPolicy.php b/app/Policies/TransactionPolicy.php
new file mode 100644
index 0000000..59cd8b1
--- /dev/null
+++ b/app/Policies/TransactionPolicy.php
@@ -0,0 +1,45 @@
+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();
+ }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
index 4787537..6c04933 100644
--- a/app/Providers/RouteServiceProvider.php
+++ b/app/Providers/RouteServiceProvider.php
@@ -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();
diff --git a/composer.json b/composer.json
index f329e18..31e882a 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
diff --git a/composer.lock b/composer.lock
index 4d31018..1c9eb31 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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"
diff --git a/config/image-resizer.php b/config/image-resizer.php
index 15f5eb2..7448f8f 100644
--- a/config/image-resizer.php
+++ b/config/image-resizer.php
@@ -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,
diff --git a/database/migrations/2023_03_20_152616_add_invoice_header_to_general_configs.php b/database/migrations/2023_03_20_152616_add_invoice_header_to_general_configs.php
new file mode 100644
index 0000000..15d15c5
--- /dev/null
+++ b/database/migrations/2023_03_20_152616_add_invoice_header_to_general_configs.php
@@ -0,0 +1,62 @@
+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();
+ }
+}
diff --git a/resources/assets/fonts/bo/roboto/RobotoMono-Bold.ttf b/resources/assets/fonts/bo/roboto/RobotoMono-Bold.ttf
new file mode 100644
index 0000000..8eff26c
Binary files /dev/null and b/resources/assets/fonts/bo/roboto/RobotoMono-Bold.ttf differ
diff --git a/resources/assets/fonts/bo/roboto/RobotoMono-BoldItalic.ttf b/resources/assets/fonts/bo/roboto/RobotoMono-BoldItalic.ttf
new file mode 100644
index 0000000..9929964
Binary files /dev/null and b/resources/assets/fonts/bo/roboto/RobotoMono-BoldItalic.ttf differ
diff --git a/resources/assets/fonts/bo/roboto/RobotoMono-Italic.ttf b/resources/assets/fonts/bo/roboto/RobotoMono-Italic.ttf
new file mode 100644
index 0000000..cf2b5b3
Binary files /dev/null and b/resources/assets/fonts/bo/roboto/RobotoMono-Italic.ttf differ
diff --git a/resources/assets/fonts/bo/roboto/RobotoMono-Regular.ttf b/resources/assets/fonts/bo/roboto/RobotoMono-Regular.ttf
new file mode 100644
index 0000000..d9371a1
Binary files /dev/null and b/resources/assets/fonts/bo/roboto/RobotoMono-Regular.ttf differ
diff --git a/resources/theme b/resources/theme
index ab2545e..33947ec 160000
--- a/resources/theme
+++ b/resources/theme
@@ -1 +1 @@
-Subproject commit ab2545ed02b9a1f1eea6809c876e752856eadf34
+Subproject commit 33947ec0111809cefa8f29440708b77a0d01f550
diff --git a/resources/views/documents/order-invoice.blade.php b/resources/views/documents/order-invoice.blade.php
new file mode 100644
index 0000000..2d8397b
--- /dev/null
+++ b/resources/views/documents/order-invoice.blade.php
@@ -0,0 +1,358 @@
+
+
+
+
+ {{ $title }}
+
+
+
+
+
+@php
+$customerIsRefunded = !$transactionType->equals(\App\Enums\Order\TransactionType::debit());
+$amountPrefix = $customerIsRefunded ? '- ' : '';
+@endphp
+
+
+
+
+ {{-- LOGO and INTROLINES FROM GENERAL CONFIGS --}}
+
+
+
+ {{-- LOGO --}}
+
+
+ |
+ {{-- INTROLINES --}}
+
+ {{ config('app.name') }}
+
+ @foreach ($introLines as $introLine)
+ {{ $introLine }}
+ @if(!$loop->last) @endif
+ @endforeach
+
+ |
+
+
+
+
+ {{-- SHIPPING AND BILLING --}}
+
+ {{ Str::limit(Str::transliterate($billing->lastname) . ' '. Str::transliterate($billing->firstname), 37, '.') }}
+ {{ Str::limit(Str::transliterate($billing->line1), 37, '.') }}
+ @if(!empty($billing->line2)){{ Str::limit(Str::transliterate($billing->line2), 37, '.') }}
@endif
+ {!! Str::limit(Str::transliterate(clean($billing->postal_code)) . ' '. Str::transliterate(clean($billing->city)), 37, '.') !!}
+
+
+
+
+
+ {{-- TYPE --}}
+
+ @if(!$customerIsRefunded){{
+ Str::upper(__('Facture'))
+ }}@else{{
+ Str::upper(__('Avoir'))
+ }}@endif
+ |
+ {{-- BILLING --}}
+ |
+
+ | |
+ {{ Str::upper(__('Adresse de livraison')) }} |
+ {{-- SHIPPING --}}
+
+ {{ Str::transliterate($shipping->lastname) }} {{ Str::transliterate($shipping->firstname) }}
+ {{ Str::transliterate($shipping->line1) }}
+ @if(!empty($shipping->line2)){{ Str::transliterate($shipping->line2) }} @endif
+ {{ Str::transliterate($shipping->postal_code) }} {{ Str::transliterate($shipping->city) }}
+ |
+
+
+
+ {{-- DATE AND REFERENCES --}}
+
+
+ | {{ __('Date') }} |
+ {{
+ Carbon\Carbon::parse($created_at)->isoFormat('DD\/MM\/YYYY')
+ }} |
+ {{ __('N° Commande') }} |
+ {{ $orderReference }} |
+ {{ __('N° Facture') }} |
+ {{ $transactionReference }} |
+
+
+
+
+ @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)
+
+
+ {{-- LOGO --}}
+
+
+ |
+ {{-- APPNAME --}}
+
+ {{ config('app.name') }}
+ |
+
+
+ @endif
+
+ {{-- TABLE HEADER --}}
+
+
+ | {{ __('Désignation') }} |
+ {{ __('Qte') }} |
+ {{ __('Prix U. TTC') }} |
+ @if($hasTaxes)
+ {{ __('Tva') }} |
+ @endif
+ {{ __('S.Tot TTC') }} |
+
+
+
+ @foreach($basketPagedList as $basketItem)
+
+
+ {{ Str::limit($basketItem->name, 45, '.') }}
+ {{ $basketItem->options->reference }}
+ |
+ {{ $amountPrefix }}{{ $basketItem->qty }} |
+ {{ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($basketItem->options->price_ttc, '') }} |
+ @if($hasTaxes)
+ {{ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($basketItem->tax, '') }} |
+ @endif
+ {{ $amountPrefix }}{{ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($basketItem->subtotal, '') }} |
+
+ @endforeach
+ @for ($i = $perPage - $basketPagedList->count(); $i > 0; $i--)
+
+ @for ($k = $columns; $k > 0; $k--)
+ |
+ @endfor
+
+ @endfor
+
+
+ @php $page++; $perPage = $perPageAfterFirstPage; $lastPage = \App\Lib\Helpers\CollectionHelper::paginateManually(collect($basket), $perPage)->lastPage(); @endphp
+ @if($basketPagedList->currentPage() < $basketPagedList->lastPage())
+
+ @endif
+ @endwhile
+
+
+
+ | |
+
+
+ @if($hasTaxes)
+
+ | {{ __('Tva') }} |
+ {{
+ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($totalTaxes)
+ }} |
+
+ @endif
+
+ | {{ __('Total TTC') }} |
+ {{ $amountPrefix }}{{
+ \App\Lib\Helpers\ToolboxHelper::fmtCurrency($totalTtc)
+ }} |
+
+
+ |
+
+
+ @if(!$hasTaxes)
+
+
+ |
+ {{ __('TVA non applicable, article 293B du code général des impôts') }} |
+
+
+ @endif
+
+
+
+
+
+
+
+
+
diff --git a/resources/views/pages/orders/parts/transactions.blade.php b/resources/views/pages/orders/parts/transactions.blade.php
index f9b63bd..972d1cb 100644
--- a/resources/views/pages/orders/parts/transactions.blade.php
+++ b/resources/views/pages/orders/parts/transactions.blade.php
@@ -3,8 +3,6 @@
{{ __('Transactions') }}
@foreach ($order->transactions as $transaction)
- {{-- FIXME: Invoice download here --}}
- {{--
{{ __('Facture') }} --}}
-
@@ -40,6 +38,11 @@
{{ __('Montant :amount', [
'amount' => app('phpMoneyFormatter')->format($transaction->amount)
]) }}
+ @can('download', $transaction)
+
{{
+ __('Télécharger la facture')
+ }}
+ @endcan
diff --git a/routes/breadcrumbs-fo.php b/routes/breadcrumbs-fo.php
index 505351a..fe93ba5 100644
--- a/routes/breadcrumbs-fo.php
+++ b/routes/breadcrumbs-fo.php
@@ -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');
diff --git a/routes/web-bo.php b/routes/web-bo.php
index 08d6db5..26e4762 100644
--- a/routes/web-bo.php
+++ b/routes/web-bo.php
@@ -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');
});
});
diff --git a/routes/web-fo.php b/routes/web-fo.php
index b88976e..cf08eeb 100644
--- a/routes/web-fo.php
+++ b/routes/web-fo.php
@@ -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');