// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//	LibInventariosO.rpt
//  Sistema: AdminPAQ v1.1
//	Este archivo contiene la biblioteca de funcione del módulo de inventarios.
//
//	Consideraciones especiales:
//	Elaboró:    Eduardo Pérez Figueroa
//	Fecha: 	    21 de Junio del 2001
//
//  Historia de revisiones:
//	Autor:      Eduardo Pérez Figueroa
//	Fecha:      11 de Octubre del 2001
//              Modificaciones a cálculo de costos debido al nuevo acumulado del costo de las salidas y al costo 
//              contenido en el campo cCostoEspecifico.
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// Historia de modificaciones
//  Fecha      Nombre   IDLotus                     Descripcion
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Columnas 1; 1



// ========================================================================================================================================
// ========================================================================================================================================
// F U N C I O N E S: De uso general en los reportes de AdminPAQ [Deberian estar en LibAdminPAQ.rpt]
// ========================================================================================================================================
// ========================================================================================================================================
// ********************************************************************************************************************
//  fIdDocumentoMovtoOwner
//	Esta función regresa el ID del documento del movimiento owner del movimiento oculto. 
//  La razón de ser de ésta función es que los movimientos ocultos no heredan el campo cIdDocumento de su owner.
//
//  Parámetros: atMovtoOculto = Movimiento oculto (características, componentes o traspasos)
//              atMovtoOwner = Movimiento owner
//  Salidas:    lCosto = Costo del periodo especificado
// *****************************************************************************
DefFunc fIdDocumentoMovtoOwner(atMovtoOculto: TABLA; atMovtoOwner: TABLA)

  Si atMovtoOculto(cIdMovtoOwner)
    atMovtoOwner.Busca[@Str(atMovtoOculto(cIdMovtoOwner);0)] // Busca el movimiento owner
    Si atMovtoOwner->Encontro
      Retorna atMovtoOwner(cIdDocumento)
    FinSi
  FinSi
  Retorna 0
FinFunc



// ========================================================================================================================================
// ========================================================================================================================================
// F U N C I O N E S: C o s t o s :   M o v i m i e n t o  / H i s t o r i c o s
// ========================================================================================================================================
// ========================================================================================================================================
// ********************************************************************************************************************
//  fCostoEspecifico
//	Esta función obtiene el costo de un movimiento leyéndolo del campo "cCostoEspecifico".
// ********************************************************************************************************************
DefFunc fCostoEspecifico(atMovimiento:TABLA; atTipoCambio:TABLA; aIdMoneda)

  // Por definición, el costo específico es costo total.
  // Convierte el costo a la moneda indicada. Nota: El costo SIEMPRE está expresado en la Moneda Base
  Retorna fConvierteTipoCambio(atMovimiento(cCostoEspecifico); 1; aIdMoneda; atMovimiento(cFecha); atTipoCambio)
FinFunc


// ********************************************************************************************************************
//  fCostoMovimiento
//	Esta función obtiene o calcula el costo de un movimiento. Sirve para cualquier método de costeo del producto.
// ********************************************************************************************************************
DefFunc fCostoMovimiento(atProducto:TABLA; atMovimiento:TABLA; atCostoHistorico:TABLA; atTipoCambio:TABLA; aIdMoneda)

  Retorna fCostoEspecifico(atMovimiento; atTipoCambio; aIdMoneda)
FinFunc


// ********************************************************************************************************************
//  fCostoHistorico
//	Esta función obtiene el costo unitario promedio o último costo a una fecha determinada.
//
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// Historia de modificaciones
//  Fecha      Nombre   IDLotus                     Descripcion
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************************************************************
DefFunc fCostoHistoricoProducto(acIdProducto; acIdAlmacen; aFecha; aTipoCosto; atCostoHistorico:TABLA; atTipoCambio:TABLA; aIdMoneda)
Local lCostoHistorico

  lCostoHistorico = 0.0

  // NOTA: Es obligatorio volver a fijar el índice a fin de que las búsquedas parciales funcionen correctamente
  atCostoHistorico.Usaindice['iProdAlmacenFecha']    // cIdProducto+cIdAlmacen+cFechaCostoH+cTipoCosto

  // Busca el registro de costo histórico de acuerdo a la fecha del movimiento
  atCostoHistorico.Busca[acIdProducto & ':' & acIdAlmacen & ':' & aFecha]
  // Localización del movimiento más cercano al Producto / Almacen / Fecha
  Si !atCostoHistorico->Encontro
    // Busquedas parciales: Cursor en el registro más cercano que cumple con la llave
    // NOTA: La propiedad Llave de la TABLA nos regresa la llave más cercana que cumple con el criterio de la búsqueda
    //       La llave está en formato cadena y no tiene ningún separador entre los campos, por lo que es necesario revisar
    //       en FoxPro la definición del índice para descomponer los elementos de la Llave
    // En los Costos Historicos SIEMPRE se debe de devolver el registro más cercano a la fecha, pero sin sobrepasarla.
    // Al realizar la busqueda parcial se puede sobrepasar la llave. 
    atCostoHistorico.Busca[@Val(@SubStr(atCostoHistorico->Llave;0;11)) & ':' & @Val(@SubStr(atCostoHistorico->Llave;11;11)) & ':' & @SubStr(atCostoHistorico->Llave;22;8)]
    atCostoHistorico.saltareg[-1]
    Si atCostoHistorico(cIdProducto) != acIdProducto O atCostoHistorico(cIdAlmacen) != acIdAlmacen O atCostoHistorico(cFechaCostoH) > aFecha
      atCostoHistorico.Busca[acIdProducto & ':' & acIdAlmacen & ':' & aFecha]
    FinSi
  FinSi

  Si atCostoHistorico->Encontro
    // Regresa el costo según el método de costeo
    lCostoHistorico = (aTipoCosto = 3 O aTipoCosto = 4)? atCostoHistorico(cUltimoCostoH) : atCostoHistorico(cCostoH)
    // Convierte el costo histórico a la moneda del reporte. 
    // Nota: El costo SIEMPRE está expresado en la Moneda Base
    lCostoHistorico = fConvierteTipoCambio(lCostoHistorico; 1; aIdMoneda; atCostoHistorico(cFechaCostoH); atTipoCambio)
  FinSi
  Retorna lCostoHistorico
FinFunc



// ========================================================================================================================================
// ========================================================================================================================================
// F U N C I O N E S: U n i d a d e s
// ========================================================================================================================================
// ========================================================================================================================================
// ********************************************************************************************************************
//  fUnidadesEquivalentes
//	Esta función convierte unidades en una unidad base a unidades en una unidad equivalente.
// ********************************************************************************************************************
DefFunc fUnidadesEquivalentes(aUnidades; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia:TABLA)
Local aUnidadesEquivalentes

  aUnidadesEquivalentes = aUnidades
  Si aIdUnidadBase != aIdUnidadEquivalente
    atEquivalencia.Busca[aIdUnidadBase & ':' & aIdUnidadEquivalente]
    Si atEquivalencia->Encontro
      aUnidadesEquivalentes = aUnidades * atEquivalencia(cFactorConversion)      
    Finsi
  FinSi  
  Retorna aUnidadesEquivalentes
FinFunc


// ********************************************************************************************************************
//  fUnidades
//	Esta función obtiene las unidades del movimiento basado en el parámetro aUnidad.
// ********************************************************************************************************************
DefFunc fUnidades(aUnidad; aIdUnidadBase; aIdUnidad; atMovimiento:TABLA; atEquivalencia:TABLA)
Local lUnidades

  lUnidades = 0
  Escoge aUnidad
    Caso 0 // Unidad base del producto
      lUnidades = atMovimiento(cUnidades)
    FinCaso
    Caso 1 // Unidad no equivalente
      lUnidades = atMovimiento(cUnidadesNC)
    FinCaso
    Caso 2 // Unidad equivalente
      lUnidades = fUnidadesEquivalentes(atMovimiento(cUnidades); aIdUnidadBase; aIdUnidad; atEquivalencia)
    FinCaso
  FinEscoge
  Retorna lUnidades
FinFunc



// ========================================================================================================================================
// ========================================================================================================================================
// F U N C I O N E S: G e n e r a l e s
// ========================================================================================================================================
// ========================================================================================================================================
// ********************************************************************************************************************
//  mVerificarExistenciaCongelada
//  Verifica si un producto tiene tiene existencias congeladas
//
//  Salidas     1  = El producto tiene existencias congeladas
//              0  = El producto no tiene existencias congeladas
// ********************************************************************************************************************
DefFunc fVerificarExistenciaCongelada(acIdProducto; acIdAlmacen; acIdEjercicio; atExistencia:TABLA)
Local lIndiceAntExistencia, lExistenciaCongelada

  lExistenciaCongelada = 0

  // Respaldar el índice de la TABLA de existencias
  lIndiceAntExistencia = atExistencia->Indice

  Si acIdAlmacen = 0   
    // Se busca si el producto tiene existencias congeladas en cualquier almacén
    atExistencia.Usaindice['iProdEjerTipo']         // cIdProducto+cIdEjercicio+cTipoExistencia
    Mientras[atExistencia.Busca[acIdProducto & ':' & acIdEjercicio & ':1']; atExistencia->Encontro; atExistencia.Busca Siguiente[2]]
      Si atExistencia->Encontro Y atExistencia(cBanCongelado)  
        // verificar si esta congelda, si es así no tiene caso continuar
        lExistenciaCongelada = atExistencia(cBanCongelado)
        FinCiclo
      FinSi 
    FinMientras
  SiNo
    // Se busca si el producto tiene existencias congeladas en un almacén en particular
    atExistencia.Usaindice['iProdEjerAlmacenTipo']  // cIdProducto+cIdEjercicio+cIdAlmacen+cTipoExistencia
    atExistencia.Busca[acIdProducto & ':' & acIdEjercicio & ':' & acIdAlmacen & ':1' ]
    Si atExistencia->Encontro
      lExistenciaCongelada = atExistencia(cBanCongelado)
    FinSi 
  FinSi

  // Regresar al índice original de existencias
  atExistencia.Usaindice[lIndiceAntExistencia]
  Retorna lExistenciaCongelada
FinFunc


// ********************************************************************************************************************
//  fGastosProrrateados
//	Esta función calcula los gastos prorrateados que pertenecen a un movimiento de un documento.
// ********************************************************************************************************************
DefFunc fGastosProrrateados(atMovimiento: TABLA; atDocumento: TABLA)
Local lGastosProrrateados, lTotalGastos, lTotalMovimiento, lTotalDocumento

  // Calcula el total de gastos de compra
  lTotalGastos = atDocumento(cGasto1) + atDocumento(cGasto2) + atDocumento(cGasto3)
  // Calcula el total del movimiento:   No se consideran impuestos ni retenciones, Sí se consideran descuentos del movimiento
  lTotalMovimiento = atMovimiento(cNeto) - atMovimiento(cDescuento1) - atMovimiento(cDescuento2) - atMovimiento(cDescuento3) - atMovimiento(cDescuento4) - atMovimiento(cDescuento5)
  // Calcula el total del movimiento:   No se consideran impuestos ni retenciones, No se consideran descuentos globales del documento
  lTotalDocumento = atDocumento(cNeto) - atDocumento(cDescuentoMov)
  // La parte proporcional de los gastos se calcula con una regla de tres:
  // Si al total de documentos le corresponde el total de gastos entonces cuanto le corresponde al total de un movimiento
  lGastosProrrateados = lTotalMovimiento * lTotalGastos / lTotalDocumento
  Retorna lGastosProrrateados  
FinFunc


// ********************************************************************************************************************
//  fStockMinMax
//  Esta función lee y calcula el stock mínimo y máximo del producto para un almacén en particular o general.
// ********************************************************************************************************************
DefFunc fStockMinMax(acIdProducto; acIdAlmacen; aUnidad; aIdUnidadBase; aIdUnidadEquivalente; aStockMinimo; aStockMaximo; atExistenciaMinMax:TABLA; atEquivalencia:TABLA)

  // Asume cero en el stock mínimo y máximo
  aStockMinimo = 0.0
  aStockMaximo = 0.0

  Si acIdAlmacen = 0 
    // Todos los Almacenes
    atExistenciaMinMax.Usaindice['cIdProducto']
    Mientras[atExistenciaMinMax.Busca[@Str(acIdProducto;0)]; atExistenciaMinMax->Encontro; atExistenciaMinMax.Busca Siguiente[1]]
      Si aUnidad = 1
        // Si se solicitó en unidades no convertibles
        aStockMinimo = aStockMinimo + atExistenciaMinMax(cExistMinNoConvertible)
        aStockMaximo = aStockMaximo + atExistenciaMinMax(cExistMaxNoConvertible)
      Sino 
        // Unidades base o equivalentes
        aStockMinimo = aStockMinimo + atExistenciaMinMax(cExistenciaMinBase)
        aStockMaximo = aStockMaximo + atExistenciaMinMax(cExistenciaMaxBase)
      FinSi
    FinMientras
  Sino 
    // Almacén en particular
    atExistenciaMinMax.Usaindice['iAlmacenProducto']
    atExistenciaMinMax.Busca[acIdAlmacen & ':' & acIdProducto]
    Si atExistenciaMinMax->Encontro
      Si aUnidad = 1
        // Si se solicitó en unidades no convertibles
        aStockMinimo = atExistenciaMinMax(cExistMinNoConvertible)
        aStockMaximo = atExistenciaMinMax(cExistMaxNoConvertible)
      Sino 
        // Unidades base o equivalentes
        aStockMinimo = atExistenciaMinMax(cExistenciaMinBase)
        aStockMaximo = atExistenciaMinMax(cExistenciaMaxBase)
      FinSi
    FinSi
  FinSi

  // Convierte a unidades equivalentes de ser necesario
  Si aUnidad = 2
    aStockMinimo = fUnidadesEquivalentes(aStockMinimo; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia)
    aStockMaximo = fUnidadesEquivalentes(aStockMaximo; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia)
  FinSi
FinFunc


// ********************************************************************************************************************
//  fVerificarMovtosSinAfectar
//  Esta función busca si un producto tiene movimientos sin afectar antes de la fecha de corte. 
//  Si es así regresa la fecha del primer movimiento sin afectar
// ********************************************************************************************************************
DefFunc fVerificarMovtosSinAfectar(acIdProducto; atMovimiento: TABLA; atMovtoOwner: TABLA; atDocumento: TABLA)
Local lCicloDoctoDe,    lListaIdDocumentoDe,   lIdDocumentoDe
Local lCicloAfectacion, llListaTipoAfectacion, lTipoAfectacion
Local lIndiceAntMovimiento, lIndiceAntMovtoOwner, lIndiceAntDocumento
Local lFechaSinAfectar

  lFechaSinAfectar = ''

  // Respaldar los índices de las tablas 
  lIndiceAntMovimiento = atMovimiento->Indice
  lIndiceAntMovtoOwner = atMovtoOwner->Indice
  lIndiceAntDocumento = atDocumento->Indice

  // Indice para recorrido de movimientos
  atMovimiento.Usaindice['iProductoDoctoDeAfecFecha']  // cIdProducto+cIdDocumentoDe+cAfectadoInventario+cFecha
  atMovtoOwner.Usaindice['']                           // Indice primario: cIdMovimiento
  atDocumento.Usaindice['']                            // Indice primario: cIdDocumento 

  // Lista de documentos a considerar: Remisiones, Facturas, Devoluciones sobre venta, Devoluciones de remisión, 
  //  Productos en consignación, Compras, Devoluciones al proveedor, Devoluciones de consignación, Entradas, Salidas, Traspasos
  lListaIdDocumentoDe = '3,4,5,6,18,19,20,21,32,33,34'
  // Lista de Tipos de Afectación: 0=Movimientos sin afectar en existencias ni costos, 2=Movimientos sin afectar en costos
  lListaTipoAfectacion = '0,2' 
  Mientras[lCicloDoctoDe = 0; lCicloDoctoDe < 11; lCicloDoctoDe = lCicloDoctoDe + 1]
    lIdDocumentoDe = @Escoge(lListaIdDocumentoDe;lCicloDoctoDe)
    Mientras[lCicloAfectacion = 0; lCicloAfectacion < 2; lCicloAfectacion = lCicloAfectacion + 1]
      lTipoAfectacion =  @Escoge(lListaTipoAfectacion;lCicloAfectacion)
      Mientras[atMovimiento.Busca[acIdProducto & ':' & lIdDocumentoDe & ':' & lTipoAfectacion]; atMovimiento->Encontro Y lFechasinAfectar = ''; atMovimiento.Busca Siguiente[3]]
        // Si llega al final del rango solicitado se sale
        Si atMovimiento(cIdProducto) != acIdProducto O atMovimiento(cIdDocumentoDe) != lIdDocumentoDe O atMovimiento(cAfectadoInventario) != lTipoAfectacion
          FinCiclo
        FinSi
        // Validación: Discrimina los movimientos que no son entradas ni salidas
        Si atMovimiento(cAfectaExistencia) != 1 y atMovimiento(cAfectaExistencia) != 2
          Continua
        FinSi
        // Validación: Discrimina los movimientos de documentos cancelados
        Si atMovimiento(cIdDocumento)
          atDocumento.Busca[@Str(atMovimiento(cIdDocumento);0)]
        Sino 
          lIdDocumento = fIdDocumentoMovtoOwner(atMovimiento; atMovtoOwner) 
          atDocumento.Busca[@Str(lIdDocumento;0)]
        Finsi
        Si atDocumento->Encontro Y atDocumento(cCancelado) = 1   
          Continua   
        Finsi
        // Si el movimiento no esta cancelado entonces no está afectado
        lFechaSinAfectar = atMovimiento(cFecha)
        FinCiclo
      FinMientras
    FinMientras
  FinMientras

  // Restaura los índices originales
  atMovimiento.Usaindice[lIndiceAntMovimiento]
  atMovtoOwner.Usaindice[lIndiceAntMovtoOwner]
  atDocumento.Usaindice[lIndiceAntDocumento]

  Retorna lFechaSinAfectar
FinFunc



// ========================================================================================================================================
// ========================================================================================================================================
// F U N C I O N E S: C o s t o s
// ========================================================================================================================================
// ========================================================================================================================================
// Inicia la posición del campo "cEntradasPeriodo1" y "cSalidasPeriodo1" (empezando en cero)
constante kNUMCAMPO_ENTRADAS_UNIDADES    9
Constante kNUMCAMPO_SALIDAS_UNIDADES    21
// Ejemplo: Si kNUMCAMPO_ENTRADAS_UNIDADES =  9 y aPeriodo = 1
//          Entonces el campo a leer es 9 + 1 - 1 = 9, el cual corresponde a cEntradasPeriodo1
// Ejemplo: Si kNUMCAMPO_ENTRADAS_UNIDADES = 21 y aPeriodo = 4
//          Entonces el campo a leer es 21 + 4 - 1 = 24, el cual corresponde a cEntradasPeriodo4

// Inicia la posición de los campos "cCostoEntradasPeriodo1" y "cCostoSalidasPeriodo1" (empezando en cero)
Constante kNUMCAMPO_ENTRADAS_COSTOS     33
Constante kNUMCAMPO_SALIDAS_COSTOS      45
// Ejemplo: Si kNUMCAMPO_ENTRADAS_COSTOS = 33 y aPeriodo = 1
//          Entonces el campo a leer es 33 + 1 - 1 = 33, el cual corresponde a cCostoEntradasPeriodo1
// Ejemplo: Si kNUMCAMPO_ENTRADAS_COSTOS = 33 y aPeriodo = 4
//          Entonces el campo a leer es 33 + 4 - 1 = 36, el cual corresponde a cCostoEntradasPeriodo4

// ********************************************************************************************************************
//  fCostoPeriodo
//	Esta función lee el costo inicial de un determinado periodo en la TABLA de existencias.
// ********************************************************************************************************************
DefFunc fCostoPeriodo(aPeriodo; aIdMoneda; aFecha; atExistencia:TABLA; atTipoCambio:TABLA)
Local lCosto

  lCosto = 0.00
  Si aPeriodo = 0
    lCosto = atExistencia(cCostoInicialEntradas) - atExistencia(cCostoInicialSalidas)
  Sino 
    lCosto = atExistencia->CampoVal[kNUMCAMPO_ENTRADAS_COSTOS + aPeriodo - 1] - atExistencia->CampoVal[kNUMCAMPO_SALIDAS_COSTOS + aPeriodo - 1]
  Finsi

  // Convierte el costo a la moneda del reporte. 
  // Nota: El costo SIEMPRE está expresado en la Moneda Base
  lCosto = fConvierteTipoCambio(lCosto; 1; aIdMoneda; aFecha; atTipoCambio)
  Retorna lCosto
FinFunc


// ********************************************************************************************************************
// Nombre:    fExistenciaPeriodo
// Objetivo:  Esta función lee la existencia total de un determinado periodo en la TABLA de existencias.
// ********************************************************************************************************************
DefFunc fExistenciaPeriodo(aPeriodo; aUnidad; aIdUnidadBase; aIdUnidad; atExistencia:TABLA; atEquivalencia:TABLA)
Local lExistencia

  // Existencia del periodo
  Si aPeriodo = 0
    lExistencia = atExistencia(cEntradasIniciales) - atExistencia(cSalidasIniciales)
  Sino
    lExistencia = atExistencia->CampoVal[kNUMCAMPO_ENTRADAS_UNIDADES + aPeriodo - 1] - atExistencia->CampoVal[kNUMCAMPO_SALIDAS_UNIDADES + aPeriodo - 1]
  FinSi

  // Convierte las unidades
  Si aUnidad = 2 
    lExistencia = fUnidadesEquivalentes(lExistencia; aIdUnidadBase; aIdUnidad; atEquivalencia)
  FinSi
  Retorna lExistencia
FinFunc


// ********************************************************************************************************************
// ********************************************************************************************************************
DefFunc fCostoInicialPeriodo(aFecha; aPeriodo; atExistencia:TABLA; atTipoCambio:TABLA; aIdMoneda)
  // El Costo Inicial del Periodo es el costo del periodo anterior
  Retorna fCostoPeriodo(aPeriodo-1; aIdMoneda; aFecha; atExistencia; atTipoCambio)
FinFunc
// ********************************************************************************************************************
// ********************************************************************************************************************
DefFunc fExistenciaInicialPeriodo(aPeriodo; aUnidad; aIdUnidadBase; aIdUnidad; atExistencia:TABLA; atEquivalencia:TABLA)
  // La Existencia Inicial del Periodo es la existencia del periodo anterior
  Retorna fExistenciaPeriodo(aPeriodo-1; aUnidad; aIdUnidadBase; aIdUnidad; atExistencia; atEquivalencia)
FinFunc



// ********************************************************************************************************************
// Nombre:    fExistenciaCostoProducto
// Objetivo:  Esta función calcula la existencia y el costo de un producto a una fecha determinada
//            NOTA: Expresa la existencia en unidades base, equivalentes o no convertibles
//
//              El calculo se realiza a través de la TABLA de Aculumados de existencia y costos (MGW10030) y la TABLA 
//              de movimientos. La TABLA de Acumulados de existencia y costo se detalla por ejercicio, producto y almacén
//
//              Proceso:
//              De la TABLA de Acumulados de existencias y costos se toma el resultado de la resta del acumulado de 
//              entradas menos el acumulado de salidas de costos, pertenecientes al periodo anterior al de la fecha 
//              recibida. A partir de aqui se barren los movimientos del producto a partir del dia 1 y hasta el dia 
//              anterior a la fecha recibida. Los movimientos de entrada se suman y los movimientos de salida se restan. 
//              De esta forma se obtiene el costo total  a dicha fecha. Lo mismo sucede para las unidades. Al final el 
//              costo promedio es la división del costo total entre la existencia total.
//              Ejemplo:
//              Se desea obtener el costo del Producto N a la fecha del 15 de septiembre de 2004 para todos los almacenes
//              En este caso se busca en MGW10030 el registro del producto N, para el ejercicio 2004 y para el almacén 
//              cero (Todos los almacenes). En este registro se toma el acumulado de entradas del mes de Agosto y se le
//              resta el acumulado de salidas del mismo mes. Hasta aqui tenemos el costo total al 31 de agosto. Ahora se 
//              recorren los movimientos del producto N en todos los almacenes y que tengan fecha del 1 de septiembre y 
//              hasta el 14 de septiembre, sumando las entradas y restando las salidas.
//              Ahora si tenemos el costo del producto N para todos los almacenes al 15 de septiembre.
//
// Entradas:  acIdProducto            Id del Producto
//            acIdAlmacen             Id del Almacén
//            aFecha                  Fecha
//            aTipoUnidad             Tipo de Unidad: 
//                                      0 -> Unidad base
//                                      1 -> Unidad no equivalente
//                                      2 -> Unidad equivalente
//            aIdUnidadBase           Id de la Unidad Base del Producto
//            aIdUnidadEquivalente    Id de la Unidad Equivalente
//            aTipoCosteo             Tipo de Evaluación de Costos
//                                      0 = Definido por el producto
//                                      1 = Costo Promedio (Global)
//                                      2 = Costo Promedio (Por Almacén)
//                                      3 = Ultimo Costo (Global)
//                                      4 = Ultimo Costo (Por Almacén)      
//            aIdMoneda               Id de la Moneda para expresar el Costo
//            atMovimiento            TABLA de Movimientos
//            atExistencia            TABLA de Existencias / Costos
//            atEquivalencia          TABLA de Equivalencias entre Unidades
//            atEjercicio             TABLA de Ejercicios y Periodos
//            atCostoHistorico        TABLA de Costos Historicos
//            atTipoCambio            TABLA de Tipos de Cambio
// Salidas 
//            aExistenciaProducto     Existencia del Producto
//            aCostoTotalProducto     Costo Total del Producto
//            aCostoUnitarioProducto  Costo Unitario del Producto
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// Historia de modificaciones
//  Fecha      Nombre   IDLotus                     Descripcion
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// ********************************************************************************************************************
DefFunc fExistenciaCostoProducto(aExistenciaProducto; aCostoProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; aTipoUnidad; aIdUnidadBase; aIdUnidadEquivalente; aTipoCosteo; aIdMoneda; atMovimiento:TABLA; atExistencia:TABLA; atEquivalencia:TABLA; atCostoHistorico:TABLA; atTipoCambio:TABLA)
Local lRespaldoTipoUnidad
Local lEsPeriodoCompleto
Local lLlave, lNumCamposLlave
Local lUnidadesMovto
Local lFechaInicial

  // NOTA TECNICA: En caso de existencia en Unidad Equivalente (aTipoUnidad = 2), primero se obtendra la Existencia en la Unidad Base y al
  //                final se convertirá a la Unidad Equivalente (aIdUnidadBase  -> aIdUnidadEquivalente)
  //                Por lo cual a partir de estas instrucciones aTipoUnidad será 0 (Unidades Base) o 1 (Unidades No Equivalentes)
  lRespaldoTipoUnidad = aTipoUnidad
  Si aTipoUnidad = 2
    // Asume Unidad Base
    aTipoUnidad = 0 
  FinSi

  // Inicialización de la Existencia / Costo
  aExistenciaProducto = 0.00
  aCostoProducto = 0.00
 
  //*******************************************************************************************************************
  // A C U M U L A D O     E X I S T E N C I A  /  C O S T O
  //*******************************************************************************************************************
  // En caso de que la fecha sea el último dia del mes entonces se debe de leer el acumulado del mes actual y no barrer los movimientos
  Si @Substr(fFechaSiguiente(aFecha); 4; 2) !=  @Substr(aFecha; 4; 2)
    lEsPeriodoCompleto = 1
  Sino
    lEsPeriodoCompleto = 0
    aPeriodo = aPeriodo - 1
  FinSi

  // En primer lugar realiza la busqueda del Acumulado de Existencia y Costo del periodo
  // Por omisión: Existencia general
  lLlave = acIdProducto & ':' & aIdEjercicio
  lNumCamposLlave = 2
  Si acIdAlmacen != 0
    // Existencia de un almacén en particular. Por omisón: Existencia en Unidades Base
    lLlave = acIdProducto & ':' & aIdEjercicio & ':' & acIdAlmacen & ':1'
    Si aTipoUnidad = 1
      // Existencia en Unidades No Equivalentes
      lLlave = acIdProducto & ':' & aIdEjercicio & ':' & acIdAlmacen & ':2'
    FinSi
    lNumCamposLlave = 4
  FinSi

  // Recorrer cada uno de los registros de Existencia / Costo para el Producto / Ejercicio / Almacén / TipoExistencia
  Mientras[atExistencia.Busca[lLlave]; atExistencia->Encontro; atExistencia.Busca Siguiente[lNumCamposLlave]]
    // Discrimina el registro en función de la unidad del reporte
    // aTipoUnidad                    = 0 -> Unidad base, 1 -> Unidad no equivalente
    // atExistencia(cTipoExistencia)  = 1 -> Unidad base, 2 -> Unidad no equivalente
    Si (aTipoUnidad = 0 y atExistencia(cTipoExistencia) = 2) O (aTipoUnidad = 1 y atExistencia(cTipoExistencia) = 1) 
      Continua
    Finsi

    // Existencia del periodo
    Si aPeriodo = 0
      aExistenciaProducto = aExistenciaProducto + atExistencia(cEntradasIniciales)    - atExistencia(cSalidasIniciales)
      aCostoProducto      = aCostoProducto      + atExistencia(cCostoInicialEntradas) - atExistencia(cCostoInicialSalidas)
    Sino
      aExistenciaProducto = aExistenciaProducto + atExistencia->CampoVal[kNUMCAMPO_ENTRADAS_UNIDADES + aPeriodo - 1] - atExistencia->CampoVal[kNUMCAMPO_SALIDAS_UNIDADES + aPeriodo - 1]
      aCostoProducto      = aCostoProducto      + atExistencia->CampoVal[kNUMCAMPO_ENTRADAS_COSTOS + aPeriodo - 1] - atExistencia->CampoVal[kNUMCAMPO_SALIDAS_COSTOS + aPeriodo - 1]
    FinSi
  FinMientras
  //*******************************************************************************************************************
  // M O V I M I E N T O S
  //*******************************************************************************************************************
  // En caso de no ser el periodo completo entonces barrer los movimientos desde el dia 1 hasta la fecha indicada
  Si lEsPeriodoCompleto = 0
    lFechaInicial = @Substr(aFecha;0;6) & '01'

    // NOTA: Es obligatorio volver a fijar el índice a fin de que las búsquedas parciales funcionen correctamente
    Si acIdAlmacen = 0
      // Todos los almacenes
      atMovimiento.Usaindice['iProductoFechaAfecta']      // cIdProducto+cFecha+cAfectaExistencia
      atMovimiento.Busca[acIdProducto & ':' & lFechaInicial]
      lNumCamposLlave = 1 // La Fecha no se incluye ya que es variable
    Sino
      // Almacen
      atMovimiento.Usaindice['iProductoAlmacenFechaAfec'] // cIdProducto+cIdAlmacen+cFecha+cAfectaExistencia
      atMovimiento.Busca[acIdProducto & ':' & acIdAlmacen & ':' & lFechaInicial]
      lNumCamposLlave = 2 // La Fecha no se incluye ya que es variable
    FinSi
    Si !atMovimiento->Encontro
      // Busquedas parciales: Cursor en el registro más cercano que cumple con la llave
      // NOTA: La propiedad Llave de la TABLA nos regresa la llave más cercana que cumple con el criterio de la búsqueda
      //       La llave está en formato cadena y no tiene ningún separador entre los campos, por lo que es necesario revisar
      //       en FoxPro la definición del índice para descomponer los elementos de la Llave
      // Revisar si la llave actual corresponde al Producto / Almacén y al Rango de Fechas.
      Si acIdProducto = @Val(@SUBSTR(atMovimiento->Llave;0;11))
        Si acIdAlmacen = 0
          Si @SUBSTR(atMovimiento->Llave;11;8) >= lFechaInicial Y @SUBSTR(atMovimiento->Llave;11;8) <= aFecha
            // Realizar nuevamente la búsqueda a fin de posicionarnos en el registro y activar la propiedad Encontro
            atMovimiento.Busca[acIdProducto & ':' & @SUBSTR(atMovimiento->Llave;11;8)]
          FinSi
        Sino
          Si acIdAlmacen = @Val(@SUBSTR(atMovimiento->Llave;11;11))
            Si @SUBSTR(atMovimiento->Llave;22;8) >= lFechaInicial Y @SUBSTR(atMovimiento->Llave;22;8) <= aFecha
              // Realizar nuevamente la búsqueda a fin de posicionarnos en el registro y activar la propiedad Encontro
              atMovimiento.Busca[acIdProducto & ':' & acIdAlmacen & ':' & @SUBSTR(atMovimiento->Llave;22;8)]
            FinSi
          FinSi
        FinSi        
      FinSi        
    FinSi

    // Recorrer los movimientos hasta que sea diferente producto, almacén o se rompa el rango de fechas
    Mientras atMovimiento->Encontro Y (atMovimiento(cFecha) <= aFecha)
      // Discrimina movimientos: Sin afectar, No son entradas ni salidas
      Si (atMovimiento(cAfectadoInventario) = 1) Y (atMovimiento(cAfectaExistencia) = 1 O atMovimiento(cAfectaExistencia) = 2)
        Si aTipoUnidad = 0
          // Unidad base del producto
          lUnidadesMovto = atMovimiento(cUnidades)
        Sino
          // Unidad no equivalente
          lUnidadesMovto = atMovimiento(cUnidadesNC)
        FinSi     
        Si atMovimiento(cAfectaExistencia) = 1 
          aExistenciaProducto = aExistenciaProducto + lUnidadesMovto
          aCostoProducto      = aCostoProducto + atMovimiento(cCostoEspecifico)
        Sino
          aExistenciaProducto = aExistenciaProducto - lUnidadesMovto
          aCostoProducto      = aCostoProducto - atMovimiento(cCostoEspecifico)
        FinSi
      FinSi

      // Siguiente registro con el producto o Producto/almacen
      atMovimiento.Busca Siguiente[lNumCamposLlave]
    FinMientras
  FinSi

  Escoge aTipoCosteo
    Caso 0 // Especifico / Definido por el Producto
           // No se necesita ajuste alguno
    FinCaso
    Caso 1 // Costo Promedio (Global)
           aCostoProducto = aExistenciaProducto * fCostoHistoricoProducto(acIdProducto; 0; aFecha; 1; atCostoHistorico; atTipoCambio; 1)
    FinCaso
    Caso 2 // Costo Promedio (Por Almacén)
           aCostoProducto = aExistenciaProducto * fCostoHistoricoProducto(acIdProducto; acIdAlmacen; aFecha; 2; atCostoHistorico; atTipoCambio; 1)
    FinCaso
    Caso 3 // Ultimo Costo (Global)
           aCostoProducto = aExistenciaProducto * fCostoHistoricoProducto(acIdProducto; 0; aFecha; 3; atCostoHistorico; atTipoCambio; 1)
    FinCaso
    Caso 4 // Ultimo Costo (Por Almacén)      
           aCostoProducto = aExistenciaProducto * fCostoHistoricoProducto(acIdProducto; acIdAlmacen; aFecha; 4; atCostoHistorico; atTipoCambio; 1)
    FinCaso
  FinEscoge

  // NOTA TECNICA: En caso de existencia en Unidad Equivalente (aTipoUnidad = 2), primero se obtendra la Existencia en la Unidad Base y al
  //                final se convertirá a la Unidad Equivalente (aIdUnidadBase  -> aIdUnidadEquivalente)
  //                Por lo cual a partir de estas instrucciones aTipoUnidad será 0 (Unidades Base) o 1 (Unidades No Equivalentes)
  aTipoUnidad = lRespaldoTipoUnidad
  Si aTipoUnidad = 2
    // Respaldar la Existencia del Producto; la cual está en Unidad Base
    lRespaldoTipoUnidad = aExistenciaProducto
    // Expresar la Existencia en la Unidad Equivalente
    aExistenciaProducto = fUnidadesEquivalentes(aExistenciaProducto; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia)
    // Expresar el Costo en la Unidad Equivalente
    // Para lo cual se divide el Costo Base entre el Factor de Conversión (Unidades base / Unidades equivalentes)
    aCostoProducto = aCostoProducto * (lRespaldoTipoUnidad / aExistenciaProducto)
  FinSi

  // El costo está en Moneda Base así que ahora lo expresamos en la Moneda indicada
  Si aIdMoneda != 1
    aCostoProducto = fConvierteTipoCambio(aCostoProducto; 1; aIdMoneda; aFecha; atTipoCambio)
  FinSi
FinFunc
// ********************************************************************************************************************
DefFunc fExistenciaProducto(aExistenciaProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; aTipoUnidad; aIdUnidadBase; aIdUnidadEquivalente; atMovimiento:TABLA; atExistencia:TABLA; atEquivalencia:TABLA; atCostoHistorico:TABLA; atTipoCambio:TABLA)
Local lCostoProducto

  Retorna fExistenciaCostoProducto(&aExistenciaProducto; &lCostoProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; aTipoUnidad; aIdUnidadBase; aIdUnidadEquivalente; 0; 1; atMovimiento; atExistencia; atEquivalencia; atCostoHistorico; atTipoCambio)
FinFunc
// ********************************************************************************************************************
DefFunc fCostoProducto(aCostoProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; aTipoCosteo; aIdMoneda; atMovimiento:TABLA; atExistencia:TABLA; atEquivalencia:TABLA; atCostoHistorico:TABLA; atTipoCambio:TABLA)
Local lExistenciaProducto

  Retorna fExistenciaCostoProducto(&lExistenciaProducto; &aCostoProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; 0; 0; 0; aTipoCosteo; aIdMoneda; atMovimiento; atExistencia; atEquivalencia; atCostoHistorico; atTipoCambio)
FinFunc
// ********************************************************************************************************************
DefFunc fExistenciaProducto2(aExistenciaProducto; acIdProducto; acIdAlmacen; aFecha; aTipoUnidad; aIdUnidadBase; aIdUnidadEquivalente; atMovimiento:TABLA; atExistencia:TABLA; atEquivalencia:TABLA; atCostoHistorico:TABLA; atTipoCambio:TABLA; atEjercicio:TABLA)
Local lCostoProducto, lPeriodo, lIdEjercicio

  // Obtención del periodo y ejercicio de la fecha
  fEjercicioPeriodo(aFecha; &lPeriodo; &lIdEjercicio; atEjercicio)

  Retorna fExistenciaCostoProducto(&aExistenciaProducto; &lCostoProducto; acIdProducto; acIdAlmacen; aFecha; lPeriodo; lIdEjercicio; aTipoUnidad; aIdUnidadBase; aIdUnidadEquivalente; 0; 1; atMovimiento; atExistencia; atEquivalencia; atCostoHistorico; atTipoCambio)
FinFunc
// ********************************************************************************************************************
DefFunc fCostoProducto2(aCostoProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; aTipoCosteo; aIdMoneda; atMovimiento:TABLA; atExistencia:TABLA; atEquivalencia:TABLA; atCostoHistorico:TABLA; atTipoCambio:TABLA; atEjercicio:TABLA)
Local lExistenciaProducto, lPeriodo, lIdEjercicio

  // Obtención del periodo y ejercicio de la fecha
  fEjercicioPeriodo(aFecha; &lPeriodo; &lIdEjercicio; atEjercicio)

  Retorna fExistenciaCostoProducto(&lExistenciaProducto; &aCostoProducto; acIdProducto; acIdAlmacen; aFecha; lPeriodo; lIdEjercicio; aTipoUnidad; aIdUnidadBase; aIdUnidadEquivalente; 0; 1; atMovimiento; atExistencia; atEquivalencia; atCostoHistorico; atTipoCambio)
FinFunc


// ********************************************************************************************************************
// ********************************************************************************************************************
DefFunc fCostoPromedioProducto(aExistenciaProducto; aCostoProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; aIdMoneda; atMovimiento:TABLA; atExistencia:TABLA; atEquivalencia:TABLA; atCostoHistorico:TABLA; atTipoCambio:TABLA)

  fExistenciaCostoProducto(&aExistenciaProducto; &aCostoProducto; acIdProducto; acIdAlmacen; aFecha; aPeriodo; aIdEjercicio; 0; 0; 0; 0; aIdMoneda; atMovimiento; atExistencia; atEquivalencia; atCostoHistorico; atTipoCambio) 
  // En caso de existencia negativa regresar como costo promedio el Ultimo costo
  Si aExistenciaProducto > 0
    Retorna (aCostoProducto / aExistenciaProducto)
  Sino
    // Expresar el Ultimo Costo en la moneda requerida
    Retorna fCostoHistoricoProducto(acIdProducto; acIdAlmacen; aFecha; 3; atCostoHistorico; atTipoCambio; aIdMoneda)
  FinSi
FinFunc


// ========================================================================================================================================
// ========================================================================================================================================
// F U N C I O N E S: No utilizadas por los reportes predeterminados de AdminPAQ
// ========================================================================================================================================
// ========================================================================================================================================
// ********************************************************************************************************************
//  fCostoEntradaCapa
//	Esta función obtiene o calcula el costo de un movimiento de entrada cuando el método de costeo sea UEPS o PEPS.
// ********************************************************************************************************************
DefFunc fCostoEntradaCapa(atMovimiento: TABLA; atMovimientoCapa: TABLA; atCapa: TABLA; atTipoCambio: TABLA; aIdMoneda; aCostoUnitario; aIdCapa)
Local lCosto

  lCosto = 0.0
  aCostoUnitario = 0.0
  aIdCapa = 0
  // Busca el movimiento de capa que corresponde a la entrada  
  // Puede encontrarse más de uno, pero hay que discriminar los lotes y pedimentos
  Mientras[atMovimientoCapa.Busca[@Str(atMovimiento(cIdMovimiento);0)]; atMovimientoCapa->Encontro; atMovimientoCapa.Busca Siguiente[1]]
    atCapa.Busca[@Str(atMovimientoCapa(cIdCapa);0)]
    Si atCapa->Encontro
      // Discrimina lotes y pedimentos
      Si atCapa(cTipoCapa) != 5 // la capa no es de costeo UEPS o PEPS
        Continua
      FinSi
      // calcula el costo total en base al costo unitario
      lCosto = atCapa(cCosto) * atMovimiento(cUnidades)
      // Convierte el costo a la moneda del reporte. Nota: El costo SIEMPRE está expresado en la moneda base (ID = 1)
      lCosto = fConvierteTipoCambio(lCosto; 1; aIdMoneda; atMovimiento(cFecha); atTipoCambio)

      // Regresa el costo unitario
      // Convierte el costo unitario a la moneda del reporte. Nota: El costo SIEMPRE está expresado en la moneda base (ID = 1)
      aCostoUnitario = fConvierteTipoCambio(atCapa(cCosto); 1; aIdMoneda; atMovimiento(cFecha); atTipoCambio)
      aIdCapa = atMovimientoCapa(cIdCapa)
      FinCiclo
    FinSi   
  FinMientras 
  Retorna lCosto 
FinFunc
