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:
+42
-27
@@ -11198,12 +11198,20 @@ function smartShopping(PDO $db): void {
|
||||
if ($buyCount > 0 && $totalUsed > $buyCount * 5 && $daysSinceFirst < 999) {
|
||||
$need14 = ($buyCount / $daysSinceFirst) * 14;
|
||||
}
|
||||
$suggestedQty = (int) max(1, min(10, (int)($need14 + 0.3)));
|
||||
$suggestedUnit = 'conf';
|
||||
// conf + package weight: express suggestion in g/ml, not raw conf count from mis-tracked grams.
|
||||
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) {
|
||||
// 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;
|
||||
$suggestedUnit = 'conf';
|
||||
|
||||
@@ -11212,7 +11220,7 @@ function smartShopping(PDO $db): void {
|
||||
// use defQty as the minimum purchase unit and round to nearest multiple.
|
||||
// This ensures we never suggest less than one "reference pack".
|
||||
$pkgs = (int) max(1, (int)($need14 / $defQty + 0.3));
|
||||
$pkgs = min(10, $pkgs);
|
||||
$pkgs = min(3, $pkgs);
|
||||
$suggestedQty = $pkgs * (int)$defQty;
|
||||
$suggestedUnit = $unit;
|
||||
$suggestedApprox = true; // always "almeno" — no confirmed pkg size
|
||||
@@ -11234,8 +11242,8 @@ function smartShopping(PDO $db): void {
|
||||
}
|
||||
|
||||
} elseif ($unit === 'pz') {
|
||||
// No package info → raw pz count, approximate
|
||||
$suggestedQty = (int) max(1, min(10, (int)($need14 + 0.3)));
|
||||
// No package info → raw pz count, approximate (cap 5 — not 14-day bulk buy)
|
||||
$suggestedQty = (int) max(1, min(5, (int)($need14 + 0.3)));
|
||||
$suggestedUnit = 'pz';
|
||||
$suggestedApprox = ($suggestedQty > 1);
|
||||
}
|
||||
@@ -11278,17 +11286,17 @@ function smartShopping(PDO $db): void {
|
||||
if ($gap > 0) {
|
||||
if ($unit === 'conf') {
|
||||
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;
|
||||
$suggestedUnit = strtolower($pkgUnit);
|
||||
$suggestedApprox = true;
|
||||
} else {
|
||||
$suggestedQty = (int)max(1, min(10, (int)ceil($gap)));
|
||||
$suggestedQty = (int)max(1, min(3, (int)ceil($gap)));
|
||||
$suggestedUnit = 'conf';
|
||||
$suggestedApprox = false;
|
||||
}
|
||||
} elseif ($unit === 'pz') {
|
||||
$suggestedQty = (int)max(1, min(10, (int)ceil($gap)));
|
||||
$suggestedQty = (int)max(1, min(5, (int)ceil($gap)));
|
||||
$suggestedUnit = 'pz';
|
||||
$suggestedApprox = $suggestedQty > 1;
|
||||
} 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.
|
||||
* Each shopping-list line is priced as ONE typical retail purchase — not 14-day restock stock.
|
||||
*/
|
||||
function _resolveShoppingPriceItem(string $name, array $smartItems): array {
|
||||
$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) {
|
||||
$unit = trim($si['unit'] ?? 'conf');
|
||||
$defQty = (float)($si['default_qty'] ?? 0);
|
||||
$unit = trim($si['unit'] ?? 'conf');
|
||||
$defQty = (float)($si['default_qty'] ?? 0);
|
||||
$pkgUnit = trim($si['package_unit'] ?? '');
|
||||
|
||||
// Packaged goods (conf + weight/volume): one package at list price.
|
||||
if ($unit === 'conf' && $defQty > 0 && $pkgUnit !== '') {
|
||||
return [
|
||||
'name' => $name,
|
||||
@@ -12881,16 +12883,31 @@ function _resolveShoppingPriceItem(string $name, array $smartItems): array {
|
||||
'package_unit' => $pkgUnit,
|
||||
];
|
||||
}
|
||||
|
||||
// Sold by piece: 2–3 items typical for a single shop trip.
|
||||
if ($unit === 'pz') {
|
||||
$gramsPerPiece = ($defQty >= 20) ? $defQty : 200.0;
|
||||
return [
|
||||
'name' => $name,
|
||||
'quantity' => 2,
|
||||
'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,
|
||||
'package_unit' => $pkgUnit,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $name,
|
||||
'quantity' => 1,
|
||||
@@ -13288,13 +13305,11 @@ function _calcEstimatedTotal(float $pricePerUnit, string $priceUnitLabel, float
|
||||
$weightKg = $qty;
|
||||
}
|
||||
if ($weightKg <= 0) {
|
||||
// Two cases:
|
||||
// A) defQty was 0 (no weight data at all) → "–" is more honest than a fake price.
|
||||
// B) defQty was 1-19 (suspicious: the value was stored as a piece count, not grams;
|
||||
// the assignment was intentionally skipped by the defQty<20 guard above).
|
||||
// In case B, fall back to ppu × qty so the badge shows something rather than €0.00.
|
||||
if (in_array($unit, ['pz', 'conf']) && $defQty > 0) {
|
||||
return round($pricePerUnit * max(1.0, $qty), 2);
|
||||
// Piece/count units with €/kg AI price: estimate weight per piece (never €/kg × piece count).
|
||||
if (in_array($unit, ['pz', 'conf'], true)) {
|
||||
$gramsPerPiece = ($defQty >= 20) ? $defQty : 200.0;
|
||||
$weightKg = max(1.0, $qty) * $gramsPerPiece / 1000.0;
|
||||
return round($pricePerUnit * $weightKg, 2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+20
-9
@@ -12128,19 +12128,11 @@ async function syncShoppingPriceTotal(forceRefresh = false) {
|
||||
function _buildPricePayload() {
|
||||
return shoppingItems.map((item) => {
|
||||
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) {
|
||||
const unit = smart.unit || 'conf';
|
||||
const defQty = parseFloat(smart.default_qty) || 0;
|
||||
const pkgUnit = smart.package_unit || '';
|
||||
// One shopping-list line ≈ one retail purchase (not 14-day restock qty).
|
||||
if (unit === 'conf' && defQty > 0 && pkgUnit) {
|
||||
return {
|
||||
name: item.name,
|
||||
@@ -12150,6 +12142,25 @@ function _buildPricePayload() {
|
||||
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: '' };
|
||||
});
|
||||
|
||||
@@ -135,17 +135,6 @@ foreach ($bugs as $num => $msg) {
|
||||
closeIssue($token, $repo, $num, $dryRun);
|
||||
}
|
||||
|
||||
// ── Feature / enhancement backlog (close with acknowledgment) ───────────────
|
||||
$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);
|
||||
}
|
||||
// Feature/enhancement issues stay OPEN — do not bulk-close backlog items here.
|
||||
|
||||
echo "Done.\n";
|
||||
|
||||
Reference in New Issue
Block a user