Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
- Added: Low Stock Reports
- Added: New Permissions
  • Loading branch information
Blair2004 committed Nov 6, 2021
1 parent de9b349 commit 9c1bd45
Show file tree
Hide file tree
Showing 29 changed files with 609 additions and 2,093 deletions.
46 changes: 44 additions & 2 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Console;

use App\Jobs\ClearHoldOrdersJob;
use App\Jobs\DetectLowStockProductsJob;
use App\Jobs\ExecuteExpensesJob;
use App\Jobs\PurgeOrderStorageJob;
use App\Jobs\StockProcurementJob;
Expand Down Expand Up @@ -34,16 +35,57 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule)
{
$schedule->command( 'telescope:prune' )->daily();

/**
* Will check hourly if the script
* can perform asynchronous tasks.
*/
$schedule->job( new TaskSchedulingPingJob )->hourly();

/**
* Will execute expenses job daily.
*/
$schedule->job( new ExecuteExpensesJob )->daily( '00:01' );
$schedule->job( new StockProcurementJob() )->daily( '00:05' );

/**
* Will check procurement awaiting automatic
* stocking to update their status.
*/
$schedule->job( new StockProcurementJob() )->daily( '00:05' );

/**
* Will purge stoarge orders daily.
*/
$schedule->job( new PurgeOrderStorageJob )->daily( '15:00' );

/**
* Will clear hold orders that has expired.
*/
$schedule->job( new ClearHoldOrdersJob )->dailyAt( '14:00' );
$schedule->job( new TrackLaidAwayOrdersJob )->dailyAt( '13:00' ); // we don't want all job to run daily at the same time

/**
* Will detect products that has reached the threashold of
* low inventory to trigger a notification and an event.
*/
$schedule->job( new DetectLowStockProductsJob )->dailyAt( '00:02' );

/**
* Will track orders saved with instalment and
* trigger relevant notifications.
*/
$schedule->job( new TrackLaidAwayOrdersJob )->dailyAt( '13:00' );

/**
* @var ModulesService
*/
$modules = app()->make( ModulesService::class );

/**
* We want to make sure Modules Kernel get injected
* on the process so that modules jobs can also be scheduled.
*/
collect( $modules->getEnabled() )->each( function( $module ) use ( $schedule ) {

$filePath = $module[ 'path' ] . 'Console' . DIRECTORY_SEPARATOR . 'Kernel.php';

if ( is_file( $filePath ) ) {
Expand Down
13 changes: 13 additions & 0 deletions app/Crud/ProductCrud.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,19 @@ public function getForm( $entry = null )
'label' => __( 'Sale Price' ),
'description' => __( 'Define the regular selling price.' ),
'validation' => 'required',
], [
'type' => 'number',
'errors' => [],
'name' => 'low_quantity',
'label' => __( 'Low Quantity' ),
'description' => __( 'Which quantity should be assumed low.' ),
], [
'type' => 'switch',
'errors' => [],
'name' => 'stock_alert_enabled',
'label' => __( 'Stock Alert' ),
'options' => Helper::kvToJsOptions([ __( 'No' ), __( 'Yes' ) ]),
'description' => __( 'Define whether the stock alert should be enabled for this unit.' ),
], [
'type' => 'number',
'errors' => [],
Expand Down
103 changes: 2 additions & 101 deletions app/Crud/ProviderProductsCrud.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,107 +154,8 @@ public function getForm( $entry = null )
'general' => [
'label' => __( 'General' ),
'fields' => [
[
'type' => 'text',
'name' => 'id',
'label' => __( 'Id' ),
'value' => $entry->id ?? '',
], [
'type' => 'text',
'name' => 'name',
'label' => __( 'Name' ),
'value' => $entry->name ?? '',
], [
'type' => 'text',
'name' => 'gross_purchase_price',
'label' => __( 'Gross_purchase_price' ),
'value' => $entry->gross_purchase_price ?? '',
], [
'type' => 'text',
'name' => 'net_purchase_price',
'label' => __( 'Net_purchase_price' ),
'value' => $entry->net_purchase_price ?? '',
], [
'type' => 'text',
'name' => 'procurement_id',
'label' => __( 'Procurement_id' ),
'value' => $entry->procurement_id ?? '',
], [
'type' => 'text',
'name' => 'product_id',
'label' => __( 'Product_id' ),
'value' => $entry->product_id ?? '',
], [
'type' => 'text',
'name' => 'purchase_price',
'label' => __( 'Purchase_price' ),
'value' => $entry->purchase_price ?? '',
], [
'type' => 'text',
'name' => 'quantity',
'label' => __( 'Quantity' ),
'value' => $entry->quantity ?? '',
], [
'type' => 'text',
'name' => 'available_quantity',
'label' => __( 'Available_quantity' ),
'value' => $entry->available_quantity ?? '',
], [
'type' => 'text',
'name' => 'tax_group_id',
'label' => __( 'Tax_group_id' ),
'value' => $entry->tax_group_id ?? '',
], [
'type' => 'text',
'name' => 'barcode',
'label' => __( 'Barcode' ),
'value' => $entry->barcode ?? '',
], [
'type' => 'text',
'name' => 'expiration_date',
'label' => __( 'Expiration_date' ),
'value' => $entry->expiration_date ?? '',
], [
'type' => 'text',
'name' => 'tax_type',
'label' => __( 'Tax_type' ),
'value' => $entry->tax_type ?? '',
], [
'type' => 'text',
'name' => 'tax_value',
'label' => __( 'Tax_value' ),
'value' => $entry->tax_value ?? '',
], [
'type' => 'text',
'name' => 'total_purchase_price',
'label' => __( 'Total_purchase_price' ),
'value' => $entry->total_purchase_price ?? '',
], [
'type' => 'text',
'name' => 'unit_id',
'label' => __( 'Unit_id' ),
'value' => $entry->unit_id ?? '',
], [
'type' => 'text',
'name' => 'author',
'label' => __( 'Author' ),
'value' => $entry->author ?? '',
], [
'type' => 'text',
'name' => 'uuid',
'label' => __( 'Uuid' ),
'value' => $entry->uuid ?? '',
], [
'type' => 'text',
'name' => 'created_at',
'label' => __( 'Created_at' ),
'value' => $entry->created_at ?? '',
], [
'type' => 'text',
'name' => 'updated_at',
'label' => __( 'Updated_at' ),
'value' => $entry->updated_at ?? '',
], ]
// ...
]
]
]
];
Expand Down
36 changes: 36 additions & 0 deletions app/Events/LowStockProductsCountedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class LowStockProductsCountedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
2 changes: 1 addition & 1 deletion app/Exceptions/ModuleVersionMismatchException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ModuleVersionMismatchException extends Exception
public function render( $message )
{
$message = $this->getMessage();
$title = $this->title ?: __( 'Module Version Mismatch' );
$title = $this->title ?? __( 'Module Version Mismatch' );
return response()->view( 'pages.errors.module-exception', compact( 'message', 'title' ), 500 );
}
}
13 changes: 13 additions & 0 deletions app/Http/Controllers/Dashboard/ReportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ public function soldStock()
]);
}

public function lowStockReport()
{
return $this->view( 'pages.dashboard.reports.low-stock-report', [
'title' => __( 'Low Stock Report' ),
'description' => __( 'Provides an overview of the product which stock are low.' )
]);
}

public function profit()
{
return $this->view( 'pages.dashboard.reports.profit-report', [
Expand Down Expand Up @@ -268,4 +276,9 @@ public function getMyReport( Request $request )
{
return $this->reportService->getCashierDashboard( Auth::id() );
}

public function getLowStock( Request $request )
{
return $this->reportService->getLowStockProducts();
}
}
62 changes: 62 additions & 0 deletions app/Jobs/DetectLowStockProductsJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace App\Jobs;

use App\Events\LowStockProductsCountedEvent;
use App\Models\ProductUnitQuantity;
use App\Models\Role;
use App\Services\NotificationService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class DetectLowStockProductsJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$products = ProductUnitQuantity::stockAlertEnabled()
->whereRaw( 'low_quantity > quantity' )
->count();

if ( $products > 0 ) {
LowStockProductsCountedEvent::dispatch();

/**
* @var NotificationService
*/
$notificationService = app()->make( NotificationService::class );
$notificationService->create([
'title' => __( 'Low Stock Alert' ),
'description' => sprintf(
__( '%s product(s) has low stock. Check those products to reorder them before the stock reach zero.' ),
$products
),
'identifier' => 'ns.low-stock-products',
'url' => ns()->route( 'ns.dashboard.reports-low-stock' ),
])->dispatchForGroupNamespaces([
Role::ADMIN,
Role::STOREADMIN
]);
}
}
}
5 changes: 5 additions & 0 deletions app/Models/ProductUnitQuantity.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ public function scopeWithProduct( Builder $query, $id )
{
return $query->where( 'product_id', $id );
}

public function scopeStockAlertEnabled( Builder $query )
{
return $query->where( 'stock_alert_enabled', true );
}
}
7 changes: 6 additions & 1 deletion app/Services/MenuService.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,15 @@ public function buildMenus()
'href' => ns()->url( '/dashboard/reports/sales' )
],
'products-report' => [
'label' => __( 'Products Report' ),
'label' => __( 'Best Sales' ),
'permissions' => [ 'nexopos.reports.products-report' ],
'href' => ns()->url( '/dashboard/reports/products-report' )
],
'low-stock' => [
'label' => __( 'Low Stock Report' ),
'permissions' => [ 'nexopos.reports.low-stock' ],
'href' => ns()->url( '/dashboard/reports/low-stock' )
],
'sold-stock' => [
'label' => __( 'Sold Stock' ),
'href' => ns()->url( '/dashboard/reports/sold-stock' )
Expand Down
4 changes: 3 additions & 1 deletion app/Services/ModulesService.php
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,11 @@ public function dependenciesCheck( $module = null )
'<'
)
) {
$this->disable( $module[ 'namespace' ] );

throw new ModuleVersionMismatchException( __(
sprintf(
__( 'The module "%s" has been disabled it\'s not compatible with the current version of NexoPOS %s, but requires %s. ' ),
__( 'The module "%s" has been disabled as it\'s not compatible with the current version of NexoPOS %s, but requires %s. ' ),
$module[ 'name' ],
config( 'nexopos.version' ),
$module[ 'core' ][ 'min-version' ]
Expand Down
3 changes: 3 additions & 0 deletions app/Services/ProductService.php
Original file line number Diff line number Diff line change
Expand Up @@ -581,9 +581,12 @@ private function __computeUnitQuantities( $fields, $product )
* explicitely how everything is saved here.
*/
$unitQuantity->sale_price = $this->currency->define( $group[ 'sale_price_edit' ] )->getRaw();
$unitQuantity->sale_price = $this->currency->define( $group[ 'sale_price_edit' ] )->getRaw();
$unitQuantity->sale_price_edit = $this->currency->define( $group[ 'sale_price_edit' ] )->getRaw();
$unitQuantity->wholesale_price_edit = $this->currency->define( $group[ 'wholesale_price_edit' ] )->getRaw();
$unitQuantity->preview_url = $group[ 'preview_url' ] ?? '';
$unitQuantity->low_quantity = $group[ 'low_quantity' ];
$unitQuantity->stock_alert_enabled = $group[ 'stock_alert_enabled' ];

/**
* Let's compute the tax only
Expand Down
Loading

0 comments on commit 9c1bd45

Please sign in to comment.