Added: Sales Module
This commit is contained in:
parent
6bcfcc92bc
commit
ef77acd2f0
|
@ -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>
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'Sale'
|
||||
];
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
});
|
|
@ -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');
|
||||
|
||||
});
|
|
@ -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\\": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "Sale",
|
||||
"alias": "sale",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Sale\\Providers\\SaleServiceProvider"
|
||||
],
|
||||
"aliases": {},
|
||||
"files": [],
|
||||
"requires": []
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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">
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -72,7 +72,7 @@ return [
|
|||
|
||||
'decimal_point' => '.',
|
||||
|
||||
'thousand_separator' => ',',
|
||||
'thousand_separator' => '',
|
||||
|
||||
],
|
||||
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
"Expense": true,
|
||||
"People": true,
|
||||
"Currency": true,
|
||||
"Setting": true
|
||||
"Setting": true,
|
||||
"Sale": true
|
||||
}
|
|
@ -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="#">
|
||||
|
|
|
@ -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">×</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>
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue