samoulla-backend / controllers /shippingController.js
Samoulla Sync Bot
Auto-deploy Samoulla Backend: d3bc7c68dc6751f011c7ea16f23964dab14c7703
2a3c9e9
const bostaService = require('../utils/bostaService');
const ShippingPrice = require('../models/shippingPriceModel');
// Get all Cities (Governorates) merged with our local price data
exports.getGovernorates = async (req, res) => {
try {
const bostaData = await bostaService.getBostaCities();
const governorates = bostaData.data || bostaData;
// Fetch our local pricing data
const localPrices = await ShippingPrice.find();
const priceMap = new Map(localPrices.map((p) => [p.bostaCityId, p]));
// Merge price data into governorates list
const mergedGovernorates = governorates.map((gov) => {
const localData = priceMap.get(gov._id);
return {
...gov,
// Normalize names for frontend (Bosta uses name and nameAr strings)
name: {
ar:
gov.nameAr ||
gov.name ||
(gov._id === 'Jrb6X6ucjiYgMP4T7' ? 'الإسكندرية' : ''),
en: gov.name || (gov._id === 'Jrb6X6ucjiYgMP4T7' ? 'Alexandria' : ''),
},
fees: localData ? localData.fees || 0 : 0,
deliveryDays: localData ? localData.deliveryDays || 3 : 3,
isActive: localData ? localData.isActive !== false : true,
};
});
res.status(200).json({
status: 'success',
results: mergedGovernorates.length,
data: {
governorates: mergedGovernorates,
},
});
} catch (err) {
res.status(500).json({
status: 'error',
message: 'Failed to fetch governorates',
error: err.message,
});
}
};
// Update or Create Shipping Fee for a City
exports.updateShippingFee = async (req, res) => {
try {
const {
bostaCityId,
bostaZoneId,
type = 'city',
fees,
deliveryDays,
areaNameAr,
areaNameEn,
isActive,
} = req.body;
if (!bostaCityId) {
return res.status(400).json({
status: 'fail',
message: 'Bosta City ID is required',
});
}
// Search by both City and Zone ID to ensure uniqueness for zones
// For better control, we'll explicitly pick which one to match if multiple exist
// But usually there should be only one city document
const shippingPrice = await ShippingPrice.findOneAndUpdate(
{ bostaCityId, bostaZoneId: bostaZoneId || null },
{
fees,
deliveryDays,
areaNameAr,
areaNameEn,
type,
isActive,
},
{
new: true,
upsert: true,
runValidators: true,
setDefaultsOnInsert: true,
},
);
res.status(200).json({
status: 'success',
data: {
shippingPrice,
},
});
} catch (err) {
res.status(500).json({
status: 'error',
message: 'Failed to update shipping fee',
error: err.message,
});
}
};
// Get Zones (Areas/Districts) for a City
exports.getCities = async (req, res) => {
try {
const { id } = req.params; // governorate ID
const data = await bostaService.getBostaZones(id);
const zones = data.data || data;
// Fetch local prices for these zones
const localPrices = await ShippingPrice.find({
bostaCityId: id,
type: 'zone',
});
const priceMap = new Map(localPrices.map((p) => [p.bostaZoneId, p]));
const mergedZones = zones.map((zone) => {
const localData = priceMap.get(zone._id);
return {
...zone,
name: {
ar: zone.nameAr || zone.name,
en: zone.name,
},
fees: localData ? localData.fees : 0,
isActive: localData ? localData.isActive : true,
};
});
res.status(200).json({
status: 'success',
results: mergedZones.length,
data: {
cities: mergedZones,
},
});
} catch (err) {
res.status(500).json({
status: 'error',
message: 'Failed to fetch cities',
error: err.message,
});
}
};
// Get Districts for a City/Governorate
exports.getDistricts = async (req, res) => {
try {
const { id } = req.params; // governorate ID
const data = await bostaService.getBostaDistricts(id);
const districts = data.data || data;
const normalizedDistricts = districts.map((d) => ({
_id: d.districtId,
zoneId: d.zoneId,
name: {
ar: d.districtOtherName || d.districtName,
en: d.districtName,
},
}));
res.status(200).json({
status: 'success',
results: normalizedDistricts.length,
data: {
districts: normalizedDistricts,
},
});
} catch (err) {
res.status(500).json({
status: 'error',
message: 'Failed to fetch districts',
error: err.message,
});
}
};