Added: POS System

This commit is contained in:
Fahim 2021-08-13 01:04:25 +06:00
parent c197ade809
commit e6f907dfe6
27 changed files with 882 additions and 60 deletions

View File

@ -0,0 +1,98 @@
<?php
namespace Modules\Sale\Http\Controllers;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Modules\People\Entities\Customer;
use Modules\Product\Entities\Category;
use Modules\Product\Entities\Product;
use Modules\Sale\Entities\Sale;
use Modules\Sale\Entities\SaleDetails;
use Modules\Sale\Entities\SalePayment;
use Modules\Sale\Http\Requests\StorePosSaleRequest;
class PosController extends Controller
{
public function index() {
Cart::instance('sale')->destroy();
$customers = Customer::all();
$product_categories = Category::all();
return view('sale::pos.index', compact('product_categories', 'customers'));
}
public function store(StorePosSaleRequest $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' => now()->format('Y-m-d'),
'reference' => 'PSL',
'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' => 'Completed',
'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_discount_type' => $cart_item->options->product_discount_type,
'product_tax_amount' => $cart_item->options->product_tax * 100,
]);
$product = Product::findOrFail($cart_item->id);
$product->update([
'product_quantity' => $product->product_quantity - $cart_item->qty
]);
}
Cart::instance('sale')->destroy();
SalePayment::create([
'date' => now()->format('Y-m-d'),
'reference' => 'INV/'.$sale->reference,
'amount' => $sale->paid_amount,
'sale_id' => $sale->id,
'payment_method' => $request->payment_method
]);
});
toast('POS Sale Created!', 'success');
return redirect()->route('sales.index');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Modules\Sale\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StorePosSaleRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'customer_id' => 'required|numeric',
'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',
'note' => 'nullable|string|max:1000'
];
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Gate::allows('create_pos_sales');
}
}

View File

@ -0,0 +1,67 @@
@extends('layouts.app')
@section('title', 'POS')
@section('third_party_stylesheets')
@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">POS</li>
</ol>
@endsection
@section('content')
<div class="container-fluid">
<div class="row">
<div class="col-12">
@include('utils.alerts')
</div>
<div class="col-lg-7">
<livewire:search-product/>
<livewire:pos.product-list :categories="$product_categories"/>
</div>
<div class="col-lg-5">
<livewire:pos.checkout :cart-instance="'sale'" :customers="$customers"/>
</div>
</div>
</div>
@endsection
@push('page_scripts')
<script src="{{ asset('js/jquery-mask-money.js') }}"></script>
<script>
$(document).ready(function () {
window.addEventListener('showCheckoutModal', event => {
$('#checkoutModal').modal('show');
$('#paid_amount').maskMoney({
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
allowZero: false,
});
$('#total_amount').maskMoney({
prefix:'{{ settings()->currency->symbol }}',
thousands:'{{ settings()->currency->thousand_separator }}',
decimal:'{{ settings()->currency->decimal_separator }}',
allowZero: true,
});
$('#paid_amount').maskMoney('mask');
$('#total_amount').maskMoney('mask');
$('#checkout-form').submit(function () {
var paid_amount = $('#paid_amount').maskMoney('unmasked')[0];
$('#paid_amount').val(paid_amount);
var total_amount = $('#total_amount').maskMoney('unmasked')[0];
$('#total_amount').val(total_amount);
});
});
});
</script>
@endpush

View File

@ -13,6 +13,10 @@
Route::group(['middleware' => 'auth'], function () { Route::group(['middleware' => 'auth'], function () {
//POS
Route::get('/app/pos', 'PosController@index')->name('app.pos.index');
Route::post('/app/pos', 'PosController@store')->name('app.pos.store');
//Generate PDF //Generate PDF
Route::get('/sales/pdf/{id}', function ($id) { Route::get('/sales/pdf/{id}', function ($id) {
$sale = \Modules\Sale\Entities\Sale::findOrFail($id); $sale = \Modules\Sale\Entities\Sale::findOrFail($id);

View File

@ -0,0 +1,208 @@
<?php
namespace App\Http\Livewire\Pos;
use Gloudemans\Shoppingcart\Facades\Cart;
use Livewire\Component;
class Checkout extends Component
{
public $listeners = ['productSelected', 'discountModalRefresh'];
public $cart_instance;
public $customers;
public $global_discount;
public $global_tax;
public $shipping;
public $quantity;
public $check_quantity;
public $discount_type;
public $item_discount;
public $data;
public $customer_id;
public $total_amount;
public function mount($cartInstance, $customers) {
$this->cart_instance = $cartInstance;
$this->customers = $customers;
$this->global_discount = 0;
$this->global_tax = 0;
$this->shipping = 0.00;
$this->check_quantity = [];
$this->quantity = [];
$this->discount_type = [];
$this->item_discount = [];
$this->total_amount = 0;
}
public function hydrate() {
$this->total_amount = $this->calculateTotal();
$this->updatedCustomerId();
}
public function render() {
$cart_items = Cart::instance($this->cart_instance)->content();
return view('livewire.pos.checkout', [
'cart_items' => $cart_items
]);
}
public function proceed() {
$this->dispatchBrowserEvent('showCheckoutModal');
}
public function calculateTotal() {
return Cart::instance($this->cart_instance)->total() + $this->shipping;
}
public function resetCart() {
Cart::instance($this->cart_instance)->destroy();
}
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,
'product_discount_type' => 'fixed',
'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;
$this->total_amount = $this->calculateTotal();
}
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,
'product_discount_type' => $cart_item->options->product_discount_type,
]
]);
}
public function updatedDiscountType($value, $name) {
$this->item_discount[$name] = 0;
}
public function discountModalRefresh($product_id, $row_id) {
$this->updateQuantity($row_id, $product_id);
}
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]
]);
$discount_amount = $this->item_discount[$product_id];
$this->updateCartOptions($row_id, $product_id, $cart_item, $discount_amount);
} elseif ($this->discount_type[$product_id] == 'percentage') {
$discount_amount = $cart_item->price * ($this->item_discount[$product_id] / 100);
Cart::instance($this->cart_instance)
->update($row_id, [
'price' => ($cart_item->price + $cart_item->options->product_discount) - (($cart_item->price * $this->item_discount[$product_id] / 100))
]);
$this->updateCartOptions($row_id, $product_id, $cart_item, $discount_amount);
}
session()->flash('discount_message' . $product_id, 'Discount added to the product!');
}
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];
}
public function updateCartOptions($row_id, $product_id, $cart_item, $discount_amount) {
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' => $discount_amount,
'product_discount_type' => $this->discount_type[$product_id],
]]);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Pos;
use Livewire\Component;
class Filter extends Component
{
public $categories;
public $category;
public $showCount;
public function mount($categories) {
$this->categories = $categories;
}
public function render() {
return view('livewire.pos.filter');
}
public function updatedCategory() {
$this->emitUp('selectedCategory', $this->category);
}
public function updatedShowCount() {
$this->emitUp('showCount', $this->category);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Http\Livewire\Pos;
use Livewire\Component;
use Livewire\WithPagination;
use Modules\Product\Entities\Product;
class ProductList extends Component
{
protected $listeners = [
'selectedCategory' => 'categoryChanged',
'showCount' => 'showCountChanged'
];
public $categories;
public $products;
public $limit = 9;
public function mount($categories) {
$this->products = Product::limit($this->limit)->get();
$this->categories = $categories;
}
public function render() {
return view('livewire.pos.product-list');
}
public function categoryChanged($category_id) {
if ($category_id == '*') {
$this->products = Product::limit($this->limit)->get();
} else {
$this->products = Product::where('category_id', $category_id)->limit($this->limit)->get();
}
}
public function showCountChanged($value) {
$this->limit = $value;
}
public function selectProduct($product) {
$this->emit('productSelected', $product);
}
}

1
public/css/select2-coreui.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1
public/css/select2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

2
public/js/select2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
resources/js/app.js vendored
View File

@ -3,3 +3,7 @@ require('@coreui/coreui/dist/js/coreui.bundle.min');
require('datatables.net-bs4'); require('datatables.net-bs4');
require('datatables.net-buttons-bs4'); require('datatables.net-buttons-bs4');
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})

View File

@ -17,15 +17,20 @@
<body class="c-app flex-row align-items-center"> <body class="c-app flex-row align-items-center">
<div class="container"> <div class="container">
<div class="row mb-3">
<div class="col-12 d-flex justify-content-center">
<img width="200" src="{{ asset('images/logo-dark.png') }}" alt="Logo">
</div>
</div>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="{{ Route::has('register') ? 'col-md-8' : 'col-md-5' }}">
@if(Session::has('account_deactivated')) @if(Session::has('account_deactivated'))
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
{{ Session::get('account_deactivated') }} {{ Session::get('account_deactivated') }}
</div> </div>
@endif @endif
<div class="card-group"> <div class="card-group">
<div class="card p-4"> <div class="card p-4 border-0 shadow-sm">
<div class="card-body"> <div class="card-body">
<form method="post" action="{{ url('/login') }}"> <form method="post" action="{{ url('/login') }}">
@csrf @csrf
@ -70,6 +75,7 @@
</form> </form>
</div> </div>
</div> </div>
@if(Route::has('register'))
<div class="card text-white bg-primary py-5 d-md-down-none" style="width:44%"> <div class="card text-white bg-primary py-5 d-md-down-none" style="width:44%">
<div class="card-body text-center"> <div class="card-body text-center">
<div> <div>
@ -79,6 +85,7 @@
</div> </div>
</div> </div>
</div> </div>
@endif
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,10 +12,8 @@
<!-- CoreUI CSS --> <!-- CoreUI CSS -->
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous"> <link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
<!-- Font Awesome --> <!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog=="
crossorigin="anonymous"/>
</head> </head>
<body class="c-app flex-row align-items-center"> <body class="c-app flex-row align-items-center">
<div class="container"> <div class="container">
@ -36,7 +34,7 @@
<div class="input-group mb-3"> <div class="input-group mb-3">
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text"> <span class="input-group-text">
<i class="cil-envelope-open"></i> <i class="bi bi-envelope"></i>
</span> </span>
</div> </div>
<input type="email" <input type="email"

View File

@ -0,0 +1,19 @@
<!-- Dropezone CSS -->
<link rel="stylesheet" href="{{ asset('css/dropzone.css') }}">
<!-- CoreUI CSS -->
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
@yield('third_party_stylesheets')
@livewireStyles
@stack('page_css')
<style>
div.dataTables_wrapper div.dataTables_length select {
width: 65px;
display: inline-block;
}
</style>

View File

@ -0,0 +1,11 @@
<script src="{{ mix('js/app.js') }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/1.4.0/perfect-scrollbar.js"></script>
<script src="{{ asset('vendor/datatables/buttons.server-side.js') }}"></script>
@include('sweetalert::alert')
@yield('third_party_scripts')
@livewireScripts
@stack('page_scripts')

View File

@ -5,25 +5,7 @@
<title>@yield('title') || {{ config('app.name') }}</title> <title>@yield('title') || {{ config('app.name') }}</title>
<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'> <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
<!-- Dropezone CSS --> @include('includes.main-css')
<link rel="stylesheet" href="{{ asset('css/dropzone.css') }}">
<!-- CoreUI CSS -->
<link rel="stylesheet" href="{{ mix('css/app.css') }}" crossorigin="anonymous">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
@yield('third_party_stylesheets')
@livewireStyles
@stack('page_css')
<style>
div.dataTables_wrapper div.dataTables_length select {
width: 65px;
display: inline-block;
}
</style>
</head> </head>
<body class="c-app"> <body class="c-app">
@ -46,23 +28,6 @@
@include('layouts.footer') @include('layouts.footer')
</div> </div>
<script src="{{ mix('js/app.js') }}"></script> @include('includes.main-js')
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/1.4.0/perfect-scrollbar.js"></script>
<script src="{{ asset('vendor/datatables/buttons.server-side.js') }}"></script>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
@include('sweetalert::alert')
@yield('third_party_scripts')
@livewireScripts
@stack('page_scripts')
</body> </body>
</html> </html>

View File

@ -11,7 +11,7 @@
</ul> </ul>
<ul class="c-header-nav ml-auto mr-4"> <ul class="c-header-nav ml-auto mr-4">
<li class="c-header-nav-item mr-3"> <li class="c-header-nav-item mr-3">
<a class="btn btn-primary btn-pill" href="#"> <a class="btn btn-primary btn-pill {{ request()->routeIs('app.pos.index') ? 'disabled' : '' }}" href="{{ route('app.pos.index') }}">
<i class="bi bi-cart mr-1"></i> POS System <i class="bi bi-cart mr-1"></i> POS System
</a> </a>
</li> </li>

View File

@ -1,7 +1,8 @@
<div class="c-sidebar c-sidebar-dark c-sidebar-fixed c-sidebar-lg-show" id="sidebar"> <div class="c-sidebar c-sidebar-dark c-sidebar-fixed c-sidebar-lg-show {{ request()->routeIs('app.pos.*') ? 'c-sidebar-minimized' : '' }}" id="sidebar">
<div class="c-sidebar-brand d-md-down-none"> <div class="c-sidebar-brand d-md-down-none">
<a href="{{ route('home') }}"> <a href="{{ route('home') }}">
<img src="{{ asset('images/logo.png') }}" alt="Site Logo" width="110"> <img class="c-sidebar-brand-full" src="{{ asset('images/logo.png') }}" alt="Site Logo" width="110">
<img class="c-sidebar-brand-minimized" src="{{ asset('images/logo.png') }}" alt="Site Logo" width="40">
</a> </a>
</div> </div>
<ul class="c-sidebar-nav"> <ul class="c-sidebar-nav">

View File

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

View File

@ -0,0 +1,147 @@
<div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<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="form-group">
<label for="customer_id">Customer <span class="text-danger">*</span></label>
<div class="input-group">
<div class="input-group-prepend">
<a href="{{ route('customers.create') }}" class="btn btn-primary">
<i class="bi bi-person-plus"></i>
</a>
</div>
<select wire:model="customer_id" id="customer_id" class="form-control">
<option value="" selected>Select Customer</option>
@foreach($customers as $customer)
<option value="{{ $customer->id }}">{{ $customer->customer_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr class="text-center">
<th class="align-middle">Product</th>
<th class="align-middle">Price</th>
<th class="align-middle">Quantity</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->price) }}
</td>
<td class="align-middle">
@include('livewire.includes.product-cart-quantity')
</td>
<td class="align-middle text-center">
<a href="#" wire:click.prevent="removeItem('{{ $cart_item->rowId }}')">
<i class="bi bi-x-circle font-2xl text-danger"></i>
</a>
</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">
<div class="col-md-12">
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th>Order Tax ({{ $global_tax }}%)</th>
<td>(+) {{ format_currency(Cart::instance($cart_instance)->tax()) }}</td>
</tr>
<tr>
<th>Discount ({{ $global_discount }}%)</th>
<td>(-) {{ format_currency(Cart::instance($cart_instance)->discount()) }}</td>
</tr>
<tr>
<th>Shipping</th>
<input type="hidden" value="{{ $shipping }}" name="shipping_amount">
<td>(+) {{ format_currency($shipping) }}</td>
</tr>
<tr class="text-primary">
<th>Grand Total</th>
@php
$total_with_shipping = Cart::instance($cart_instance)->total() + (float) $shipping
@endphp
<th>
(=) {{ format_currency($total_with_shipping) }}
</th>
</tr>
</table>
</div>
</div>
</div>
<div class="form-row">
<div class="col-lg-4">
<div class="form-group">
<label for="tax_percentage">Order Tax (%)</label>
<input wire:model.lazy="global_tax" type="number" class="form-control" 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.lazy="global_discount" type="number" class="form-control" 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.lazy="shipping" type="number" class="form-control" min="0" value="0" required step="0.01">
</div>
</div>
</div>
<div class="form-group d-flex justify-content-center flex-wrap mb-0">
<button wire:click="resetCart" type="button" class="btn btn-pill btn-danger mr-3"><i class="bi bi-x"></i> Reset</button>
<button wire:loading.attr="disabled" wire:click="proceed" type="button" class="btn btn-pill btn-primary" {{ $total_amount == 0 ? 'disabled' : '' }}><i class="bi bi-check"></i> Proceed</button>
</div>
</div>
</div>
{{--Checkout Modal--}}
@include('livewire.pos.includes.checkout-modal')
</div>

View File

@ -0,0 +1,27 @@
<div>
<div class="form-row">
<div class="col-md-7">
<div class="form-group">
<label>Product Category</label>
<select wire:model="category" class="form-control">
<option value="*">All Products</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->category_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-md-5">
<div class="form-group">
<label>Product Count</label>
<select wire:model="showCount" class="form-control">
<option value="9">9 Products</option>
<option value="15">15 Products</option>
<option value="21">21 Products</option>
<option value="30">30 Products</option>
<option value="*">All Products</option>
</select>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,106 @@
<div class="modal fade" id="checkoutModal" tabindex="-1" role="dialog" aria-labelledby="checkoutModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="checkoutModalLabel">
<i class="bi bi-cart-check text-primary"></i> Confirm Sale
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="checkout-form" action="{{ route('app.pos.store') }}" method="POST">
@csrf
<div class="modal-body">
@if (session()->has('checkout_message'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<div class="alert-body">
<span>{{ session('checkout_message') }}</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
</div>
@endif
<div class="row">
<div class="col-lg-7">
<input type="hidden" value="{{ $customer_id }}" name="customer_id">
<input type="hidden" value="{{ $global_tax }}" name="tax_percentage">
<input type="hidden" value="{{ $global_discount }}" name="discount_percentage">
<input type="hidden" value="{{ $shipping }}" name="shipping_amount">
<div class="form-row">
<div class="col-lg-6">
<div class="form-group">
<label for="total_amount">Total Amount <span class="text-danger">*</span></label>
<input id="total_amount" type="text" class="form-control" name="total_amount" value="{{ $total_amount }}" readonly required>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<label for="paid_amount">Received Amount <span class="text-danger">*</span></label>
<input id="paid_amount" type="text" class="form-control" name="paid_amount" value="{{ $total_amount }}" required>
</div>
</div>
</div>
<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 class="form-group">
<label for="note">Note (If Needed)</label>
<textarea name="note" id="note" rows="5" class="form-control"></textarea>
</div>
</div>
<div class="col-lg-5">
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th>Total Products</th>
<td>
<span class="badge badge-success">
{{ Cart::instance($cart_instance)->count() }}
</span>
</td>
</tr>
<tr>
<th>Order Tax ({{ $global_tax }}%)</th>
<td>(+) {{ format_currency(Cart::instance($cart_instance)->tax()) }}</td>
</tr>
<tr>
<th>Discount ({{ $global_discount }}%)</th>
<td>(-) {{ format_currency(Cart::instance($cart_instance)->discount()) }}</td>
</tr>
<tr>
<th>Shipping</th>
<input type="hidden" value="{{ $shipping }}" name="shipping_amount">
<td>(+) {{ format_currency($shipping) }}</td>
</tr>
<tr class="text-primary">
<th>Grand Total</th>
@php
$total_with_shipping = Cart::instance($cart_instance)->total() + (float) $shipping
@endphp
<th>
(=) {{ format_currency($total_with_shipping) }}
</th>
</tr>
</table>
</div>
</div>
</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">Submit</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,39 @@
<div>
<div class="card border-0 shadow-sm mt-3">
<div class="card-body">
<livewire:pos.filter :categories="$categories"/>
<div class="row position-relative">
<div wire:loading.flex class="col-12 position-absolute justify-content-center align-items-center" style="top:0;right:0;left:0;bottom:0;background-color: rgba(255,255,255,0.5);z-index: 99;">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
@forelse($products as $product)
<div wire:click.prevent="selectProduct({{ $product }})" class="col-lg-4 col-md-6" style="cursor: pointer;">
<div class="card border-0 shadow">
<div class="position-relative">
<img height="140" src="{{ $product->getFirstMediaUrl('images') }}" class="card-img-top" alt="Product Image">
<div class="badge badge-info mb-3 position-absolute" style="left:10px;top: 10px;">Stock: {{ $product->product_quantity }}</div>
</div>
<div class="card-body">
<div class="mb-2">
<h6 style="font-size: 13px;" class="card-title mb-0">{{ $product->product_name }}</h6>
<span class="badge badge-success">
{{ $product->product_code }}
</span>
</div>
<p class="card-text font-weight-bold">{{ format_currency($product->product_price) }}</p>
</div>
</div>
</div>
@empty
<div class="col-12">
<div class="alert alert-warning mb-0">
Products Not Found...
</div>
</div>
@endforelse
</div>
</div>
</div>
</div>

View File

@ -10,7 +10,12 @@
</div> </div>
</div> </div>
@endif @endif
<div class="table-responsive"> <div class="table-responsive position-relative">
<div wire:loading.flex class="col-12 position-absolute justify-content-center align-items-center" style="top:0;right:0;left:0;bottom:0;background-color: rgba(255,255,255,0.5);z-index: 99;">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<table class="table table-bordered"> <table class="table table-bordered">
<thead class="thead-dark"> <thead class="thead-dark">
<tr> <tr>
@ -116,19 +121,19 @@
<div class="col-lg-4"> <div class="col-lg-4">
<div class="form-group"> <div class="form-group">
<label for="tax_percentage">Order Tax (%)</label> <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> <input wire:model.lazy="global_tax" type="number" class="form-control" name="tax_percentage" min="0" max="100" value="{{ $global_tax }}" required>
</div> </div>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-4">
<div class="form-group"> <div class="form-group">
<label for="discount_percentage">Discount (%)</label> <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> <input wire:model.lazy="global_discount" type="number" class="form-control" name="discount_percentage" min="0" max="100" value="{{ $global_discount }}" required>
</div> </div>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-4">
<div class="form-group"> <div class="form-group">
<label for="shipping_amount">Shipping</label> <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"> <input wire:model.lazy="shipping" type="number" class="form-control" name="shipping_amount" min="0" value="0" required step="0.01">
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<div class="position-relative"> <div class="position-relative">
<div class="card mb-0"> <div class="card mb-0 border-0 shadow-sm">
<div class="card-body"> <div class="card-body">
<div class="form-group mb-0"> <div class="form-group mb-0">
<div class="input-group"> <div class="input-group">

View File

@ -12,7 +12,7 @@ Route::get('/', function () {
return view('auth.login'); return view('auth.login');
})->middleware('guest'); })->middleware('guest');
Auth::routes(); Auth::routes(['register' => false]);
Route::group(['middleware' => 'auth'], function () { Route::group(['middleware' => 'auth'], function () {
Route::get('/home', 'HomeController@index') Route::get('/home', 'HomeController@index')