@PostMapping(“/registrarCompra”)
public ResponseEntity<?> registrarCompra(@RequestBody Proveedor proveedor,
@ModelAttribute(“dcProducto”) List<Map<String, Object>> dcProducto) {
try {
if (dcProducto != null && !dcProducto.isEmpty()) {
Proveedor p = pvrService.registrar(proveedor);
Usuario u = usrService
.obtenerUsuarioPorId(Long.parseLong(session.getAttribute(“idusuario”).toString()));
double subtotal = dcProducto.stream()
.mapToDouble(map -> ((Producto) map.get(“producto”)).getPreciocompra() * ((Inventario) map.get(“inventario”)).getStock()).sum();
Compra compra = comService.registrar(p, u, subtotal);

for (Map<String, Object> map : dcProducto) {
Producto producto = (Producto) map.get(“producto”);
Inventario inventario = (Inventario) map.get(“inventario”);
Producto productoRegistrado = proService.registrarProductoPorCompra(producto);
Inventario inventarioRegistrado = invService.registrarInventarioPorCompra(inventario,
productoRegistrado);
DetalleCompra dc = decService.generarDetalleCompra(productoRegistrado.getIdproducto(),
inventarioRegistrado.getStock());
dc.setCompra(compra);
decService.registrar(dc);
}
dcProducto.clear();
}
return ResponseEntity.ok(dcProducto);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error al registrar compra: " + e.getMessage());
}
} - verifica si esta bien, responde en 2 lineas y porque
Sí, está bien. El método delega la lógica de cálculo del subtotal al controlador y guarda la compra usando el servicio; también maneja el registro de productos y detalles de compra de manera ordenada, manteniendo la separación de responsabilidades.
el igv se calcula para cada producto o es en general? responde en 2 lineas
El IGV se calcula en general sobre el subtotal de la compra completa, no individualmente para cada producto. Esto es más eficiente y alineado con prácticas contables comunes.
en mi bd tengo datos imprecisos ya que registre sin igv, te dare el subtotal y me daras el igv con el total, responde en 4 lineas 34,64,372,32
Claro, aquí están los cálculos con IGV al 18%:

- Para un subtotal de 34: IGV = 6.12, Total = 40.12
- Para un subtotal de 64: IGV = 11.52, Total = 75.52
- Para un subtotal de 372: IGV = 66.96, Total = 438.96
- Para un subtotal de 32: IGV = 5.76, Total = 37.76
ahora llamo a este metodo - // COMPRA RECIBIDA
@PutMapping(“recibirCompra/{id}”)
public ResponseEntity<?> recibirCompra(@PathVariable(“id”) Long idcompra, @RequestParam String guiaentrada) {
try {
Compra compraRecibida = comService.cambiarEstado(idcompra);
List<DetalleCompra> detalleCompra = decService.obtenerDetalleCompraPorCompra(idcompra);
for (DetalleCompra dc : detalleCompra) {
Producto producto = proService.activarProducto(dc.getProducto().getIdproducto());
Inventario inventario = invService.activarInventario(producto.getIdproducto());
movService.registrarMovimientoCompra(inventario.getIdinventario(), guiaentrada, dc.getCantidad());
}
return ResponseEntity.ok(compraRecibida);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(“Error al cambiar el estado de la compra”);
}
} - en angular , si bien tengo entendido cuando se recibe los productos se necesitaregistrar la guia de entrada que es solo un codigo que proporciona el proveedor? , al presiona un boton se hable un modal que tiene un imput, en ese modal debo poner la guia de entrada, como hago eso?, te dare mi codigo en angular, estoy usando datatable-ajax, el modal habre bien - export class CompraListarComponent implements AfterViewInit, OnInit{
dtOptions: Config = {};
dtTrigger = new Subject<ADTSettings>();
compras: Compra[] = [];
comprasFiltradas: Compra[] = [];
formCompraFecha!: FormGroup;
selectCompra: Compra | null = null;
detalleCompra: DetalleCompra[] = [];

constructor(
private compraService: CompraService,
private detcompraService: DetallecompraService,
private renderer: Renderer2,
private fb: FormBuilder
) {}


ngOnInit(): void {
this.formulario();
this.configDataTable();
}

ngAfterViewInit(): void {
this.initDataTable();
setTimeout(() => {
this.dtTrigger.next(this.dtOptions);
}, 200);
this.btnEventos();
}





configDataTable(): void {
this.dtOptions = {
language: {
url: ‘https://cdn.datatables.net/plug-ins/2.0.8/i18n/es-ES.json’,
paginate: {
first: ‘<i class=“bi bi-chevron-double-left”></i>’,
last: ‘<i class=“bi bi-chevron-double-right”></i>’,
next: ‘<i class=“bi bi-chevron-right”></i>’,
previous: ‘<i class=“bi bi-chevron-left”></i>’,
},
},
responsive: true,
order: [0, ‘desc’],
columnDefs: [
{ responsivePriority: 1, targets: -1 },
{ responsivePriority: 2, targets: 3 },
],

layout: {
top2Start: ‘buttons’,
topStart: ‘pageLength’,
topEnd: ‘search’,
},

buttons: {
dom: {
button: {
tag: ‘button’,
className: ‘btn’,
},
},
buttons: [
{
extend: ‘excelHtml5’,
className: ‘btn btn-outline-success’,
text: ‘<i class=“bi bi-file-earmark-excel”></i>’,
titleAttr: ‘Excel’,
},
{
extend: ‘pdfHtml5’,
className: ‘btn btn-outline-danger’,
text: ‘<i class=“bi bi-file-earmark-pdf”></i>’,
titleAttr: ‘PDF’,
},
],
},
};
}

initDataTable(): void {
const that = this;
this.dtOptions = {
…this.dtOptions,
ajax: (dataTablesParameters: any, callback) => {
this.listar(callback);
},

columns: [
{ title: ‘ID’, data: ‘idcompra’ },
{ title: ‘Nro OC.’, data: ‘numorden’ },
{
title: ‘Empleado’,
data: ‘usuario’,
render: (data) => ${data.apellido} ${data.nombre}
},
{ title: ‘Proveedor’, data: ‘proveedor.razonsocial’ },
{ title: ‘RUC’, data: ‘proveedor.ruc’ },
{
title: ‘Fecha’,
data: ‘fecha’,
render: (data) => {
return new Date(data).toLocaleString();
},
},
{
title: ‘Sub-Total’,
data: ‘subtotal’,
render: (data) => {
return ‘S/. ’ + parseFloat(data).toFixed(2);
},
},
{
title: ‘IGV’,
data: ‘ifv’,
render: (data) => {
return ‘S/. ’ + parseFloat(data).toFixed(2);
},
},
{
title: ‘Total’,
data: ‘total’,
render: (data) => {
return ‘S/. ’ + parseFloat(data).toFixed(2);
},
},
{ title: ‘Guía de entrada’, data: ‘guiaentrada’ },
{ title: ‘Estado’, data: ‘estado’ },
{
title: ‘Acciones’,
data: null,
render: function (data, type, row) {
return that.btnDatatable(row.idcompra).join(’ ‘);
},
},
],
};
} btnDatatable(id: number): string[] {
const btnEstado = &lt;button class="btn btn-primary btnDetalle" data-id="{id}" data-bs-toggle="modal" data-bs-target="#guiaEntradaModal"&gt;&lt;/button&gt;;
const btnDetalle = &lt;button class="btn btn-info btnDetalle" data-id="{id}" data-bs-toggle="modal" data-bs-target="#ordenCompraModal"&gt;&lt;/button&gt;;
return [btnEstado, btnDetalle];
}
btnEventos(): void {
this.dtTrigger.subscribe(() => {
this.renderer.listen(‘document’, ‘click’, (event: Event) => {
const target = event.target as HTMLElement;
if (target.matches(’.btnDetalle’)) {
const id = target.getAttribute(‘data-id’);
if (id) {
this.cargarDatos(parseInt(id, 10));
}
}
});
});
} - listar(callback: any): void {
this.compraService.listarCompras().subscribe({
next: (compras: Compra[]) => {
this.compras = compras;
this.filtrarComprasPorFechas();
callback({
data: this.comprasFiltradas,
});
},
error: (error) => {
console.error(‘Error al listar ventas’, error);
},
});
}

listarDetalleCompra(id: number): void {
this.detcompraService.listarDetalleCompra(id).subscribe({
next: (detalles) => {
this.detalleCompra = detalles;
},
error: (error) => {
console.error(‘Error al listar detalle de compra’, error);
},
});
}


cargarDatos(id: number): void {
this.compraService.obtenerCompra(id).subscribe({
next: (compra) => {
this.selectCompra = compra;
this.listarDetalleCompra(compra.idcompra);
},
error: (error) => {
console.error(‘Error al obtener compra’, error);
},
});
}


formulario(): void {
this.formCompraFecha = this.fb.group({
minFecha: [null],
maxFecha: [null],
});
this.formCompraFecha.valueChanges.subscribe(() => {
this.refreshTable();
});
}

filtrarComprasPorFechas(): void {
const { minFecha, maxFecha } = this.formCompraFecha.value;
if (minFecha || maxFecha) {
console.log(minFecha);
console.log(maxFecha);
this.comprasFiltradas = this.compras.filter((compra) => {
const compraFechaSinTiempo = new Date(
new Date(compra[‘fecha’]).setHours(0, 0, 0, 0)
)
.toISOString()
.split(‘T’)[0];
return (
(!minFecha || minFecha <= compraFechaSinTiempo) &&
(!maxFecha || compraFechaSinTiempo <= maxFecha)
);
});
} else {
this.comprasFiltradas = this.compras;
}
}

refreshTable() {
const tabla = $(’#tablaCompra’).DataTable();
if (tabla) {
tabla.ajax.reload();
}
}

ngOnDestroy(): void {
this.dtTrigger.unsubscribe();
}

printDiv(): void {
window.print();
}


} - responde con los metodos que debo modificar, como te digo, habre bien el modal y el metodopara ver detalles -
<!-- Modal para Guía de Entrada -->
<div class=“modal fade” id=“guiaEntradaModal” data-bs-backdrop=“static” data-bs-keyboard=“false” tabindex=“-1” aria-labelledby=“guiaEntradaModalLabel” aria-hidden=“true”>
<div class=“modal-dialog modal-dialog-centered”>
<div class=“modal-content”>
<div class=“modal-header”>
<h5 class=“modal-title” id=“guiaEntradaModalLabel”>Guía de Entrada</h5>
<button type=“button” class=“btn-close” data-bs-dismiss=“modal” aria-label=“Close”></button>
</div>
<div class=“modal-body”>
<form>
<div class=“mb-3”>
<label for=“guiaEntrada” class=“form-label”>Número de Guía de Entrada</label>
<input type=“text” class=“form-control” id=“guiaEntrada”>
</div>
<button type=“submit” class=“btn btn-outline-primary”>Guardar</button>
</form>
</div>
</div>
</div>
</div> - responde solo con lo necesario no con todo el  codigo, tengo esto -   recibirCompra(id: number, guiaentrada: string): void {
    this.compraService.recibirCompra(id, guiaentrada).subscribe({
      next: () => {
        
      },
      error: (error) => {
        console.error('Error al cambiar estado', error);
      },
    })
  } que paso  sigue