fix: recipe quantities for conf+weight; move modal remembers location

- PHP (all 3 recipe endpoints): conf products with weight/volume package_unit
  (e.g., 300g/conf) now keep qty_number in sub-units (grams/ml) instead of
  converting to fractional conf. Post-normalisation block converts any AI-
  returned fractional conf values to grams automatically.
- JS submitRecipeUse: vacuum state now read from actual inventory item at the
  used location (_recipeUseContext.items), not from recipe ingredient data.
- JS showRecipeMoveModal: now uses _prefMoveLocCache preference system —
  after 2 consistent choices the modal is skipped automatically. 'Stay' button
  records the choice. Added _recipeMoveCancelStay() helper.
- JS confirmRecipeMove: records move choice via _recordMoveLocChoice();
  accepts optional forcedVacuum param for preference-triggered auto-moves.

Closes #108 (duplicate of #107 — data dir permissions)
This commit is contained in:
dadaloop82
2026-05-19 16:51:37 +00:00
parent f77b3259ad
commit 87eac171bf
2 changed files with 95 additions and 20 deletions
+32 -8
View File
@@ -12734,11 +12734,13 @@ async function submitRecipeUse(useAll) {
setTimeout(() => showToast(t('recipes.finished_added_bring_toast'), 'info'), 1500);
}
// Check low stock → Bring! prompt, then offer move
// Check low stock → shopping prompt, then offer move
const moveCallback = result.remaining > 0
? () => setTimeout(() => {
const ingData = _cachedRecipe?.recipe?.ingredients?.[_recipeUseContext?.idx];
const wasVacuum = !!(ingData?.vacuum_sealed);
// Get vacuum state from the actual inventory item at this location
const cachedItems = _recipeUseContext?.items || [];
const itemAtLoc = cachedItems.find(i => i.location === location);
const wasVacuum = !!(itemAtLoc?.vacuum_sealed);
showRecipeMoveModal(productId, location, result.remaining, result.opened_id, wasVacuum);
}, 300)
: null;
@@ -12758,6 +12760,21 @@ async function submitRecipeUse(useAll) {
}
function showRecipeMoveModal(productId, fromLoc, remaining, openedId, wasVacuum) {
// Set context for recording the choice
_pendingMoveCtx = { productId, fromLoc, openedId };
// If a preference exists, skip the modal entirely
const prefMoveLoc = _getPreferredMoveLoc(productId, fromLoc);
if (prefMoveLoc) {
if (prefMoveLoc === fromLoc) {
closeModal();
} else {
confirmRecipeMove(productId, fromLoc, prefMoveLoc, openedId, wasVacuum);
}
_pendingMoveCtx = null;
return;
}
const otherLocs = Object.entries(LOCATIONS).filter(([k]) => k !== fromLoc);
const locButtons = otherLocs.map(([k, v]) =>
`<button type="button" class="loc-btn" onclick="clearMoveModalTimer();confirmRecipeMove(${productId}, '${fromLoc}', '${k}', ${openedId || 0})">${v.icon} ${v.label}</button>`
@@ -12776,18 +12793,25 @@ function showRecipeMoveModal(productId, fromLoc, remaining, openedId, wasVacuum)
<p style="margin-bottom:12px">${t('move.question_short').replace('{thing}', openedId ? t('move.thing_opened') : t('move.thing_rest'))}</p>
<div class="location-selector">${locButtons}</div>
${vacuumRow}
<button type="button" id="btn-move-stay" class="btn btn-secondary full-width move-countdown-btn" style="margin-top:12px" onclick="clearMoveModalTimer();closeModal()">${t('move.stay_btn').replace('{location}', LOCATIONS[fromLoc]?.label || fromLoc)}</button>
<button type="button" id="btn-move-stay" class="btn btn-secondary full-width move-countdown-btn" style="margin-top:12px" onclick="clearMoveModalTimer();_recipeMoveCancelStay(${productId}, '${fromLoc}', ${openedId || 0})">${t('move.stay_btn').replace('{location}', LOCATIONS[fromLoc]?.label || fromLoc)}</button>
</div>
`;
document.getElementById('modal-overlay').style.display = 'flex';
// Hide the native kiosk settings button while the modal is open (prevents touch bleed-through)
try { if (typeof _kioskBridge !== 'undefined') _kioskBridge.setNativeSettingsVisible(false); } catch (_) {}
startMoveModalCountdown('btn-move-stay', () => { closeModal(); });
startMoveModalCountdown('btn-move-stay', () => { _recipeMoveCancelStay(productId, fromLoc, openedId || 0); });
}
async function confirmRecipeMove(productId, fromLoc, toLoc, openedId) {
function _recipeMoveCancelStay(productId, fromLoc, openedId) {
_recordMoveLocChoice(productId, fromLoc, fromLoc);
_pendingMoveCtx = null;
closeModal();
}
async function confirmRecipeMove(productId, fromLoc, toLoc, openedId, forcedVacuum) {
clearMoveModalTimer();
const newVacuum = document.getElementById('move-vacuum-check')?.checked ? 1 : 0;
_recordMoveLocChoice(productId, fromLoc, toLoc);
_pendingMoveCtx = null;
const newVacuum = forcedVacuum !== undefined ? (forcedVacuum ? 1 : 0) : (document.getElementById('move-vacuum-check')?.checked ? 1 : 0);
closeModal();
try {
if (openedId) {