Added: Sales Module

This commit is contained in:
Fahim 2021-08-02 23:29:23 +06:00
parent 6bcfcc92bc
commit ef77acd2f0
59 changed files with 1645 additions and 41 deletions

View File

@ -2,10 +2,6 @@
@section('title', 'Create Adjustment')
@push('page_css')
@livewireStyles
@endpush
@section('breadcrumb')
<ol class="breadcrumb border-0 m-0">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>

View File

@ -76,14 +76,9 @@
<script>
$(document).ready(function () {
$('#amount').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
});
$('#amount').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
});
$('#expense-form').submit(function () {

View File

@ -76,14 +76,9 @@
<script>
$(document).ready(function () {
$('#amount').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
});
$('#amount').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
});
$('#amount').maskMoney('mask');

View File

@ -143,14 +143,14 @@
<script>
$(document).ready(function () {
$('#product_cost').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
});
$('#product_price').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
});
$('#product-form').submit(function () {

View File

@ -144,14 +144,14 @@
<script>
$(document).ready(function () {
$('#product_cost').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
});
$('#product_price').maskMoney({
prefix:'$',
thousands:',',
decimal:'.',
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
});
$('#product_cost').maskMoney('mask');

View File

View File

@ -0,0 +1,5 @@
<?php
return [
'name' => 'Sale'
];

View File

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSalesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sales', function (Blueprint $table) {
$table->id();
$table->date('date');
$table->string('reference');
$table->unsignedBigInteger('customer_id')->nullable();
$table->string('customer_name');
$table->integer('tax_percentage')->default(0);
$table->integer('tax_amount')->default(0);
$table->integer('discount_percentage')->default(0);
$table->integer('discount_amount')->default(0);
$table->integer('shipping_amount')->default(0);
$table->integer('total_amount');
$table->integer('paid_amount');
$table->integer('due_amount');
$table->string('status');
$table->string('payment_status');
$table->string('payment_method');
$table->text('note')->nullable();
$table->foreign('customer_id')->references('id')->on('customers')->nullOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sales');
}
}

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSaleDetailsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sale_details', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('sale_id');
$table->unsignedBigInteger('product_id')->nullable();
$table->string('product_name');
$table->string('product_code');
$table->integer('quantity');
$table->integer('price');
$table->integer('unit_price');
$table->integer('sub_total');
$table->integer('product_discount_amount');
$table->string('product_discount_type')->default('fixed');
$table->integer('product_tax_amount');
$table->foreign('sale_id')->references('id')
->on('sales')->cascadeOnDelete();
$table->foreign('product_id')->references('id')
->on('products')->nullOnDelete();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sale_details');
}
}

View File

View File

@ -0,0 +1,21 @@
<?php
namespace Modules\Sale\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class SaleDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
// $this->call("OthersTableSeeder");
}
}

View File

View File

View File

@ -0,0 +1,41 @@
<?php
namespace Modules\Sale\Entities;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Sale extends Model
{
use HasFactory;
protected $guarded = [];
public function saleDetails() {
return $this->hasMany(SaleDetails::class, 'sale_id', 'id');
}
public function getShippingAmountAttribute($value) {
return $value / 100;
}
public function getPaidAmountAttribute($value) {
return $value / 100;
}
public function getTotalAmountAttribute($value) {
return $value / 100;
}
public function getDueAmountAttribute($value) {
return $value / 100;
}
public function getTaxAmountAttribute($value) {
return $value / 100;
}
public function getDiscountAmountAttribute($value) {
return $value / 100;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Modules\Sale\Entities;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\Product\Entities\Product;
class SaleDetails extends Model
{
use HasFactory;
protected $guarded = [];
protected $with = ['product'];
public function product() {
return $this->belongsTo(Product::class, 'product_id', 'id');
}
public function sale() {
return $this->belongsTo(Sale::class, 'sale_id', 'id');
}
public function getPriceAttribute($value) {
return $value / 100;
}
public function getUnitPriceAttribute($value) {
return $value / 100;
}
public function getSubTotalAttribute($value) {
return $value / 100;
}
public function getProductDiscountAmountAttribute($value) {
return $value / 100;
}
public function getProductTaxAmountAttribute($value) {
return $value / 100;
}
}

View File

View File

@ -0,0 +1,211 @@
<?php
namespace Modules\Sale\Http\Controllers;
use App\DataTables\SalesDataTable;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Modules\People\Entities\Customer;
use Modules\Product\Entities\Product;
use Modules\Sale\Entities\Sale;
use Modules\Sale\Entities\SaleDetails;
use Modules\Sale\Http\Requests\StoreSaleRequest;
use Modules\Sale\Http\Requests\UpdateSaleRequest;
class SaleController extends Controller
{
public function index(SalesDataTable $dataTable) {
abort_if(Gate::denies('access_sales'), 403);
return $dataTable->render('sale::index');
}
public function create() {
abort_if(Gate::denies('create_sales'), 403);
Cart::instance('sale')->destroy();
return view('sale::create');
}
public function store(StoreSaleRequest $request) {
DB::transaction(function () use ($request) {
$due_amount = $request->total_amount - $request->paid_amount;
if ($due_amount == $request->total_amount) {
$payment_status = 'Unpaid';
} elseif ($due_amount > 0) {
$payment_status = 'Partial';
} else {
$payment_status = 'Paid';
}
$sale = Sale::create([
'date' => $request->date,
'reference' => $request->reference,
'customer_id' => $request->customer_id,
'customer_name' => Customer::findOrFail($request->customer_id)->customer_name,
'tax_percentage' => $request->tax_percentage,
'discount_percentage' => $request->discount_percentage,
'shipping_amount' => $request->shipping_amount * 100,
'paid_amount' => $request->paid_amount * 100,
'total_amount' => $request->total_amount * 100,
'due_amount' => $due_amount * 100,
'status' => $request->status,
'payment_status' => $payment_status,
'payment_method' => $request->payment_method,
'note' => $request->note,
'tax_amount' => Cart::instance('sale')->tax() * 100,
'discount_amount' => Cart::instance('sale')->discount() * 100,
]);
foreach (Cart::instance('sale')->content() as $cart_item) {
SaleDetails::create([
'sale_id' => $sale->id,
'product_id' => $cart_item->id,
'product_name' => $cart_item->name,
'product_code' => $cart_item->options->code,
'quantity' => $cart_item->qty,
'price' => $cart_item->price * 100,
'unit_price' => $cart_item->options->unit_price * 100,
'sub_total' => $cart_item->options->sub_total * 100,
'product_discount_amount' => $cart_item->options->product_discount * 100,
'product_tax_amount' => $cart_item->options->product_tax * 100,
]);
if ($request->status == 'Shipped' || $request->status == 'Completed') {
$product = Product::findOrFail($cart_item->id);
$product->update([
'product_quantity' => $product->product_quantity - $cart_item->qty
]);
}
}
Cart::instance('sale')->destroy();
});
toast('Sale Created!', 'success');
return redirect()->route('sales.index');
}
public function show(Sale $sale) {
abort_if(Gate::denies('show_sales'), 403);
return view('sale::show', compact('sale'));
}
public function edit(Sale $sale) {
abort_if(Gate::denies('edit_sales'), 403);
$sale_details = $sale->saleDetails;
Cart::instance('sale')->destroy();
$cart = Cart::instance('sale');
foreach ($sale_details as $sale_detail) {
$cart->add([
'id' => $sale_detail->product_id,
'name' => $sale_detail->product_name,
'qty' => $sale_detail->quantity,
'price' => $sale_detail->price,
'weight' => 1,
'options' => [
'product_discount' => $sale_detail->product_discount_amount,
'sub_total' => $sale_detail->sub_total,
'code' => $sale_detail->product_code,
'stock' => Product::findOrFail($sale_detail->product_id)->product_quantity,
'product_tax' => $sale_detail->product_tax_amount,
'unit_price' => $sale_detail->unit_price
]
]);
}
return view('sale::edit', compact('sale'));
}
public function update(UpdateSaleRequest $request, Sale $sale) {
DB::transaction(function () use ($request, $sale) {
$due_amount = $request->total_amount - $request->paid_amount;
if ($due_amount == $request->total_amount) {
$payment_status = 'Unpaid';
} elseif ($due_amount > 0) {
$payment_status = 'Partial';
} else {
$payment_status = 'Paid';
}
foreach ($sale->saleDetails as $sale_detail) {
if ($sale->status == 'Shipped' || $sale->status == 'Completed') {
$product = Product::findOrFail($sale_detail->product_id);
$product->update([
'product_quantity' => $product->product_quantity + $sale_detail->quantity
]);
}
$sale_detail->delete();
}
$sale->update([
'date' => $request->date,
'reference' => $request->reference,
'customer_id' => $request->customer_id,
'customer_name' => Customer::findOrFail($request->customer_id)->customer_name,
'tax_percentage' => $request->tax_percentage,
'discount_percentage' => $request->discount_percentage,
'shipping_amount' => $request->shipping_amount * 100,
'paid_amount' => $request->paid_amount * 100,
'total_amount' => $request->total_amount * 100,
'due_amount' => $due_amount * 100,
'status' => $request->status,
'payment_status' => $payment_status,
'payment_method' => $request->payment_method,
'note' => $request->note,
'tax_amount' => Cart::instance('sale')->tax() * 100,
'discount_amount' => Cart::instance('sale')->discount() * 100,
]);
foreach (Cart::instance('sale')->content() as $cart_item) {
SaleDetails::create([
'sale_id' => $sale->id,
'product_id' => $cart_item->id,
'product_name' => $cart_item->name,
'product_code' => $cart_item->options->code,
'quantity' => $cart_item->qty,
'price' => $cart_item->price * 100,
'unit_price' => $cart_item->options->unit_price * 100,
'sub_total' => $cart_item->options->sub_total * 100,
'product_discount_amount' => $cart_item->options->product_discount * 100,
'product_tax_amount' => $cart_item->options->product_tax * 100,
]);
if ($request->status == 'Shipped' || $request->status == 'Completed') {
$product = Product::findOrFail($cart_item->id);
$product->update([
'product_quantity' => $product->product_quantity - $cart_item->qty
]);
}
}
Cart::instance('sale')->destroy();
});
toast('Sale Updated!', 'info');
return redirect()->route('sales.index');
}
public function destroy(Sale $sale) {
abort_if(Gate::denies('delete_sales'), 403);
}
}

View File

View File

View File

@ -0,0 +1,40 @@
<?php
namespace Modules\Sale\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreSaleRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'customer_id' => 'required|numeric',
'reference' => 'required|string|max:255|unique:sales,reference',
'tax_percentage' => 'required|integer|min:0|max:100',
'discount_percentage' => 'required|integer|min:0|max:100',
'shipping_amount' => 'required|numeric',
'total_amount' => 'required|numeric',
'paid_amount' => 'required|numeric',
'status' => 'required|string|max:255',
'payment_method' => 'required|string|max:255',
'note' => 'nullable|string|max:1000'
];
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('create_sales');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Modules\Sale\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class UpdateSaleRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'customer_id' => 'required|numeric',
'reference' => 'required|string|max:255|unique:sales,reference,' . $this->sale->id,
'tax_percentage' => 'required|integer|min:0|max:100',
'discount_percentage' => 'required|integer|min:0|max:100',
'shipping_amount' => 'required|numeric',
'total_amount' => 'required|numeric',
'paid_amount' => 'required|numeric|max:' . $this->sale->total_amount,
'status' => 'required|string|max:255',
'payment_method' => 'required|string|max:255',
'note' => 'nullable|string|max:1000'
];
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('edit_sales');
}
}

View File

View File

@ -0,0 +1,69 @@
<?php
namespace Modules\Sale\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* The module namespace to assume when generating URLs to actions.
*
* @var string
*/
protected $moduleNamespace = 'Modules\Sale\Http\Controllers';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*
* @return void
*/
public function boot()
{
parent::boot();
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('Sale', '/Routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace)
->group(module_path('Sale', '/Routes/api.php'));
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace Modules\Sale\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Factory;
class SaleServiceProvider extends ServiceProvider
{
/**
* @var string $moduleName
*/
protected $moduleName = 'Sale';
/**
* @var string $moduleNameLower
*/
protected $moduleNameLower = 'sale';
/**
* Boot the application events.
*
* @return void
*/
public function boot()
{
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->register(RouteServiceProvider::class);
}
/**
* Register config.
*
* @return void
*/
protected function registerConfig()
{
$this->publishes([
module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'),
], 'config');
$this->mergeConfigFrom(
module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
);
}
/**
* Register views.
*
* @return void
*/
public function registerViews()
{
$viewPath = resource_path('views/modules/' . $this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'Resources/views');
$this->publishes([
$sourcePath => $viewPath
], ['views', $this->moduleNameLower . '-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
}
/**
* Register translations.
*
* @return void
*/
public function registerTranslations()
{
$langPath = resource_path('lang/modules/' . $this->moduleNameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
} else {
$this->loadTranslationsFrom(module_path($this->moduleName, 'Resources/lang'), $this->moduleNameLower);
}
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (\Config::get('view.paths') as $path) {
if (is_dir($path . '/modules/' . $this->moduleNameLower)) {
$paths[] = $path . '/modules/' . $this->moduleNameLower;
}
}
return $paths;
}
}

View File

View File

View File

View File

View File

View File

@ -0,0 +1,130 @@
@extends('layouts.app')
@section('title', 'Create Sale')
@section('breadcrumb')
<ol class="breadcrumb border-0 m-0">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
<li class="breadcrumb-item"><a href="{{ route('sales.index') }}">Sales</a></li>
<li class="breadcrumb-item active">Add</li>
</ol>
@endsection
@section('content')
<div class="container-fluid mb-4">
<div class="row">
<div class="col-12">
<livewire:search-product/>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-body">
@include('utils.alerts')
<form id="sale-form" action="{{ route('sales.store') }}" method="POST">
@csrf
<div class="form-row">
<div class="col-lg-4">
<div class="form-group">
<label for="reference">Reference <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="reference" required>
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="customer_id">Customer <span class="text-danger">*</span></label>
<select class="form-control" name="customer_id" id="customer_id" required>
@foreach(\Modules\People\Entities\Customer::all() as $customer)
<option value="{{ $customer->id }}">{{ $customer->customer_name }}</option>
@endforeach
</select>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="date">Date <span class="text-danger">*</span></label>
<input type="date" class="form-control" name="date" required value="{{ \Carbon\Carbon::now()->format('Y-m-d') }}">
</div>
</div>
</div>
</div>
<livewire:product-cart :cartInstance="'sale'"/>
<div class="form-row">
<div class="col-lg-4">
<div class="form-group">
<label for="status">Status <span class="text-danger">*</span></label>
<select class="form-control" name="status" id="status" required>
<option value="Pending">Pending</option>
<option value="Shipped">Shipped</option>
<option value="Completed">Completed</option>
</select>
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="payment_method">Payment Method <span class="text-danger">*</span></label>
<select class="form-control" name="payment_method" id="payment_method" required>
<option value="Cash">Cash</option>
<option value="Credit Card">Credit Card</option>
<option value="Bank Transfer">Bank Transfer</option>
<option value="Cheque">Cheque</option>
<option value="Other">Other</option>
</select>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="paid_amount">Amount Received <span class="text-danger">*</span></label>
<input id="paid_amount" type="text" class="form-control" name="paid_amount" required>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="note">Note (If Needed)</label>
<textarea name="note" id="note" rows="5" class="form-control"></textarea>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-primary">
Create Sale <i class="bi bi-check"></i>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('page_scripts')
<script src="{{ asset('js/jquery-mask-money.js') }}"></script>
<script>
$(document).ready(function () {
$('#paid_amount').maskMoney({
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
allowZero: true,
});
$('#sale-form').submit(function () {
var paid_amount = $('#paid_amount').maskMoney('unmasked')[0];
$('#paid_amount').val(paid_amount);
});
});
</script>
@endpush

View File

@ -0,0 +1,133 @@
@extends('layouts.app')
@section('title', 'Edit Sale')
@section('breadcrumb')
<ol class="breadcrumb border-0 m-0">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
<li class="breadcrumb-item"><a href="{{ route('sales.index') }}">Sales</a></li>
<li class="breadcrumb-item active">Edit</li>
</ol>
@endsection
@section('content')
<div class="container-fluid mb-4">
<div class="row">
<div class="col-12">
<livewire:search-product/>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-body">
@include('utils.alerts')
<form id="sale-form" action="{{ route('sales.update', $sale) }}" method="POST">
@csrf
@method('patch')
<div class="form-row">
<div class="col-lg-4">
<div class="form-group">
<label for="reference">Reference <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="reference" required value="{{ $sale->reference }}">
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="customer_id">Customer <span class="text-danger">*</span></label>
<select class="form-control" name="customer_id" id="customer_id" required>
@foreach(\Modules\People\Entities\Customer::all() as $customer)
<option {{ $sale->customer_id == $customer->id ? 'selected' : '' }} value="{{ $customer->id }}">{{ $customer->customer_name }}</option>
@endforeach
</select>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="date">Date <span class="text-danger">*</span></label>
<input type="date" class="form-control" name="date" required value="{{ $sale->date }}">
</div>
</div>
</div>
</div>
<livewire:product-cart :cartInstance="'sale'" :sale="$sale"/>
<div class="form-row">
<div class="col-lg-4">
<div class="form-group">
<label for="status">Status <span class="text-danger">*</span></label>
<select class="form-control" name="status" id="status" required>
<option {{ $sale->status == 'Pending' ? 'selected' : '' }} value="Pending">Pending</option>
<option {{ $sale->status == 'Shipped' ? 'selected' : '' }} value="Shipped">Shipped</option>
<option {{ $sale->status == 'Completed' ? 'selected' : '' }} value="Completed">Completed</option>
</select>
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="payment_method">Payment Method <span class="text-danger">*</span></label>
<select class="form-control" name="payment_method" id="payment_method" required>
<option {{ $sale->payment_method == 'Cash' ? 'selected' : '' }} value="Cash">Cash</option>
<option {{ $sale->payment_method == 'Credit Card' ? 'selected' : '' }} value="Credit Card">Credit Card</option>
<option {{ $sale->payment_method == 'Bank Transfer' ? 'selected' : '' }} value="Bank Transfer">Bank Transfer</option>
<option {{ $sale->payment_method == 'Cheque' ? 'selected' : '' }} value="Cheque">Cheque</option>
<option {{ $sale->payment_method == 'Other' ? 'selected' : '' }} value="Other">Other</option>
</select>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="from-group">
<div class="form-group">
<label for="paid_amount">Amount Received <span class="text-danger">*</span></label>
<input id="paid_amount" type="text" class="form-control" name="paid_amount" required value="{{ $sale->paid_amount }}" min="0" max="{{ $sale->total_amount }}">
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="note">Note (If Needed)</label>
<textarea name="note" id="note" rows="5" class="form-control">{{ $sale->note }}</textarea>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-primary">
Update Sale <i class="bi bi-check"></i>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('page_scripts')
<script src="{{ asset('js/jquery-mask-money.js') }}"></script>
<script>
$(document).ready(function () {
$('#paid_amount').maskMoney({
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
allowZero: true,
});
$('#paid_amount').maskMoney('mask');
$('#paid_amount').maskMoney('mask');
$('#sale-form').submit(function () {
var paid_amount = $('#paid_amount').maskMoney('unmasked')[0];
$('#paid_amount').val(paid_amount);
});
});
</script>
@endpush

View File

@ -0,0 +1,40 @@
@extends('layouts.app')
@section('title', 'Sales')
@section('third_party_stylesheets')
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.25/css/dataTables.bootstrap4.min.css">
@endsection
@section('breadcrumb')
<ol class="breadcrumb border-0 m-0">
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
<li class="breadcrumb-item active">Sales</li>
</ol>
@endsection
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
<a href="{{ route('sales.create') }}" class="btn btn-primary">
Add Sale <i class="bi bi-plus"></i>
</a>
<hr>
<div class="table-responsive">
{!! $dataTable->table() !!}
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('page_scripts')
{!! $dataTable->scripts() !!}
@endpush

View File

@ -0,0 +1,24 @@
@can('edit_sales')
<a href="{{ route('sales.edit', $data->id) }}" class="btn btn-info btn-sm">
<i class="bi bi-pencil"></i>
</a>
@endcan
@can('show_sales')
<a href="{{ route('sales.show', $data->id) }}" class="btn btn-primary btn-sm">
<i class="bi bi-eye"></i>
</a>
@endcan
@can('delete_sales')
<button id="delete" class="btn btn-danger btn-sm" onclick="
event.preventDefault();
if (confirm('Are you sure? It will delete the dNPata permanently!')) {
document.getElementById('destroy{{ $data->id }}').submit()
}
">
<i class="bi bi-trash"></i>
<form id="destroy{{ $data->id }}" class="d-none" action="{{ route('sales.destroy', $data->id) }}" method="POST">
@csrf
@method('delete')
</form>
</button>
@endcan

View File

@ -0,0 +1,13 @@
@if ($data->payment_status == 'Partial')
<span class="badge badge-info">
{{ $data->payment_status }}
</span>
@elseif ($data->payment_status == 'Paid')
<span class="badge badge-success">
{{ $data->payment_status }}
</span>
@else
<span class="badge badge-danger">
{{ $data->payment_status }}
</span>
@endif

View File

@ -0,0 +1,13 @@
@if ($data->status == 'Pending')
<span class="badge badge-info">
{{ $data->status }}
</span>
@elseif ($data->status == 'Shipped')
<span class="badge badge-primary">
{{ $data->status }}
</span>
@else
<span class="badge badge-success">
{{ $data->status }}
</span>
@endif

View File

View File

@ -0,0 +1,18 @@
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:api')->get('/sale', function (Request $request) {
return $request->user();
});

View File

@ -0,0 +1,18 @@
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::group(['middleware' => 'auth'], function () {
Route::resource('sales', 'SaleController');
});

View File

View File

View File

@ -0,0 +1,23 @@
{
"name": "nwidart/sale",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Sale\\": ""
}
}
}

13
Modules/Sale/module.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "Sale",
"alias": "sale",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Sale\\Providers\\SaleServiceProvider"
],
"aliases": {},
"files": [],
"requires": []
}

17
Modules/Sale/package.json Normal file
View File

@ -0,0 +1,17 @@
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"cross-env": "^7.0",
"laravel-mix": "^5.0.1",
"laravel-mix-merge-manifest": "^0.1.2"
}
}

14
Modules/Sale/webpack.mix.js vendored Normal file
View File

@ -0,0 +1,14 @@
const dotenvExpand = require('dotenv-expand');
dotenvExpand(require('dotenv').config({ path: '../../.env'/*, debug: true*/}));
const mix = require('laravel-mix');
require('laravel-mix-merge-manifest');
mix.setPublicPath('../../public').mergeManifest();
mix.js(__dirname + '/Resources/assets/js/app.js', 'js/sale.js')
.sass( __dirname + '/Resources/assets/sass/app.scss', 'css/sale.css');
if (mix.inProduction()) {
mix.version();
}

View File

@ -15,7 +15,7 @@
<div class="col-lg-12">
@include('utils.alerts')
<div class="card">
<div class="card-header bg-info text-white">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">General Settings</h5>
</div>
<div class="card-body">

View File

@ -0,0 +1,96 @@
<?php
namespace App\DataTables;
use Modules\Sale\Entities\Sale;
use Yajra\DataTables\Html\Button;
use Yajra\DataTables\Html\Column;
use Yajra\DataTables\Html\Editor\Editor;
use Yajra\DataTables\Html\Editor\Fields;
use Yajra\DataTables\Services\DataTable;
class SalesDataTable extends DataTable
{
public function dataTable($query) {
return datatables()
->eloquent($query)
->addColumn('total_amount', function ($data) {
return format_currency($data->total_amount);
})
->addColumn('paid_amount', function ($data) {
return format_currency($data->paid_amount);
})
->addColumn('due_amount', function ($data) {
return format_currency($data->due_amount);
})
->addColumn('status', function ($data) {
return view('sale::partials.status', compact('data'));
})
->addColumn('payment_status', function ($data) {
return view('sale::partials.payment-status', compact('data'));
})
->addColumn('action', function ($data) {
return view('sale::partials.actions', compact('data'));
});
}
public function query(Sale $model) {
return $model->newQuery();
}
public function html() {
return $this->builder()
->setTableId('sales-table')
->columns($this->getColumns())
->minifiedAjax()
->dom("<'row'<'col-md-3'l><'col-md-5 mb-2'B><'col-md-4'f>> .
'tr' .
<'row'<'col-md-5'i><'col-md-7 mt-2'p>>")
->buttons(
Button::make('excel')
->text('<i class="bi bi-file-earmark-excel-fill"></i> Excel'),
Button::make('print')
->text('<i class="bi bi-printer-fill"></i> Print'),
Button::make('reset')
->text('<i class="bi bi-x-circle"></i> Reset'),
Button::make('reload')
->text('<i class="bi bi-arrow-repeat"></i> Reload')
);
}
protected function getColumns() {
return [
Column::make('reference')
->className('text-center align-middle'),
Column::make('customer_name')
->title('Customer')
->className('text-center align-middle'),
Column::computed('status')
->className('text-center align-middle'),
Column::computed('total_amount')
->className('text-center align-middle'),
Column::computed('paid_amount')
->className('text-center align-middle'),
Column::computed('due_amount')
->className('text-center align-middle'),
Column::computed('payment_status')
->className('text-center align-middle'),
Column::computed('action')
->exportable(false)
->printable(false)
->className('text-center align-middle')
];
}
protected function filename() {
return 'Sales_' . date('YmdHis');
}
}

View File

@ -7,11 +7,15 @@ if (!function_exists('settings')) {
}
if (!function_exists('format_currency')) {
function format_currency($value) {
function format_currency($value, $format = true) {
if (!$format) {
return $value;
}
if (settings()->default_currency_position == 'prefix') {
$formatted_value = settings()->currency->symbol . number_format($value, 2, settings()->currency->decimal_separator, settings()->currency->thousand_separator);
$formatted_value = settings()->currency->symbol . number_format((float) $value, 2, settings()->currency->decimal_separator, settings()->currency->thousand_separator);
} else {
$formatted_value = number_format($value, 2, settings()->currency->decimal_separator, settings()->currency->thousand_separator) . settings()->currency->symbol;
$formatted_value = number_format((float) $value, 2, settings()->currency->decimal_separator, settings()->currency->thousand_separator) . settings()->currency->symbol;
}
return $formatted_value;

View File

@ -35,12 +35,12 @@ class ProductTable extends Component
if (in_array($product, array_map(function ($adjustment) {
return $adjustment['product'];
}, $this->products))) {
return session()->flash('message', 'Already exists in the product table!');
return session()->flash('message', 'Already exists in the product list!');
}
break;
case false:
if (in_array($product, $this->products)) {
return session()->flash('message', 'Already exists in the product table!');
return session()->flash('message', 'Already exists in the product list!');
}
break;
default:

View File

@ -0,0 +1,176 @@
<?php
namespace App\Http\Livewire;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Support\Facades\Request;
use Livewire\Component;
class ProductCart extends Component
{
public $listeners = ['productSelected'];
public $cart_instance;
public $global_discount;
public $global_tax;
public $shipping;
public $quantity;
public $check_quantity;
public $discount_type;
public $item_discount;
public $sale;
public function mount($cartInstance, $sale = null) {
$this->cart_instance = $cartInstance;
if ($sale) {
$this->sale = $sale;
$this->global_discount = $sale->discount_percentage;
$this->global_tax = $sale->tax_percentage;
$this->shipping = $sale->shipping_amount;
$this->updatedGlobalTax();
$this->updatedGlobalDiscount();
$sale_details = $this->sale->saleDetails;
foreach ($sale_details as $sale_detail) {
$this->check_quantity[$sale_detail->product_id] = [$sale_detail->product->product_quantity];
$this->quantity[$sale_detail->product_id] = $sale_detail->quantity;
$this->discount_type[$sale_detail->product_id] = 'fixed';
$this->item_discount[$sale_detail->product_id] = $sale_detail->product_discount_amount;
}
} else {
$this->global_discount = 0;
$this->global_tax = 0;
$this->shipping = 0.00;
$this->check_quantity = [];
$this->quantity = [];
$this->discount_type = [];
$this->item_discount = [];
}
}
public function render() {
$cart_items = Cart::instance($this->cart_instance)->content();
return view('livewire.product-cart', [
'cart_items' => $cart_items
]);
}
public function productSelected($product) {
$cart = Cart::instance($this->cart_instance);
$exists = $cart->search(function ($cartItem, $rowId) use ($product) {
return $cartItem->id == $product['id'];
});
if ($exists->isNotEmpty()) {
session()->flash('message', 'Product exists in the cart!');
return;
}
$cart->add([
'id' => $product['id'],
'name' => $product['product_name'],
'qty' => 1,
'price' => $this->calculate($product)['price'],
'weight' => 1,
'options' => [
'product_discount' => 0.00,
'sub_total' => $this->calculate($product)['sub_total'],
'code' => $product['product_code'],
'stock' => $product['product_quantity'],
'product_tax' => $this->calculate($product)['product_tax'],
'unit_price' => $this->calculate($product)['unit_price']
]
]);
$this->check_quantity[$product['id']] = $product['product_quantity'];
$this->quantity[$product['id']] = 1;
$this->discount_type[$product['id']] = 'fixed';
$this->item_discount[$product['id']] = 0;
}
public function removeItem($row_id) {
Cart::instance($this->cart_instance)->remove($row_id);
}
public function updatedGlobalTax() {
Cart::instance($this->cart_instance)->setGlobalTax((integer)$this->global_tax);
}
public function updatedGlobalDiscount() {
Cart::instance($this->cart_instance)->setGlobalDiscount((integer)$this->global_discount);
}
public function updateQuantity($row_id, $product_id) {
if ($this->check_quantity[$product_id] < $this->quantity[$product_id]) {
session()->flash('message', 'The requested quantity is not available in stock.');
return;
}
Cart::instance($this->cart_instance)->update($row_id, $this->quantity[$product_id]);
$cart_item = Cart::instance($this->cart_instance)->get($row_id);
Cart::instance($this->cart_instance)->update($row_id, [
'options' => [
'sub_total' => $cart_item->price * $cart_item->qty,
'code' => $cart_item->options->code,
'stock' => $cart_item->options->stock,
'product_tax' => $cart_item->options->product_tax,
'unit_price' => $cart_item->options->unit_price,
'product_discount' => $cart_item->options->product_discount,
]
]);
}
public function setProductDiscount($row_id, $product_id) {
$cart_item = Cart::instance($this->cart_instance)->get($row_id);
if ($this->discount_type[$product_id] == 'fixed') {
Cart::instance($this->cart_instance)->update($row_id, ['price' => ($cart_item->price + $cart_item->options->product_discount) - $this->item_discount[$product_id]]);
Cart::instance($this->cart_instance)->update($row_id, ['options' => [
'sub_total' => $cart_item->price * $cart_item->qty,
'code' => $cart_item->options->code,
'stock' => $cart_item->options->stock,
'product_tax' => $cart_item->options->product_tax,
'unit_price' => $cart_item->options->unit_price,
'product_discount' => $this->item_discount[$product_id],
]]);
}
}
public function calculate($product) {
$price = 0;
$unit_price = 0;
$product_tax = 0;
$sub_total = 0;
if ($product['product_tax_type'] == 1) {
$price = $product['product_price'] + ($product['product_price'] * ($product['product_order_tax'] / 100));
$unit_price = $product['product_price'];
$product_tax = $product['product_price'] * ($product['product_order_tax'] / 100);
$sub_total = $product['product_price'] + ($product['product_price'] * ($product['product_order_tax'] / 100));
}
elseif ($product['product_tax_type'] == 2) {
$price = $product['product_price'];
$unit_price = $product['product_price'] - ($product['product_price'] * ($product['product_order_tax'] / 100));
$product_tax = $product['product_price'] * ($product['product_order_tax'] / 100);
$sub_total = $product['product_price'];
}
else {
$price = $product['product_price'];
$unit_price = $product['product_price'];
$product_tax = 0.00;
$sub_total = $product['product_price'];
}
return ['price' => $price, 'unit_price' => $unit_price, 'product_tax' => $product_tax, 'sub_total' => $sub_total];
}
}

View File

@ -24,7 +24,7 @@ class SearchProduct extends Component
public function updatedQuery() {
$this->searchResults = Product::where('product_name', 'like', '%' . $this->query . '%')
->orWhere('product_code', 'like', '%' . $this->query . '%')
->get();
->take(6)->get();
}
public function resetQuery() {

View File

@ -72,7 +72,7 @@ return [
'decimal_point' => '.',
'thousand_separator' => ',',
'thousand_separator' => '',
],

View File

@ -6,5 +6,6 @@
"Expense": true,
"People": true,
"Currency": true,
"Setting": true
"Setting": true,
"Sale": true
}

View File

@ -62,6 +62,30 @@
</li>
@endcan
@can('access_sales')
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown {{ request()->routeIs('sales*') ? 'c-show' : '' }}">
<a class="c-sidebar-nav-link c-sidebar-nav-dropdown-toggle" href="#">
<i class="c-sidebar-nav-icon bi bi-receipt" style="line-height: 1;"></i> Sales
</a>
@can('create_sales')
<ul class="c-sidebar-nav-dropdown-items">
<li class="c-sidebar-nav-item">
<a class="c-sidebar-nav-link {{ request()->routeIs('sales.create') ? 'c-active' : '' }}" href="{{ route('sales.create') }}">
<i class="c-sidebar-nav-icon bi bi-journal-plus" style="line-height: 1;"></i> Create Sale
</a>
</li>
</ul>
@endcan
<ul class="c-sidebar-nav-dropdown-items">
<li class="c-sidebar-nav-item">
<a class="c-sidebar-nav-link {{ request()->routeIs('sales.index') ? 'c-active' : '' }}" href="{{ route('sales.index') }}">
<i class="c-sidebar-nav-icon bi bi-journals" style="line-height: 1;"></i> All Sales
</a>
</li>
</ul>
</li>
@endcan
@can('access_expenses')
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown {{ request()->routeIs('expenses.*') || request()->routeIs('expense-categories.*') ? 'c-show' : '' }}">
<a class="c-sidebar-nav-link c-sidebar-nav-dropdown-toggle" href="#">

View File

@ -0,0 +1,41 @@
<!-- Button trigger Discount Modal -->
<span role="button" class="badge badge-warning pointer-event" data-toggle="modal" data-target="#discountModal{{ $cart_item->id }}">
<i class="bi bi-pencil-square text-white"></i>
</span>
<!-- Discount Modal -->
<div wire:ignore.self class="modal fade" id="discountModal{{ $cart_item->id }}" tabindex="-1" role="dialog" aria-labelledby="discountModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="discountModalLabel">
{{ $cart_item->name }}
<br>
<span class="badge badge-success">
{{ $cart_item->options->code }}
</span>
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form wire:submit.prevent="setProductDiscount('{{ $cart_item->rowId }}', '{{ $cart_item->id }}')" method="POST">
<div class="modal-body">
<div class="form-group">
<label>Discount Type <span class="text-danger">*</span></label>
<select wire:model.defer="discount_type.{{ $cart_item->id }}" class="form-control" required>
<option {{ $discount_type[$cart_item->id] == 'fixed' ? 'selected' : '' }} value="fixed">Fixed</option>
</select>
</div>
<div class="form-group">
<label>Discount <span class="text-danger">*</span></label>
<input wire:model.defer="item_discount.{{ $cart_item->id }}" type="number" class="form-control" value="{{ $item_discount[$cart_item->id] }}" step="0.01">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,8 @@
<form wire:submit.prevent="updateQuantity('{{ $cart_item->rowId }}', '{{ $cart_item->id }}')">
<div class="form-inline">
<input wire:model.lazy="quantity.{{ $cart_item->id }}" style="width: 90px;" type="number" class="form-control" value="{{ $cart_item->qty }}" min="1">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check"></i>
</button>
</div>
</form>

View File

@ -0,0 +1,135 @@
<div>
<div>
@if (session()->has('message'))
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<div class="alert-body">
<span>{{ session('message') }}</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
</div>
@endif
<div class="table-responsive">
<table class="table table-bordered">
<thead class="thead-dark">
<tr class="align-middle">
<th class="align-middle">Product</th>
<th class="align-middle">Net Unit Price</th>
<th class="align-middle">Stock</th>
<th class="align-middle">Quantity</th>
<th class="align-middle">Discount</th>
<th class="align-middle">Tax</th>
<th class="align-middle">Sub Total</th>
<th class="align-middle">Action</th>
</tr>
</thead>
<tbody>
@if($cart_items->isNotEmpty())
@foreach($cart_items as $cart_item)
<tr>
<td class="align-middle">
{{ $cart_item->name }} <br>
<span class="badge badge-success">
{{ $cart_item->options->code }}
</span>
@include('livewire.includes.product-cart-modal')
</td>
<td class="align-middle">{{ format_currency($cart_item->options->unit_price) }}</td>
<td class="align-middle text-center">
<span class="badge badge-info">{{ $cart_item->options->stock }}</span>
</td>
<td class="align-middle">
@include('livewire.includes.product-cart-quantity')
</td>
<td class="align-middle">
{{ format_currency($cart_item->options->product_discount) }}
</td>
<td class="align-middle">
{{ format_currency($cart_item->options->product_tax) }}
</td>
<td class="align-middle">
{{ format_currency($cart_item->options->sub_total) }}
</td>
<td class="align-middle text-center">
<button wire:click.prevent="removeItem('{{ $cart_item->rowId }}')" type="button" class="btn btn-danger">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8" class="text-center">
<span class="text-danger">
Please search & select products!
</span>
</td>
</tr>
@endif
</tbody>
</table>
</div>
</div>
<div class="row justify-content-md-end">
<div class="col-md-4">
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th>Order Tax ({{ $global_tax }}%)</th>
<td>(+) {{ format_currency(Cart::instance('sale')->tax()) }}</td>
</tr>
<tr>
<th>Discount ({{ $global_discount }}%)</th>
<td>(-) {{ format_currency(Cart::instance('sale')->discount()) }}</td>
</tr>
<tr>
<th>Shipping</th>
<input type="hidden" value="{{ $shipping }}" name="shipping_amount">
<td>(+) {{ format_currency($shipping) }}</td>
</tr>
<tr>
<th>Grand Total</th>
@php
$total_with_shipping = Cart::instance('sale')->total() + (float) $shipping
@endphp
<th>
(=) {{ format_currency($total_with_shipping) }}
</th>
</tr>
</table>
</div>
</div>
</div>
<input type="hidden" name="total_amount" value="{{ $total_with_shipping }}">
<div class="form-row">
<div class="col-lg-4">
<div class="form-group">
<label for="tax_percentage">Order Tax (%)</label>
<input wire:model.debounce="global_tax" type="number" class="form-control" name="tax_percentage" min="0" max="100" value="{{ $global_tax }}" required>
</div>
</div>
<div class="col-lg-4">
<div class="form-group">
<label for="discount_percentage">Discount (%)</label>
<input wire:model.debounce="global_discount" type="number" class="form-control" name="discount_percentage" min="0" max="100" value="{{ $global_discount }}" required>
</div>
</div>
<div class="col-lg-4">
<div class="form-group">
<label for="shipping_amount">Shipping</label>
<input wire:model.debounce="shipping" type="number" class="form-control" name="shipping_amount" min="0" value="0" required step="0.01">
</div>
</div>
</div>
</div>