File: /var/www/api_matriculas/app/Http/Controllers/PaymentConceptConfigController.php
<?php
namespace App\Http\Controllers;
use App\Models\PaymentConcept;
use App\Models\PaymentConceptDetail;
use App\Models\CurrencyType;
use App\Models\PeriodPricingConfig;
use Illuminate\Http\Request;
use Exception;
class PaymentConceptConfigController extends Controller
{
/**
* GET /api/payment-concepts
* Listar todos los conceptos de pago con count de detalles.
*/
public function listConcepts()
{
try {
$concepts = PaymentConcept::where('deleted', false)
->withCount(['details' => fn($q) => $q->where('deleted', false)])
->with('currency_type:id,currency_type,currency_symbol')
->orderBy('id')
->get();
return genericResponse(['data' => $concepts, 'message' => 'ok', 'code' => 200], 200);
} catch (Exception $e) {
return genericResponse(['error' => $e->getMessage()], $e->getCode() ?: 500);
}
}
/**
* PUT /api/payment-concepts/{id}
* Actualizar status y code_odoo de un concepto (nombre NO editable).
*/
public function updateConcept($id, Request $request)
{
try {
$concept = PaymentConcept::where('deleted', false)->findOrFail($id);
$concept->update($request->only(['status', 'code_odoo']));
return genericResponse(['data' => $concept, 'message' => 'Concepto actualizado', 'code' => 200], 200);
} catch (Exception $e) {
return genericResponse(['error' => $e->getMessage()], $e->getCode() ?: 500);
}
}
/**
* GET /api/payment-concepts/{id}/details
* Listar detalles de un concepto.
*/
public function listDetails($conceptId)
{
try {
$details = PaymentConceptDetail::where('payment_concept_id', $conceptId)
->where('deleted', false)
->with('currency_type:id,currency_type,currency_symbol')
->orderBy('id')
->get();
return genericResponse(['data' => $details, 'message' => 'ok', 'code' => 200], 200);
} catch (Exception $e) {
return genericResponse(['error' => $e->getMessage()], $e->getCode() ?: 500);
}
}
/**
* POST /api/payment-concepts/{id}/details
* Crear un nuevo detalle de concepto.
*/
public function storeDetail($conceptId, Request $request)
{
$request->validate([
'description' => 'required|string|max:255',
'code' => 'required|string|max:50',
'price_regular' => 'nullable|numeric',
'currency_type_id' => 'required|integer|exists:currency_types,id',
'required_element' => 'nullable|boolean',
'family_payment' => 'nullable|boolean',
'status' => 'nullable|boolean',
'code_odoo' => 'nullable|string|max:100',
]);
try {
// Verificar que no exista otro detalle con el mismo code
$exists = PaymentConceptDetail::where('code', $request->code)
->where('deleted', false)
->exists();
if ($exists) {
return genericResponse(['error' => "Ya existe un detalle con el código '{$request->code}'"], 422);
}
$detail = PaymentConceptDetail::create([
'payment_concept_id' => $conceptId,
'description' => strtoupper($request->description),
'code' => strtoupper($request->code),
'price_regular' => $request->price_regular,
'currency_type_id' => $request->currency_type_id,
'required_element' => $request->required_element ?? 1,
'requires_payment' => 0,
'requires_assignment'=> 0,
'family_payment' => $request->family_payment ?? 0,
'status' => $request->status ?? 1,
'code_odoo' => $request->code_odoo,
]);
$detail->load('currency_type:id,currency_type,currency_symbol');
return genericResponse(['data' => $detail, 'message' => 'Detalle creado', 'code' => 201], 201);
} catch (Exception $e) {
return genericResponse(['error' => $e->getMessage()], $e->getCode() ?: 500);
}
}
/**
* PUT /api/payment-concepts/details/{detailId}
* Actualizar un detalle.
*/
public function updateDetail($detailId, Request $request)
{
try {
$detail = PaymentConceptDetail::where('deleted', false)->findOrFail($detailId);
$fields = $request->only([
'description', 'code', 'price_regular', 'currency_type_id',
'required_element', 'family_payment', 'status', 'code_odoo',
]);
if (isset($fields['description'])) $fields['description'] = strtoupper($fields['description']);
if (isset($fields['code'])) $fields['code'] = strtoupper($fields['code']);
$detail->update($fields);
$detail->load('currency_type:id,currency_type,currency_symbol');
return genericResponse(['data' => $detail, 'message' => 'Detalle actualizado', 'code' => 200], 200);
} catch (Exception $e) {
return genericResponse(['error' => $e->getMessage()], $e->getCode() ?: 500);
}
}
/**
* DELETE /api/payment-concepts/details/{detailId}
* Eliminar detalle (soft delete). Valida que no tenga period_pricing_configs asociados.
*/
public function destroyDetail($detailId)
{
try {
$detail = PaymentConceptDetail::where('deleted', false)->findOrFail($detailId);
// Validar que no tenga configuraciones de precios asociadas
$hasConfigs = PeriodPricingConfig::where('concept_code', $detail->code)->exists();
if ($hasConfigs) {
return genericResponse([
'error' => 'No se puede eliminar: este concepto tiene configuraciones de precios por periodo asociadas.',
], 422);
}
$detail->update([
'deleted' => true,
'deleted_at' => now(),
'user_deleted' => auth()->id(),
]);
return genericResponse(['data' => null, 'message' => 'Detalle eliminado', 'code' => 200], 200);
} catch (Exception $e) {
return genericResponse(['error' => $e->getMessage()], $e->getCode() ?: 500);
}
}
/**
* GET /api/payment-concepts/meta/currency-types
* Listar tipos de moneda disponibles.
*/
public function currencyTypes()
{
try {
$types = CurrencyType::where('status', 1)
->select('id', 'currency_type', 'currency_symbol')
->orderBy('id')
->get();
return genericResponse(['data' => $types, 'message' => 'ok', 'code' => 200], 200);
} catch (Exception $e) {
return genericResponse(['error' => $e->getMessage()], $e->getCode() ?: 500);
}
}
}