feat: add updateContent method for abonements and corresponding route
This commit is contained in:
parent
b7d9ef8371
commit
002dbc78c1
@ -74,6 +74,28 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async updateContent(req, res) {
|
||||||
|
try {
|
||||||
|
const rawUser = req.user || {};
|
||||||
|
const actorUser = { ...rawUser, id: rawUser.id ?? rawUser.userId ?? null };
|
||||||
|
const data = await service.updateContent({
|
||||||
|
abonementId: req.params.id,
|
||||||
|
actorUser,
|
||||||
|
items: req.body.items,
|
||||||
|
});
|
||||||
|
return res.json({ success: true, data });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[ABONEMENT UPDATE CONTENT]', err);
|
||||||
|
if (err?.message === 'Not found') {
|
||||||
|
return res.status(404).json({ success: false, message: 'Abonement not found' });
|
||||||
|
}
|
||||||
|
if (err?.message === 'Forbidden') {
|
||||||
|
return res.status(403).json({ success: false, message: 'Forbidden' });
|
||||||
|
}
|
||||||
|
return res.status(400).json({ success: false, message: err.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async renew(req, res) {
|
async renew(req, res) {
|
||||||
try {
|
try {
|
||||||
const rawUser = req.user || {};
|
const rawUser = req.user || {};
|
||||||
|
|||||||
@ -256,6 +256,44 @@ class AbonemmentRepository {
|
|||||||
return this.getAbonementById(id);
|
return this.getAbonementById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async transitionContent(id, contentPayload = {}) {
|
||||||
|
const conn = await pool.getConnection();
|
||||||
|
try {
|
||||||
|
await conn.beginTransaction();
|
||||||
|
await conn.query(
|
||||||
|
`UPDATE coffee_abonements
|
||||||
|
SET pack_breakdown = ?, price = ?, currency = ?, updated_at = ?
|
||||||
|
WHERE id = ?`,
|
||||||
|
[
|
||||||
|
JSON.stringify(contentPayload.pack_breakdown || []),
|
||||||
|
contentPayload.price ?? null,
|
||||||
|
contentPayload.currency ?? 'EUR',
|
||||||
|
contentPayload.updated_at || new Date(),
|
||||||
|
id,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await conn.query(
|
||||||
|
`INSERT INTO coffee_abonement_history
|
||||||
|
(abonement_id, event_type, event_at, actor_user_id, details, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, NOW())`,
|
||||||
|
[
|
||||||
|
id,
|
||||||
|
contentPayload.event_type || 'content_updated',
|
||||||
|
contentPayload.event_at || new Date(),
|
||||||
|
contentPayload.actor_user_id || null,
|
||||||
|
JSON.stringify(contentPayload.details || {}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await conn.commit();
|
||||||
|
} catch (err) {
|
||||||
|
await conn.rollback();
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
|
return this.getAbonementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
async listDueForBilling(now) {
|
async listDueForBilling(now) {
|
||||||
const [rows] = await pool.query(
|
const [rows] = await pool.query(
|
||||||
`SELECT * FROM coffee_abonements
|
`SELECT * FROM coffee_abonements
|
||||||
|
|||||||
@ -116,6 +116,7 @@ router.get('/company-stamps/all', authMiddleware, adminOnly, forceCompanyForAdmi
|
|||||||
// Admin: coffee products
|
// Admin: coffee products
|
||||||
router.get('/admin/coffee', authMiddleware, adminOnly, CoffeeController.list);
|
router.get('/admin/coffee', authMiddleware, adminOnly, CoffeeController.list);
|
||||||
router.get('/admin/coffee/active', authMiddleware, adminOnly, CoffeeController.listActive);
|
router.get('/admin/coffee/active', authMiddleware, adminOnly, CoffeeController.listActive);
|
||||||
|
router.get('/coffee/active', authMiddleware, CoffeeController.listActive);
|
||||||
|
|
||||||
|
|
||||||
// Matrix GETs
|
// Matrix GETs
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const PoolController = require('../controller/pool/PoolController'); // <-- new
|
|||||||
const MatrixController = require('../controller/matrix/MatrixController'); // <-- new
|
const MatrixController = require('../controller/matrix/MatrixController'); // <-- new
|
||||||
const AffiliateController = require('../controller/affiliate/AffiliateController'); // <-- new
|
const AffiliateController = require('../controller/affiliate/AffiliateController'); // <-- new
|
||||||
const NewsController = require('../controller/news/NewsController');
|
const NewsController = require('../controller/news/NewsController');
|
||||||
|
const AbonemmentController = require('../controller/abonemments/AbonemmentController');
|
||||||
|
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
@ -58,6 +59,7 @@ router.patch('/admin/news/:id/status', authMiddleware, adminOnly, NewsController
|
|||||||
// Personal profile (self-service) - no admin guard
|
// Personal profile (self-service) - no admin guard
|
||||||
router.patch('/profile/personal/basic', authMiddleware, PersonalProfileController.updateBasic);
|
router.patch('/profile/personal/basic', authMiddleware, PersonalProfileController.updateBasic);
|
||||||
router.patch('/profile/personal/bank', authMiddleware, PersonalProfileController.updateBank);
|
router.patch('/profile/personal/bank', authMiddleware, PersonalProfileController.updateBank);
|
||||||
|
router.patch('/abonements/:id/content', authMiddleware, AbonemmentController.updateContent);
|
||||||
|
|
||||||
// Add other PATCH routes here as needed
|
// Add other PATCH routes here as needed
|
||||||
|
|
||||||
|
|||||||
@ -449,6 +449,67 @@ class AbonemmentService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateContent({ abonementId, actorUser, items }) {
|
||||||
|
const abon = await this.repo.getAbonementById(abonementId);
|
||||||
|
if (!abon) throw new Error('Not found');
|
||||||
|
if (!this.canManageAbonement(abon, actorUser)) throw new Error('Forbidden');
|
||||||
|
if (!['active', 'paused'].includes(abon.status)) {
|
||||||
|
throw new Error('Only active or paused abonements can be updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(items) || items.length === 0) {
|
||||||
|
throw new Error('items must be a non-empty array');
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalPacks = 0;
|
||||||
|
let totalPrice = 0;
|
||||||
|
const breakdown = [];
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const coffeeId = item?.coffeeId;
|
||||||
|
const packs = Number(item?.quantity ?? 0);
|
||||||
|
if (!coffeeId) throw new Error('coffeeId is required for each item');
|
||||||
|
if (!Number.isFinite(packs) || packs <= 0) {
|
||||||
|
throw new Error('quantity must be a positive integer per item');
|
||||||
|
}
|
||||||
|
|
||||||
|
const product = await this.getCoffeeProduct(coffeeId);
|
||||||
|
if (!product || !product.is_active) throw new Error(`Product ${coffeeId} not available`);
|
||||||
|
|
||||||
|
totalPacks += packs;
|
||||||
|
totalPrice += packs * Number(product.price);
|
||||||
|
breakdown.push({
|
||||||
|
coffee_table_id: coffeeId,
|
||||||
|
coffee_title: product.title || null,
|
||||||
|
packs,
|
||||||
|
price_per_pack: Number(product.price),
|
||||||
|
currency: product.currency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalPacks !== 6 && totalPacks !== 12) {
|
||||||
|
throw new Error('Order must contain exactly 6 packs (60 capsules) or 12 packs (120 capsules).');
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousPacks = Array.isArray(abon.pack_breakdown)
|
||||||
|
? abon.pack_breakdown.reduce((sum, item) => sum + Number(item?.packs || item?.quantity || 0), 0)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return this.repo.transitionContent(abonementId, {
|
||||||
|
pack_breakdown: breakdown,
|
||||||
|
price: Number(totalPrice.toFixed(2)),
|
||||||
|
currency: breakdown[0]?.currency || abon.currency || 'EUR',
|
||||||
|
actor_user_id: actorUser?.id || null,
|
||||||
|
event_type: 'content_updated',
|
||||||
|
details: {
|
||||||
|
pack_group: abon.pack_group,
|
||||||
|
previous_total_packs: previousPacks,
|
||||||
|
total_packs: totalPacks,
|
||||||
|
effective_from: 'next_billing_cycle',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async renew({ abonementId, actorUser, invoiceId }) {
|
async renew({ abonementId, actorUser, invoiceId }) {
|
||||||
const abon = await this.repo.getAbonementById(abonementId);
|
const abon = await this.repo.getAbonementById(abonementId);
|
||||||
if (!abon) throw new Error('Not found');
|
if (!abon) throw new Error('Not found');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user