Compare commits
2 Commits
0aadef56ed
...
c81a1ea588
| Author | SHA1 | Date |
|---|---|---|
|
|
c81a1ea588 | |
|
|
ab3485984e |
Binary file not shown.
|
|
@ -1,6 +1,7 @@
|
||||||
package com.denniseckerskorn.controllers.finance_management;
|
package com.denniseckerskorn.controllers.finance_management;
|
||||||
|
|
||||||
import com.denniseckerskorn.dtos.finance_management_dtos.PaymentDTO;
|
import com.denniseckerskorn.dtos.finance_management_dtos.PaymentDTO;
|
||||||
|
import com.denniseckerskorn.entities.finance.Invoice;
|
||||||
import com.denniseckerskorn.entities.finance.Payment;
|
import com.denniseckerskorn.entities.finance.Payment;
|
||||||
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
||||||
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
||||||
|
|
@ -29,18 +30,28 @@ public class PaymentController {
|
||||||
|
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
@Operation(summary = "Create a new payment and mark invoice as PAID")
|
@Operation(summary = "Create a new payment and mark invoice as PAID")
|
||||||
public ResponseEntity<PaymentDTO> create(@Valid @RequestBody PaymentDTO dto) throws DuplicateEntityException {
|
public ResponseEntity<PaymentDTO> create(@Valid @RequestBody PaymentDTO dto)
|
||||||
Payment saved = paymentService.save(dto.toEntity());
|
throws DuplicateEntityException, EntityNotFoundException {
|
||||||
|
|
||||||
|
Invoice invoice = paymentService.getInvoiceById(dto.getInvoiceId());
|
||||||
|
Payment saved = paymentService.save(dto.toEntityWithInvoice(invoice));
|
||||||
|
|
||||||
return new ResponseEntity<>(new PaymentDTO(saved), HttpStatus.CREATED);
|
return new ResponseEntity<>(new PaymentDTO(saved), HttpStatus.CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PutMapping("/update")
|
@PutMapping("/update")
|
||||||
@Operation(summary = "Update an existing payment and adjust invoice status")
|
@Operation(summary = "Update an existing payment and adjust invoice status")
|
||||||
public ResponseEntity<PaymentDTO> update(@Valid @RequestBody PaymentDTO dto) throws EntityNotFoundException, InvalidDataException {
|
public ResponseEntity<PaymentDTO> update(@Valid @RequestBody PaymentDTO dto)
|
||||||
Payment updated = paymentService.update(dto.toEntity());
|
throws EntityNotFoundException, InvalidDataException {
|
||||||
|
|
||||||
|
Invoice invoice = paymentService.getInvoiceById(dto.getInvoiceId());
|
||||||
|
Payment updated = paymentService.update(dto.toEntityWithInvoice(invoice));
|
||||||
|
|
||||||
return new ResponseEntity<>(new PaymentDTO(updated), HttpStatus.OK);
|
return new ResponseEntity<>(new PaymentDTO(updated), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/getById/{id}")
|
@GetMapping("/getById/{id}")
|
||||||
@Operation(summary = "Get payment by ID")
|
@Operation(summary = "Get payment by ID")
|
||||||
public ResponseEntity<PaymentDTO> getById(@PathVariable Integer id) throws EntityNotFoundException, InvalidDataException {
|
public ResponseEntity<PaymentDTO> getById(@PathVariable Integer id) throws EntityNotFoundException, InvalidDataException {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package com.denniseckerskorn.controllers.finance_management;
|
package com.denniseckerskorn.controllers.finance_management;
|
||||||
|
|
||||||
import com.denniseckerskorn.dtos.finance_management_dtos.ProductServiceDTO;
|
import com.denniseckerskorn.dtos.finance_management_dtos.ProductServiceDTO;
|
||||||
|
import com.denniseckerskorn.entities.finance.IVAType;
|
||||||
import com.denniseckerskorn.entities.finance.ProductService;
|
import com.denniseckerskorn.entities.finance.ProductService;
|
||||||
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
||||||
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
||||||
import com.denniseckerskorn.exceptions.InvalidDataException;
|
import com.denniseckerskorn.exceptions.InvalidDataException;
|
||||||
|
import com.denniseckerskorn.services.finance_services.IVATypeService;
|
||||||
import com.denniseckerskorn.services.finance_services.ProductServiceService;
|
import com.denniseckerskorn.services.finance_services.ProductServiceService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
@ -22,25 +24,30 @@ import java.util.stream.Collectors;
|
||||||
public class ProductServiceController {
|
public class ProductServiceController {
|
||||||
|
|
||||||
private final ProductServiceService productServiceService;
|
private final ProductServiceService productServiceService;
|
||||||
|
private final IVATypeService ivaTypeService;
|
||||||
|
|
||||||
public ProductServiceController(ProductServiceService productServiceService) {
|
public ProductServiceController(ProductServiceService productServiceService, IVATypeService ivaTypeService) {
|
||||||
this.productServiceService = productServiceService;
|
this.productServiceService = productServiceService;
|
||||||
|
this.ivaTypeService = ivaTypeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
@Operation(summary = "Create a new product/service")
|
@Operation(summary = "Create a new product/service")
|
||||||
public ResponseEntity<ProductServiceDTO> create(@Valid @RequestBody ProductServiceDTO dto) throws DuplicateEntityException {
|
public ResponseEntity<ProductServiceDTO> create(@Valid @RequestBody ProductServiceDTO dto) throws DuplicateEntityException {
|
||||||
ProductService saved = productServiceService.save(dto.toEntity());
|
IVAType ivaType = ivaTypeService.findById(dto.getIvaTypeId());
|
||||||
|
ProductService saved = productServiceService.save(dto.toEntityWithIVA(ivaType));
|
||||||
return new ResponseEntity<>(new ProductServiceDTO(saved), HttpStatus.CREATED);
|
return new ResponseEntity<>(new ProductServiceDTO(saved), HttpStatus.CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/update")
|
@PutMapping("/update")
|
||||||
@Operation(summary = "Update an existing product/service")
|
@Operation(summary = "Update an existing product/service")
|
||||||
public ResponseEntity<ProductServiceDTO> update(@Valid @RequestBody ProductServiceDTO dto) throws EntityNotFoundException, InvalidDataException {
|
public ResponseEntity<ProductServiceDTO> update(@Valid @RequestBody ProductServiceDTO dto) throws EntityNotFoundException, InvalidDataException {
|
||||||
ProductService updated = productServiceService.update(dto.toEntity());
|
IVAType ivaType = ivaTypeService.findById(dto.getIvaTypeId());
|
||||||
|
ProductService updated = productServiceService.update(dto.toEntityWithIVA(ivaType));
|
||||||
return new ResponseEntity<>(new ProductServiceDTO(updated), HttpStatus.OK);
|
return new ResponseEntity<>(new ProductServiceDTO(updated), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/getById/{id}")
|
@GetMapping("/getById/{id}")
|
||||||
@Operation(summary = "Get product/service by ID")
|
@Operation(summary = "Get product/service by ID")
|
||||||
public ResponseEntity<ProductServiceDTO> getById(@PathVariable Integer id) throws EntityNotFoundException, InvalidDataException {
|
public ResponseEntity<ProductServiceDTO> getById(@PathVariable Integer id) throws EntityNotFoundException, InvalidDataException {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.denniseckerskorn.dtos.finance_management_dtos;
|
package com.denniseckerskorn.dtos.finance_management_dtos;
|
||||||
|
|
||||||
|
import com.denniseckerskorn.entities.finance.Invoice;
|
||||||
import com.denniseckerskorn.entities.finance.Payment;
|
import com.denniseckerskorn.entities.finance.Payment;
|
||||||
import com.denniseckerskorn.enums.PaymentMethodValues;
|
import com.denniseckerskorn.enums.PaymentMethodValues;
|
||||||
import com.denniseckerskorn.enums.StatusValues;
|
import com.denniseckerskorn.enums.StatusValues;
|
||||||
|
|
@ -27,7 +28,8 @@ public class PaymentDTO {
|
||||||
@NotNull
|
@NotNull
|
||||||
private StatusValues status;
|
private StatusValues status;
|
||||||
|
|
||||||
public PaymentDTO() {}
|
public PaymentDTO() {
|
||||||
|
}
|
||||||
|
|
||||||
public PaymentDTO(Payment entity) {
|
public PaymentDTO(Payment entity) {
|
||||||
this.id = entity.getId();
|
this.id = entity.getId();
|
||||||
|
|
@ -44,6 +46,9 @@ public class PaymentDTO {
|
||||||
|
|
||||||
public Payment toEntity() {
|
public Payment toEntity() {
|
||||||
Payment payment = new Payment();
|
Payment payment = new Payment();
|
||||||
|
if (this.id != null) {
|
||||||
|
payment.setId(this.id);
|
||||||
|
}
|
||||||
payment.setId(this.id);
|
payment.setId(this.id);
|
||||||
payment.setPaymentDate(this.paymentDate);
|
payment.setPaymentDate(this.paymentDate);
|
||||||
payment.setAmount(this.amount);
|
payment.setAmount(this.amount);
|
||||||
|
|
@ -52,6 +57,17 @@ public class PaymentDTO {
|
||||||
return payment;
|
return payment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Payment toEntityWithInvoice(Invoice invoice) {
|
||||||
|
Payment payment = new Payment();
|
||||||
|
payment.setPaymentDate(this.paymentDate);
|
||||||
|
payment.setAmount(this.amount);
|
||||||
|
payment.setPaymentMethod(this.paymentMethod);
|
||||||
|
payment.setStatus(this.status);
|
||||||
|
payment.setInvoice(invoice);
|
||||||
|
return payment;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Getters y setters...
|
// Getters y setters...
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.denniseckerskorn.dtos.finance_management_dtos;
|
package com.denniseckerskorn.dtos.finance_management_dtos;
|
||||||
|
|
||||||
|
import com.denniseckerskorn.entities.finance.IVAType;
|
||||||
import com.denniseckerskorn.entities.finance.ProductService;
|
import com.denniseckerskorn.entities.finance.ProductService;
|
||||||
import com.denniseckerskorn.enums.StatusValues;
|
import com.denniseckerskorn.enums.StatusValues;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
@ -54,6 +55,18 @@ public class ProductServiceDTO {
|
||||||
return ps;
|
return ps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProductService toEntityWithIVA(IVAType ivaType) {
|
||||||
|
ProductService entity = new ProductService();
|
||||||
|
entity.setId(this.id);
|
||||||
|
entity.setName(this.name);
|
||||||
|
entity.setPrice(this.price);
|
||||||
|
entity.setType(this.type);
|
||||||
|
entity.setStatus(this.status);
|
||||||
|
entity.setIvaType(ivaType);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Getters y setters...
|
// Getters y setters...
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,6 @@ public interface ProductServiceRepository extends JpaRepository<ProductService,
|
||||||
boolean existsByName(String name);
|
boolean existsByName(String name);
|
||||||
|
|
||||||
boolean existsByIvaTypeId(Integer ivaTypeId);
|
boolean existsByIvaTypeId(Integer ivaTypeId);
|
||||||
|
|
||||||
|
ProductService findByName(String name);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,20 @@ public class PaymentService extends AbstractService<Payment, Integer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
payment.setInvoice(invoice);
|
payment.setInvoice(invoice);
|
||||||
invoice.setPayment(payment);
|
|
||||||
|
|
||||||
|
Payment savedPayment = super.save(payment);
|
||||||
|
|
||||||
|
invoice.setPayment(savedPayment);
|
||||||
invoice.setStatus(StatusValues.PAID);
|
invoice.setStatus(StatusValues.PAID);
|
||||||
invoiceService.update(invoice);
|
invoiceService.update(invoice);
|
||||||
return super.save(payment);
|
|
||||||
|
return savedPayment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
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);
|
||||||
validate(entity);
|
validate(entity);
|
||||||
|
|
@ -97,6 +102,7 @@ public class PaymentService extends AbstractService<Payment, Integer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
private void updateInvoiceStatus(Payment payment) {
|
private void updateInvoiceStatus(Payment payment) {
|
||||||
Invoice invoice = payment.getInvoice();
|
Invoice invoice = payment.getInvoice();
|
||||||
invoice.setStatus(StatusValues.PAID);
|
invoice.setStatus(StatusValues.PAID);
|
||||||
|
|
@ -116,4 +122,9 @@ public class PaymentService extends AbstractService<Payment, Integer> {
|
||||||
return paymentRepository.findByInvoice_User_Id(userId);
|
return paymentRepository.findByInvoice_User_Id(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Invoice getInvoiceById(Integer id) {
|
||||||
|
return invoiceService.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
package com.denniseckerskorn.services.finance_services;
|
package com.denniseckerskorn.services.finance_services;
|
||||||
|
|
||||||
|
import com.denniseckerskorn.entities.finance.IVAType;
|
||||||
import com.denniseckerskorn.entities.finance.ProductService;
|
import com.denniseckerskorn.entities.finance.ProductService;
|
||||||
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
||||||
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
import com.denniseckerskorn.exceptions.EntityNotFoundException;
|
||||||
import com.denniseckerskorn.exceptions.InvalidDataException;
|
import com.denniseckerskorn.exceptions.InvalidDataException;
|
||||||
import com.denniseckerskorn.repositories.finance_repositories.ProductServiceRepository;
|
import com.denniseckerskorn.repositories.finance_repositories.ProductServiceRepository;
|
||||||
import com.denniseckerskorn.services.AbstractService;
|
import com.denniseckerskorn.services.AbstractService;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -18,29 +20,55 @@ public class ProductServiceService extends AbstractService<ProductService, Integ
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ProductServiceService.class);
|
private static final Logger logger = LoggerFactory.getLogger(ProductServiceService.class);
|
||||||
private final ProductServiceRepository productServiceRepository;
|
private final ProductServiceRepository productServiceRepository;
|
||||||
|
private final IVATypeService ivaTypeService;
|
||||||
|
|
||||||
public ProductServiceService(ProductServiceRepository productServiceRepository) {
|
public ProductServiceService(ProductServiceRepository productServiceRepository, IVATypeService ivaTypeService) {
|
||||||
super(productServiceRepository);
|
super(productServiceRepository);
|
||||||
this.productServiceRepository = productServiceRepository;
|
this.productServiceRepository = productServiceRepository;
|
||||||
|
this.ivaTypeService = ivaTypeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public ProductService save(ProductService entity) throws IllegalArgumentException, DuplicateEntityException {
|
public ProductService save(ProductService entity) throws IllegalArgumentException, DuplicateEntityException {
|
||||||
logger.info("Saving product/service: {}", entity);
|
logger.info("Saving product/service: {}", entity);
|
||||||
|
|
||||||
|
if (entity.getIvaType() == null || entity.getIvaType().getId() == null) {
|
||||||
|
throw new IllegalArgumentException("Product must have an IVA type assigned");
|
||||||
|
}
|
||||||
|
|
||||||
|
IVAType ivaType = ivaTypeService.findById(entity.getIvaType().getId());
|
||||||
|
entity.setIvaType(ivaType);
|
||||||
|
|
||||||
validateProduct(entity);
|
validateProduct(entity);
|
||||||
if (productServiceRepository.existsByName(entity.getName())) {
|
|
||||||
|
ProductService existingProduct = productServiceRepository.findByName(entity.getName());
|
||||||
|
if (existingProduct != null && productServiceRepository.existsByName(existingProduct.getName())) {
|
||||||
throw new DuplicateEntityException("Product/Service with this name already exists");
|
throw new DuplicateEntityException("Product/Service with this name already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.save(entity);
|
return super.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public ProductService update(ProductService entity) throws IllegalArgumentException, InvalidDataException, EntityNotFoundException {
|
public ProductService update(ProductService entity) throws IllegalArgumentException, InvalidDataException, EntityNotFoundException {
|
||||||
logger.info("Updating product/service: {}", entity);
|
logger.info("Updating product/service: {}", entity);
|
||||||
|
|
||||||
|
if (entity.getIvaType() == null || entity.getIvaType().getId() == null) {
|
||||||
|
throw new IllegalArgumentException("Product must have an IVA type assigned");
|
||||||
|
}
|
||||||
|
|
||||||
|
IVAType ivaType = ivaTypeService.findById(entity.getIvaType().getId());
|
||||||
|
entity.setIvaType(ivaType);
|
||||||
|
|
||||||
validateProduct(entity);
|
validateProduct(entity);
|
||||||
|
|
||||||
return super.update(entity);
|
return super.update(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProductService> findAll() {
|
public List<ProductService> findAll() {
|
||||||
logger.info("Retrieving all products/services");
|
logger.info("Retrieving all products/services");
|
||||||
|
|
@ -67,11 +95,11 @@ public class ProductServiceService extends AbstractService<ProductService, Integ
|
||||||
if (product.getPrice() == null || product.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
|
if (product.getPrice() == null || product.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
throw new InvalidDataException("Product price must be greater than 0");
|
throw new InvalidDataException("Product price must be greater than 0");
|
||||||
}
|
}
|
||||||
if (product.getIvaType() == null) {
|
|
||||||
throw new InvalidDataException("Product must have an IVA type assigned");
|
|
||||||
}
|
|
||||||
if (product.getStatus() == null) {
|
if (product.getStatus() == null) {
|
||||||
throw new InvalidDataException("Product status cannot be null");
|
throw new InvalidDataException("Product status cannot be null");
|
||||||
}
|
}
|
||||||
|
if (product.getIvaType() == null || product.getIvaType().getId() == null) {
|
||||||
|
throw new IllegalArgumentException("Product must have an IVA type assigned");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.denniseckerskorn.services.finance_management_services;
|
package com.denniseckerskorn.services.finance_services;
|
||||||
|
|
||||||
import com.denniseckerskorn.entities.finance.IVAType;
|
import com.denniseckerskorn.entities.finance.IVAType;
|
||||||
import com.denniseckerskorn.entities.finance.ProductService;
|
import com.denniseckerskorn.entities.finance.ProductService;
|
||||||
|
|
@ -6,7 +6,6 @@ import com.denniseckerskorn.enums.StatusValues;
|
||||||
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
import com.denniseckerskorn.exceptions.DuplicateEntityException;
|
||||||
import com.denniseckerskorn.exceptions.InvalidDataException;
|
import com.denniseckerskorn.exceptions.InvalidDataException;
|
||||||
import com.denniseckerskorn.repositories.finance_repositories.ProductServiceRepository;
|
import com.denniseckerskorn.repositories.finance_repositories.ProductServiceRepository;
|
||||||
import com.denniseckerskorn.services.finance_services.ProductServiceService;
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
@ -23,6 +22,9 @@ class ProductServiceServiceTest {
|
||||||
@Mock
|
@Mock
|
||||||
private ProductServiceRepository productServiceRepository;
|
private ProductServiceRepository productServiceRepository;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IVATypeService ivaTypeService;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private EntityManager entityManager;
|
private EntityManager entityManager;
|
||||||
|
|
||||||
|
|
@ -38,17 +40,18 @@ class ProductServiceServiceTest {
|
||||||
|
|
||||||
ivaType = new IVAType();
|
ivaType = new IVAType();
|
||||||
ivaType.setId(1);
|
ivaType.setId(1);
|
||||||
ivaType.setPercentage(new BigDecimal("21.00"));
|
ivaType.setPercentage(BigDecimal.valueOf(21));
|
||||||
ivaType.setDescription("IVA General");
|
ivaType.setDescription("IVA General");
|
||||||
|
|
||||||
product = new ProductService();
|
product = new ProductService();
|
||||||
product.setId(10);
|
product.setId(1);
|
||||||
product.setName("Clase Avanzada");
|
product.setName("Producto Test");
|
||||||
product.setType("Servicio");
|
product.setPrice(BigDecimal.valueOf(100));
|
||||||
product.setPrice(new BigDecimal("30.00"));
|
product.setType("PRODUCT");
|
||||||
product.setIvaType(ivaType);
|
|
||||||
product.setStatus(StatusValues.ACTIVE);
|
product.setStatus(StatusValues.ACTIVE);
|
||||||
|
product.setIvaType(ivaType);
|
||||||
|
|
||||||
|
// Set EntityManager in abstract service
|
||||||
Field emField = ProductServiceService.class.getSuperclass().getDeclaredField("entityManager");
|
Field emField = ProductServiceService.class.getSuperclass().getDeclaredField("entityManager");
|
||||||
emField.setAccessible(true);
|
emField.setAccessible(true);
|
||||||
emField.set(productServiceService, entityManager);
|
emField.set(productServiceService, entityManager);
|
||||||
|
|
@ -56,23 +59,23 @@ class ProductServiceServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void save_ValidProduct_ShouldReturnSaved() {
|
void save_ValidProduct_ShouldReturnSaved() {
|
||||||
|
when(ivaTypeService.findById(ivaType.getId())).thenReturn(ivaType);
|
||||||
|
when(productServiceRepository.findByName(product.getName())).thenReturn(null);
|
||||||
when(productServiceRepository.existsByName(product.getName())).thenReturn(false);
|
when(productServiceRepository.existsByName(product.getName())).thenReturn(false);
|
||||||
when(productServiceRepository.save(any())).thenReturn(product);
|
when(productServiceRepository.save(any())).thenReturn(product);
|
||||||
|
|
||||||
ProductService saved = productServiceService.save(product);
|
ProductService saved = productServiceService.save(product);
|
||||||
assertEquals("Clase Avanzada", saved.getName());
|
assertEquals(product.getName(), saved.getName());
|
||||||
|
assertEquals(product.getPrice(), saved.getPrice());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void save_DuplicateName_ShouldThrow() {
|
void save_DuplicateName_ShouldThrow() {
|
||||||
|
when(ivaTypeService.findById(ivaType.getId())).thenReturn(ivaType);
|
||||||
|
when(productServiceRepository.findByName(product.getName())).thenReturn(product);
|
||||||
when(productServiceRepository.existsByName(product.getName())).thenReturn(true);
|
when(productServiceRepository.existsByName(product.getName())).thenReturn(true);
|
||||||
assertThrows(DuplicateEntityException.class, () -> productServiceService.save(product));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
assertThrows(DuplicateEntityException.class, () -> productServiceService.save(product));
|
||||||
void save_NullName_ShouldThrow() {
|
|
||||||
product.setName(null);
|
|
||||||
assertThrows(InvalidDataException.class, () -> productServiceService.save(product));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -81,10 +84,16 @@ class ProductServiceServiceTest {
|
||||||
assertThrows(InvalidDataException.class, () -> productServiceService.save(product));
|
assertThrows(InvalidDataException.class, () -> productServiceService.save(product));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void save_NullName_ShouldThrow() {
|
||||||
|
product.setName(null);
|
||||||
|
assertThrows(InvalidDataException.class, () -> productServiceService.save(product));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void save_NoIVAType_ShouldThrow() {
|
void save_NoIVAType_ShouldThrow() {
|
||||||
product.setIvaType(null);
|
product.setIvaType(null);
|
||||||
assertThrows(InvalidDataException.class, () -> productServiceService.save(product));
|
assertThrows(IllegalArgumentException.class, () -> productServiceService.save(product));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ const PaymentForm = () => {
|
||||||
const fetchInvoices = async (userId) => {
|
const fetchInvoices = async (userId) => {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(`/invoices/getAllInvoicesByUserId/${userId}`);
|
const res = await api.get(`/invoices/getAllInvoicesByUserId/${userId}`);
|
||||||
const notPaid = res.data.filter((invoice) => invoice.status === "NOT_PAID");
|
const notPaid = res.data.filter(
|
||||||
|
(invoice) => invoice.status === "NOT_PAID"
|
||||||
|
);
|
||||||
setInvoices(notPaid);
|
setInvoices(notPaid);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
@ -48,6 +50,19 @@ const PaymentForm = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedInvoice = invoices.find(
|
||||||
|
(inv) => inv.id === parseInt(selectedInvoiceId)
|
||||||
|
);
|
||||||
|
if (!selectedInvoice) {
|
||||||
|
setError("Factura no encontrada.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseFloat(amount) < selectedInvoice.total) {
|
||||||
|
setError("El importe pagado no puede ser menor al total de la factura.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post("/payments/create", {
|
await api.post("/payments/create", {
|
||||||
invoiceId: parseInt(selectedInvoiceId),
|
invoiceId: parseInt(selectedInvoiceId),
|
||||||
|
|
@ -61,7 +76,7 @@ const PaymentForm = () => {
|
||||||
setSelectedInvoiceId("");
|
setSelectedInvoiceId("");
|
||||||
setAmount("");
|
setAmount("");
|
||||||
setPaymentMethod("CASH");
|
setPaymentMethod("CASH");
|
||||||
fetchInvoices(selectedUserId); // actualizar facturas pendientes
|
fetchInvoices(selectedUserId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
setError("❌ Error al registrar el pago.");
|
setError("❌ Error al registrar el pago.");
|
||||||
|
|
@ -71,10 +86,14 @@ const PaymentForm = () => {
|
||||||
return (
|
return (
|
||||||
<div className="content-area">
|
<div className="content-area">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Registrar Pago</h2>
|
<h2>Registrar Pago de una Factura</h2>
|
||||||
|
|
||||||
<label>Seleccionar estudiante:</label>
|
<label>Seleccionar estudiante:</label>
|
||||||
<select className="form-select" value={selectedUserId} onChange={handleStudentChange}>
|
<select
|
||||||
|
className="form-select"
|
||||||
|
value={selectedUserId}
|
||||||
|
onChange={handleStudentChange}
|
||||||
|
>
|
||||||
<option value="">-- Selecciona un estudiante --</option>
|
<option value="">-- Selecciona un estudiante --</option>
|
||||||
{students.map((s) =>
|
{students.map((s) =>
|
||||||
s.user ? (
|
s.user ? (
|
||||||
|
|
@ -96,13 +115,20 @@ const PaymentForm = () => {
|
||||||
<option value="">-- Selecciona una factura --</option>
|
<option value="">-- Selecciona una factura --</option>
|
||||||
{invoices.map((inv) => (
|
{invoices.map((inv) => (
|
||||||
<option key={inv.id} value={inv.id}>
|
<option key={inv.id} value={inv.id}>
|
||||||
#{inv.id} - {new Date(inv.date).toLocaleDateString()} - Total: {inv.total.toFixed(2)} €
|
#{inv.id} - {new Date(inv.date).toLocaleDateString()} - Total:{" "}
|
||||||
|
{inv.total.toFixed(2)} €
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{invoices.length === 0 && selectedUserId && (
|
||||||
|
<p style={{ marginTop: "1rem", color: "gray" }}>
|
||||||
|
No hay facturas pendientes de pago para este estudiante.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
{selectedInvoiceId && (
|
{selectedInvoiceId && (
|
||||||
<>
|
<>
|
||||||
<label>Importe pagado (€):</label>
|
<label>Importe pagado (€):</label>
|
||||||
|
|
@ -121,15 +147,18 @@ const PaymentForm = () => {
|
||||||
onChange={(e) => setPaymentMethod(e.target.value)}
|
onChange={(e) => setPaymentMethod(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="CASH">Efectivo</option>
|
<option value="CASH">Efectivo</option>
|
||||||
<option value="CARD">Tarjeta</option>
|
<option value="CREDIT_CARD">Tarjeta</option>
|
||||||
<option value="TRANSFER">Transferencia</option>
|
<option value="BANK_TRANSFER">Transferencia</option>
|
||||||
<option value="BIZUM">Bizum</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<ErrorMessage message={error} type="error" />
|
<ErrorMessage message={error} type="error" />
|
||||||
<ErrorMessage message={success} type="success" />
|
<ErrorMessage message={success} type="success" />
|
||||||
|
|
||||||
<button className="btn btn-primary" style={{ marginTop: "1rem" }} onClick={handleSubmit}>
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
style={{ marginTop: "1rem" }}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
💳 Confirmar Pago
|
💳 Confirmar Pago
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import api from "../../api/axiosConfig";
|
||||||
|
import ErrorMessage from "../common/ErrorMessage";
|
||||||
|
|
||||||
|
const ProductForm = ({ onProductAdded }) => {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [price, setPrice] = useState("");
|
||||||
|
const [type, setType] = useState("PRODUCT");
|
||||||
|
const [status, setStatus] = useState("ACTIVE");
|
||||||
|
const [ivaTypes, setIvaTypes] = useState([]);
|
||||||
|
const [selectedIvaId, setSelectedIvaId] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [success, setSuccess] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
api.get("/iva-types/getAll").then((res) => setIvaTypes(res.data));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError("");
|
||||||
|
setSuccess("");
|
||||||
|
|
||||||
|
if (!name || !price || !selectedIvaId || !type || !status) {
|
||||||
|
setError("Todos los campos son obligatorios.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ivaTypeIdParsed = parseInt(selectedIvaId);
|
||||||
|
if (isNaN(ivaTypeIdParsed)) {
|
||||||
|
setError("Debes seleccionar un tipo de IVA válido.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.post("/products-services/create", {
|
||||||
|
name,
|
||||||
|
price: parseFloat(price),
|
||||||
|
ivaTypeId: ivaTypeIdParsed,
|
||||||
|
type,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSuccess("✅ Producto/servicio creado correctamente.");
|
||||||
|
setName("");
|
||||||
|
setPrice("");
|
||||||
|
setSelectedIvaId("");
|
||||||
|
setType("PRODUCT");
|
||||||
|
setStatus("ACTIVE");
|
||||||
|
|
||||||
|
if (onProductAdded) onProductAdded(); // refrescar lista si se usa en conjunto
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setError("❌ Error al crear el producto/servicio.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="content-area">
|
||||||
|
<div className="card">
|
||||||
|
<h2>Crear Producto o Servicio</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<label>Nombre:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-input"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label>Precio (€):</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-input"
|
||||||
|
value={price}
|
||||||
|
onChange={(e) => setPrice(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label>Tipo de IVA:</label>
|
||||||
|
<select
|
||||||
|
className="form-select"
|
||||||
|
value={selectedIvaId}
|
||||||
|
onChange={(e) => setSelectedIvaId(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">-- Selecciona un IVA --</option>
|
||||||
|
{ivaTypes.map((iva) => (
|
||||||
|
<option key={iva.id} value={iva.id}>
|
||||||
|
{iva.name} ({iva.percentage}%)
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label>Tipo:</label>
|
||||||
|
<select
|
||||||
|
className="form-select"
|
||||||
|
value={type}
|
||||||
|
onChange={(e) => setType(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="PRODUCT">Producto</option>
|
||||||
|
<option value="SERVICE">Servicio</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label>Estado:</label>
|
||||||
|
<select
|
||||||
|
className="form-select"
|
||||||
|
value={status}
|
||||||
|
onChange={(e) => setStatus(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="ACTIVE">Activo</option>
|
||||||
|
<option value="INACTIVE">Inactivo</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<ErrorMessage message={error} type="error" />
|
||||||
|
<ErrorMessage message={success} type="success" />
|
||||||
|
|
||||||
|
<button className="btn btn-primary" type="submit" style={{ marginTop: "1rem" }}>
|
||||||
|
💾 Guardar
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductForm;
|
||||||
|
|
@ -26,6 +26,8 @@ import InvoiceForm from '../forms/InvoiceForm';
|
||||||
import InvoiceList from '../lists/InvoiceList';
|
import InvoiceList from '../lists/InvoiceList';
|
||||||
import PaymentForm from '../forms/PaymentForm';
|
import PaymentForm from '../forms/PaymentForm';
|
||||||
import PaymentList from '../lists/PaymentList';
|
import PaymentList from '../lists/PaymentList';
|
||||||
|
import ProductForm from '../forms/ProductForm';
|
||||||
|
import ProductList from '../lists/ProductList';
|
||||||
import '../styles/MainLayout.css';
|
import '../styles/MainLayout.css';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -73,6 +75,9 @@ const MainLayout = () => {
|
||||||
<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/create" element={<PaymentForm />} />
|
||||||
<Route path="/admin/finance/payments/list" element={<PaymentList />} />
|
<Route path="/admin/finance/payments/list" element={<PaymentList />} />
|
||||||
|
<Route path="/admin/finance/products/create" element={<ProductForm />} />
|
||||||
|
<Route path="/admin/finance/products/list" element={<ProductList />} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ const PaymentList = () => {
|
||||||
return (
|
return (
|
||||||
<div className="content-area">
|
<div className="content-area">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Listado de Pagos</h2>
|
<h2>Listado de Facturas Pagadas por Estudiante</h2>
|
||||||
|
|
||||||
<label>Seleccionar estudiante:</label>
|
<label>Seleccionar estudiante:</label>
|
||||||
<select className="form-select" value={selectedUserId} onChange={handleStudentChange}>
|
<select className="form-select" value={selectedUserId} onChange={handleStudentChange}>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import api from "../../api/axiosConfig";
|
||||||
|
import "../styles/ContentArea.css";
|
||||||
|
|
||||||
|
const ProductList = () => {
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
const [ivaTypes, setIvaTypes] = useState([]);
|
||||||
|
const [editIndex, setEditIndex] = useState(null);
|
||||||
|
const [editableProduct, setEditableProduct] = useState(null);
|
||||||
|
|
||||||
|
const fetchProducts = async () => {
|
||||||
|
const res = await api.get("/products-services/getAll");
|
||||||
|
setProducts(res.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchIvaTypes = async () => {
|
||||||
|
const res = await api.get("/iva-types/getAll");
|
||||||
|
setIvaTypes(res.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchProducts();
|
||||||
|
fetchIvaTypes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("IVA Types cargados:", ivaTypes);
|
||||||
|
}, [ivaTypes]);
|
||||||
|
|
||||||
|
const startEdit = (index) => {
|
||||||
|
setEditIndex(index);
|
||||||
|
setEditableProduct({ ...products[index] }); // contiene ivaTypeId directamente
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
setEditIndex(null);
|
||||||
|
setEditableProduct(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
setEditableProduct((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: field === "ivaTypeId" ? parseInt(value) : value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
const dto = {
|
||||||
|
id: editableProduct.id,
|
||||||
|
name: editableProduct.name,
|
||||||
|
price: parseFloat(editableProduct.price),
|
||||||
|
type: editableProduct.type,
|
||||||
|
status: editableProduct.status,
|
||||||
|
ivaTypeId: editableProduct.ivaTypeId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await api.put("/products-services/update", dto);
|
||||||
|
alert("✅ Producto actualizado correctamente.");
|
||||||
|
setEditIndex(null);
|
||||||
|
setEditableProduct(null);
|
||||||
|
fetchProducts();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("❌ Error al actualizar el producto.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
if (window.confirm("¿Estás seguro de que deseas eliminar este producto?")) {
|
||||||
|
await api.delete(`/products-services/deleteById/${id}`);
|
||||||
|
fetchProducts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIVADisplay = (ivaTypeId) => {
|
||||||
|
const iva = ivaTypes.find((i) => i.id === Number(ivaTypeId));
|
||||||
|
return iva ? `${iva.percentage}%` : "Sin IVA";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="content-area">
|
||||||
|
<h2>Lista de Productos y Servicios</h2>
|
||||||
|
|
||||||
|
<table className="table" style={{ marginTop: "2rem" }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Precio</th>
|
||||||
|
<th>IVA</th>
|
||||||
|
<th>Tipo</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{products.map((p, index) => {
|
||||||
|
const isEditing = index === editIndex;
|
||||||
|
return (
|
||||||
|
<tr key={p.id}>
|
||||||
|
<td>{p.id}</td>
|
||||||
|
<td>
|
||||||
|
{isEditing ? (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editableProduct.name}
|
||||||
|
onChange={(e) => handleChange("name", e.target.value)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
p.name
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{isEditing ? (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={editableProduct.price}
|
||||||
|
step="0.01"
|
||||||
|
onChange={(e) => handleChange("price", e.target.value)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
`${p.price.toFixed(2)} €`
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{isEditing ? (
|
||||||
|
<select
|
||||||
|
value={editableProduct.ivaTypeId || ""}
|
||||||
|
onChange={(e) => handleChange("ivaTypeId", e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">-- IVA --</option>
|
||||||
|
{ivaTypes.map((iva) => (
|
||||||
|
<option key={iva.id} value={iva.id}>
|
||||||
|
{iva.name} ({iva.percentage}%)
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
getIVADisplay(p.ivaTypeId)
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{isEditing ? (
|
||||||
|
<select
|
||||||
|
value={editableProduct.type}
|
||||||
|
onChange={(e) => handleChange("type", e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="PRODUCT">Producto</option>
|
||||||
|
<option value="SERVICE">Servicio</option>
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
p.type
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{isEditing ? (
|
||||||
|
<select
|
||||||
|
value={editableProduct.status}
|
||||||
|
onChange={(e) => handleChange("status", e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="ACTIVE">Activo</option>
|
||||||
|
<option value="INACTIVE">Inactivo</option>
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
p.status
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<button className="btn btn-primary btn-sm" onClick={handleSave}>
|
||||||
|
💾 Guardar
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-secondary btn-sm" onClick={cancelEdit}>
|
||||||
|
❌ Cancelar
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button className="btn btn-secondary btn-sm" onClick={() => startEdit(index)}>
|
||||||
|
✏️ Editar
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-danger btn-sm" onClick={() => handleDelete(p.id)}>
|
||||||
|
🗑️ Eliminar
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductList;
|
||||||
Loading…
Reference in New Issue