From fab7e2b72ac05fbdc218aed0d03bdbded16314e5 Mon Sep 17 00:00:00 2001 From: adriano Date: Tue, 10 Jun 2025 14:15:34 -0300 Subject: [PATCH] feat: added api usage period by date --- controllers/apiUsagePricing.js | 146 ++++++++++++++++++++++++++++++++- models/API_Products.js | 55 +++++++++++++ routes/apiPriceRoute.js | 8 +- 3 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 models/API_Products.js diff --git a/controllers/apiUsagePricing.js b/controllers/apiUsagePricing.js index c540898..3e258d5 100644 --- a/controllers/apiUsagePricing.js +++ b/controllers/apiUsagePricing.js @@ -1,10 +1,12 @@ const { StatusCodes } = require("http-status-codes") const API_Pricing = require("../models/API_Pricing.js") +const mongoose = require('mongoose'); const { mustContainProperties, calculateApiUsage, } = require('../utils') const API_Usage = require("../models/API_Usage.js") +const API_Products = require("../models/API_Products.js") const billingSumUsage = require("../utils/billingSumUsage.js") const moment = require('moment') const API_Call = require("../models/API_Call.js") @@ -47,6 +49,145 @@ const setApiPricing = async (req, res) => { res.status(StatusCodes.OK).json({ apiPricing }) } +const updateProductPricing = async (req, res) => { + const { price, name, description } = req.body; + const { id } = req.params; + + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(StatusCodes.BAD_REQUEST).json({ + error: 'ID do produto inválido.' + }); + } + + let existingProduct = await API_Products.findById(id); + + if (!existingProduct) { + return res.status(StatusCodes.NOT_FOUND).json({ + error: 'Produto não encontrado.' + }); + } + + if (price) { + const activePriceIndex = existingProduct.priceHistory.findIndex(priceEntry => !priceEntry.endDate); + + if (activePriceIndex !== -1 && existingProduct.priceHistory[activePriceIndex].price != price) { + existingProduct.priceHistory[activePriceIndex].endDate = new Date(); + } + else { + return res.status(StatusCodes.BAD_REQUEST).json({ + msg: `Product with the same price ${price}` + }); + } + } + + const newPriceEntry = { + startDate: new Date(), + endDate: null, // Novo preço ativo + price + }; + + if (description) existingProduct.description = description.trim(); + if (name) existingProduct.name = name.trim(); + if (price) existingProduct.priceHistory.push(newPriceEntry); + + await existingProduct.save(); + + res.status(StatusCodes.OK).json({ + msg: 'Preço do produto atualizado com sucesso!', + product: existingProduct + }); + +}; + +const setProductPricing = async (req, res) => { + const { name, description, price } = req.body; + + console.log("price: ", price) + + mustContainProperties(req, ['name']); + + const normalizedName = name.trim(); + + let existingProduct = await API_Products.findOne({ name: normalizedName }); + + if (existingProduct) { + if (price) { + + const activePriceIndex = existingProduct.priceHistory.findIndex(priceEntry => !priceEntry.endDate); + + if (activePriceIndex !== -1 && existingProduct.priceHistory[activePriceIndex].price != price) { + existingProduct.priceHistory[activePriceIndex].endDate = new Date(); + } + else { + return res.status(StatusCodes.BAD_REQUEST).json({ + msg: `Product with the same price ${price}` + }); + } + + const newPriceEntry = { + startDate: new Date(), + endDate: null, // Novo preço ativo + price + }; + + existingProduct.priceHistory.push(newPriceEntry); + + if (description) existingProduct.description = description.trim(); + + await existingProduct.save(); + + return res.status(StatusCodes.OK).json({ + msg: 'Preço do produto atualizado com sucesso!', + product: existingProduct + }); + } else { + return res.status(StatusCodes.CONFLICT).json({ + error: 'Produto já existe. Para atualizar o preço, forneça o campo "price".', + product: existingProduct + }); + } + } + + // Se o produto não existe, criar um novo + const productData = { + name: normalizedName, + description: description?.trim() || '', + priceHistory: [] + }; + + if (price) { + const priceEntry = { + startDate: new Date(), // Data atual automática + endDate: null, // Preço ativo (sem data de fim) + price, + }; + + productData.priceHistory.push(priceEntry); + } + + console.log('---------> Dados do produto a ser criado:', productData); + + // Criar o produto + const newProduct = new API_Products(productData); + await newProduct.save(); + + res.status(StatusCodes.CREATED).json({ + msg: 'Produto criado com sucesso!', + product: newProduct + }); +}; + +const listProductPricing = async (req, res) => { + + const products = await API_Products.find(); + + res.status(StatusCodes.OK).json({ + msg: 'ok', + products + }) + +} + const registerUsage = async (req, res) => { const { provider, @@ -122,7 +263,7 @@ const registerWhatsappUsage = async (req, res) => { 'billable', 'pricing_model', 'companyPhone', - 'clientPhone', + 'clientPhone', ]) const apiPricing = await API_Pricing.findOne({ @@ -320,6 +461,9 @@ const getUsage = async (req, res) => { module.exports = { setApiPricing, + setProductPricing, + updateProductPricing, + listProductPricing, registerUsage, registerAPICall, registerOperation, diff --git a/models/API_Products.js b/models/API_Products.js new file mode 100644 index 0000000..e93bbcf --- /dev/null +++ b/models/API_Products.js @@ -0,0 +1,55 @@ +const mongoose = require('../db/connect'); + +const { Schema } = mongoose; + +/** + * @description Sub-schema para o histórico de preços. + * A opção `strict: false` permite adicionar campos de preço dinâmicos (ex: tokenPrice, sttSecondPrice) + * que podem variar conforme o `pricingModel` do produto principal. + * A opção `_id: false` evita que o Mongoose crie um ObjectId para cada entrada no histórico. + */ +const priceHistorySchema = new Schema({ + startDate: { + type: Date, + required: [true, 'A data de início da vigência é obrigatória.'], + }, + endDate: { + type: Date, + default: null, // Um valor nulo significa que o preço está atualmente vigente. + }, + // price: { + // type: String, + // required: [true, 'O preço é obrigatório'], + // } +}, { _id: false, strict: false }); + +/** + * @description Schema principal para os produtos da API. + */ +const apiProductSchema = new Schema({ + name: { + type: String, + required: [true, 'O nome do produto é obrigatório.'], + trim: true, + unique: true, // Garante que não hajam produtos com o mesmo nome. + }, + description: { + type: String, + trim: true, + }, + // pricingModel: { + // type: String, + // required: [true, 'O modelo de precificação é obrigatório.'], + // trim: true, + // }, + priceHistory: { + type: [priceHistorySchema], + default: [], // O produto pode ser criado sem um histórico de preços inicial. + }, +}, { + timestamps: true, // Adiciona os campos `createdAt` e `updatedAt` automaticamente. +}); + +const API_Products = mongoose.model('API_Products', apiProductSchema); + +module.exports = API_Products; diff --git a/routes/apiPriceRoute.js b/routes/apiPriceRoute.js index e3fe30d..85ca8e8 100644 --- a/routes/apiPriceRoute.js +++ b/routes/apiPriceRoute.js @@ -2,6 +2,9 @@ const express = require('express') const router = express.Router() const { authorization, } = require('../middleware/authentication') const { setApiPricing, + setProductPricing, + updateProductPricing, + listProductPricing, registerUsage, getUsage, registerAPICall, @@ -10,6 +13,9 @@ const { setApiPricing, registerWhatsappUsage} = require('../controllers/apiUsagePricing') router.route('/create').post(authorization, setApiPricing) +router.route('/create-product').post(authorization, setProductPricing) +router.route('/create-product').get(authorization, listProductPricing) +router.route('/create-product/:id').patch(authorization, updateProductPricing) router.route('/usage').post(authorization, registerUsage) router.route('/usage-whatsapp').post(authorization, registerWhatsappUsage) router.route('/report').post(authorization, getUsage) @@ -17,6 +23,4 @@ router.route('/api-call').post(authorization, registerAPICall) router.route('/api-operation').post(authorization, registerOperation) router.route('/api-register-all').post(authorization, registerAll) - - module.exports = router