324 lines
9.3 KiB
JavaScript
324 lines
9.3 KiB
JavaScript
const pool = require("../db/pool");
|
|
|
|
function normalizeItemName(itemName) {
|
|
return String(itemName || "").trim().toLowerCase();
|
|
}
|
|
|
|
async function findOrCreateItem(itemName) {
|
|
const normalizedName = normalizeItemName(itemName);
|
|
const existing = await pool.query(
|
|
"SELECT id, name FROM items WHERE name ILIKE $1",
|
|
[normalizedName]
|
|
);
|
|
|
|
if (existing.rowCount > 0) {
|
|
return {
|
|
itemId: existing.rows[0].id,
|
|
itemName: existing.rows[0].name,
|
|
};
|
|
}
|
|
|
|
const created = await pool.query(
|
|
"INSERT INTO items (name) VALUES ($1) RETURNING id, name",
|
|
[normalizedName]
|
|
);
|
|
|
|
return {
|
|
itemId: created.rows[0].id,
|
|
itemName: created.rows[0].name,
|
|
};
|
|
}
|
|
|
|
async function getAvailableItemRecord(householdId, storeId, itemId) {
|
|
const result = await pool.query(
|
|
`WITH manageable_items AS (
|
|
SELECT DISTINCT hl.item_id
|
|
FROM household_lists hl
|
|
WHERE hl.household_id = $1
|
|
AND hl.store_id = $2
|
|
UNION
|
|
SELECT hsai.item_id
|
|
FROM household_store_available_items hsai
|
|
WHERE hsai.household_id = $1
|
|
AND hsai.store_id = $2
|
|
),
|
|
latest_list_items AS (
|
|
SELECT DISTINCT ON (hl.item_id)
|
|
hl.item_id,
|
|
hl.custom_image,
|
|
hl.custom_image_mime_type,
|
|
hl.modified_on,
|
|
hl.id
|
|
FROM household_lists hl
|
|
WHERE hl.household_id = $1
|
|
AND hl.store_id = $2
|
|
ORDER BY hl.item_id, hl.modified_on DESC NULLS LAST, hl.id DESC
|
|
)
|
|
SELECT
|
|
mi.item_id,
|
|
i.name AS item_name,
|
|
ENCODE(COALESCE(hsai.custom_image, lli.custom_image), 'base64') AS item_image,
|
|
COALESCE(hsai.custom_image_mime_type, lli.custom_image_mime_type) AS image_mime_type,
|
|
hic.item_type,
|
|
hic.item_group,
|
|
hic.zone,
|
|
hsai.created_at,
|
|
hsai.updated_at,
|
|
(hsai.item_id IS NOT NULL OR hic.item_id IS NOT NULL) AS has_managed_settings
|
|
FROM manageable_items mi
|
|
JOIN items i ON i.id = mi.item_id
|
|
LEFT JOIN latest_list_items lli ON lli.item_id = mi.item_id
|
|
LEFT JOIN household_store_available_items hsai
|
|
ON hsai.household_id = $1
|
|
AND hsai.store_id = $2
|
|
AND hsai.item_id = mi.item_id
|
|
LEFT JOIN household_item_classifications hic
|
|
ON hic.household_id = $1
|
|
AND hic.store_id = $2
|
|
AND hic.item_id = mi.item_id
|
|
WHERE mi.item_id = $3`,
|
|
[householdId, storeId, itemId]
|
|
);
|
|
|
|
return result.rows[0] || null;
|
|
}
|
|
|
|
exports.listAvailableItems = async (householdId, storeId, query = "") => {
|
|
const trimmedQuery = String(query || "").trim();
|
|
const values = [householdId, storeId];
|
|
let filterClause = "";
|
|
|
|
if (trimmedQuery) {
|
|
values.push(`%${trimmedQuery}%`);
|
|
filterClause = "WHERE i.name ILIKE $3";
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`WITH manageable_items AS (
|
|
SELECT DISTINCT hl.item_id
|
|
FROM household_lists hl
|
|
WHERE hl.household_id = $1
|
|
AND hl.store_id = $2
|
|
UNION
|
|
SELECT hsai.item_id
|
|
FROM household_store_available_items hsai
|
|
WHERE hsai.household_id = $1
|
|
AND hsai.store_id = $2
|
|
),
|
|
latest_list_items AS (
|
|
SELECT DISTINCT ON (hl.item_id)
|
|
hl.item_id,
|
|
hl.custom_image,
|
|
hl.custom_image_mime_type,
|
|
hl.modified_on,
|
|
hl.id
|
|
FROM household_lists hl
|
|
WHERE hl.household_id = $1
|
|
AND hl.store_id = $2
|
|
ORDER BY hl.item_id, hl.modified_on DESC NULLS LAST, hl.id DESC
|
|
)
|
|
SELECT
|
|
mi.item_id,
|
|
i.name AS item_name,
|
|
ENCODE(COALESCE(hsai.custom_image, lli.custom_image), 'base64') AS item_image,
|
|
COALESCE(hsai.custom_image_mime_type, lli.custom_image_mime_type) AS image_mime_type,
|
|
hic.item_type,
|
|
hic.item_group,
|
|
hic.zone,
|
|
hsai.created_at,
|
|
hsai.updated_at,
|
|
(hsai.item_id IS NOT NULL OR hic.item_id IS NOT NULL) AS has_managed_settings
|
|
FROM manageable_items mi
|
|
JOIN items i ON i.id = mi.item_id
|
|
LEFT JOIN latest_list_items lli ON lli.item_id = mi.item_id
|
|
LEFT JOIN household_store_available_items hsai
|
|
ON hsai.household_id = $1
|
|
AND hsai.store_id = $2
|
|
AND hsai.item_id = mi.item_id
|
|
LEFT JOIN household_item_classifications hic
|
|
ON hic.household_id = $1
|
|
AND hic.store_id = $2
|
|
AND hic.item_id = mi.item_id
|
|
${filterClause}
|
|
ORDER BY i.name ASC
|
|
LIMIT 100`,
|
|
values
|
|
);
|
|
|
|
return result.rows;
|
|
};
|
|
|
|
exports.getAvailableItemById = async (householdId, storeId, itemId) =>
|
|
getAvailableItemRecord(householdId, storeId, itemId);
|
|
|
|
exports.getAvailableItemImageByName = async (householdId, storeId, itemName) => {
|
|
const normalizedName = normalizeItemName(itemName);
|
|
const result = await pool.query(
|
|
`SELECT
|
|
hsai.item_id,
|
|
i.name AS item_name,
|
|
hsai.custom_image,
|
|
hsai.custom_image_mime_type
|
|
FROM household_store_available_items hsai
|
|
JOIN items i ON i.id = hsai.item_id
|
|
WHERE hsai.household_id = $1
|
|
AND hsai.store_id = $2
|
|
AND i.name ILIKE $3`,
|
|
[householdId, storeId, normalizedName]
|
|
);
|
|
|
|
return result.rows[0] || null;
|
|
};
|
|
|
|
exports.createAvailableItem = async (
|
|
householdId,
|
|
storeId,
|
|
itemName,
|
|
imageBuffer = null,
|
|
mimeType = null
|
|
) => {
|
|
const { itemId } = await findOrCreateItem(itemName);
|
|
|
|
await pool.query(
|
|
`INSERT INTO household_store_available_items
|
|
(household_id, store_id, item_id, custom_image, custom_image_mime_type, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, NOW())`,
|
|
[householdId, storeId, itemId, imageBuffer, mimeType]
|
|
);
|
|
|
|
return getAvailableItemRecord(householdId, storeId, itemId);
|
|
};
|
|
|
|
exports.updateAvailableItem = async (householdId, storeId, itemId, updates = {}) => {
|
|
const {
|
|
itemName,
|
|
imageBuffer,
|
|
mimeType,
|
|
removeImage = false,
|
|
} = updates;
|
|
|
|
const existing = await pool.query(
|
|
`SELECT item_id
|
|
FROM household_store_available_items
|
|
WHERE household_id = $1
|
|
AND store_id = $2
|
|
AND item_id = $3`,
|
|
[householdId, storeId, itemId]
|
|
);
|
|
|
|
const assignments = ["updated_at = NOW()"];
|
|
const values = [householdId, storeId, itemId];
|
|
let parameterIndex = values.length;
|
|
let targetItemId = itemId;
|
|
|
|
if (itemName !== undefined && String(itemName).trim() !== "") {
|
|
const { itemId: nextItemId } = await findOrCreateItem(itemName);
|
|
targetItemId = nextItemId;
|
|
parameterIndex += 1;
|
|
assignments.push(`item_id = $${parameterIndex}`);
|
|
values.push(nextItemId);
|
|
}
|
|
|
|
if (existing.rowCount === 0) {
|
|
if (imageBuffer && mimeType) {
|
|
await pool.query(
|
|
`INSERT INTO household_store_available_items
|
|
(household_id, store_id, item_id, custom_image, custom_image_mime_type, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, NOW())
|
|
ON CONFLICT (household_id, store_id, item_id)
|
|
DO UPDATE SET
|
|
custom_image = EXCLUDED.custom_image,
|
|
custom_image_mime_type = EXCLUDED.custom_image_mime_type,
|
|
updated_at = NOW()`,
|
|
[householdId, storeId, targetItemId, imageBuffer, mimeType]
|
|
);
|
|
} else if (targetItemId !== itemId) {
|
|
await pool.query(
|
|
`INSERT INTO household_store_available_items
|
|
(household_id, store_id, item_id, updated_at)
|
|
VALUES ($1, $2, $3, NOW())
|
|
ON CONFLICT (household_id, store_id, item_id)
|
|
DO UPDATE SET updated_at = NOW()`,
|
|
[householdId, storeId, targetItemId]
|
|
);
|
|
}
|
|
|
|
return getAvailableItemRecord(householdId, storeId, targetItemId);
|
|
}
|
|
|
|
if (removeImage) {
|
|
assignments.push("custom_image = NULL", "custom_image_mime_type = NULL");
|
|
} else if (imageBuffer && mimeType) {
|
|
parameterIndex += 1;
|
|
assignments.push(`custom_image = $${parameterIndex}`);
|
|
values.push(imageBuffer);
|
|
|
|
parameterIndex += 1;
|
|
assignments.push(`custom_image_mime_type = $${parameterIndex}`);
|
|
values.push(mimeType);
|
|
}
|
|
|
|
const result = await pool.query(
|
|
`UPDATE household_store_available_items
|
|
SET ${assignments.join(", ")}
|
|
WHERE household_id = $1
|
|
AND store_id = $2
|
|
AND item_id = $3
|
|
RETURNING item_id`,
|
|
values
|
|
);
|
|
|
|
if (result.rowCount === 0) {
|
|
return null;
|
|
}
|
|
|
|
return getAvailableItemRecord(householdId, storeId, result.rows[0].item_id);
|
|
};
|
|
|
|
exports.deleteAvailableItem = async (householdId, storeId, itemId) => {
|
|
const result = await pool.query(
|
|
`DELETE FROM household_store_available_items
|
|
WHERE household_id = $1
|
|
AND store_id = $2
|
|
AND item_id = $3`,
|
|
[householdId, storeId, itemId]
|
|
);
|
|
|
|
return result.rowCount > 0;
|
|
};
|
|
|
|
exports.importCurrentListItems = async (householdId, storeId) => {
|
|
const result = await pool.query(
|
|
`INSERT INTO household_store_available_items
|
|
(household_id, store_id, item_id, custom_image, custom_image_mime_type, updated_at)
|
|
SELECT
|
|
hl.household_id,
|
|
hl.store_id,
|
|
hl.item_id,
|
|
hl.custom_image,
|
|
hl.custom_image_mime_type,
|
|
NOW()
|
|
FROM household_lists hl
|
|
WHERE hl.household_id = $1
|
|
AND hl.store_id = $2
|
|
ON CONFLICT (household_id, store_id, item_id) DO NOTHING
|
|
RETURNING item_id`,
|
|
[householdId, storeId]
|
|
);
|
|
|
|
return result.rowCount;
|
|
};
|
|
|
|
exports.hasAvailableItems = async (householdId, storeId) => {
|
|
const result = await pool.query(
|
|
`SELECT 1
|
|
FROM household_store_available_items
|
|
WHERE household_id = $1
|
|
AND store_id = $2
|
|
LIMIT 1`,
|
|
[householdId, storeId]
|
|
);
|
|
|
|
return result.rowCount > 0;
|
|
};
|