@RestResource(urlMapping='/api/products/*')
global with sharing class ProductApi {
// GET: Recuperar productos por External_ID__c (sin captura de log)
@HttpGet
global static List<Product2> getProducts() {
RestRequest req = RestContext.request;
String externalId = req.requestURI.substringAfterLast('/');
if (String.isNotBlank(externalId)) {
// Recuperar producto por External_ID__c
List<Product2> products = [SELECT Id, Name, ProductCode, Description, IsActive, External_ID__c
FROM Product2 WHERE External_ID__c = :externalId];
return products;
} else {
// Recuperar todos los productos si no se proporciona External_ID__c
List<Product2> products = [SELECT Id, Name, ProductCode, Description, IsActive, External_ID__c
FROM Product2];
return products;
}
}
// PATCH: Crear o actualizar un producto y establecer su precio usando solo External_ID__c
@HttpPatch
global static Product2 upsertProduct(
String externalId,
String name,
String productCode,
String description,
Boolean isActive,
Decimal price
) {
RestRequest req = RestContext.request;
Product2 product = null;
try {
if (String.isBlank(externalId)) {
throw new CustomException('El campo External_ID__c es obligatorio para realizar un upsert.');
}
if (price == null || price < 0) {
throw new CustomException('El precio debe ser un valor positivo.');
}
// Crear o actualizar el producto basado en External_ID__c
product = new Product2(
External_ID__c = externalId,
Name = name,
ProductCode = productCode,
Description = description,
IsActive = isActive
);
upsert product External_ID__c;
// Buscar el Standard Pricebook
List<Pricebook2> standardPricebookList = [SELECT Id FROM Pricebook2 WHERE IsStandard = true LIMIT 1];
if (standardPricebookList.isEmpty()) {
throw new CustomException('No se encontró el Standard Pricebook.');
}
Pricebook2 standardPricebook = standardPricebookList[0];
// Verificar si ya existe un PricebookEntry para este producto
List<PricebookEntry> existingEntries = [
SELECT Id, UnitPrice
FROM PricebookEntry
WHERE Product2Id = :product.Id AND Pricebook2Id = :standardPricebook.Id
];
if (existingEntries.isEmpty()) {
// Crear un nuevo PricebookEntry si no existe
PricebookEntry newEntry = new PricebookEntry(
Pricebook2Id = standardPricebook.Id,
Product2Id = product.Id,
UnitPrice = price,
IsActive = true
);
insert newEntry;
} else {
// Actualizar el PricebookEntry existente
PricebookEntry existingEntry = existingEntries[0];
existingEntry.UnitPrice = price;
update existingEntry;
}
// Log success
logIntegrationDetails('PATCH', req, product, null); // No hay error, pasamos null para la excepción
return product;
} catch (Exception e) {
// Log error
logIntegrationDetails('PATCH', req, null, e); // Capturamos el error y lo pasamos al log
throw e; // Vuelve a lanzar la excepción para que se maneje como corresponde
}
}
// DELETE: Eliminar un producto por External_ID__c
@HttpDelete
global static void deleteProduct() {
RestRequest req = RestContext.request;
String productIdOrExternalId = req.requestURI.substringAfterLast('/');
if (String.isBlank(productIdOrExternalId)) {
throw new CustomException('Debe proporcionar el ID o el External_ID__c para eliminar.');
}
Product2 productToDelete;
if (productIdOrExternalId.startsWith('a') && productIdOrExternalId.length() == 18) {
// Buscar por Id estándar
productToDelete = [SELECT Id, IsDeleted FROM Product2 WHERE Id = :productIdOrExternalId AND IsDeleted = false LIMIT 1];
} else {
// Buscar por External_ID__c
productToDelete = [SELECT Id, IsDeleted FROM Product2 WHERE External_ID__c = :productIdOrExternalId AND IsDeleted = false LIMIT 1];
}
// Si el producto no existe o está eliminado, lanzamos una excepción
if (productToDelete == null || productToDelete.IsDeleted) {
throw new CustomException('El producto no existe o ha sido eliminado.');
}
// Eliminar cualquier PricebookEntry relacionado en el Standard Pricebook
List<Pricebook2> standardPricebookList = [SELECT Id FROM Pricebook2 WHERE IsStandard = true LIMIT 1];
if (!standardPricebookList.isEmpty()) {
Pricebook2 standardPricebook = standardPricebookList[0];
List<PricebookEntry> entriesToDelete = [
SELECT Id
FROM PricebookEntry
WHERE Product2Id = :productToDelete.Id AND Pricebook2Id = :standardPricebook.Id
];
delete entriesToDelete;
}
delete productToDelete;
// Registrar el log de la integración
try {
logIntegrationDetails('DELETE', req, productToDelete, null);
} catch (Exception e) {
// Si algo sale mal, capturamos la excepción y la mostramos (aunque puede que ya esté registrada)
System.debug('Error al registrar el log: ' + e.getMessage());
}
}
// Método auxiliar para registrar los detalles de las solicitudes y respuestas en Integration_Log__c
private static void logIntegrationDetails(String operation, RestRequest req, SObject response, Exception e) {
// Crear el log de la solicitud y la respuesta
Integration_Log__c log = new Integration_Log__c();
// Si ocurrió un error, capturamos la excepción
if (e != null) {
log.Estado__c = 'Error';
log.Response__c = 'Error: ' + e.getMessage(); // Guardamos el mensaje de la excepción en Response__c
} else {
log.Estado__c = 'Success'; // Si todo fue exitoso, lo marcamos como "Success"
log.Response__c = JSON.serialize(response); // Serializamos la respuesta
}
// Asignar el nombre del log con el nombre del objeto y la fecha actual
String logName = (response != null ? response.getSObjectType().getDescribe().getName() : 'UnknownObject') + ' - ' + DateTime.now().format();
log.Name = logName;
// Capturamos los parámetros de la solicitud
log.Request__c = 'Params: ' + req.params;
// Si se está creando/actualizando un producto, asociamos el producto con el log
if (response instanceof Product2) {
log.Product__c = ((Product2) response).Id;
}
insert log; // Insertamos el log en la base de datos
}
// Clase de excepción personalizada
global class CustomException extends Exception {}
}