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) {
|
||||
try {
|
||||
const rawUser = req.user || {};
|
||||
|
||||
@ -256,6 +256,44 @@ class AbonemmentRepository {
|
||||
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) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT * FROM coffee_abonements
|
||||
|
||||
@ -116,6 +116,7 @@ router.get('/company-stamps/all', authMiddleware, adminOnly, forceCompanyForAdmi
|
||||
// Admin: coffee products
|
||||
router.get('/admin/coffee', authMiddleware, adminOnly, CoffeeController.list);
|
||||
router.get('/admin/coffee/active', authMiddleware, adminOnly, CoffeeController.listActive);
|
||||
router.get('/coffee/active', authMiddleware, CoffeeController.listActive);
|
||||
|
||||
|
||||
// Matrix GETs
|
||||
|
||||
@ -12,6 +12,7 @@ const PoolController = require('../controller/pool/PoolController'); // <-- new
|
||||
const MatrixController = require('../controller/matrix/MatrixController'); // <-- new
|
||||
const AffiliateController = require('../controller/affiliate/AffiliateController'); // <-- new
|
||||
const NewsController = require('../controller/news/NewsController');
|
||||
const AbonemmentController = require('../controller/abonemments/AbonemmentController');
|
||||
|
||||
const multer = require('multer');
|
||||
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
|
||||
router.patch('/profile/personal/basic', authMiddleware, PersonalProfileController.updateBasic);
|
||||
router.patch('/profile/personal/bank', authMiddleware, PersonalProfileController.updateBank);
|
||||
router.patch('/abonements/:id/content', authMiddleware, AbonemmentController.updateContent);
|
||||
|
||||
// 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 }) {
|
||||
const abon = await this.repo.getAbonementById(abonementId);
|
||||
if (!abon) throw new Error('Not found');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user