Import Export Grocery List

And misc improvements
This commit is contained in:
2026-03-30 21:05:12 -05:00
parent 5e0b096082
commit 9a78c4564e
9 changed files with 919 additions and 225 deletions
+126 -5
View File
@@ -202,12 +202,16 @@ function migrateLegacyGroceriesIfNeeded(): array {
}
/**
* Create catalog row + list line (used by grocery_item_create and meal → grocery).
* Append one shopping line using already-loaded catalog and lists (mutates arrays).
*
* @param array<int, array<string, mixed>> $catalog
* @param array{byStore: array<string, array<int, array<string, mixed>>>} $lists
* @param array<int, array<string, mixed>> $stores
* @return array{ok: bool, item?: array<string, mixed>, error?: string}
*/
function groceryAppendShoppingLine(
function groceryAppendShoppingLineIntoBuffers(
array &$catalog,
array &$lists,
array $stores,
string $storeId,
string $name,
@@ -234,7 +238,6 @@ function groceryAppendShoppingLine(
$price = trim($price);
$image = trim($image);
$catalog = normalizeCatalogList(readJsonFile('grocery_catalog.json'));
$dedupeKey = groceryCatalogDedupeKey($storeId, $name, $size);
$catalogId = null;
foreach ($catalog as $i => $c) {
@@ -296,12 +299,58 @@ function groceryAppendShoppingLine(
$line['mealTitle'] = $mealTitleMeta;
}
$lists = normalizeGroceryLists(readJsonFile('grocery_lists.json'));
if (!isset($lists['byStore'][$storeId])) {
$lists['byStore'][$storeId] = [];
}
$lists['byStore'][$storeId][] = $line;
return ['ok' => true, 'item' => $line];
}
/**
* Create catalog row + list line (used by grocery_item_create and meal → grocery).
*
* @param array<int, array<string, mixed>> $stores
* @return array{ok: bool, item?: array<string, mixed>, error?: string}
*/
function groceryAppendShoppingLine(
array $stores,
string $storeId,
string $name,
string $description = '',
string $size = '',
string $quantity = '1',
string $price = '',
string $image = '',
string $source = 'manual',
int $recurringIntervalDays = 0,
?string $mealId = null,
?string $mealTitleMeta = null
): array {
$catalog = normalizeCatalogList(readJsonFile('grocery_catalog.json'));
$lists = normalizeGroceryLists(readJsonFile('grocery_lists.json'));
$res = groceryAppendShoppingLineIntoBuffers(
$catalog,
$lists,
$stores,
$storeId,
$name,
$description,
$size,
$quantity,
$price,
$image,
$source,
$recurringIntervalDays,
$mealId,
$mealTitleMeta
);
if (!$res['ok']) {
return $res;
}
if (!writeJsonFile('grocery_catalog.json', $catalog)) {
return ['ok' => false, 'error' => 'Failed to save catalog'];
}
@@ -309,7 +358,79 @@ function groceryAppendShoppingLine(
return ['ok' => false, 'error' => 'Failed to save grocery list'];
}
return ['ok' => true, 'item' => $line];
return $res;
}
/**
* Append several meal shopping lines in one disk transaction (catalog + lists).
*
* @param array<int, array<string, mixed>> $stores
* @param list<array{name?:mixed, storeId?:mixed, description?:mixed, size?:mixed, quantity?:mixed, price?:mixed, image?:mixed}> $itemRows
* @return int number of lines successfully added
*/
function groceryAppendMealItemRowsBatch(
array $stores,
array $itemRows,
string $mealId,
string $mealTitle
): int {
if (count($itemRows) === 0) {
return 0;
}
$defaultStore = groceryFirstStoreId($stores);
if ($defaultStore === '') {
return 0;
}
$catalog = normalizeCatalogList(readJsonFile('grocery_catalog.json'));
$lists = normalizeGroceryLists(readJsonFile('grocery_lists.json'));
$added = 0;
foreach ($itemRows as $row) {
if (!is_array($row)) {
continue;
}
$name = trim((string) ($row['name'] ?? ''));
if ($name === '') {
continue;
}
$sid = trim((string) ($row['storeId'] ?? ''));
if ($sid === '' || findStoreById($stores, $sid) === null) {
$sid = $defaultStore;
}
$res = groceryAppendShoppingLineIntoBuffers(
$catalog,
$lists,
$stores,
$sid,
$name,
trim((string) ($row['description'] ?? '')),
trim((string) ($row['size'] ?? '')),
trim((string) ($row['quantity'] ?? '1')) ?: '1',
trim((string) ($row['price'] ?? '')),
trim((string) ($row['image'] ?? '')),
'meal_plan',
0,
$mealId !== '' ? $mealId : null,
$mealTitle !== '' ? $mealTitle : null
);
if ($res['ok']) {
$added++;
}
}
if ($added === 0) {
return 0;
}
if (!writeJsonFile('grocery_catalog.json', $catalog)) {
return 0;
}
if (!writeJsonFile('grocery_lists.json', $lists)) {
return 0;
}
return $added;
}
/**
+1 -36
View File
@@ -251,42 +251,7 @@ function pushMealItemsToGrocery(array $meal, array $stores): int {
}
$mealId = (string) ($meal['id'] ?? '');
$mealTitle = (string) ($meal['title'] ?? '');
$defaultStore = groceryFirstStoreId($stores);
if ($defaultStore === '') {
return 0;
}
$added = 0;
foreach ($items as $row) {
if (!is_array($row)) {
continue;
}
$name = trim((string) ($row['name'] ?? ''));
if ($name === '') {
continue;
}
$sid = trim((string) ($row['storeId'] ?? ''));
if ($sid === '' || findStoreById($stores, $sid) === null) {
$sid = $defaultStore;
}
$res = groceryAppendShoppingLine(
$stores,
$sid,
$name,
trim((string) ($row['description'] ?? '')),
trim((string) ($row['size'] ?? '')),
trim((string) ($row['quantity'] ?? '1')) ?: '1',
trim((string) ($row['price'] ?? '')),
trim((string) ($row['image'] ?? '')),
'meal_plan',
0,
$mealId !== '' ? $mealId : null,
$mealTitle !== '' ? $mealTitle : null
);
if ($res['ok']) {
$added++;
}
}
return $added;
return groceryAppendMealItemRowsBatch($stores, $items, $mealId, $mealTitle);
}
function mealDayShortLabel(string $weekStart, int $offset): string {