Added payment options but it doesnt work correctly

This commit is contained in:
Dennis Eckerskorn 2025-05-14 23:50:04 +02:00
parent 9d52a212fa
commit 0aadef56ed
9 changed files with 325 additions and 39 deletions

View File

@ -62,4 +62,13 @@ public class PaymentController {
paymentService.removePayment(id); paymentService.removePayment(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT); return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} }
@GetMapping("/getAllByUserId/{userId}")
@Operation(summary = "Get all payments by user ID")
public ResponseEntity<List<PaymentDTO>> getAllByUserId(@PathVariable Integer userId) {
List<PaymentDTO> dtos = paymentService.findAllByUserId(userId)
.stream().map(PaymentDTO::new).collect(Collectors.toList());
return new ResponseEntity<>(dtos, HttpStatus.OK);
}
} }

View File

@ -1,8 +1,12 @@
package com.denniseckerskorn.repositories.finance_repositories; package com.denniseckerskorn.repositories.finance_repositories;
import com.denniseckerskorn.entities.finance.Invoice;
import com.denniseckerskorn.entities.finance.Payment; import com.denniseckerskorn.entities.finance.Payment;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PaymentRepository extends JpaRepository<Payment, Integer> { public interface PaymentRepository extends JpaRepository<Payment, Integer> {
boolean existsByInvoiceId(Integer invoiceId); boolean existsByInvoiceId(Integer invoiceId);
List<Payment> findByInvoice_User_Id(Integer userId);
} }

View File

@ -8,7 +8,6 @@ import com.denniseckerskorn.exceptions.EntityNotFoundException;
import com.denniseckerskorn.exceptions.InvalidDataException; import com.denniseckerskorn.exceptions.InvalidDataException;
import com.denniseckerskorn.repositories.finance_repositories.PaymentRepository; import com.denniseckerskorn.repositories.finance_repositories.PaymentRepository;
import com.denniseckerskorn.services.AbstractService; import com.denniseckerskorn.services.AbstractService;
import com.denniseckerskorn.services.finance_services.InvoiceService;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -33,16 +32,29 @@ public class PaymentService extends AbstractService<Payment, Integer> {
@Override @Override
@Transactional @Transactional
public Payment save(Payment entity) throws DuplicateEntityException { public Payment save(Payment payment) throws DuplicateEntityException {
logger.info("Saving payment: {}", entity); logger.info("Saving payment: {}", payment);
validate(entity); validate(payment);
if (paymentRepository.existsByInvoiceId(entity.getInvoice().getId())) {
Integer invoiceId = payment.getInvoice().getId();
if (paymentRepository.existsByInvoiceId(invoiceId)) {
throw new DuplicateEntityException("A payment already exists for this invoice"); throw new DuplicateEntityException("A payment already exists for this invoice");
} }
updateInvoiceStatus(entity);
return super.save(entity); Invoice invoice = invoiceService.findById(invoiceId);
if (invoice == null) {
throw new EntityNotFoundException("Invoice not found");
}
payment.setInvoice(invoice);
invoice.setPayment(payment);
invoice.setStatus(StatusValues.PAID);
invoiceService.update(invoice);
return super.save(payment);
} }
@Override @Override
public Payment update(Payment entity) throws EntityNotFoundException, InvalidDataException { public Payment update(Payment entity) throws EntityNotFoundException, InvalidDataException {
logger.info("Updating payment: {}", entity); logger.info("Updating payment: {}", entity);
@ -100,4 +112,8 @@ public class PaymentService extends AbstractService<Payment, Integer> {
invoiceService.update(invoice); invoiceService.update(invoice);
} }
public List<Payment> findAllByUserId(Integer userId) {
return paymentRepository.findByInvoice_User_Id(userId);
}
} }

View File

@ -29,9 +29,6 @@ class PaymentServiceTest {
@Mock @Mock
private InvoiceService invoiceService; private InvoiceService invoiceService;
@Mock
private EntityManager entityManager;
@InjectMocks @InjectMocks
private PaymentService paymentService; private PaymentService paymentService;
@ -44,59 +41,64 @@ class PaymentServiceTest {
invoice = new Invoice(); invoice = new Invoice();
invoice.setId(1); invoice.setId(1);
invoice.setTotal(new BigDecimal("59.99")); invoice.setTotal(new BigDecimal("140.00"));
invoice.setStatus(StatusValues.NOT_PAID); invoice.setStatus(StatusValues.NOT_PAID);
payment = new Payment(); payment = new Payment();
payment.setId(101); payment.setId(101);
payment.setInvoice(invoice); payment.setInvoice(invoice);
payment.setAmount(new BigDecimal("59.99")); payment.setAmount(new BigDecimal("140.00"));
payment.setPaymentDate(LocalDateTime.now().minusDays(1)); payment.setPaymentDate(LocalDateTime.now().minusDays(1));
payment.setPaymentMethod(PaymentMethodValues.CREDIT_CARD); payment.setPaymentMethod(PaymentMethodValues.CASH);
payment.setStatus(StatusValues.PAID); payment.setStatus(StatusValues.PAID);
Field emField = PaymentService.class.getSuperclass().getDeclaredField("entityManager");
emField.setAccessible(true);
emField.set(paymentService, entityManager);
} }
@Test @Test
void save_ShouldUpdateInvoiceStatusAndSavePayment() { void save_ShouldUpdateInvoiceStatusAndSavePayment() {
when(paymentRepository.existsByInvoiceId(1)).thenReturn(false); when(paymentRepository.existsByInvoiceId(1)).thenReturn(false);
when(invoiceService.findById(1)).thenReturn(invoice);
when(paymentRepository.save(any())).thenReturn(payment); when(paymentRepository.save(any())).thenReturn(payment);
Payment saved = paymentService.save(payment); Payment saved = paymentService.save(payment);
assertEquals(StatusValues.PAID, payment.getInvoice().getStatus());
assertEquals(StatusValues.PAID, saved.getInvoice().getStatus());
verify(invoiceService).update(invoice); verify(invoiceService).update(invoice);
verify(paymentRepository).save(payment);
} }
@Test @Test
void save_DuplicatePayment_ShouldThrow() { void save_WhenDuplicate_ShouldThrow() {
when(paymentRepository.existsByInvoiceId(1)).thenReturn(true); when(paymentRepository.existsByInvoiceId(1)).thenReturn(true);
assertThrows(DuplicateEntityException.class, () -> paymentService.save(payment)); assertThrows(DuplicateEntityException.class, () -> paymentService.save(payment));
verify(invoiceService, never()).update(any());
} }
@Test @Test
void save_InvalidAmount_ShouldThrow() { void save_WhenInvoiceNull_ShouldThrow() {
payment.setAmount(BigDecimal.ZERO);
assertThrows(InvalidDataException.class, () -> paymentService.save(payment));
}
@Test
void save_InvalidPaymentDate_ShouldThrow() {
payment.setPaymentDate(LocalDateTime.now().plusDays(1));
assertThrows(InvalidDataException.class, () -> paymentService.save(payment));
}
@Test
void save_InvalidMethod_ShouldThrow() {
payment.setPaymentMethod(null);
assertThrows(InvalidDataException.class, () -> paymentService.save(payment));
}
@Test
void save_InvoiceNull_ShouldThrow() {
payment.setInvoice(null); payment.setInvoice(null);
assertThrows(InvalidDataException.class, () -> paymentService.save(payment));
}
@Test
void save_WhenInvalidAmount_ShouldThrow() {
payment.setAmount(BigDecimal.ZERO);
assertThrows(InvalidDataException.class, () -> paymentService.save(payment));
}
@Test
void save_WhenFutureDate_ShouldThrow() {
payment.setPaymentDate(LocalDateTime.now().plusDays(1));
assertThrows(InvalidDataException.class, () -> paymentService.save(payment));
}
@Test
void save_WhenMissingMethod_ShouldThrow() {
payment.setPaymentMethod(null);
assertThrows(InvalidDataException.class, () -> paymentService.save(payment)); assertThrows(InvalidDataException.class, () -> paymentService.save(payment));
} }
} }

View File

@ -0,0 +1,142 @@
import React, { useEffect, useState } from "react";
import api from "../../api/axiosConfig";
import ErrorMessage from "../common/ErrorMessage";
import "../styles/ContentArea.css";
const PaymentForm = () => {
const [students, setStudents] = useState([]);
const [selectedUserId, setSelectedUserId] = useState("");
const [invoices, setInvoices] = useState([]);
const [selectedInvoiceId, setSelectedInvoiceId] = useState("");
const [amount, setAmount] = useState("");
const [paymentMethod, setPaymentMethod] = useState("CASH");
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
useEffect(() => {
api.get("/students/getAll").then((res) => setStudents(res.data));
}, []);
const fetchInvoices = async (userId) => {
try {
const res = await api.get(`/invoices/getAllInvoicesByUserId/${userId}`);
const notPaid = res.data.filter((invoice) => invoice.status === "NOT_PAID");
setInvoices(notPaid);
} catch (err) {
console.error(err);
setError("Error al cargar facturas.");
}
};
const handleStudentChange = (e) => {
const userId = e.target.value;
setSelectedUserId(userId);
setSelectedInvoiceId("");
setInvoices([]);
if (userId) {
fetchInvoices(userId);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
setSuccess("");
if (!selectedInvoiceId || !amount || !paymentMethod) {
setError("Completa todos los campos.");
return;
}
try {
await api.post("/payments/create", {
invoiceId: parseInt(selectedInvoiceId),
paymentDate: new Date().toISOString(),
amount: parseFloat(amount),
paymentMethod,
status: "PAID",
});
setSuccess("✅ Pago registrado correctamente.");
setSelectedInvoiceId("");
setAmount("");
setPaymentMethod("CASH");
fetchInvoices(selectedUserId); // actualizar facturas pendientes
} catch (err) {
console.error(err);
setError("❌ Error al registrar el pago.");
}
};
return (
<div className="content-area">
<div className="card">
<h2>Registrar Pago</h2>
<label>Seleccionar estudiante:</label>
<select className="form-select" value={selectedUserId} onChange={handleStudentChange}>
<option value="">-- Selecciona un estudiante --</option>
{students.map((s) =>
s.user ? (
<option key={s.id} value={s.user.id}>
{s.user.name} {s.user.lastName} ({s.user.email})
</option>
) : null
)}
</select>
{invoices.length > 0 && (
<>
<label>Seleccionar factura:</label>
<select
className="form-select"
value={selectedInvoiceId}
onChange={(e) => setSelectedInvoiceId(e.target.value)}
>
<option value="">-- Selecciona una factura --</option>
{invoices.map((inv) => (
<option key={inv.id} value={inv.id}>
#{inv.id} - {new Date(inv.date).toLocaleDateString()} - Total: {inv.total.toFixed(2)}
</option>
))}
</select>
</>
)}
{selectedInvoiceId && (
<>
<label>Importe pagado ():</label>
<input
className="form-input"
type="number"
step="0.01"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<label>Método de pago:</label>
<select
className="form-select"
value={paymentMethod}
onChange={(e) => setPaymentMethod(e.target.value)}
>
<option value="CASH">Efectivo</option>
<option value="CARD">Tarjeta</option>
<option value="TRANSFER">Transferencia</option>
<option value="BIZUM">Bizum</option>
</select>
<ErrorMessage message={error} type="error" />
<ErrorMessage message={success} type="success" />
<button className="btn btn-primary" style={{ marginTop: "1rem" }} onClick={handleSubmit}>
💳 Confirmar Pago
</button>
</>
)}
</div>
</div>
);
};
export default PaymentForm;

View File

@ -24,6 +24,8 @@ import MembershipForm from '../forms/MembershipCreateForm';
import MembershipList from '../lists/MembershipList'; import MembershipList from '../lists/MembershipList';
import InvoiceForm from '../forms/InvoiceForm'; import InvoiceForm from '../forms/InvoiceForm';
import InvoiceList from '../lists/InvoiceList'; import InvoiceList from '../lists/InvoiceList';
import PaymentForm from '../forms/PaymentForm';
import PaymentList from '../lists/PaymentList';
import '../styles/MainLayout.css'; import '../styles/MainLayout.css';
@ -69,6 +71,10 @@ const MainLayout = () => {
<Route path="/admin/finance/invoices/create" element={<InvoiceForm />} /> <Route path="/admin/finance/invoices/create" element={<InvoiceForm />} />
<Route path="/admin/finance/invoices/list" element={<InvoiceList />} /> <Route path="/admin/finance/invoices/list" element={<InvoiceList />} />
<Route path="/admin/finance/payments/create" element={<PaymentForm />} />
<Route path="/admin/finance/payments/list" element={<PaymentList />} />
{/* Profile Page*/} {/* Profile Page*/}
<Route path="/profile" element={<ProfilePage />} /> <Route path="/profile" element={<ProfilePage />} />

View File

@ -39,7 +39,8 @@ const SidebarAdmin = () => {
<h4>💵 Finanzas</h4> <h4>💵 Finanzas</h4>
<button onClick={() => navigate('/admin/finance/invoices/create')}>🧾 Crear Factura</button> <button onClick={() => navigate('/admin/finance/invoices/create')}>🧾 Crear Factura</button>
<button onClick={() => navigate('/admin/finance/invoices/list')}>📄 Ver Facturas</button> <button onClick={() => navigate('/admin/finance/invoices/list')}>📄 Ver Facturas</button>
<button onClick={() => navigate('/admin/finance/payments/create')}>💳 Nuevo Pago</button> <button onClick={() => navigate('/admin/finance/payments/create')}>📄 Nuevo Pago</button>
<button onClick={() => navigate('/admin/finance/payments/list')}>💳 Facturas Pagadas</button>
<button onClick={() => navigate('/admin/finance/products/create')}>🛒 Añadir Productos</button> <button onClick={() => navigate('/admin/finance/products/create')}>🛒 Añadir Productos</button>
<button onClick={() => navigate('/admin/finance/products/list')}>📦 Ver Productos</button> <button onClick={() => navigate('/admin/finance/products/list')}>📦 Ver Productos</button>
<button onClick={() => navigate('/admin/finance/ivatypes/create')}>💱 Añadir Tipo de IVA</button> <button onClick={() => navigate('/admin/finance/ivatypes/create')}>💱 Añadir Tipo de IVA</button>

View File

@ -0,0 +1,106 @@
import React, { useState, useEffect } from "react";
import api from "../../api/axiosConfig";
import ErrorMessage from "../common/ErrorMessage";
import '../styles/ContentArea.css';
const PaymentList = () => {
const [students, setStudents] = useState([]);
const [selectedUserId, setSelectedUserId] = useState("");
const [payments, setPayments] = useState([]);
const [error, setError] = useState("");
useEffect(() => {
api.get("/students/getAll").then((res) => setStudents(res.data));
}, []);
const fetchPayments = async (userId) => {
try {
const res = await api.get(`/payments/getAllByUserId/${userId}`);
setPayments(res.data);
} catch (err) {
console.error(err);
setError("Error al cargar pagos.");
}
};
const handleStudentChange = (e) => {
const userId = e.target.value;
setSelectedUserId(userId);
if (userId) {
fetchPayments(userId);
} else {
setPayments([]);
}
};
const handleDelete = async (id) => {
if (window.confirm("¿Seguro que quieres eliminar este pago?")) {
try {
await api.delete(`/payments/deleteById/${id}`);
fetchPayments(selectedUserId);
} catch (err) {
console.error(err);
setError("Error al eliminar el pago.");
}
}
};
return (
<div className="content-area">
<div className="card">
<h2>Listado de Pagos</h2>
<label>Seleccionar estudiante:</label>
<select className="form-select" value={selectedUserId} onChange={handleStudentChange}>
<option value="">-- Selecciona un estudiante --</option>
{students.map((s) =>
s.user ? (
<option key={s.id} value={s.user.id}>
{s.user.name} {s.user.lastName} ({s.user.email})
</option>
) : null
)}
</select>
<ErrorMessage message={error} />
{payments.length > 0 && (
<table className="table" style={{ marginTop: "1rem" }}>
<thead>
<tr>
<th>ID</th>
<th>Factura</th>
<th>Fecha</th>
<th>Monto ()</th>
<th>Método</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{payments.map((p) => (
<tr key={p.id}>
<td>{p.id}</td>
<td>#{p.invoiceId}</td>
<td>{new Date(p.paymentDate).toLocaleDateString()}</td>
<td>{p.amount.toFixed(2)}</td>
<td>{p.paymentMethod}</td>
<td>{p.status}</td>
<td>
{/* Botón editar lo montamos luego */}
{/* <button className="btn btn-secondary btn-sm" onClick={() => handleEdit(p)}>✏️</button> */}
<button className="btn btn-danger btn-sm" onClick={() => handleDelete(p.id)}>
🗑
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
);
};
export default PaymentList;