const { getDb } = require('../config/db'); const { AppError } = require('../middleware/error-handler'); const { formatMoney, parseMoney } = require('../utils/money'); const { fetchProviderServices } = require('./provider.service'); function parseMarkupPercent(value = process.env.DEFAULT_MARKUP_PERCENT || '0') { const raw = String(value).trim(); if (!/^\d+(?:\.\d{1,4})?$/.test(raw)) { throw new AppError('Invalid markup percent', 400, 'INVALID_MARKUP_PERCENT'); } return raw; } function calculateSellPrice(providerPrice, markupPercent = process.env.DEFAULT_MARKUP_PERCENT || '0') { const price = parseMoney(String(providerPrice)); const markup = BigInt(Math.round(Number(markupPercent) * 10000)); const sellPrice = (price * (1000000n + markup) + 500000n) / 1000000n; return formatMoney(sellPrice); } function normalizeProviderService(service, markupPercent) { const providerServiceId = String(service.service ?? service.provider_service_id ?? '').trim(); const name = String(service.name ?? '').trim(); const category = String(service.category ?? 'Uncategorized').trim() || 'Uncategorized'; const providerPrice = String(service.rate ?? service.price ?? service.provider_price ?? '').trim(); const min = Number(service.min); const max = Number(service.max); if (!providerServiceId || !name || !providerPrice || !Number.isInteger(min) || !Number.isInteger(max)) { throw new AppError('Provider service item is invalid', 502, 'INVALID_PROVIDER_SERVICE'); } return { provider_service_id: providerServiceId, name, category, provider_price: formatMoney(parseMoney(providerPrice)), markup_percent: markupPercent, sell_price: calculateSellPrice(providerPrice, markupPercent), min, max, is_active: true }; } async function syncServicesFromProvider(markupPercentInput) { const markupPercent = parseMarkupPercent(markupPercentInput); const providerServices = await fetchProviderServices(); const services = providerServices.map((service) => normalizeProviderService(service, markupPercent)); const db = getDb(); await db.transaction(async (trx) => { for (const service of services) { const existing = await trx('services') .select('id') .where({ provider_service_id: service.provider_service_id }) .first(); if (existing) { await trx('services').where({ id: existing.id }).update(service); } else { await trx('services').insert(service); } } }); return { synced: services.length }; } async function listActiveServices() { return getDb()('services') .select('id', 'provider_service_id', 'name', 'category', 'sell_price', 'min', 'max', 'is_active', 'updated_at') .where({ is_active: true }) .orderBy('category', 'asc') .orderBy('name', 'asc'); } async function listAdminServices() { return getDb()('services') .select('id', 'provider_service_id', 'name', 'category', 'provider_price', 'markup_percent', 'sell_price', 'min', 'max', 'is_active', 'updated_at') .orderBy('category', 'asc') .orderBy('name', 'asc'); } async function updateService(serviceId, input) { const db = getDb(); const service = await db('services').where({ id: serviceId }).first(); if (!service) { throw new AppError('Service not found', 404, 'SERVICE_NOT_FOUND'); } const update = {}; if (input.name !== undefined) update.name = input.name; if (input.category !== undefined) update.category = input.category; if (input.min !== undefined) update.min = input.min; if (input.max !== undefined) update.max = input.max; if (input.is_active !== undefined) update.is_active = input.is_active; if (input.sell_price !== undefined) update.sell_price = formatMoney(parseMoney(input.sell_price)); if (input.markup_percent !== undefined) { update.markup_percent = parseMarkupPercent(input.markup_percent); update.sell_price = calculateSellPrice(service.provider_price, update.markup_percent); } if (Object.keys(update).length === 0) { throw new AppError('No service changes provided', 400, 'NO_SERVICE_CHANGES'); } await db('services').where({ id: serviceId }).update(update); return db('services') .select('id', 'provider_service_id', 'name', 'category', 'provider_price', 'markup_percent', 'sell_price', 'min', 'max', 'is_active', 'updated_at') .where({ id: serviceId }) .first(); } module.exports = { calculateSellPrice, listActiveServices, listAdminServices, syncServicesFromProvider, updateService };