mx.edu.um.mateo.inventario.dao.impl.EntradaDaoHibernate.java Source code

Java tutorial

Introduction

Here is the source code for mx.edu.um.mateo.inventario.dao.impl.EntradaDaoHibernate.java

Source

/*
 * The MIT License
 *
 * Copyright 2012 Universidad de Montemorelos A. C.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package mx.edu.um.mateo.inventario.dao.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import mx.edu.um.mateo.general.dao.BaseDao;
import mx.edu.um.mateo.general.model.Usuario;
import mx.edu.um.mateo.general.utils.Constantes;
import mx.edu.um.mateo.inventario.dao.CancelacionDao;
import mx.edu.um.mateo.inventario.dao.EntradaDao;
import mx.edu.um.mateo.inventario.model.Almacen;
import mx.edu.um.mateo.inventario.model.Cancelacion;
import mx.edu.um.mateo.inventario.model.Entrada;
import mx.edu.um.mateo.inventario.model.Estatus;
import mx.edu.um.mateo.inventario.model.Folio;
import mx.edu.um.mateo.inventario.model.LoteEntrada;
import mx.edu.um.mateo.inventario.model.LoteSalida;
import mx.edu.um.mateo.inventario.model.Producto;
import mx.edu.um.mateo.inventario.model.Salida;
import mx.edu.um.mateo.inventario.model.XEntrada;
import mx.edu.um.mateo.inventario.model.XLoteEntrada;
import mx.edu.um.mateo.inventario.model.XLoteSalida;
import mx.edu.um.mateo.inventario.model.XProducto;
import mx.edu.um.mateo.inventario.model.XSalida;
import mx.edu.um.mateo.inventario.utils.NoCuadraException;
import mx.edu.um.mateo.inventario.utils.NoEstaAbiertaException;
import mx.edu.um.mateo.inventario.utils.NoEstaCerradaException;
import mx.edu.um.mateo.inventario.utils.NoSePuedeCerrarEnCeroException;
import mx.edu.um.mateo.inventario.utils.NoSePuedeCerrarException;
import mx.edu.um.mateo.inventario.utils.ProductoNoSoportaFraccionException;
import org.hibernate.Criteria;
import org.hibernate.LockOptions;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

/**
 *
 * @author J. David Mendoza <jdmendoza@um.edu.mx>
 */
@Repository
@Transactional
public class EntradaDaoHibernate extends BaseDao implements EntradaDao {

    @Autowired
    private CancelacionDao cancelacionDao;

    public EntradaDaoHibernate() {
        log.info("Nueva instancia de EntradaDao");
    }

    @Override
    @Transactional(readOnly = true)
    public Map<String, Object> lista(Map<String, Object> params) {
        log.debug("Buscando lista de entradas con params {}", params);
        if (params == null) {
            params = new HashMap<>();
        }

        if (!params.containsKey("max")) {
            params.put("max", 10);
        } else {
            params.put("max", Math.min((Integer) params.get("max"), 100));
        }

        if (params.containsKey("pagina")) {
            Long pagina = (Long) params.get("pagina");
            Long offset = (pagina - 1) * (Integer) params.get("max");
            params.put("offset", offset.intValue());
        }

        if (!params.containsKey("offset")) {
            params.put("offset", 0);
        }
        Criteria criteria = currentSession().createCriteria(Entrada.class);
        Criteria countCriteria = currentSession().createCriteria(Entrada.class);
        criteria.createAlias("estatus", "est");
        countCriteria.createAlias("estatus", "est");

        if (params.containsKey("almacen")) {
            criteria.createCriteria("almacen").add(Restrictions.idEq(params.get("almacen")));
            countCriteria.createCriteria("almacen").add(Restrictions.idEq(params.get("almacen")));
        }

        if (params.containsKey("proveedorId")) {
            criteria.createCriteria("proveedor").add(Restrictions.idEq(params.get("proveedorId")));
            countCriteria.createCriteria("proveedor").add(Restrictions.idEq(params.get("proveedorId")));
        }

        if (params.containsKey("fechaIniciado")) {
            log.debug("Buscando desde {}", params.get("fechaIniciado"));
            criteria.add(Restrictions.ge("fechaCreacion", params.get("fechaIniciado")));
            countCriteria.add(Restrictions.ge("fechaCreacion", params.get("fechaIniciado")));
        } else {
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.DAY_OF_MONTH, 1);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 1);
            log.debug("Asignando busqueda desde {}", calendar.getTime());
            criteria.add(Restrictions.ge("fechaCreacion", calendar.getTime()));
            countCriteria.add(Restrictions.ge("fechaCreacion", calendar.getTime()));
        }

        if (params.containsKey("fechaTerminado")) {
            log.debug("Buscando hasta {}", params.get("fechaTerminado"));
            criteria.add(Restrictions.le("fechaCreacion", params.get("fechaTerminado")));
            countCriteria.add(Restrictions.le("fechaCreacion", params.get("fechaTerminado")));
        }

        if (params.containsKey(Constantes.ABIERTA) || params.containsKey(Constantes.CERRADA)
                || params.containsKey(Constantes.PENDIENTE) || params.containsKey(Constantes.FACTURADA)
                || params.containsKey(Constantes.CANCELADA) || params.containsKey(Constantes.DEVOLUCION)) {
            Disjunction propiedades = Restrictions.disjunction();
            if (params.containsKey(Constantes.ABIERTA)) {
                propiedades.add(Restrictions.eq("est.nombre", Constantes.ABIERTA));
            }
            if (params.containsKey(Constantes.CERRADA)) {
                propiedades.add(Restrictions.eq("est.nombre", Constantes.CERRADA));
            }
            if (params.containsKey(Constantes.PENDIENTE)) {
                propiedades.add(Restrictions.eq("est.nombre", Constantes.PENDIENTE));
            }
            if (params.containsKey(Constantes.FACTURADA)) {
                propiedades.add(Restrictions.eq("est.nombre", Constantes.FACTURADA));
            }
            if (params.containsKey(Constantes.CANCELADA)) {
                propiedades.add(Restrictions.eq("est.nombre", Constantes.CANCELADA));
            }
            criteria.add(propiedades);
            countCriteria.add(propiedades);

            if (params.containsKey(Constantes.DEVOLUCION)) {
                criteria.add(Restrictions.eq("devolucion", Boolean.TRUE));
                countCriteria.add(Restrictions.eq("devolucion", Boolean.TRUE));
            }
        }

        if (params.containsKey("filtro")) {
            String filtro = (String) params.get("filtro");
            Disjunction propiedades = Restrictions.disjunction();
            propiedades.add(Restrictions.ilike("folio", filtro, MatchMode.ANYWHERE));
            propiedades.add(Restrictions.ilike("factura", filtro, MatchMode.ANYWHERE));
            propiedades.add(Restrictions.ilike("comentarios", filtro, MatchMode.ANYWHERE));
            criteria.add(propiedades);
            countCriteria.add(propiedades);
        }

        if (params.containsKey("order")) {
            String campo = (String) params.get("order");
            if (params.get("sort").equals("desc")) {
                criteria.addOrder(Order.desc(campo));
            } else {
                criteria.addOrder(Order.asc(campo));
            }
        } else {
            criteria.addOrder(Order.asc("est.prioridad"));
        }

        if (!params.containsKey("reporte")) {
            criteria.setFirstResult((Integer) params.get("offset"));
            criteria.setMaxResults((Integer) params.get("max"));
        }
        params.put("entradas", criteria.list());

        countCriteria.setProjection(Projections.rowCount());
        params.put("cantidad", (Long) countCriteria.list().get(0));

        return params;
    }

    @SuppressWarnings("unchecked")
    @Override
    @Transactional(readOnly = true)
    public List<Entrada> buscaEntradasParaFactura(Map<String, Object> params) {
        log.debug("Buscando lista de entradas con params {}", params);
        if (params == null) {
            params = new HashMap<>();
        }

        if (!params.containsKey("max")) {
            params.put("max", 10);
        } else {
            params.put("max", Math.min((Integer) params.get("max"), 100));
        }

        if (!params.containsKey("offset")) {
            params.put("offset", 0);
        }
        Criteria criteria = currentSession().createCriteria(Entrada.class);

        if (params.containsKey("almacen")) {
            criteria.createCriteria("almacen").add(Restrictions.idEq(params.get("almacen")));
        }

        if (params.containsKey("filtro")) {
            String filtro = (String) params.get("filtro");
            Disjunction propiedades = Restrictions.disjunction();
            propiedades.add(Restrictions.ilike("folio", filtro, MatchMode.ANYWHERE));
            propiedades.add(Restrictions.ilike("factura", filtro, MatchMode.ANYWHERE));
            propiedades.add(Restrictions.ilike("comentarios", filtro, MatchMode.ANYWHERE));
            criteria.add(propiedades);
        }

        if (params.containsKey("facturaId")) {
            Query query = currentSession().createQuery(
                    "select e.id from FacturaAlmacen f inner join f.entradas as e where f.id = :facturaId");
            query.setLong("facturaId", (Long) params.get("facturaId"));
            List<Long> idsDeEntradas = query.list();
            log.debug("idsDeEntradas: {}", idsDeEntradas);
            if (idsDeEntradas != null && idsDeEntradas.size() > 0) {
                criteria.add(Restrictions.not(Restrictions.in("id", idsDeEntradas)));
            }
        }

        criteria.add(Restrictions.eq("devolucion", Boolean.TRUE));
        criteria.createCriteria("estatus").add(Restrictions.eq("nombre", Constantes.CERRADA));
        criteria.addOrder(Order.desc("fechaModificacion"));

        criteria.setFirstResult((Integer) params.get("offset"));
        criteria.setMaxResults((Integer) params.get("max"));

        return criteria.list();
    }

    @Override
    @Transactional(readOnly = true)
    public Entrada obtiene(Long id) {
        return (Entrada) currentSession().get(Entrada.class, id);
    }

    @Override
    @Transactional(readOnly = true)
    public Entrada carga(Long id) {
        return (Entrada) currentSession().load(Entrada.class, id);
    }

    @Override
    public Entrada crea(Entrada entrada, Usuario usuario) {
        Session session = currentSession();
        if (usuario != null) {
            entrada.setAlmacen(usuario.getAlmacen());
        }
        Query query = currentSession().createQuery("select e from Estatus e where e.nombre = :nombre");
        query.setString("nombre", Constantes.ABIERTA);
        Estatus estatus = (Estatus) query.uniqueResult();
        entrada.setEstatus(estatus);
        entrada.setFolio(getFolioTemporal(entrada.getAlmacen()));
        Date fecha = new Date();
        entrada.setFechaCreacion(fecha);
        entrada.setFechaModificacion(fecha);
        session.save(entrada);

        audita(entrada, usuario, Constantes.CREAR, fecha, false);

        session.flush();
        return entrada;
    }

    @Override
    public Entrada crea(Entrada entrada) {
        return this.crea(entrada, null);
    }

    @Override
    @Transactional(rollbackFor = { NoEstaAbiertaException.class })
    public Entrada actualiza(Entrada entrada) throws NoEstaAbiertaException {
        return this.actualiza(entrada, null);
    }

    @Override
    @Transactional(rollbackFor = { NoEstaAbiertaException.class })
    public Entrada actualiza(Entrada otraEntrada, Usuario usuario) throws NoEstaAbiertaException {
        Entrada entrada = (Entrada) currentSession().get(Entrada.class, otraEntrada.getId());
        switch (entrada.getEstatus().getNombre()) {
        case Constantes.ABIERTA:
            Session session = currentSession();
            entrada.setVersion(otraEntrada.getVersion());
            entrada.setFactura(otraEntrada.getFactura());
            entrada.setFechaFactura(otraEntrada.getFechaFactura());
            entrada.setComentarios(otraEntrada.getComentarios());
            entrada.setTipoCambio(otraEntrada.getTipoCambio());
            entrada.setDevolucion(otraEntrada.getDevolucion());
            entrada.setIva(otraEntrada.getIva());
            entrada.setTotal(otraEntrada.getTotal());
            entrada.setProveedor(otraEntrada.getProveedor());
            Date fecha = new Date();
            entrada.setFechaModificacion(fecha);
            session.update(entrada);

            audita(entrada, usuario, Constantes.ACTUALIZAR, fecha, false);

            session.flush();
            return entrada;
        default:
            throw new NoEstaAbiertaException("No se puede actualizar una entrada que no este abierta");
        }
    }

    @Override
    @Transactional(rollbackFor = { NoEstaAbiertaException.class, NoSePuedeCerrarException.class,
            NoCuadraException.class, NoSePuedeCerrarEnCeroException.class })
    public String pendiente(Long entradaId, Usuario usuario) throws NoSePuedeCerrarException, NoCuadraException,
            NoSePuedeCerrarEnCeroException, NoEstaAbiertaException {
        Entrada entrada = (Entrada) currentSession().get(Entrada.class, entradaId);
        if (entrada != null) {
            if (entrada.getEstatus().getNombre().equals(Constantes.ABIERTA)) {
                if (usuario != null) {
                    entrada.setAlmacen(usuario.getAlmacen());
                }

                Date fecha = new Date();
                entrada = preparaParaCerrar(entrada, usuario, fecha);

                Query query = currentSession().createQuery("select e from Estatus e where e.nombre = :nombre");
                query.setString("nombre", Constantes.PENDIENTE);
                Estatus estatus = (Estatus) query.uniqueResult();
                entrada.setEstatus(estatus);
                entrada.setFolio(getFolio(entrada.getAlmacen()));
                entrada.setFechaModificacion(fecha);

                currentSession().update(entrada);

                audita(entrada, usuario, Constantes.ACTUALIZAR, fecha, true);

                currentSession().flush();
                return entrada.getFolio();
            } else {
                throw new NoEstaAbiertaException("No se puede actualizar una entrada que no este abierta");
            }
        } else {
            throw new NoSePuedeCerrarException("No se puede cerrar la entrada pues no existe");
        }

    }

    @Override
    @Transactional(rollbackFor = { NoSePuedeCerrarException.class, NoCuadraException.class,
            NoSePuedeCerrarEnCeroException.class, NoEstaAbiertaException.class })
    public String cierra(Long entradaId, Usuario usuario) throws NoSePuedeCerrarException, NoCuadraException,
            NoSePuedeCerrarEnCeroException, NoEstaAbiertaException {
        Entrada entrada = (Entrada) currentSession().get(Entrada.class, entradaId);
        entrada = cierra(entrada, usuario);
        return entrada.getFolio();
    }

    @Override
    @Transactional(rollbackFor = { NoSePuedeCerrarException.class, NoCuadraException.class,
            NoSePuedeCerrarEnCeroException.class, NoEstaAbiertaException.class })
    public Entrada cierra(Entrada entrada, Usuario usuario) throws NoSePuedeCerrarException, NoCuadraException,
            NoSePuedeCerrarEnCeroException, NoEstaAbiertaException {
        if (entrada != null) {
            if (entrada.getEstatus().getNombre().equals(Constantes.ABIERTA)) {
                if (usuario != null) {
                    entrada.setAlmacen(usuario.getAlmacen());
                }

                Date fecha = new Date();
                entrada = preparaParaCerrar(entrada, usuario, fecha);
                Query query = currentSession().createQuery("select e from Estatus e where e.nombre = :nombre");
                query.setString("nombre", Constantes.CERRADA);
                Estatus estatus = (Estatus) query.uniqueResult();
                entrada.setEstatus(estatus);
                entrada.setFolio(getFolio(entrada.getAlmacen()));
                entrada.setFechaModificacion(fecha);

                currentSession().update(entrada);

                audita(entrada, usuario, Constantes.ACTUALIZAR, fecha, true);

                currentSession().flush();
                return entrada;
            } else {
                throw new NoEstaAbiertaException("No se puede actualizar una entrada que no este abierta");
            }
        } else {
            throw new NoSePuedeCerrarException("No se puede cerrar la entrada pues no existe");
        }
    }

    @Override
    @Transactional(rollbackFor = { NoSePuedeCerrarException.class })
    public Entrada cierraPendiente(Entrada entrada, Usuario usuario) throws NoSePuedeCerrarException {
        Entrada pendiente = (Entrada) currentSession().get(Entrada.class, entrada.getId());
        if (entrada.getVersion() != pendiente.getVersion()) {
            throw new NoSePuedeCerrarException("No es la ultima version de la entrada");
        }
        pendiente.setFactura(entrada.getFactura());
        pendiente.setFechaFactura(entrada.getFechaFactura());
        pendiente.setComentarios(entrada.getComentarios());
        entrada = pendiente;

        Query query = currentSession().createQuery("select e from Estatus e where e.nombre = :nombre");
        query.setString("nombre", Constantes.CERRADA);
        Estatus estatus = (Estatus) query.uniqueResult();
        entrada.setEstatus(estatus);
        Date fecha = new Date();
        entrada.setFechaModificacion(fecha);

        currentSession().update(entrada);

        audita(entrada, usuario, Constantes.ACTUALIZAR, fecha, false);

        currentSession().flush();
        return entrada;
    }

    @Override
    @Transactional(rollbackFor = { NoEstaAbiertaException.class })
    public String elimina(Long id) throws NoEstaAbiertaException {
        return this.elimina(id, null);
    }

    @Override
    @Transactional(rollbackFor = { NoEstaAbiertaException.class })
    public String elimina(Long id, Usuario usuario) throws NoEstaAbiertaException {
        Entrada entrada = obtiene(id);
        if (entrada.getEstatus().getNombre().equals(Constantes.ABIERTA)) {
            String nombre = entrada.getFolio();
            currentSession().delete(entrada);
            audita(entrada, usuario, Constantes.ELIMINAR, new Date(), false);

            currentSession().flush();
            return nombre;
        } else {
            throw new NoEstaAbiertaException("No se puede eliminar una entrada que no este abierta");
        }
    }

    @Override
    @Transactional(rollbackFor = { NoEstaAbiertaException.class, ProductoNoSoportaFraccionException.class })
    public LoteEntrada creaLote(LoteEntrada lote)
            throws ProductoNoSoportaFraccionException, NoEstaAbiertaException {
        log.debug("Creando lote {}", lote);
        lote.setProducto((Producto) currentSession().get(Producto.class, lote.getProducto().getId()));
        lote.setEntrada((Entrada) currentSession().get(Entrada.class, lote.getEntrada().getId()));
        if (lote.getEntrada().getEstatus().getNombre().equals(Constantes.ABIERTA)) {
            if (!lote.getProducto().getFraccion()) {
                BigDecimal[] resultado = lote.getCantidad().divideAndRemainder(new BigDecimal("1"));
                if (resultado[1].doubleValue() > 0) {
                    throw new ProductoNoSoportaFraccionException();
                }
            }

            BigDecimal subtotal = lote.getPrecioUnitario().multiply(lote.getCantidad());
            BigDecimal iva = subtotal.multiply(lote.getProducto().getIva()).setScale(2, RoundingMode.HALF_UP);
            lote.setIva(iva);
            lote.setFechaCreacion(new Date());

            currentSession().save(lote);

            return lote;
        } else {
            throw new NoEstaAbiertaException("No se puede crear un lote en una entrada que no este abierta");
        }
    }

    @Override
    @Transactional(rollbackFor = { NoEstaAbiertaException.class })
    public Long eliminaLote(Long id) throws NoEstaAbiertaException {
        log.debug("Eliminando lote {}", id);
        LoteEntrada lote = (LoteEntrada) currentSession().get(LoteEntrada.class, id);
        if (lote.getEntrada().getEstatus().getNombre().equals(Constantes.ABIERTA)) {
            id = lote.getEntrada().getId();
            currentSession().delete(lote);
            currentSession().flush();
            return id;
        }
        throw new NoEstaAbiertaException("No se pudo eliminar el lote " + id);
    }

    private String getFolioTemporal(Almacen almacen) {
        Query query = currentSession()
                .createQuery("select f from Folio f where f.nombre = :nombre and f.almacen.id = :almacenId");
        query.setString("nombre", "ENTRADA-TEMPORAL");
        query.setLong("almacenId", almacen.getId());
        query.setLockOptions(LockOptions.UPGRADE);
        Folio folio = (Folio) query.uniqueResult();
        if (folio == null) {
            folio = new Folio("ENTRADA-TEMPORAL");
            folio.setAlmacen(almacen);
            currentSession().save(folio);
            currentSession().flush();
            return getFolioTemporal(almacen);
        }
        folio.setValor(folio.getValor() + 1);
        java.text.NumberFormat nf = java.text.DecimalFormat.getInstance();
        nf.setGroupingUsed(false);
        nf.setMinimumIntegerDigits(9);
        nf.setMaximumIntegerDigits(9);
        nf.setMaximumFractionDigits(0);
        StringBuilder sb = new StringBuilder();
        sb.append("TE-");
        sb.append(almacen.getEmpresa().getOrganizacion().getCodigo());
        sb.append(almacen.getEmpresa().getCodigo());
        sb.append(almacen.getCodigo());
        sb.append(nf.format(folio.getValor()));
        return sb.toString();
    }

    private String getFolio(Almacen almacen) {
        Query query = currentSession()
                .createQuery("select f from Folio f where f.nombre = :nombre and f.almacen.id = :almacenId");
        query.setString("nombre", "ENTRADA");
        query.setLong("almacenId", almacen.getId());
        query.setLockOptions(LockOptions.UPGRADE);
        Folio folio = (Folio) query.uniqueResult();
        if (folio == null) {
            folio = new Folio("ENTRADA");
            folio.setAlmacen(almacen);
            currentSession().save(folio);
            return getFolio(almacen);
        }
        folio.setValor(folio.getValor() + 1);
        java.text.NumberFormat nf = java.text.DecimalFormat.getInstance();
        nf.setGroupingUsed(false);
        nf.setMinimumIntegerDigits(9);
        nf.setMaximumIntegerDigits(9);
        nf.setMaximumFractionDigits(0);
        StringBuilder sb = new StringBuilder();
        sb.append("E-");
        sb.append(almacen.getEmpresa().getOrganizacion().getCodigo());
        sb.append(almacen.getEmpresa().getCodigo());
        sb.append(almacen.getCodigo());
        sb.append(nf.format(folio.getValor()));
        return sb.toString();
    }

    private BigDecimal costoPromedio(LoteEntrada lote) {
        Producto producto = lote.getProducto();

        BigDecimal cantidad = lote.getCantidad();
        BigDecimal viejoBalance = producto.getPrecioUnitario().multiply(producto.getExistencia());
        BigDecimal nuevoBalance = lote.getPrecioUnitario().multiply(cantidad);

        BigDecimal balanceTotal = viejoBalance.add(nuevoBalance);
        BigDecimal articulos = cantidad.add(producto.getExistencia());
        return balanceTotal.divide(articulos, 10, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
    }

    private Entrada preparaParaCerrar(Entrada entrada, Usuario usuario, Date fecha)
            throws NoCuadraException, NoSePuedeCerrarEnCeroException {
        BigDecimal iva = entrada.getIva();
        BigDecimal total = entrada.getTotal();
        entrada.setIva(BigDecimal.ZERO);
        entrada.setTotal(BigDecimal.ZERO);
        for (LoteEntrada lote : entrada.getLotes()) {
            Producto producto = lote.getProducto();
            producto.setPrecioUnitario(costoPromedio(lote));
            if (!entrada.getDevolucion()) {
                producto.setUltimoPrecio(lote.getPrecioUnitario());
            }
            producto.setExistencia(producto.getExistencia().add(lote.getCantidad()));
            producto.setFechaModificacion(fecha);
            currentSession().update(producto);
            auditaProducto(producto, usuario, Constantes.ACTUALIZAR, entrada.getId(), null, fecha);

            BigDecimal subtotal = lote.getPrecioUnitario().multiply(lote.getCantidad());
            entrada.setIva(entrada.getIva().add(lote.getIva()));
            entrada.setTotal(entrada.getTotal().add(subtotal.add(lote.getIva())));
        }
        if (total.equals(BigDecimal.ZERO)) {
            throw new NoSePuedeCerrarEnCeroException("No se puede cerrar la entrada en cero");
        }
        // Si tanto el iva o el total difieren mas de un 5% del valor que
        // viene en la factura lanzar excepcion
        if (iva.compareTo(entrada.getIva()) != 0 || total.compareTo(entrada.getTotal()) != 0) {
            BigDecimal variacion = new BigDecimal("0.05");
            BigDecimal topeIva = entrada.getIva().multiply(variacion);
            BigDecimal topeTotal = entrada.getTotal().multiply(variacion);
            if (iva.compareTo(entrada.getIva()) < 0 || total.compareTo(entrada.getTotal()) < 0) {
                if (iva.compareTo(entrada.getIva().subtract(topeIva)) >= 0
                        && total.compareTo(entrada.getTotal().subtract(topeTotal)) >= 0) {
                    // todavia puede pasar
                } else {
                    throw new NoCuadraException("No se puede cerrar porque no cuadran los totales");
                }
            } else {
                if (iva.compareTo(entrada.getIva().add(topeIva)) <= 0
                        && total.compareTo(entrada.getTotal().add(topeTotal)) <= 0) {
                    // todavia puede pasar
                } else {
                    throw new NoCuadraException("No se puede cerrar porque no cuadran los totales");
                }
            }
        }

        return entrada;
    }

    @SuppressWarnings("unchecked")
    @Override
    @Transactional(readOnly = true)
    public Map<String, Object> preCancelacion(Long id, Usuario usuario) throws NoEstaCerradaException {
        log.info("{} mando llamar precancelacion de entrada {}", usuario, id);
        Entrada entrada = (Entrada) currentSession().get(Entrada.class, id);
        if (entrada.getEstatus().getNombre().equals(Constantes.CERRADA)
                || entrada.getEstatus().getNombre().equals(Constantes.FACTURADA)) {
            Set<Producto> productos = new HashSet<>();
            for (LoteEntrada lote : entrada.getLotes()) {
                productos.add(lote.getProducto());
            }

            log.debug("Buscando entradas que contengan los productos {} despues de la fecha {}", productos,
                    entrada.getFechaModificacion());
            Query query = currentSession()
                    .createQuery("select e from Entrada e inner join e.lotes le inner join e.estatus es "
                            + "where(es.nombre = 'CERRADA' or es.nombre = 'PENDIENTE') "
                            + "and le.producto in (:productos) " + "and e.fechaModificacion > :fecha");
            query.setParameterList("productos", productos);
            query.setTimestamp("fecha", entrada.getFechaModificacion());
            List<Entrada> entradas = (List<Entrada>) query.list();
            for (Entrada e : entradas) {
                log.debug("ENTRADA: {}", e);
                for (LoteEntrada lote : e.getLotes()) {
                    productos.add(lote.getProducto());
                }
            }
            entradas.add(entrada);

            query = currentSession()
                    .createQuery("select s from Salida s inner join s.lotes ls inner join s.estatus es "
                            + "where es.nombre = 'CERRADA' " + "and ls.producto in (:productos) "
                            + "and s.fechaModificacion > :fecha");
            query.setParameterList("productos", productos);
            query.setTimestamp("fecha", entrada.getFechaModificacion());
            List<Salida> salidas = (List<Salida>) query.list();
            for (Salida salida : salidas) {
                log.debug("SALIDA: {}", salida);
                for (LoteSalida lote : salida.getLotes()) {
                    productos.add(lote.getProducto());
                }
            }

            Map<Long, Producto> productosCancelados = new HashMap<>();
            Map<Long, Producto> productosSinHistoria = new HashMap<>();
            for (Producto producto : productos) {
                log.debug("Buscando historial de {}", producto);
                query = currentSession()
                        .createQuery("select xp from XProducto xp " + "where xp.productoId = :productoId "
                                + "and (xp.actividad = 'CREAR' or actividad = 'ACTUALIZAR') "
                                + "and xp.fechaCreacion < :fecha "
                                + "and (xp.entradaId is null or xp.entradaId != :entradaId) "
                                + "order by xp.fechaCreacion desc");
                query.setLong("productoId", producto.getId());
                query.setTimestamp("fecha", entrada.getFechaModificacion());
                query.setLong("entradaId", entrada.getId());
                query.setMaxResults(1);
                List<XProducto> xproductos = (List<XProducto>) query.list();
                if (xproductos != null && xproductos.get(0) != null) {
                    XProducto xproducto = xproductos.get(0);
                    log.debug("Encontre historia del producto {}", xproducto);
                    Producto p = new Producto();
                    BeanUtils.copyProperties(xproducto, p);
                    p.setTipoProducto(producto.getTipoProducto());
                    p.setAlmacen(producto.getAlmacen());
                    productosCancelados.put(producto.getId(), p);
                } else {
                    log.debug("No encontre historia del producto {}", producto);
                    Producto p = new Producto();
                    BeanUtils.copyProperties(producto, p);
                    p.setPrecioUnitario(BigDecimal.ZERO);
                    p.setUltimoPrecio(BigDecimal.ZERO);
                    p.setExistencia(BigDecimal.ZERO);
                    productosSinHistoria.put(producto.getId(), p);
                }
            }

            Map<String, Object> resultado = new HashMap<>();
            resultado.put("entrada", entrada);
            resultado.put("productos", productos);
            if (entradas != null && entradas.size() > 0) {
                resultado.put("entradas", entradas);
            }
            if (salidas != null && salidas.size() > 0) {
                resultado.put("salidas", salidas);
            }
            if (productosCancelados.size() > 0) {
                resultado.put("productosCancelados", productosCancelados.values());
            }
            if (productosSinHistoria.size() > 0) {
                resultado.put("productosSinHistoria", productosSinHistoria.values());
            }
            return resultado;
        } else {
            throw new NoEstaCerradaException("La entrada no se puede cancelar porque no esta cerrada o facturada",
                    entrada);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    @Transactional(rollbackFor = { NoEstaCerradaException.class })
    public Cancelacion cancelar(Long id, Usuario usuario, String comentarios) throws NoEstaCerradaException {
        log.info("{} esta cancelando entrada {}", usuario, id);
        Entrada entrada = (Entrada) currentSession().get(Entrada.class, id);
        if (entrada.getEstatus().getNombre().equals(Constantes.CERRADA)
                || entrada.getEstatus().getNombre().equals(Constantes.FACTURADA)) {
            Set<Producto> productos = new HashSet<>();
            for (LoteEntrada lote : entrada.getLotes()) {
                productos.add(lote.getProducto());
            }

            log.debug("Buscando entradas que contengan los productos {} despues de la fecha {}", productos,
                    entrada.getFechaModificacion());
            Query query = currentSession()
                    .createQuery("select e from Entrada e inner join e.lotes le inner join e.estatus es "
                            + "where(es.nombre = 'CERRADA' or es.nombre = 'PENDIENTE') "
                            + "and le.producto in (:productos) " + "and e.fechaModificacion > :fecha");
            query.setParameterList("productos", productos);
            query.setTimestamp("fecha", entrada.getFechaModificacion());
            List<Entrada> entradas = (List<Entrada>) query.list();
            for (Entrada e : entradas) {
                log.debug("ENTRADA: {}", e);
                for (LoteEntrada lote : e.getLotes()) {
                    productos.add(lote.getProducto());
                }
            }
            entradas.add(entrada);

            query = currentSession()
                    .createQuery("select s from Salida s inner join s.lotes ls inner join s.estatus es "
                            + "where es.nombre = 'CERRADA' " + "and ls.producto in (:productos) "
                            + "and s.fechaModificacion > :fecha");
            query.setParameterList("productos", productos);
            query.setTimestamp("fecha", entrada.getFechaModificacion());
            List<Salida> salidas = (List<Salida>) query.list();
            for (Salida s : salidas) {
                log.debug("SALIDA: {}", s);
                for (LoteSalida lote : s.getLotes()) {
                    productos.add(lote.getProducto());
                }
            }

            Date fecha = new Date();
            for (Producto producto : productos) {
                log.debug("Buscando historial de {}", producto);
                query = currentSession()
                        .createQuery("select xp from XProducto xp " + "where xp.productoId = :productoId "
                                + "and (xp.actividad = 'CREAR' or actividad = 'ACTUALIZAR') "
                                + "and xp.fechaCreacion < :fecha "
                                + "and (xp.entradaId is null or xp.entradaId != :entradaId) "
                                + "order by xp.fechaCreacion desc");
                query.setLong("productoId", producto.getId());
                query.setTimestamp("fecha", entrada.getFechaModificacion());
                query.setLong("entradaId", entrada.getId());
                query.setMaxResults(1);
                List<XProducto> xproductos = (List<XProducto>) query.list();
                if (xproductos != null && xproductos.get(0) != null) {
                    XProducto xproducto = xproductos.get(0);
                    log.debug("Encontre historia del producto {}", xproducto);
                    producto.setPrecioUnitario(xproducto.getPrecioUnitario());
                    producto.setUltimoPrecio(xproducto.getUltimoPrecio());
                    producto.setExistencia(xproducto.getExistencia());
                    producto.setFechaModificacion(fecha);
                } else {
                    log.debug("No encontre historia del producto {}", producto);
                    producto.setPrecioUnitario(BigDecimal.ZERO);
                    producto.setUltimoPrecio(BigDecimal.ZERO);
                    producto.setExistencia(BigDecimal.ZERO);
                    producto.setFechaModificacion(fecha);
                }
                currentSession().update(producto);
            }

            query = currentSession().createQuery("select e from Estatus e where e.nombre = :nombre");
            query.setString("nombre", Constantes.CANCELADA);
            Estatus cancelada = (Estatus) query.uniqueResult();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            String fechaString = sdf.format(fecha);
            for (Entrada e : entradas) {
                e.setFactura(e.getFactura() + "C" + fechaString);
                e.setEstatus(cancelada);
                e.setFechaModificacion(fecha);
                currentSession().update(e);

                audita(e, usuario, Constantes.CANCELAR, fecha, true);
            }

            for (Salida s : salidas) {
                s.setReporte(s.getReporte() + "C" + fechaString);
                s.setEstatus(cancelada);
                s.setFechaModificacion(fecha);
                currentSession().update(s);

                auditaSalida(s, usuario, Constantes.CANCELAR, fecha, true);
            }

            // Crear cancelacion
            Cancelacion cancelacion = new Cancelacion();
            cancelacion.setFolio(cancelacionDao.getFolio(entrada.getAlmacen()));
            cancelacion.setComentarios(comentarios);
            cancelacion.setEntrada(entrada);
            cancelacion.setProductos(productos);
            if (entradas != null && entradas.size() > 0) {
                cancelacion.setEntradas(entradas);
            }
            if (salidas != null && salidas.size() > 0) {
                cancelacion.setSalidas(salidas);
            }
            cancelacion = cancelacionDao.crea(cancelacion, usuario);
            currentSession().flush();
            for (Producto producto : productos) {
                auditaProducto(producto, usuario, Constantes.CANCELAR, null, cancelacion.getId(), fecha);
            }

            return cancelacion;
        } else {
            throw new NoEstaCerradaException("La entrada no se puede cancelar porque no esta cerrada o facturada",
                    entrada);
        }
    }

    private void auditaProducto(Producto producto, Usuario usuario, String actividad, Long entradaId,
            Long cancelacionId, Date fecha) {
        XProducto xproducto = new XProducto();
        BeanUtils.copyProperties(producto, xproducto, new String[] { "id", "version" });
        xproducto.setId(null);
        xproducto.setProductoId(producto.getId());
        xproducto.setEntradaId(entradaId);
        xproducto.setCancelacionId(cancelacionId);
        xproducto.setTipoProductoId(producto.getTipoProducto().getId());
        xproducto.setAlmacenId(producto.getAlmacen().getId());
        xproducto.setFechaCreacion(fecha);
        xproducto.setActividad(actividad);
        xproducto.setCreador((usuario != null) ? usuario.getUsername() : "sistema");
        currentSession().save(xproducto);
    }

    private void audita(Entrada entrada, Usuario usuario, String actividad, Date fecha, Boolean conLotes) {
        XEntrada xentrada = new XEntrada();
        BeanUtils.copyProperties(entrada, xentrada, new String[] { "id", "version" });
        xentrada.setEntradaId(entrada.getId());
        xentrada.setAlmacenId(entrada.getAlmacen().getId());
        xentrada.setProveedorId(entrada.getProveedor().getId());
        xentrada.setEstatusId(entrada.getEstatus().getId());
        xentrada.setFechaCreacion(fecha);
        xentrada.setActividad(actividad);
        xentrada.setCreador((usuario != null) ? usuario.getUsername() : "sistema");
        currentSession().save(xentrada);
        if (conLotes) {
            for (LoteEntrada lote : entrada.getLotes()) {
                XLoteEntrada xlote = new XLoteEntrada();
                BeanUtils.copyProperties(lote, xlote, new String[] { "id", "version" });
                xlote.setLoteEntradaId(lote.getId());
                xlote.setEntradaId(entrada.getId());
                xlote.setProductoId(lote.getProducto().getId());
                xlote.setActividad(actividad);
                xlote.setCreador((usuario != null) ? usuario.getUsername() : "sistema");
                xlote.setFechaCreacion(fecha);
                currentSession().save(xlote);
            }
        }
    }

    private void auditaSalida(Salida salida, Usuario usuario, String actividad, Date fecha, Boolean conLotes) {
        XSalida xsalida = new XSalida();
        BeanUtils.copyProperties(salida, xsalida, new String[] { "id", "version" });
        xsalida.setSalidaId(salida.getId());
        xsalida.setAlmacenId(salida.getAlmacen().getId());
        xsalida.setClienteId(salida.getCliente().getId());
        xsalida.setEstatusId(salida.getEstatus().getId());
        xsalida.setFechaCreacion(fecha);
        xsalida.setActividad(actividad);
        xsalida.setCreador((usuario != null) ? usuario.getUsername() : "sistema");
        currentSession().save(xsalida);
        if (conLotes) {
            for (LoteSalida lote : salida.getLotes()) {
                XLoteSalida xlote = new XLoteSalida();
                BeanUtils.copyProperties(lote, xlote, new String[] { "id", "version" });
                xlote.setLoteSalidaId(lote.getId());
                xlote.setSalidaId(salida.getId());
                xlote.setProductoId(lote.getProducto().getId());
                xlote.setActividad(actividad);
                xlote.setCreador((usuario != null) ? usuario.getUsername() : "sistema");
                xlote.setFechaCreacion(fecha);
                currentSession().save(xlote);
            }
        }
    }
}