Initial SMM panel implementation
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user