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