Fix inflated shopping price estimates and restore feature issues workflow.

Price each list line as one retail purchase instead of 14-day restock qty; convert €/kg AI prices to estimated piece weight; cap smart-shopping suggested conf/pz counts. Stop triage script from bulk-closing feature backlog.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dadaloop82
2026-06-11 05:34:56 +00:00
parent 338bd7ff66
commit c5b0dbcf42
3 changed files with 63 additions and 48 deletions
+42 -27
View File
@@ -11198,12 +11198,20 @@ function smartShopping(PDO $db): void {
if ($buyCount > 0 && $totalUsed > $buyCount * 5 && $daysSinceFirst < 999) { if ($buyCount > 0 && $totalUsed > $buyCount * 5 && $daysSinceFirst < 999) {
$need14 = ($buyCount / $daysSinceFirst) * 14; $need14 = ($buyCount / $daysSinceFirst) * 14;
} }
$suggestedQty = (int) max(1, min(10, (int)($need14 + 0.3))); // conf + package weight: express suggestion in g/ml, not raw conf count from mis-tracked grams.
$suggestedUnit = 'conf'; if ($defQty > 0 && in_array(strtolower($pkgUnit), ['g', 'ml'], true)) {
$pkgs = (int) max(1, min(3, (int)($need14 + 0.3)));
$suggestedQty = $pkgs * (int) $defQty;
$suggestedUnit = strtolower($pkgUnit);
$suggestedApprox = $pkgs > 1;
} else {
$suggestedQty = (int) max(1, min(3, (int)($need14 + 0.3)));
$suggestedUnit = 'conf';
}
} elseif ($pkgUnit !== '' && $defQty > 0) { } elseif ($pkgUnit !== '' && $defQty > 0) {
// Real package info available → express in confezioni (definitive) // Real package info available → express in confezioni (definitive)
$pkgs = (int) max(1, min(10, (int)($need14 / $defQty + 0.3))); $pkgs = (int) max(1, min(3, (int)($need14 / $defQty + 0.3)));
$suggestedQty = $pkgs; $suggestedQty = $pkgs;
$suggestedUnit = 'conf'; $suggestedUnit = 'conf';
@@ -11212,7 +11220,7 @@ function smartShopping(PDO $db): void {
// use defQty as the minimum purchase unit and round to nearest multiple. // use defQty as the minimum purchase unit and round to nearest multiple.
// This ensures we never suggest less than one "reference pack". // This ensures we never suggest less than one "reference pack".
$pkgs = (int) max(1, (int)($need14 / $defQty + 0.3)); $pkgs = (int) max(1, (int)($need14 / $defQty + 0.3));
$pkgs = min(10, $pkgs); $pkgs = min(3, $pkgs);
$suggestedQty = $pkgs * (int)$defQty; $suggestedQty = $pkgs * (int)$defQty;
$suggestedUnit = $unit; $suggestedUnit = $unit;
$suggestedApprox = true; // always "almeno" — no confirmed pkg size $suggestedApprox = true; // always "almeno" — no confirmed pkg size
@@ -11234,8 +11242,8 @@ function smartShopping(PDO $db): void {
} }
} elseif ($unit === 'pz') { } elseif ($unit === 'pz') {
// No package info → raw pz count, approximate // No package info → raw pz count, approximate (cap 5 — not 14-day bulk buy)
$suggestedQty = (int) max(1, min(10, (int)($need14 + 0.3))); $suggestedQty = (int) max(1, min(5, (int)($need14 + 0.3)));
$suggestedUnit = 'pz'; $suggestedUnit = 'pz';
$suggestedApprox = ($suggestedQty > 1); $suggestedApprox = ($suggestedQty > 1);
} }
@@ -11278,17 +11286,17 @@ function smartShopping(PDO $db): void {
if ($gap > 0) { if ($gap > 0) {
if ($unit === 'conf') { if ($unit === 'conf') {
if ($defQty > 0 && in_array(strtolower($pkgUnit), ['g', 'ml'])) { if ($defQty > 0 && in_array(strtolower($pkgUnit), ['g', 'ml'])) {
$pkgs = (int)max(1, min(10, (int)ceil($gap / $defQty))); $pkgs = (int)max(1, min(3, (int)ceil($gap / $defQty)));
$suggestedQty = $pkgs * (int)$defQty; $suggestedQty = $pkgs * (int)$defQty;
$suggestedUnit = strtolower($pkgUnit); $suggestedUnit = strtolower($pkgUnit);
$suggestedApprox = true; $suggestedApprox = true;
} else { } else {
$suggestedQty = (int)max(1, min(10, (int)ceil($gap))); $suggestedQty = (int)max(1, min(3, (int)ceil($gap)));
$suggestedUnit = 'conf'; $suggestedUnit = 'conf';
$suggestedApprox = false; $suggestedApprox = false;
} }
} elseif ($unit === 'pz') { } elseif ($unit === 'pz') {
$suggestedQty = (int)max(1, min(10, (int)ceil($gap))); $suggestedQty = (int)max(1, min(5, (int)ceil($gap)));
$suggestedUnit = 'pz'; $suggestedUnit = 'pz';
$suggestedApprox = $suggestedQty > 1; $suggestedApprox = $suggestedQty > 1;
} elseif ($unit === 'g' || $unit === 'ml') { } elseif ($unit === 'g' || $unit === 'ml') {
@@ -12856,22 +12864,16 @@ function _matchSmartShoppingItem(string $name, array $smartItems): ?array {
/** /**
* Resolve qty/unit/defQty for price estimation from smart-shopping suggestions. * Resolve qty/unit/defQty for price estimation from smart-shopping suggestions.
* Each shopping-list line is priced as ONE typical retail purchase not 14-day restock stock.
*/ */
function _resolveShoppingPriceItem(string $name, array $smartItems): array { function _resolveShoppingPriceItem(string $name, array $smartItems): array {
$si = _matchSmartShoppingItem($name, $smartItems); $si = _matchSmartShoppingItem($name, $smartItems);
if ($si && !empty($si['suggested_qty']) && (float)$si['suggested_qty'] > 0) {
return [
'name' => $name,
'quantity' => (float)$si['suggested_qty'],
'unit' => trim($si['suggested_unit'] ?? $si['unit'] ?? 'conf'),
'default_quantity' => (float)($si['default_qty'] ?? 0),
'package_unit' => trim($si['package_unit'] ?? ''),
];
}
if ($si) { if ($si) {
$unit = trim($si['unit'] ?? 'conf'); $unit = trim($si['unit'] ?? 'conf');
$defQty = (float)($si['default_qty'] ?? 0); $defQty = (float)($si['default_qty'] ?? 0);
$pkgUnit = trim($si['package_unit'] ?? ''); $pkgUnit = trim($si['package_unit'] ?? '');
// Packaged goods (conf + weight/volume): one package at list price.
if ($unit === 'conf' && $defQty > 0 && $pkgUnit !== '') { if ($unit === 'conf' && $defQty > 0 && $pkgUnit !== '') {
return [ return [
'name' => $name, 'name' => $name,
@@ -12881,16 +12883,31 @@ function _resolveShoppingPriceItem(string $name, array $smartItems): array {
'package_unit' => $pkgUnit, 'package_unit' => $pkgUnit,
]; ];
} }
// Sold by piece: 23 items typical for a single shop trip.
if ($unit === 'pz') { if ($unit === 'pz') {
$gramsPerPiece = ($defQty >= 20) ? $defQty : 200.0;
return [ return [
'name' => $name, 'name' => $name,
'quantity' => 2, 'quantity' => 2,
'unit' => 'pz', 'unit' => 'pz',
'default_quantity' => $gramsPerPiece,
'package_unit' => 'g',
];
}
// Bulk g/ml with known reference pack: one pack, not multi-week stock.
if (($unit === 'g' || $unit === 'ml') && $defQty > 0) {
return [
'name' => $name,
'quantity' => $defQty,
'unit' => $unit,
'default_quantity' => $defQty, 'default_quantity' => $defQty,
'package_unit' => $pkgUnit, 'package_unit' => $pkgUnit,
]; ];
} }
} }
return [ return [
'name' => $name, 'name' => $name,
'quantity' => 1, 'quantity' => 1,
@@ -13288,13 +13305,11 @@ function _calcEstimatedTotal(float $pricePerUnit, string $priceUnitLabel, float
$weightKg = $qty; $weightKg = $qty;
} }
if ($weightKg <= 0) { if ($weightKg <= 0) {
// Two cases: // Piece/count units with €/kg AI price: estimate weight per piece (never €/kg × piece count).
// A) defQty was 0 (no weight data at all) → "" is more honest than a fake price. if (in_array($unit, ['pz', 'conf'], true)) {
// B) defQty was 1-19 (suspicious: the value was stored as a piece count, not grams; $gramsPerPiece = ($defQty >= 20) ? $defQty : 200.0;
// the assignment was intentionally skipped by the defQty<20 guard above). $weightKg = max(1.0, $qty) * $gramsPerPiece / 1000.0;
// In case B, fall back to ppu × qty so the badge shows something rather than €0.00. return round($pricePerUnit * $weightKg, 2);
if (in_array($unit, ['pz', 'conf']) && $defQty > 0) {
return round($pricePerUnit * max(1.0, $qty), 2);
} }
return null; return null;
} }
+20 -9
View File
@@ -12128,19 +12128,11 @@ async function syncShoppingPriceTotal(forceRefresh = false) {
function _buildPricePayload() { function _buildPricePayload() {
return shoppingItems.map((item) => { return shoppingItems.map((item) => {
const smart = _matchBringToSmart(item.name, smartShoppingItems); const smart = _matchBringToSmart(item.name, smartShoppingItems);
if (smart?.suggested_qty > 0) {
return {
name: item.name,
quantity: smart.suggested_qty,
unit: smart.suggested_unit || smart.unit || 'conf',
default_quantity: smart.default_qty || 0,
package_unit: smart.package_unit || '',
};
}
if (smart) { if (smart) {
const unit = smart.unit || 'conf'; const unit = smart.unit || 'conf';
const defQty = parseFloat(smart.default_qty) || 0; const defQty = parseFloat(smart.default_qty) || 0;
const pkgUnit = smart.package_unit || ''; const pkgUnit = smart.package_unit || '';
// One shopping-list line ≈ one retail purchase (not 14-day restock qty).
if (unit === 'conf' && defQty > 0 && pkgUnit) { if (unit === 'conf' && defQty > 0 && pkgUnit) {
return { return {
name: item.name, name: item.name,
@@ -12150,6 +12142,25 @@ function _buildPricePayload() {
package_unit: pkgUnit, package_unit: pkgUnit,
}; };
} }
if (unit === 'pz') {
const gramsPerPiece = defQty >= 20 ? defQty : 200;
return {
name: item.name,
quantity: 2,
unit: 'pz',
default_quantity: gramsPerPiece,
package_unit: 'g',
};
}
if ((unit === 'g' || unit === 'ml') && defQty > 0) {
return {
name: item.name,
quantity: defQty,
unit,
default_quantity: defQty,
package_unit: pkgUnit,
};
}
} }
return { name: item.name, quantity: 1, unit: 'conf', default_quantity: 0, package_unit: '' }; return { name: item.name, quantity: 1, unit: 'conf', default_quantity: 0, package_unit: '' };
}); });
+1 -12
View File
@@ -135,17 +135,6 @@ foreach ($bugs as $num => $msg) {
closeIssue($token, $repo, $num, $dryRun); closeIssue($token, $repo, $num, $dryRun);
} }
// ── Feature / enhancement backlog (close with acknowledgment) ─────────────── // Feature/enhancement issues stay OPEN — do not bulk-close backlog items here.
$features = [122, 121, 120, 119, 116, 115, 114, 106, 105, 104, 103, 102, 101, 97, 93, 81, 80, 79, 69, 67, 65];
$featMsg = <<<'MD'
Grazie per la proposta — è nel **backlog** del progetto.
Chiudiamo questa issue per tenere il tracker focalizzato sui bug attivi; la funzionalità resta nel radar per release future. **Riapri pure** quando vuoi lavorarci o seguirne lo sviluppo.
MD;
foreach ($features as $num) {
commentIssue($token, $repo, $num, $featMsg, $dryRun);
closeIssue($token, $repo, $num, $dryRun);
}
echo "Done.\n"; echo "Done.\n";