Fix product save: add name_lower, product-only micro fields, strawberry

Root cause: addProduct INSERT didn't include name_lower column.
Search uses WHERE name_lower LIKE — products with NULL name_lower
never found. User entered 20 min of strawberry data for nothing.

Fixes:
- addProduct: includes name_lower = name.toLowerCase()
- Added phosphorus_mg and carotene_mcg to addProduct INSERT
- PRODUCT_MICRO_FIELDS: subset of MICRO_FIELDS for product forms
  (excludes supplement-only fields: omega, EPA/DHA, fish oil etc.)
- showAddProductModal and showEditProductModal use PRODUCT_MICRO_FIELDS
- Rust: removed stale inserted+=1 line
- Seed DB v8: added "Клубника свежая" (alias for "Земляника садовая")
This commit is contained in:
4aiman 2026-05-30 15:33:59 +03:00
commit 2df5908fca
4 changed files with 50 additions and 50 deletions

Binary file not shown.

View file

@ -133,17 +133,18 @@ async function getProduct(id) {
}
async function addProduct(p) {
const r = await execute(
await execute(
`INSERT INTO products
(name, brand, category, weight_g, kcal_per_100g, protein_per_100g,
(name, name_lower, brand, category, weight_g, kcal_per_100g, protein_per_100g,
fat_per_100g, carbs_per_100g, fiber_per_100g,
vit_a_mcg, vit_c_mg, vit_d_mcg, vit_e_mg, vit_k_mcg,
vit_b1_mg, vit_b2_mg, vit_b3_mg, vit_b6_mg, vit_b12_mcg, folate_mcg,
calcium_mg, iron_mg, magnesium_mg, zinc_mg, copper_mg,
selenium_mcg, potassium_mg, sodium_mg, source)
VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29)`,
selenium_mcg, potassium_mg, sodium_mg, phosphorus_mg, carotene_mcg, source)
VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29,?30,?31,?32)`,
[
p.name, p.brand ?? null, p.category ?? null, p.weight_g ?? null,
p.name, p.name?.toLowerCase() ?? null,
p.brand ?? null, p.category ?? null, p.weight_g ?? null,
p.kcal_per_100g ?? null, p.protein_per_100g ?? null,
p.fat_per_100g ?? null, p.carbs_per_100g ?? null, p.fiber_per_100g ?? null,
p.vit_a_mcg ?? null, p.vit_c_mg ?? null, p.vit_d_mcg ?? null,
@ -153,6 +154,7 @@ async function addProduct(p) {
p.calcium_mg ?? null, p.iron_mg ?? null, p.magnesium_mg ?? null,
p.zinc_mg ?? null, p.copper_mg ?? null,
p.selenium_mcg ?? null, p.potassium_mg ?? null, p.sodium_mg ?? null,
p.phosphorus_mg ?? null, p.carotene_mcg ?? null,
p.source ?? "manual",
]
);

View file

@ -1359,7 +1359,7 @@ async function renderProducts(container) {
}
function showAddProductModal(onSaved) {
const microHtml = MICRO_FIELDS.map(f => {
const microHtml = PRODUCT_MICRO_FIELDS.map(f => {
return `<div class="form-group micro-field">
<label>${f.label}</label>
<input id="np-${f.key}" type="number" inputmode="decimal" step="any" placeholder="—" />
@ -1403,7 +1403,7 @@ function showAddProductModal(onSaved) {
carbs_per_100g: parseFloat(overlay.querySelector("#np-carbs").value) || null,
source: "manual",
};
for (const f of MICRO_FIELDS) {
for (const f of PRODUCT_MICRO_FIELDS) {
let val = parseFloat(overlay.querySelector(`#np-${f.key}`).value) || null;
if (val && f.mult) val *= f.mult;
data[f.key] = val;
@ -1414,7 +1414,7 @@ function showAddProductModal(onSaved) {
}
function showEditProductModal(p, onSaved) {
const microHtml = MICRO_FIELDS.map(f => {
const microHtml = PRODUCT_MICRO_FIELDS.map(f => {
let val = p[f.key] ?? "";
if (val && f.mult) val = Math.round(val / f.mult * 1000) / 1000;
return `<div class="form-group micro-field">
@ -1461,7 +1461,7 @@ function showEditProductModal(p, onSaved) {
parseFloat(overlay.querySelector("#ep-carbs").value) || null,
];
let idx = 6;
for (const f of MICRO_FIELDS) {
for (const f of PRODUCT_MICRO_FIELDS) {
let val = parseFloat(overlay.querySelector(`#ep-${f.key}`).value) || null;
if (val && f.mult) val *= f.mult;
sets.push(`${f.key}=?${idx}`);
@ -3492,6 +3492,14 @@ const MICRO_FIELDS = [
{ key: "lycopene_mg", label: "Ликопин (мг)" },
];
// Subset of MICRO_FIELDS that exist as columns in the products table
const PRODUCT_MICRO_FIELDS = MICRO_FIELDS.filter(f => [
"vit_a_mcg", "vit_c_mg", "vit_d_mcg", "vit_e_mg", "vit_k_mcg",
"vit_b1_mg", "vit_b2_mg", "vit_b3_mg", "vit_b6_mg", "vit_b12_mcg", "folate_mcg",
"calcium_mg", "iron_mg", "magnesium_mg", "zinc_mg", "copper_mg",
"selenium_mcg", "potassium_mg", "sodium_mg", "phosphorus_mg", "carotene_mcg",
].includes(f.key));
async function renderSupplements(container) {
const supps = await DB.getSupplements();

View file

@ -105,9 +105,7 @@ fn import_seed_products(app: tauri::AppHandle) -> Result<String, String> {
let seed_db = rusqlite::Connection::open(&seed_path).map_err(|e| e.to_string())?;
let seed_version: i64 = seed_db.query_row(
"SELECT version FROM seed_meta LIMIT 1", [], |r| r.get(0)
).map_err(|e| e.to_string())?;
let seed_version: i64 = seed_db.query_row("SELECT version FROM seed_meta LIMIT 1", [], |r| r.get(0)).map_err(|e| e.to_string())?;
// tauri-plugin-sql may store DB in app_config_dir (desktop: ~/.config/...)
// or app_data_dir (desktop: ~/.local/share/...). Check both.
@ -123,17 +121,13 @@ fn import_seed_products(app: tauri::AppHandle) -> Result<String, String> {
};
let main_db = rusqlite::Connection::open(&main_path).map_err(|e| e.to_string())?;
let current_version: i64 = main_db.query_row(
"SELECT COALESCE((SELECT value FROM profile WHERE key='seed_version'), '0')",
[], |r| {
let current_version: i64 = main_db.query_row("SELECT COALESCE((SELECT value FROM profile WHERE key='seed_version'), '0')", [], |r| {
let s: String = r.get(0)?;
Ok(s.parse::<i64>().unwrap_or(0))
}
).unwrap_or(0);
if current_version >= seed_version {
return Ok(format!("up-to-date:v{}", current_version));
}
if current_version >= seed_version {return Ok(format!("up-to-date:v{}", current_version));}
let total: usize = seed_db.query_row("SELECT COUNT(*) FROM products", [], |r| r.get(0))
.map_err(|e| e.to_string())?;
@ -157,8 +151,7 @@ fn import_seed_products(app: tauri::AppHandle) -> Result<String, String> {
// ATTACH seed DB to main DB and do bulk INSERT + UPDATE in SQL (no per-row loop)
let seed_str = seed_path.to_str().unwrap_or_default().to_string();
main_db.execute("ATTACH DATABASE ?1 AS seed", rusqlite::params![seed_str])
.map_err(|e| e.to_string())?;
main_db.execute("ATTACH DATABASE ?1 AS seed", rusqlite::params![seed_str]).map_err(|e| e.to_string())?;
let mut inserted = 0usize;
@ -180,11 +173,8 @@ fn import_seed_products(app: tauri::AppHandle) -> Result<String, String> {
iron_mg, vit_a_mcg, vit_b1_mg, vit_b2_mg, vit_b3_mg, vit_c_mg,
vit_e_mg, carotene_mcg, COALESCE(nutrients_guessed, 0)
FROM seed.products",
[],
).map_err(|e| e.to_string())?;
[]
).map_err(|e| e.to_string())?;
inserted += 1;
processed += 1;
// Update KBJU + micronutrients for existing store products (seed already attached above)
// Sync nutrients_guessed flag
@ -193,32 +183,32 @@ fn import_seed_products(app: tauri::AppHandle) -> Result<String, String> {
WHERE (nutrients_guessed IS NULL OR nutrients_guessed = 0)
AND name_lower IN (SELECT s.name_lower FROM seed.products s WHERE s.nutrients_guessed = 1)"
);
// Update KBJU + micronutrients for store products that user hasn't manually edited.
// This ensures seed corrections (e.g. bread 268→243) flow through to app DB.
let _ = main_db.execute_batch(
"UPDATE main.products SET
kcal_per_100g = (SELECT s.kcal_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
protein_per_100g = (SELECT s.protein_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
fat_per_100g = (SELECT s.fat_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
carbs_per_100g = (SELECT s.carbs_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
fiber_per_100g = (SELECT s.fiber_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
nutrients_guessed= COALESCE((SELECT s.nutrients_guessed FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), nutrients_guessed),
sodium_mg = COALESCE((SELECT s.sodium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), sodium_mg),
potassium_mg = COALESCE((SELECT s.potassium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), potassium_mg),
calcium_mg = COALESCE((SELECT s.calcium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), calcium_mg),
magnesium_mg = COALESCE((SELECT s.magnesium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), magnesium_mg),
phosphorus_mg = COALESCE((SELECT s.phosphorus_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), phosphorus_mg),
iron_mg = COALESCE((SELECT s.iron_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), iron_mg),
vit_a_mcg = COALESCE((SELECT s.vit_a_mcg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_a_mcg),
vit_b1_mg = COALESCE((SELECT s.vit_b1_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_b1_mg),
vit_b2_mg = COALESCE((SELECT s.vit_b2_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_b2_mg),
vit_b3_mg = COALESCE((SELECT s.vit_b3_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_b3_mg),
vit_c_mg = COALESCE((SELECT s.vit_c_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_c_mg),
vit_e_mg = COALESCE((SELECT s.vit_e_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_e_mg),
carotene_mcg = COALESCE((SELECT s.carotene_mcg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), carotene_mcg)
WHERE (main.products.user_edited IS NULL OR main.products.user_edited = 0)
AND main.products.source IN ('magnit', '5ka', 'kdv', 'ion')
AND main.products.name_lower IN (SELECT s2.name_lower FROM seed.products s2)"
// Update KBJU + micronutrients for store products that user hasn't manually edited.
// This ensures seed corrections (e.g. bread 268→243) flow through to app DB.
let _ = main_db.execute_batch(
"UPDATE main.products SET
kcal_per_100g = (SELECT s.kcal_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
protein_per_100g = (SELECT s.protein_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
fat_per_100g = (SELECT s.fat_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
carbs_per_100g = (SELECT s.carbs_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
fiber_per_100g = (SELECT s.fiber_per_100g FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1),
nutrients_guessed= COALESCE((SELECT s.nutrients_guessed FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), nutrients_guessed),
sodium_mg = COALESCE((SELECT s.sodium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), sodium_mg),
potassium_mg = COALESCE((SELECT s.potassium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), potassium_mg),
calcium_mg = COALESCE((SELECT s.calcium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), calcium_mg),
magnesium_mg = COALESCE((SELECT s.magnesium_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), magnesium_mg),
phosphorus_mg = COALESCE((SELECT s.phosphorus_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), phosphorus_mg),
iron_mg = COALESCE((SELECT s.iron_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), iron_mg),
vit_a_mcg = COALESCE((SELECT s.vit_a_mcg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_a_mcg),
vit_b1_mg = COALESCE((SELECT s.vit_b1_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_b1_mg),
vit_b2_mg = COALESCE((SELECT s.vit_b2_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_b2_mg),
vit_b3_mg = COALESCE((SELECT s.vit_b3_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_b3_mg),
vit_c_mg = COALESCE((SELECT s.vit_c_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_c_mg),
vit_e_mg = COALESCE((SELECT s.vit_e_mg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), vit_e_mg),
carotene_mcg = COALESCE((SELECT s.carotene_mcg FROM seed.products s WHERE s.name_lower = main.products.name_lower LIMIT 1), carotene_mcg)
WHERE (main.products.user_edited IS NULL OR main.products.user_edited = 0)
AND main.products.source IN ('magnit', '5ka', 'kdv', 'ion')
AND main.products.name_lower IN (SELECT s2.name_lower FROM seed.products s2)"
);
// Update seed version AFTER all inserts+updates succeed