Skip to content
Merged

Dev #588

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Http/Controllers/ActionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ public function edit(int $id)
);

// Get the action
$action = Action::find($id);
$action = Action::query()->find($id);

// Control not found
abort_if($action === null, Response::HTTP_NOT_FOUND, '404 Not Found');
Expand Down
92 changes: 59 additions & 33 deletions app/Http/Controllers/ControlController.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ public function store(Request $request)
'scope' => 'max:32',
'objective' => 'required',
'plan_date' => 'required',
'periodicity' => 'required|integer',
'periodicity' => 'required|integer|in:-1,0,1,3,6,12',
]
);

Expand Down Expand Up @@ -791,17 +791,31 @@ public function history()
foreach ($controls as $control) {
$expandedControls->push($control);

if (($control->realisation_date === null) &&
($control->periodicity > 0) && ($control->periodicity <= 12)) {
for ($i = 1; $i <= 12 / $control->periodicity; $i++) {
$repeatedControl = clone $control;
$repeatedControl->id = null;
$repeatedControl->score = null;
$repeatedControl->observations = null;
$repeatedControl->realisation_date = null;
$repeatedControl->plan_date = Carbon::parse($control->plan_date)->addMonthsNoOverflow($i * $control->periodicity);
$expandedControls->push($repeatedControl);
if ($control->realisation_date === null) {
if ($control->periodicity === -1) {
// weekly
for ($i = 1; $i <= 52; $i++) {
$repeatedControl = clone $control;
$repeatedControl->id = null;
$repeatedControl->score = null;
$repeatedControl->observations = null;
$repeatedControl->realisation_date = null;
$repeatedControl->plan_date = Carbon::parse($control->plan_date)->addDays($i * 7);
$expandedControls->push($repeatedControl);
}
}
else if (($control->periodicity > 0) && ($control->periodicity <= 12)) {
// Monthly
for ($i = 1; $i <= 12 / $control->periodicity; $i++) {
$repeatedControl = clone $control;
$repeatedControl->id = null;
$repeatedControl->score = null;
$repeatedControl->observations = null;
$repeatedControl->realisation_date = null;
$repeatedControl->plan_date = Carbon::parse($control->plan_date)->addMonthsNoOverflow($i * $control->periodicity);
$expandedControls->push($repeatedControl);
}
}
}
}
// Return view with controls
Expand Down Expand Up @@ -1285,7 +1299,7 @@ public function doPlan(Request $request)
// Validate fields
$this->validate($request, [
'plan_date' => 'required',
'periodicity' => 'required',
'periodicity' => 'required|integer|in:-1,0,1,3,6,12',
]);

// Find the control
Expand Down Expand Up @@ -1357,10 +1371,19 @@ public function make(Request $request)
$next_date = null;
} else {
// Computer next Date
$next_date =
Carbon::createFromFormat('Y-m-d', $control->plan_date)
->addMonthsNoOverflow($control->periodicity)
->format('Y-m-d');
if ($control->periodicity === -1) {
// One week
$next_date =
Carbon::createFromFormat('Y-m-d', $control->plan_date)
->addDays(7)
->format('Y-m-d');
}
else
// Add months
$next_date =
Carbon::createFromFormat('Y-m-d', $control->plan_date)
->addMonthsNoOverflow($control->periodicity)
->format('Y-m-d');
}

// return view
Expand Down Expand Up @@ -1461,16 +1484,19 @@ public function doMake(Request $request)
$new_control->score = null;
$new_control->status = 0;
// only admin and user can update the plan_date, realisation_date and action_plan
if (Auth::User()->role === 1 || Auth::User()->role === 2) {
if (Auth::User()->isAdmin() || Auth::User()->isUser()) {
$new_control->plan_date = request('next_date');
} else {
$new_control->plan_date = date(
'Y-m-d',
strtotime(
$control->periodicity . ' months',
strtotime($control->plan_date)
)
);
if ($control->periodicity === -1)
// One week
$new_control->plan_date = Carbon::parse($control->plan_date)
->addDays(7)
->toDateString();
else
// Months
$new_control->plan_date = Carbon::parse($control->plan_date)
->addMonths($control->periodicity)
->toDateString();
Comment on lines +1490 to +1499
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify monthly increment method consistency in ControlController scheduling paths.
rg -n "addMonthsNoOverflow|addMonths\(" app/Http/Controllers/ControlController.php

Repository: dbarzin/deming

Length of output: 440


🏁 Script executed:

#!/bin/bash
# Get context around all addMonths/addMonthsNoOverflow calls to understand usage patterns
echo "=== Context around line 194 ==="
sed -n '185,210p' app/Http/Controllers/ControlController.php

echo -e "\n=== Context around line 815 ==="
sed -n '810,820p' app/Http/Controllers/ControlController.php

echo -e "\n=== Context around line 1385 ==="
sed -n '1380,1390p' app/Http/Controllers/ControlController.php

echo -e "\n=== Context around line 1498 ==="
sed -n '1490,1505p' app/Http/Controllers/ControlController.php

Repository: dbarzin/deming

Length of output: 2622


Use addMonthsNoOverflow() here to maintain consistency with other scheduling paths.

Line 1498 currently uses addMonths(), while the same scheduling operation at line 1385 and line 815 uses addMonthsNoOverflow(). This inconsistency can produce different next dates (particularly for end-of-month dates), causing schedule drift across different code paths.

🐛 Proposed fix
-                        $new_control->plan_date = Carbon::parse($control->plan_date)
-                            ->addMonths($control->periodicity)
-                            ->toDateString();
+                        $new_control->plan_date = Carbon::parse($control->plan_date)
+                            ->addMonthsNoOverflow($control->periodicity)
+                            ->toDateString();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/ControlController.php` around lines 1490 - 1499, The
scheduling branch that sets $new_control->plan_date for monthly periodicity uses
Carbon::parse($control->plan_date)->addMonths($control->periodicity)->toDateString(),
which is inconsistent with other scheduling paths; replace addMonths(...) with
addMonthsNoOverflow(...) so the code uses
Carbon::parse($control->plan_date)->addMonthsNoOverflow($control->periodicity)->toDateString()
to avoid end-of-month drift and match the other occurrences that use
addMonthsNoOverflow().

}
$new_control->save();

Expand Down Expand Up @@ -1505,13 +1531,13 @@ public function save(Request $request)
{
// Only for CISO
abort_if(
Auth::User()->role !== 1,
! Auth::User()->isAdmin(),
Response::HTTP_FORBIDDEN,
'403 Forbidden'
);

// Get the control
$control = Control::find($request->id);
$control = Control::query()->find($request->id);

// Control not found
abort_if($control === null, Response::HTTP_NOT_FOUND, '404 Not Found');
Expand All @@ -1523,7 +1549,7 @@ public function save(Request $request)
'scope' => 'max:32',
'objective' => 'required',
'plan_date' => 'required',
'periodicity' => 'required|integer',
'periodicity' => 'required|integer|in:-1,0,1,3,6,12',
]
);

Expand Down Expand Up @@ -1583,7 +1609,7 @@ public function draft(Request $request)
{
// Not for API
abort_if(
Auth::User()->role === 4,
Auth::User()->isAPI(),
Response::HTTP_FORBIDDEN,
'403 Forbidden'
);
Expand Down Expand Up @@ -1611,7 +1637,7 @@ public function draft(Request $request)
$control->score = request('score') === 0 ? null : request('score');

// only admin and user can update the plan_date and action_plan
if (Auth::User()->role === 1 || Auth::User()->role === 2) {
if (Auth::User()->isAdmin() || Auth::User()->isUser()) {
$control->plan_date = request('plan_date');
$control->action_plan = request('action_plan');
// do not save the realisation date as it is in draft
Expand All @@ -1632,7 +1658,7 @@ public function reject(Request $request)
{
// Only for Admin and user
abort_if(
! (Auth::User()->role === 1 || Auth::User()->role === 2),
! (Auth::User()->isAdmin() || Auth::User()->isUser()),
Response::HTTP_FORBIDDEN,
'403 Forbidden'
);
Expand Down Expand Up @@ -1766,15 +1792,15 @@ public function tempo(Request $request)
{
// For administrators and users only
abort_if(
Auth::User()->role !== 1 && Auth::User()->role !== 2,
! Auth::User()->isAdmin() && !Auth::User()->isUser(),
Response::HTTP_FORBIDDEN,
'403 Forbidden'
);

// get measures
if ($request->id !== null) {
// Find associate control
$measures = Measure::where('clause', '=', $request->id)->get();
$measures = Measure::query()->where('clause', '=', $request->id)->get();
} else {
$measures = Collect();
}
Expand All @@ -1795,7 +1821,7 @@ public function template(Request $request)
{
// Not for API
abort_if(
Auth::User()->role === 4,
Auth::User()->isAPI(),
Response::HTTP_FORBIDDEN,
'403 Forbidden'
);
Expand Down
32 changes: 22 additions & 10 deletions app/Http/Controllers/HomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -419,18 +419,30 @@ private function getExpandedControls()
return $controls->flatMap(function ($control) {
$expanded = collect([$control]);

if ($control->realisation_date === null && $control->periodicity > 0 && $control->periodicity <= 12) {
for ($i = 1; $i <= 12 / $control->periodicity; $i++) {
$repeatedControl = clone $control;
$repeatedControl->id = null;
$repeatedControl->score = null;
$repeatedControl->observations = null;
$repeatedControl->realisation_date = null;
$repeatedControl->plan_date = Carbon::parse($control->plan_date)->addMonthsNoOverflow($i * $control->periodicity);
$expanded->push($repeatedControl);
if ($control->realisation_date === null) {
if ($control->periodicity === -1) {
for ($i = 1; $i <= 32; $i++) {
$repeatedControl = clone $control;
$repeatedControl->id = null;
$repeatedControl->score = null;
$repeatedControl->observations = null;
$repeatedControl->realisation_date = null;
$repeatedControl->plan_date = Carbon::parse($control->plan_date)->addDays($i * 7);
$expanded->push($repeatedControl);
}
}
else if ($control->periodicity > 0 && $control->periodicity <= 12) {
for ($i = 1; $i <= 12 / $control->periodicity; $i++) {
$repeatedControl = clone $control;
$repeatedControl->id = null;
$repeatedControl->score = null;
$repeatedControl->observations = null;
$repeatedControl->realisation_date = null;
$repeatedControl->plan_date = Carbon::parse($control->plan_date)->addMonthsNoOverflow($i * $control->periodicity);
$expanded->push($repeatedControl);
}
}
Comment on lines +422 to 444
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the weekly expansion iteration counts across both controllers

echo "=== HomeController weekly iterations ==="
rg -n "periodicity === -1" -A 5 app/Http/Controllers/HomeController.php

echo ""
echo "=== ControlController weekly iterations ==="
rg -n "periodicity === -1" -A 5 app/Http/Controllers/ControlController.php

Repository: dbarzin/deming

Length of output: 1439


Inconsistency: Weekly expansion uses 32 iterations (HomeController) vs 52 (ControlController).

Line 424 uses $i <= 32 (≈8 months of weekly projections), while ControlController line 797 uses $i <= 52 (52 weeks). Both expand weekly controls identically but project different time horizons.

Either align both to the same iteration count, or add an inline comment in both locations documenting why the difference is intentional (e.g., dashboard needs shorter future projection than history view).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Http/Controllers/HomeController.php` around lines 422 - 444, The weekly
expansion loop in HomeController (the for loop using for ($i = 1; $i <= 32;
$i++) that clones $control and sets plan_date via Carbon::parse(...)->addDays($i
* 7)) is inconsistent with the similar loop in ControlController (which uses $i
<= 52); update HomeController to use the same iteration count as
ControlController (change 32 to 52) or, if the shorter horizon is intentional,
add a concise inline comment next to the for (...) in HomeController explaining
why 32 weeks is used (e.g., "dashboard shows shorter projection than history
view") so the difference is explicit and maintainable.

}

return $expanded;
});
}
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/MeasureController.php
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ public function activate(Request $request) : RedirectResponse
$request,
[
'plan_date' => 'required',
'periodicity' => 'required',
'periodicity' => 'required|integer|in:-1,0,1,3,6,12',
'measures' => 'array|min:1',
]
);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2026.03.04",
"version": "2026.03.16",
"license": "GPL-3.0",
"author": "Didier Barzin",
"repository": "https://www.github.com/dbarzin/deming",
Expand Down
1 change: 1 addition & 0 deletions resources/lang/de/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
'validate' => 'Validieren',

'once' => 'einmalig',
'weekly' => 'wöchentlich',
'monthly' => 'monatlich',
'quarterly' => 'quartal',
'biannually' => 'halbjährlich',
Expand Down
1 change: 1 addition & 0 deletions resources/lang/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'validate' => 'Validate',

'once' => 'Once',
'weekly' => 'Weekly',
'monthly' => 'Monthly',
'quarterly' => 'Quarterly',
'biannually' => 'Biannually',
Expand Down
1 change: 1 addition & 0 deletions resources/lang/fr/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'validate' => 'Valider',

'once' => 'Une fois',
'weekly' => 'Hebdomadaire',
'monthly' => 'Mensuel',
'quarterly' => 'Trimestriel',
'biannually' => 'Bisanuel',
Expand Down
34 changes: 14 additions & 20 deletions resources/views/actions/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,28 +98,22 @@
@foreach($actions as $action)
<tr>
<td>
<b id="{{ $action->reference }}"><a href="/action/show/{{ $action->id }}">{{ $action->reference==null ? ("ACT-".$action->id) : $action->reference }}<a>
<b id="{{ $action->reference }}">
<a href="/action/show/{{ $action->id }}">{{ $action->reference==null ? ("ACT-".$action->id) : $action->reference }}</a>
</b>
</td>
<td>
<p id="{{ $action->type }}">
@if ($action->type==1)
<p class="fg-red text-bold">
{{ trans('cruds.action.types.major') }}
</p>
@elseif ($action->type==2)
<p class="fg-orange text-bold">
{{ trans('cruds.action.types.minor') }}
</p>
@elseif ($action->type==3)
<p class="fg-yellow text-bold">
{{ trans('cruds.action.types.observation') }}
</p>
@elseif ($action->type==4)
<p class="fg-green text-bold">
{{ trans('cruds.action.types.opportunity') }}
</p>
@endif
</p>
<center>
@if ($action->type==1)
<span class="mif-warning fg-red" style="font-size: 1.5rem;" title="{{ trans('cruds.action.types.major') }}"></span>
@elseif ($action->type==2)
<span class="mif-warning fg-orange" style="font-size: 1.5rem;" title="{{ trans('cruds.action.types.minor') }}"></span>
@elseif ($action->type==3)
<span class="mif-eye fg-dark-gray" style="font-size: 1.5rem;" title="{{ trans('cruds.action.types.observation') }}"></span>
@elseif ($action->type==4)
<span class="mif-thumbs-up fg-green" style="font-size: 1.5rem;" title="{{ trans('cruds.action.types.opportunity') }}"></span>
@endif
</center>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</td>
<td id="{{ $action->status }}">
@if ($action->status==0)
Expand Down
1 change: 1 addition & 0 deletions resources/views/controls/create.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
<div class="cell-lg-2 cell-md-4">
<select data-role="select" id='periodicity' name="periodicity">
<option value="0" {{ old("periodicity")=="0" ? "selected" : ""}}>{{ trans('common.once') }}</option>
<option value="-1" {{ old("periodicity")=="-1" ? "selected" : ""}}>{{ trans('common.weekly') }}</option>
<option value="1" {{ old("periodicity")=="1" ? "selected" : ""}}>{{ trans('common.monthly') }}</option>
<option value="3" {{ old("periodicity")=="3" ? "selected" : ""}}>{{ trans('common.quarterly') }}</option>
<option value="6" {{ old("periodicity")=="6" ? "selected" : ""}}>{{ trans('common.biannually') }}</option>
Expand Down
1 change: 1 addition & 0 deletions resources/views/controls/edit.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
<div class="cell-lg-2 cell-md-2">
<select data-role="select" id="periodicity" name="periodicity">
<option value="0" {{ $control->periodicity==0 ? "selected" : ""}}>{{ trans('common.once') }}</option>
<option value="-1" {{ $control->periodicity==-1 ? "selected" : ""}}>{{ trans('common.weekly') }}</option>
<option value="1" {{ $control->periodicity==1 ? "selected" : ""}}>{{ trans('common.monthly') }}</option>
<option value="3" {{ $control->periodicity==3 ? "selected" : ""}}>{{ trans('common.quarterly') }}</option>
<option value="6" {{ $control->periodicity==6 ? "selected" : ""}}>{{ trans('common.biannually') }}</option>
Expand Down
4 changes: 3 additions & 1 deletion resources/views/controls/make.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@
{{ $next_date }}
@endif
(
@if ($control->periodicity==1)
@if ($control->periodicity==-1)
{{ trans("common.weekly") }}
@elseif ($control->periodicity==1)
{{ trans("common.monthly") }}
@elseif ($control->periodicity==3)
{{ trans("common.quarterly") }}
Expand Down
3 changes: 2 additions & 1 deletion resources/views/controls/plan.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@
</div>
<div class="cell-lg-2 cell-md-3">
<select name="periodicity" data-role="select">
<option value="1" {{ $control->periodicity==0 ? "selected" : ""}}>{{ trans('common.once') }}</option>
<option value="0" {{ $control->periodicity==0 ? "selected" : ""}}>{{ trans('common.once') }}</option>
<option value="-1" {{ $control->periodicity==-1 ? "selected" : ""}}>{{ trans('common.weekly') }}</option>
Comment on lines +79 to +80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Changing “once” from 1 to 0 needs a data migration/backfill plan.

With this UI remap, legacy rows using periodicity=1 can be treated as recurring monthly by controller logic (periodicity > 0 / periodicity !== 0 in app/Http/Controllers/ControlController.php and app/Http/Controllers/HomeController.php). That can create unintended future controls.

📦 Suggested rollout safeguard
+ // Add a one-time data migration/reconciliation step before enabling this mapping in production:
+ // 1) Identify legacy records that used `1` as "once"
+ // 2) Convert them to `0`
+ // 3) Keep true monthly records as `1`
+ // 4) Document the cutoff strategy in release notes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/views/controls/plan.blade.php` around lines 79 - 80, You changed
the UI option for “once” from periodicity=1 to 0 but did not include a data
migration or controller checks, which will cause legacy rows with periodicity=1
to be misinterpreted as recurring; add a reversible DB migration/backfill that
updates existing records with periodicity=1 to periodicity=0, update any
controller logic in ControlController and HomeController that currently checks
periodicity (>0 or !==0) to explicitly treat 0 as “once” (e.g., periodicity ===
0) and 1 no longer special-case, and add a small migration test and/or logging
to ensure the backfill runs safely during rollout.

<option value="1" {{ $control->periodicity==1 ? "selected" : ""}}>{{ trans('common.monthly') }}</option>
<option value="3" {{ $control->periodicity==3 ? "selected" : ""}}>{{ trans('common.quarterly') }}</option>
<option value="6" {{ $control->periodicity==6 ? "selected" : ""}}>{{ trans('common.biannually') }}</option>
Expand Down
1 change: 1 addition & 0 deletions resources/views/controls/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@
</div>
<div class="cell-lg-6 cell-md-8">
@if ($control->periodicity==0) {{ trans("common.once") }} @endif
@if ($control->periodicity==-1) {{ trans("common.weekly") }} @endif
@if ($control->periodicity==1) {{ trans("common.monthly") }} @endif
@if ($control->periodicity==3) {{ trans("common.quarterly") }} @endif
@if ($control->periodicity==6) {{ trans("common.biannually") }} @endif
Expand Down
1 change: 1 addition & 0 deletions resources/views/measures/plan.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
<div class="cell-lg-3 cell-md-4">
<select name="periodicity" data-role="select">
<option value="0" {{ $measure->periodicity==0 ? "selected" : ""}}>{{ trans('common.once') }}</option>
<option value="-1" {{ $measure->periodicity==-1 ? "selected" : ""}}>{{ trans('common.weekly') }}</option>
<option value="1" {{ $measure->periodicity==1 ? "selected" : ""}}>{{ trans('common.monthly') }}</option>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<option value="3" {{ $measure->periodicity==3 ? "selected" : ""}}>{{ trans('common.quarterly') }}</option>
<option value="6" {{ $measure->periodicity==6 ? "selected" : ""}}>{{ trans('common.biannually') }}</option>
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2026.03.04
2026.03.16