Skip to content

Commit 9c1bd45

Browse files
committed
Update
- Added: Low Stock Reports - Added: New Permissions
1 parent de9b349 commit 9c1bd45

29 files changed

+609
-2093
lines changed

app/Console/Kernel.php

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Console;
44

55
use App\Jobs\ClearHoldOrdersJob;
6+
use App\Jobs\DetectLowStockProductsJob;
67
use App\Jobs\ExecuteExpensesJob;
78
use App\Jobs\PurgeOrderStorageJob;
89
use App\Jobs\StockProcurementJob;
@@ -34,16 +35,57 @@ class Kernel extends ConsoleKernel
3435
protected function schedule(Schedule $schedule)
3536
{
3637
$schedule->command( 'telescope:prune' )->daily();
38+
39+
/**
40+
* Will check hourly if the script
41+
* can perform asynchronous tasks.
42+
*/
3743
$schedule->job( new TaskSchedulingPingJob )->hourly();
44+
45+
/**
46+
* Will execute expenses job daily.
47+
*/
3848
$schedule->job( new ExecuteExpensesJob )->daily( '00:01' );
39-
$schedule->job( new StockProcurementJob() )->daily( '00:05' );
49+
50+
/**
51+
* Will check procurement awaiting automatic
52+
* stocking to update their status.
53+
*/
54+
$schedule->job( new StockProcurementJob() )->daily( '00:05' );
55+
56+
/**
57+
* Will purge stoarge orders daily.
58+
*/
4059
$schedule->job( new PurgeOrderStorageJob )->daily( '15:00' );
60+
61+
/**
62+
* Will clear hold orders that has expired.
63+
*/
4164
$schedule->job( new ClearHoldOrdersJob )->dailyAt( '14:00' );
42-
$schedule->job( new TrackLaidAwayOrdersJob )->dailyAt( '13:00' ); // we don't want all job to run daily at the same time
4365

66+
/**
67+
* Will detect products that has reached the threashold of
68+
* low inventory to trigger a notification and an event.
69+
*/
70+
$schedule->job( new DetectLowStockProductsJob )->dailyAt( '00:02' );
71+
72+
/**
73+
* Will track orders saved with instalment and
74+
* trigger relevant notifications.
75+
*/
76+
$schedule->job( new TrackLaidAwayOrdersJob )->dailyAt( '13:00' );
77+
78+
/**
79+
* @var ModulesService
80+
*/
4481
$modules = app()->make( ModulesService::class );
4582

83+
/**
84+
* We want to make sure Modules Kernel get injected
85+
* on the process so that modules jobs can also be scheduled.
86+
*/
4687
collect( $modules->getEnabled() )->each( function( $module ) use ( $schedule ) {
88+
4789
$filePath = $module[ 'path' ] . 'Console' . DIRECTORY_SEPARATOR . 'Kernel.php';
4890

4991
if ( is_file( $filePath ) ) {

app/Crud/ProductCrud.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ public function getForm( $entry = null )
164164
'label' => __( 'Sale Price' ),
165165
'description' => __( 'Define the regular selling price.' ),
166166
'validation' => 'required',
167+
], [
168+
'type' => 'number',
169+
'errors' => [],
170+
'name' => 'low_quantity',
171+
'label' => __( 'Low Quantity' ),
172+
'description' => __( 'Which quantity should be assumed low.' ),
173+
], [
174+
'type' => 'switch',
175+
'errors' => [],
176+
'name' => 'stock_alert_enabled',
177+
'label' => __( 'Stock Alert' ),
178+
'options' => Helper::kvToJsOptions([ __( 'No' ), __( 'Yes' ) ]),
179+
'description' => __( 'Define whether the stock alert should be enabled for this unit.' ),
167180
], [
168181
'type' => 'number',
169182
'errors' => [],

app/Crud/ProviderProductsCrud.php

Lines changed: 2 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -154,107 +154,8 @@ public function getForm( $entry = null )
154154
'general' => [
155155
'label' => __( 'General' ),
156156
'fields' => [
157-
[
158-
'type' => 'text',
159-
'name' => 'id',
160-
'label' => __( 'Id' ),
161-
'value' => $entry->id ?? '',
162-
], [
163-
'type' => 'text',
164-
'name' => 'name',
165-
'label' => __( 'Name' ),
166-
'value' => $entry->name ?? '',
167-
], [
168-
'type' => 'text',
169-
'name' => 'gross_purchase_price',
170-
'label' => __( 'Gross_purchase_price' ),
171-
'value' => $entry->gross_purchase_price ?? '',
172-
], [
173-
'type' => 'text',
174-
'name' => 'net_purchase_price',
175-
'label' => __( 'Net_purchase_price' ),
176-
'value' => $entry->net_purchase_price ?? '',
177-
], [
178-
'type' => 'text',
179-
'name' => 'procurement_id',
180-
'label' => __( 'Procurement_id' ),
181-
'value' => $entry->procurement_id ?? '',
182-
], [
183-
'type' => 'text',
184-
'name' => 'product_id',
185-
'label' => __( 'Product_id' ),
186-
'value' => $entry->product_id ?? '',
187-
], [
188-
'type' => 'text',
189-
'name' => 'purchase_price',
190-
'label' => __( 'Purchase_price' ),
191-
'value' => $entry->purchase_price ?? '',
192-
], [
193-
'type' => 'text',
194-
'name' => 'quantity',
195-
'label' => __( 'Quantity' ),
196-
'value' => $entry->quantity ?? '',
197-
], [
198-
'type' => 'text',
199-
'name' => 'available_quantity',
200-
'label' => __( 'Available_quantity' ),
201-
'value' => $entry->available_quantity ?? '',
202-
], [
203-
'type' => 'text',
204-
'name' => 'tax_group_id',
205-
'label' => __( 'Tax_group_id' ),
206-
'value' => $entry->tax_group_id ?? '',
207-
], [
208-
'type' => 'text',
209-
'name' => 'barcode',
210-
'label' => __( 'Barcode' ),
211-
'value' => $entry->barcode ?? '',
212-
], [
213-
'type' => 'text',
214-
'name' => 'expiration_date',
215-
'label' => __( 'Expiration_date' ),
216-
'value' => $entry->expiration_date ?? '',
217-
], [
218-
'type' => 'text',
219-
'name' => 'tax_type',
220-
'label' => __( 'Tax_type' ),
221-
'value' => $entry->tax_type ?? '',
222-
], [
223-
'type' => 'text',
224-
'name' => 'tax_value',
225-
'label' => __( 'Tax_value' ),
226-
'value' => $entry->tax_value ?? '',
227-
], [
228-
'type' => 'text',
229-
'name' => 'total_purchase_price',
230-
'label' => __( 'Total_purchase_price' ),
231-
'value' => $entry->total_purchase_price ?? '',
232-
], [
233-
'type' => 'text',
234-
'name' => 'unit_id',
235-
'label' => __( 'Unit_id' ),
236-
'value' => $entry->unit_id ?? '',
237-
], [
238-
'type' => 'text',
239-
'name' => 'author',
240-
'label' => __( 'Author' ),
241-
'value' => $entry->author ?? '',
242-
], [
243-
'type' => 'text',
244-
'name' => 'uuid',
245-
'label' => __( 'Uuid' ),
246-
'value' => $entry->uuid ?? '',
247-
], [
248-
'type' => 'text',
249-
'name' => 'created_at',
250-
'label' => __( 'Created_at' ),
251-
'value' => $entry->created_at ?? '',
252-
], [
253-
'type' => 'text',
254-
'name' => 'updated_at',
255-
'label' => __( 'Updated_at' ),
256-
'value' => $entry->updated_at ?? '',
257-
], ]
157+
// ...
158+
]
258159
]
259160
]
260161
];
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace App\Events;
4+
5+
use Illuminate\Broadcasting\Channel;
6+
use Illuminate\Broadcasting\InteractsWithSockets;
7+
use Illuminate\Broadcasting\PresenceChannel;
8+
use Illuminate\Broadcasting\PrivateChannel;
9+
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
10+
use Illuminate\Foundation\Events\Dispatchable;
11+
use Illuminate\Queue\SerializesModels;
12+
13+
class LowStockProductsCountedEvent
14+
{
15+
use Dispatchable, InteractsWithSockets, SerializesModels;
16+
17+
/**
18+
* Create a new event instance.
19+
*
20+
* @return void
21+
*/
22+
public function __construct()
23+
{
24+
//
25+
}
26+
27+
/**
28+
* Get the channels the event should broadcast on.
29+
*
30+
* @return \Illuminate\Broadcasting\Channel|array
31+
*/
32+
public function broadcastOn()
33+
{
34+
return new PrivateChannel('channel-name');
35+
}
36+
}

app/Exceptions/ModuleVersionMismatchException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class ModuleVersionMismatchException extends Exception
99
public function render( $message )
1010
{
1111
$message = $this->getMessage();
12-
$title = $this->title ?: __( 'Module Version Mismatch' );
12+
$title = $this->title ?? __( 'Module Version Mismatch' );
1313
return response()->view( 'pages.errors.module-exception', compact( 'message', 'title' ), 500 );
1414
}
1515
}

app/Http/Controllers/Dashboard/ReportsController.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ public function soldStock()
6161
]);
6262
}
6363

64+
public function lowStockReport()
65+
{
66+
return $this->view( 'pages.dashboard.reports.low-stock-report', [
67+
'title' => __( 'Low Stock Report' ),
68+
'description' => __( 'Provides an overview of the product which stock are low.' )
69+
]);
70+
}
71+
6472
public function profit()
6573
{
6674
return $this->view( 'pages.dashboard.reports.profit-report', [
@@ -268,4 +276,9 @@ public function getMyReport( Request $request )
268276
{
269277
return $this->reportService->getCashierDashboard( Auth::id() );
270278
}
279+
280+
public function getLowStock( Request $request )
281+
{
282+
return $this->reportService->getLowStockProducts();
283+
}
271284
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Events\LowStockProductsCountedEvent;
6+
use App\Models\ProductUnitQuantity;
7+
use App\Models\Role;
8+
use App\Services\NotificationService;
9+
use Illuminate\Bus\Queueable;
10+
use Illuminate\Contracts\Queue\ShouldBeUnique;
11+
use Illuminate\Contracts\Queue\ShouldQueue;
12+
use Illuminate\Foundation\Bus\Dispatchable;
13+
use Illuminate\Queue\InteractsWithQueue;
14+
use Illuminate\Queue\SerializesModels;
15+
16+
class DetectLowStockProductsJob implements ShouldQueue
17+
{
18+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
19+
20+
/**
21+
* Create a new job instance.
22+
*
23+
* @return void
24+
*/
25+
public function __construct()
26+
{
27+
//
28+
}
29+
30+
/**
31+
* Execute the job.
32+
*
33+
* @return void
34+
*/
35+
public function handle()
36+
{
37+
$products = ProductUnitQuantity::stockAlertEnabled()
38+
->whereRaw( 'low_quantity > quantity' )
39+
->count();
40+
41+
if ( $products > 0 ) {
42+
LowStockProductsCountedEvent::dispatch();
43+
44+
/**
45+
* @var NotificationService
46+
*/
47+
$notificationService = app()->make( NotificationService::class );
48+
$notificationService->create([
49+
'title' => __( 'Low Stock Alert' ),
50+
'description' => sprintf(
51+
__( '%s product(s) has low stock. Check those products to reorder them before the stock reach zero.' ),
52+
$products
53+
),
54+
'identifier' => 'ns.low-stock-products',
55+
'url' => ns()->route( 'ns.dashboard.reports-low-stock' ),
56+
])->dispatchForGroupNamespaces([
57+
Role::ADMIN,
58+
Role::STOREADMIN
59+
]);
60+
}
61+
}
62+
}

app/Models/ProductUnitQuantity.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,9 @@ public function scopeWithProduct( Builder $query, $id )
4848
{
4949
return $query->where( 'product_id', $id );
5050
}
51+
52+
public function scopeStockAlertEnabled( Builder $query )
53+
{
54+
return $query->where( 'stock_alert_enabled', true );
55+
}
5156
}

app/Services/MenuService.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,10 +360,15 @@ public function buildMenus()
360360
'href' => ns()->url( '/dashboard/reports/sales' )
361361
],
362362
'products-report' => [
363-
'label' => __( 'Products Report' ),
363+
'label' => __( 'Best Sales' ),
364364
'permissions' => [ 'nexopos.reports.products-report' ],
365365
'href' => ns()->url( '/dashboard/reports/products-report' )
366366
],
367+
'low-stock' => [
368+
'label' => __( 'Low Stock Report' ),
369+
'permissions' => [ 'nexopos.reports.low-stock' ],
370+
'href' => ns()->url( '/dashboard/reports/low-stock' )
371+
],
367372
'sold-stock' => [
368373
'label' => __( 'Sold Stock' ),
369374
'href' => ns()->url( '/dashboard/reports/sold-stock' )

app/Services/ModulesService.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,11 @@ public function dependenciesCheck( $module = null )
422422
'<'
423423
)
424424
) {
425+
$this->disable( $module[ 'namespace' ] );
426+
425427
throw new ModuleVersionMismatchException( __(
426428
sprintf(
427-
__( 'The module "%s" has been disabled it\'s not compatible with the current version of NexoPOS %s, but requires %s. ' ),
429+
__( 'The module "%s" has been disabled as it\'s not compatible with the current version of NexoPOS %s, but requires %s. ' ),
428430
$module[ 'name' ],
429431
config( 'nexopos.version' ),
430432
$module[ 'core' ][ 'min-version' ]

0 commit comments

Comments
 (0)