/*********************************************************************************************************************************************************************** * File Name: plan_prices.controller.js (admin) * Type of Program: Controller * Description: Admin CRUD for localized price overrides per tier plan. * Routes: GET/POST/PUT/DELETE /admin/tiers/:id/prices[/:currency] * Author: Kenneth Obsequio (@lash0000) * Date Created: Jun. 29, 2026 ***********************************************************************************************************************************************************************/ 'use strict'; const mdl_TierPlans = require('../../models/tiers/tier_plans.mdl'); const mdl_PlanPrices = require('../../models/tiers/plan_prices.mdl'); const R = require('../../utils/response.util'); const logActivity = require('../../utils/logActivity.util'); const { isSupported, SUPPORTED_CURRENCIES, validateLocalizedPrice } = require('../../utils/currency.util'); // ─── GET /admin/tiers/:id/prices ───────────────────────────────────────────── exports.getPrices = async (req, res) => { try { const plan = await mdl_TierPlans.findByPk(req.params.id); if (!plan) return R.error(res, 'Plan not found.', 404); const prices = await mdl_PlanPrices.findAll({ where: { plan_id: plan.plan_id }, order: [['currency', 'ASC']], }); return R.success(res, 'Localized prices retrieved.', prices); } catch (err) { console.error('[ADMIN][GET PLAN PRICES]', err); return R.error(res, 'Could not retrieve localized prices.', 500); } }; // ─── POST /admin/tiers/:id/prices ──────────────────────────────────────────── exports.addPrice = async (req, res) => { try { const plan = await mdl_TierPlans.findByPk(req.params.id); if (!plan) return R.error(res, 'Plan not found.', 404); const { currency, price } = req.body; if (!currency || price === undefined) return R.error(res, 'currency and price are required.', 400); if (!isSupported(currency)) return R.error(res, `Unsupported currency: ${currency}.`, 400); if (currency === plan.currency) return R.error(res, `${currency} is already the plan's base currency.`, 400); if (Number(price) < 0) return R.error(res, 'Price must be 0 or greater.', 400); const exists = await mdl_PlanPrices.findOne({ where: { plan_id: plan.plan_id, currency } }); if (exists) return R.error(res, `A localized price for ${currency} already exists. Use PUT to update it.`, 409); // ── Rate validation ──────────────────────────────────────────────────────── const validation = await validateLocalizedPrice(plan.price, plan.currency, price, currency); if (validation.zone === 'block') return R.error(res, validation.message, 422); const entry = await mdl_PlanPrices.create({ plan_id: plan.plan_id, currency: currency.toUpperCase(), price: Number(price), }); logActivity(req.user?.user_id, 'add_plan_price', { entityType: 'plan_price', details: { plan_id: plan.plan_id, currency, price }, }); if (validation.zone === 'warn') return res.status(201).json({ success: true, warning: true, message: validation.message, data: entry }); return R.success(res, 'Localized price added.', entry, 201); } catch (err) { console.error('[ADMIN][ADD PLAN PRICE]', err); return R.error(res, 'Could not add localized price.', 500); } }; // ─── PUT /admin/tiers/:id/prices/:currency ─────────────────────────────────── exports.updatePrice = async (req, res) => { try { const { id, currency } = req.params; const { price } = req.body; if (price === undefined) return R.error(res, 'price is required.', 400); if (Number(price) < 0) return R.error(res, 'Price must be 0 or greater.', 400); const entry = await mdl_PlanPrices.findOne({ where: { plan_id: id, currency: currency.toUpperCase() } }); if (!entry) return R.error(res, `No localized price found for ${currency} on this plan.`, 404); // ── Rate validation ──────────────────────────────────────────────────────── const plan = await mdl_TierPlans.findByPk(id); const validation = await validateLocalizedPrice(plan.price, plan.currency, price, currency); if (validation.zone === 'block') return R.error(res, validation.message, 422); await entry.update({ price: Number(price) }); logActivity(req.user?.user_id, 'update_plan_price', { entityType: 'plan_price', details: { plan_id: id, currency, price }, }); if (validation.zone === 'warn') return res.status(200).json({ success: true, warning: true, message: validation.message, data: entry }); return R.success(res, 'Localized price updated.', entry); } catch (err) { console.error('[ADMIN][UPDATE PLAN PRICE]', err); return R.error(res, 'Could not update localized price.', 500); } }; // ─── DELETE /admin/tiers/:id/prices/:currency ──────────────────────────────── exports.removePrice = async (req, res) => { try { const { id, currency } = req.params; const entry = await mdl_PlanPrices.findOne({ where: { plan_id: id, currency: currency.toUpperCase() } }); if (!entry) return R.error(res, `No localized price found for ${currency} on this plan.`, 404); await entry.destroy(); logActivity(req.user?.user_id, 'remove_plan_price', { entityType: 'plan_price', details: { plan_id: id, currency }, }); return R.success(res, 'Localized price removed.'); } catch (err) { console.error('[ADMIN][REMOVE PLAN PRICE]', err); return R.error(res, 'Could not remove localized price.', 500); } }; // ─── GET /admin/currencies ──────────────────────────────────────────────────── exports.getCurrencies = async (_req, res) => { return R.success(res, 'Supported currencies retrieved.', SUPPORTED_CURRENCIES); };