413 lines
12 KiB
JavaScript
413 lines
12 KiB
JavaScript
const storeModel = require("../models/store.model");
|
|
const { sendError } = require("../utils/http");
|
|
const { logError } = require("../utils/logger");
|
|
|
|
function parsePositiveInteger(value) {
|
|
const parsed = Number.parseInt(String(value), 10);
|
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
}
|
|
|
|
function getHouseholdId(req) {
|
|
return req.params.householdId || req.household?.id;
|
|
}
|
|
|
|
function getLocationId(req) {
|
|
return req.params.locationId || req.params.storeId;
|
|
}
|
|
|
|
// Legacy global store catalog. Kept for system-admin compatibility.
|
|
exports.getAllStores = async (req, res) => {
|
|
try {
|
|
const stores = await storeModel.getAllStores();
|
|
res.json(stores);
|
|
} catch (error) {
|
|
logError(req, "stores.getAllStores", error);
|
|
sendError(res, 500, "Failed to fetch stores");
|
|
}
|
|
};
|
|
|
|
exports.createStore = async (req, res) => {
|
|
try {
|
|
const { name, default_zones } = req.body;
|
|
|
|
if (!name || name.trim().length === 0) {
|
|
return sendError(res, 400, "Store name is required");
|
|
}
|
|
|
|
const store = await storeModel.createStore(name.trim(), default_zones || null);
|
|
|
|
res.status(201).json({
|
|
message: "Store created successfully",
|
|
store,
|
|
});
|
|
} catch (error) {
|
|
logError(req, "stores.createStore", error);
|
|
if (error.code === "23505") {
|
|
return sendError(res, 400, "Store with this name already exists");
|
|
}
|
|
sendError(res, 500, "Failed to create store");
|
|
}
|
|
};
|
|
|
|
exports.updateStore = async (req, res) => {
|
|
try {
|
|
const { name, default_zones } = req.body;
|
|
|
|
const store = await storeModel.updateStore(req.params.storeId, {
|
|
name: name?.trim(),
|
|
default_zones,
|
|
});
|
|
|
|
if (!store) {
|
|
return sendError(res, 404, "Store not found");
|
|
}
|
|
|
|
res.json({
|
|
message: "Store updated successfully",
|
|
store,
|
|
});
|
|
} catch (error) {
|
|
logError(req, "stores.updateStore", error);
|
|
sendError(res, 500, "Failed to update store");
|
|
}
|
|
};
|
|
|
|
exports.deleteStore = async (req, res) => {
|
|
try {
|
|
await storeModel.deleteStore(req.params.storeId);
|
|
res.json({ message: "Store deleted successfully" });
|
|
} catch (error) {
|
|
logError(req, "stores.deleteStore", error);
|
|
if (error.message.includes("in use")) {
|
|
return sendError(res, 400, error.message);
|
|
}
|
|
sendError(res, 500, "Failed to delete store");
|
|
}
|
|
};
|
|
|
|
// Household-owned store/location management.
|
|
exports.getHouseholdStores = async (req, res) => {
|
|
try {
|
|
const stores = await storeModel.getHouseholdStores(getHouseholdId(req));
|
|
res.json(stores);
|
|
} catch (error) {
|
|
logError(req, "stores.getHouseholdStores", error);
|
|
sendError(res, 500, "Failed to fetch household stores");
|
|
}
|
|
};
|
|
|
|
exports.createHouseholdStore = async (req, res) => {
|
|
try {
|
|
const householdId = getHouseholdId(req);
|
|
const { name, location_name, address } = req.body;
|
|
|
|
if (!name || name.trim().length === 0) {
|
|
return sendError(res, 400, "Store name is required");
|
|
}
|
|
|
|
const store = await storeModel.createHouseholdStore(
|
|
householdId,
|
|
name,
|
|
location_name || "Default Location",
|
|
address || null,
|
|
req.user.id
|
|
);
|
|
|
|
res.status(201).json({
|
|
message: "Store location created successfully",
|
|
store,
|
|
});
|
|
} catch (error) {
|
|
logError(req, "stores.createHouseholdStore", error);
|
|
if (error.code === "23505") {
|
|
return sendError(res, 400, "Store or location already exists for this household");
|
|
}
|
|
sendError(res, 500, "Failed to create store location");
|
|
}
|
|
};
|
|
|
|
exports.updateHouseholdStore = async (req, res) => {
|
|
try {
|
|
const { name } = req.body;
|
|
const householdStoreId = parsePositiveInteger(req.params.householdStoreId);
|
|
|
|
if (!householdStoreId) {
|
|
return sendError(res, 400, "Store ID must be a positive integer");
|
|
}
|
|
|
|
if (!name || name.trim().length === 0) {
|
|
return sendError(res, 400, "Store name is required");
|
|
}
|
|
|
|
const store = await storeModel.updateHouseholdStore(getHouseholdId(req), householdStoreId, {
|
|
name,
|
|
});
|
|
|
|
if (!store) {
|
|
return sendError(res, 404, "Store not found");
|
|
}
|
|
|
|
res.json({ message: "Store updated successfully", store });
|
|
} catch (error) {
|
|
logError(req, "stores.updateHouseholdStore", error);
|
|
sendError(res, 500, "Failed to update store");
|
|
}
|
|
};
|
|
|
|
exports.deleteHouseholdStore = async (req, res) => {
|
|
try {
|
|
const householdStoreId = parsePositiveInteger(req.params.householdStoreId);
|
|
if (!householdStoreId) {
|
|
return sendError(res, 400, "Store ID must be a positive integer");
|
|
}
|
|
|
|
const deleted = await storeModel.deleteHouseholdStore(getHouseholdId(req), householdStoreId);
|
|
if (!deleted) {
|
|
return sendError(res, 404, "Store not found");
|
|
}
|
|
|
|
res.json({ message: "Store deleted successfully" });
|
|
} catch (error) {
|
|
logError(req, "stores.deleteHouseholdStore", error);
|
|
if (error.message.includes("last store location")) {
|
|
return sendError(res, 400, error.message);
|
|
}
|
|
sendError(res, 500, "Failed to delete store");
|
|
}
|
|
};
|
|
|
|
exports.addLocationToStore = async (req, res) => {
|
|
try {
|
|
const householdStoreId = parsePositiveInteger(req.params.householdStoreId);
|
|
const { name, address } = req.body;
|
|
|
|
if (!householdStoreId) {
|
|
return sendError(res, 400, "Store ID must be a positive integer");
|
|
}
|
|
|
|
if (!name || name.trim().length === 0) {
|
|
return sendError(res, 400, "Location name is required");
|
|
}
|
|
|
|
const location = await storeModel.addLocationToStore(
|
|
getHouseholdId(req),
|
|
householdStoreId,
|
|
name,
|
|
address || null,
|
|
req.user.id
|
|
);
|
|
|
|
if (!location) {
|
|
return sendError(res, 404, "Store not found");
|
|
}
|
|
|
|
res.status(201).json({
|
|
message: "Location added successfully",
|
|
store: location,
|
|
});
|
|
} catch (error) {
|
|
logError(req, "stores.addLocationToStore", error);
|
|
if (error.code === "23505") {
|
|
return sendError(res, 400, "Location already exists for this store");
|
|
}
|
|
sendError(res, 500, "Failed to add location");
|
|
}
|
|
};
|
|
|
|
exports.updateLocation = async (req, res) => {
|
|
try {
|
|
const locationId = parsePositiveInteger(getLocationId(req));
|
|
const { name, address, map_data } = req.body;
|
|
|
|
if (!locationId) {
|
|
return sendError(res, 400, "Location ID must be a positive integer");
|
|
}
|
|
|
|
const location = await storeModel.updateLocation(getHouseholdId(req), locationId, {
|
|
name,
|
|
address,
|
|
map_data,
|
|
});
|
|
|
|
if (!location) {
|
|
return sendError(res, 404, "Location not found");
|
|
}
|
|
|
|
res.json({ message: "Location updated successfully", store: location });
|
|
} catch (error) {
|
|
logError(req, "stores.updateLocation", error);
|
|
sendError(res, 500, "Failed to update location");
|
|
}
|
|
};
|
|
|
|
exports.deleteLocation = async (req, res) => {
|
|
try {
|
|
const locationId = parsePositiveInteger(getLocationId(req));
|
|
if (!locationId) {
|
|
return sendError(res, 400, "Location ID must be a positive integer");
|
|
}
|
|
|
|
const deleted = await storeModel.deleteLocation(getHouseholdId(req), locationId);
|
|
if (!deleted) {
|
|
return sendError(res, 404, "Location not found");
|
|
}
|
|
|
|
res.json({ message: "Location removed successfully" });
|
|
} catch (error) {
|
|
logError(req, "stores.deleteLocation", error);
|
|
if (error.message.includes("last store location")) {
|
|
return sendError(res, 400, error.message);
|
|
}
|
|
sendError(res, 500, "Failed to remove location");
|
|
}
|
|
};
|
|
|
|
exports.setDefaultLocation = async (req, res) => {
|
|
try {
|
|
const locationId = parsePositiveInteger(getLocationId(req));
|
|
if (!locationId) {
|
|
return sendError(res, 400, "Location ID must be a positive integer");
|
|
}
|
|
|
|
await storeModel.setDefaultLocation(getHouseholdId(req), locationId);
|
|
res.json({ message: "Default location updated successfully" });
|
|
} catch (error) {
|
|
logError(req, "stores.setDefaultLocation", error);
|
|
sendError(res, 500, "Failed to set default location");
|
|
}
|
|
};
|
|
|
|
exports.getLocationZones = async (req, res) => {
|
|
try {
|
|
const locationId = parsePositiveInteger(getLocationId(req));
|
|
if (!locationId) {
|
|
return sendError(res, 400, "Location ID must be a positive integer");
|
|
}
|
|
|
|
const zones = await storeModel.listLocationZones(getHouseholdId(req), locationId);
|
|
res.json({ zones });
|
|
} catch (error) {
|
|
logError(req, "stores.getLocationZones", error);
|
|
sendError(res, 500, "Failed to load zones");
|
|
}
|
|
};
|
|
|
|
exports.createZone = async (req, res) => {
|
|
try {
|
|
const locationId = parsePositiveInteger(getLocationId(req));
|
|
const { name, sort_order, color, map_metadata } = req.body;
|
|
|
|
if (!locationId) {
|
|
return sendError(res, 400, "Location ID must be a positive integer");
|
|
}
|
|
|
|
if (!name || name.trim().length === 0) {
|
|
return sendError(res, 400, "Zone name is required");
|
|
}
|
|
|
|
const zone = await storeModel.createZone(getHouseholdId(req), locationId, {
|
|
name,
|
|
sort_order: Number.isInteger(sort_order) ? sort_order : Number.parseInt(sort_order, 10),
|
|
color,
|
|
map_metadata,
|
|
});
|
|
|
|
res.status(201).json({ message: "Zone created successfully", zone });
|
|
} catch (error) {
|
|
logError(req, "stores.createZone", error);
|
|
if (error.code === "23505") {
|
|
return sendError(res, 400, "Zone already exists for this location");
|
|
}
|
|
sendError(res, 500, "Failed to create zone");
|
|
}
|
|
};
|
|
|
|
exports.updateZone = async (req, res) => {
|
|
try {
|
|
const locationId = parsePositiveInteger(getLocationId(req));
|
|
const zoneId = parsePositiveInteger(req.params.zoneId);
|
|
|
|
if (!locationId || !zoneId) {
|
|
return sendError(res, 400, "Location ID and zone ID must be positive integers");
|
|
}
|
|
|
|
const sortOrder = req.body.sort_order;
|
|
const zone = await storeModel.updateZone(getHouseholdId(req), locationId, zoneId, {
|
|
name: req.body.name,
|
|
sort_order:
|
|
sortOrder === undefined
|
|
? undefined
|
|
: Number.isInteger(sortOrder)
|
|
? sortOrder
|
|
: Number.parseInt(sortOrder, 10),
|
|
color: req.body.color,
|
|
map_metadata: req.body.map_metadata,
|
|
is_active: req.body.is_active,
|
|
});
|
|
|
|
if (!zone) {
|
|
return sendError(res, 404, "Zone not found");
|
|
}
|
|
|
|
res.json({ message: "Zone updated successfully", zone });
|
|
} catch (error) {
|
|
logError(req, "stores.updateZone", error);
|
|
sendError(res, 500, "Failed to update zone");
|
|
}
|
|
};
|
|
|
|
exports.deleteZone = async (req, res) => {
|
|
try {
|
|
const locationId = parsePositiveInteger(getLocationId(req));
|
|
const zoneId = parsePositiveInteger(req.params.zoneId);
|
|
|
|
if (!locationId || !zoneId) {
|
|
return sendError(res, 400, "Location ID and zone ID must be positive integers");
|
|
}
|
|
|
|
const deleted = await storeModel.deleteZone(getHouseholdId(req), locationId, zoneId);
|
|
if (!deleted) {
|
|
return sendError(res, 404, "Zone not found");
|
|
}
|
|
|
|
res.json({ message: "Zone removed successfully" });
|
|
} catch (error) {
|
|
logError(req, "stores.deleteZone", error);
|
|
sendError(res, 500, "Failed to remove zone");
|
|
}
|
|
};
|
|
|
|
// Backward-compatible handlers for the old /stores/household routes.
|
|
exports.addStoreToHousehold = async (req, res) => {
|
|
try {
|
|
const { storeId } = req.body;
|
|
if (!storeId) {
|
|
return sendError(res, 400, "Store ID is required");
|
|
}
|
|
|
|
const legacyStore = await storeModel.getStoreById(storeId);
|
|
if (!legacyStore) {
|
|
return sendError(res, 404, "Store not found");
|
|
}
|
|
|
|
const store = await storeModel.createHouseholdStore(
|
|
getHouseholdId(req),
|
|
legacyStore.name,
|
|
"Default Location",
|
|
null,
|
|
req.user.id
|
|
);
|
|
|
|
res.status(201).json({
|
|
message: "Store added to household successfully",
|
|
store,
|
|
});
|
|
} catch (error) {
|
|
logError(req, "stores.addStoreToHousehold", error);
|
|
sendError(res, 500, "Failed to add store to household");
|
|
}
|
|
};
|
|
|
|
exports.removeStoreFromHousehold = exports.deleteLocation;
|
|
exports.setDefaultStore = exports.setDefaultLocation;
|