<?php
class Service_InventoryProcess
{
  const INVENTORY_NORMAL = 1; // 良品（标准）货架
  const INVENTORY_REJECT = 2; // 不良品（次品）货架
  const INVENTORY_ADJUSTMENT = 0; // 库存调整
  const INVENTORY_SHIFTING = 1; // 移货架

  /**
   * 调整库存
   */
  public function adjustInventory($ib_id, $new_quantity, $reason) {
    $new_quantity = intval($new_quantity);
    $db = Common_Common::getAdapter();

    // 开启事务
    $db->beginTransaction();
    try{
      $ibRow = Service_InventoryBatch::getForUpdate($ib_id); // 锁行

      // 找不到批次库存
      if (empty($ibRow)) {
        throw new Exception('批次库存不存在');
      }

      // 批次库存已加锁
      if (intval($ibRow['ib_hold_status']) !== 0) {
        throw new Exception('锁定的批次库存不能调整');
      }

      // 新库存跟当前库存一致，无需调整
      if ((int) $ibRow['ib_quantity'] === $new_quantity) {
        throw new Exception('新库存与当前库存数量一致，无需调整');
      }

      // 统计相应批次核销表使用数量
      $outboundNum = Service_InventoryBatchOutbound::sumQuantity($ib_id);

      // 调整数量小于核销表数量是不行的
      if ($new_quantity < $outboundNum) {
        throw new Exception('新库存最小只能调整到'.$outboundNum);
      }

      if ($new_quantity == 0) { // 调整为0，相当于删除该批次库存
        Service_InventoryBatch::delete($ib_id);
      }
      else { // 更新批次库存
        $ibUpdate['ib_quantity'] = $new_quantity;
        $ibUpdate['ib_update_time'] = date('Y-m-d H:i:s');
        Service_InventoryBatch::update($ibUpdate, $ib_id);
      }

      // 批次库存日志
      $note = '库存调整：'.$reason;
      Service_InventoryBatchLog::log($ibRow, $ibRow['ib_quantity'], $new_quantity, $note);

      // 获取货架相关信息
      $locationCode = $ibRow['lc_code'];
      $location = Service_Location::getByField($locationCode, 'lc_code');

      if (empty($location)) {
        throw new Exception('找不到与货架【' . $locationCode . '】相关的信息');
      }

      // 获取货架相关仓库区域信息
      $waCode = $location['wa_code'];
      $warehouseArea = Service_WarehouseArea::getByField($waCode, 'wa_code');

      if (empty($warehouseArea)) {
        throw new Exception('找不到与仓库区域【' . $locationCode . '】相关的信息');
      }

      // 货架类型
      $locationType = (int) $warehouseArea['wa_type'];

      // 产品库存
      $aProductInventory = Service_ProductInventory::getByWhProduct($ibRow['warehouse_id'], $ibRow['product_id']);
      $piRow = Service_ProductInventory::getForUpdate($aProductInventory['pi_id']); // 锁行
      $changeQuantity = 0;
      $changeNote = '批次库存【' . $ibRow['ib_id'] . '】调整，';
      $piSellable = $piRow['pi_sellable'];
      $piUnsellable = $piRow['pi_unsellable'];

      // 根据相应货架类型，调整相应库存数量
      switch ($locationType) {
        // 良品（标准）货架
        case self::INVENTORY_NORMAL:
          $changeQuantity = $piRow['pi_sellable'] + $new_quantity - $ibRow['ib_quantity'];
          $piUpdate['pi_sellable'] = $piSellable = $changeQuantity;
          $changeNote .= '可用库存从' . $piRow['pi_sellable'] . '变为' . $piSellable;
          break;

        // 不良品（次品）货架
        case self::INVENTORY_REJECT:
          $changeQuantity = $piRow['pi_unsellable'] + $new_quantity - $ibRow['ib_quantity'];
          $piUpdate['pi_unsellable'] = $piUnsellable = $changeQuantity;
          $changeNote .= '不良品库存从' . $piRow['pi_unsellable'] . '变为' . $piUnsellable;
          break;

        default:
          throw new Exception('货架类型不在调整范围内');
      }

      if ($changeQuantity < 0) {
        throw new Exception('库存异常，调整失败');
      }

      // 更新产品库存
      $piUpdate['pi_update_time'] = date('Y-m-d H:i:s');
      Service_ProductInventory::update($piUpdate, $piRow['pi_id']);

      // 产品库存日志
      $piLog['product_id'] = $ibRow['product_id'];
      $piLog['product_barcode'] = $ibRow['product_barcode'];
      $piLog['warehouse_id'] = $ibRow['warehouse_id'];
      $piLog['reference_code'] = $ibRow['ib_id'];
      $piLog['user_id'] = Service_User::getUserId();
      $piLog['pil_onway'] = $piRow['pi_onway'];
      $piLog['pil_pending'] = $piRow['pi_pending'];
      $piLog['pil_sellable'] = $piSellable;
      $piLog['pil_unsellable'] = $piUnsellable;
      $piLog['pil_reserved'] = $piRow['pi_reserved'];
      $piLog['pil_shipped'] = $piRow['pi_shipped'];
      $piLog['pil_quantity'] = abs($ibRow['ib_quantity'] - $new_quantity);
      $piLog['pil_add_time'] = date('Y-m-d H:i:s');
      $piLog['pil_ip'] = $ibRow['product_id'];
      $piLog['pil_note'] = $changeNote;
      Service_ProductInventoryLog::add($piLog);

      // 库存变化日志
      $changeLog = array(
        'warehouse_id' => $ibRow['warehouse_id'],
        'product_id' => $ibRow['product_id'],
        'original_shelf_code' => $locationCode,
        'original_wa_type' => $locationType,
        'to_shelf_code' => $locationCode,
        'to_wa_type' => $locationType,
        'original_quantity' => $ibRow['ib_quantity'],
        'to_quantity' => $new_quantity,
        'change_reason' => $reason,
        'operator' => Service_User::getUserId(),
        'operation_type' => self::INVENTORY_ADJUSTMENT,
        'operation_time' => date('Y-m-d H:i:s'),
        'operation_ip' => Common_Common::getIP(),
        'operation_remark' => '',
      );
      Service_InventoryChangeProcess::log($changeLog);

      // 提交事务
      $db->commit();
    }
    catch (Exception $e) {
      // 事务回滚
      $db->rollback();
      return $e->getMessage();
    }

    return true;
  }

  /**
   * 移动货架
   */
  public function moveLocation($ib_id, $new_location, $move_quantity, $reason) {
    $db =  Common_Common::getAdapter();
    $db->beginTransaction();

    try {
      $ibRow = Service_InventoryBatch::getForUpdate($ib_id); // 锁行

      if (empty($ibRow)) {
        throw new Exception('批次库存不存在');
      }

      if (intval($ibRow['ib_hold_status']) !== 0) {
        throw new Exception('锁定的批次库存不能移货架');
      }

      if ($move_quantity > $ibRow['ib_quantity']) {
        throw new Exception('移动数量不能大于当前库存');
      }

      if ($ibRow['lc_code'] == $new_location) {
        throw new Exception('当前货架和目标货架一样，不用移动');
      }

      $aLocation = Service_Location::getByWhere(array('warehouse_id'=>$ibRow['warehouse_id'],'lc_code'=>$new_location));

      if (empty($aLocation)) {
        throw new Exception('目标货架【' . $new_location . '】不存在');
      }

      if ($aLocation['lc_status'] == 0) {
        throw new Exception('目标货架【' . $new_location . '】不可用');
      }

      // 原始货架相关信息
      $originalLoactionCode = $ibRow['lc_code'];
      $originalLocation = Service_Location::getByField($originalLoactionCode, 'lc_code');

      if (empty($originalLocation)) {
        throw new Exception('找不到与货架【' . $originalLoactionCode . '】相关的信息');
      }

      $originalLocationArea = $originalLocation['wa_code'];
      $originalWa = Service_WarehouseArea::getByField($originalLocationArea, 'wa_code');

      if (empty($originalWa)) {
        throw new Exception('找不到货架【' . $originalLoactionCode . '】对应仓库区域【' . $originalLocationArea . '】相关信息');
      }

      $originalLocationType = (int) $originalWa['wa_type'];
      $originalProduct = Service_Product::getByField($ibRow['product_id']); // 产品信息
      $originalPcId = $originalProduct ? (int) $originalProduct['pc_id'] : 0; // 产品分类ID

      // 目标货架相关信息
      $toLocationCode = $aLocation['lc_code'];
      $toLocationArea = $aLocation['wa_code'];
      $toWa = Service_WarehouseArea::getByField($toLocationArea, 'wa_code');

      if (empty($originalWa)) {
        throw new Exception('找不到货架【' . $toLocationCode . '】对应仓库区域【' . $toLocationArea . '】相关信息');
      }

      $toLocationType = (int) $toWa['wa_type'];
      $toPcId = (int) $toWa['pc_id']; // 目标货架能放的品类ID

      // 产品分类ID跟目标货架所在区域品类不同，抛出异常
      if (0 !== $toPcId && $originalPcId !== $toPcId) {
        throw new Exception('目标货架品类与该产品品类不一致');
      }

      $lcIbRow = Service_InventoryBatch::getByCondition(array('warehouse_id' => $ibRow['warehouse_id'], 'lc_code' => $new_location));

      // 判断目标货架是否产品，若有，与当前产品是否有冲突（主要表现在有保质期的产品）
      /*if (!empty($lcIbRow)) {
        // 遍历目标货架上所有产品，看是否与当前产品存在冲突
        foreach ($lcIbRow as $key => $value) {
          if ((int) $ibRow['product_id'] !== (int) $value['product_id']) {
            continue ;
          }

          // 判断相同产品的保质期是否一致，不一致抛出异常
          if (trim($ibRow['quality_date']) !== trim($lcIbRow['quality_date'])) {
            throw new Exception('目标货架已存在该产品，但保质期与当前产品不一致');
          }
        }
      }*/

      // 批次库存调整
      $this->resetInventoryBatch($ibRow, $originalLocationType, $toLocationCode, $toLocationType, $move_quantity, $reason);
      // 产品总库存调整
      $this->resetProductInventory($ibRow, $originalLocationType, $toLocationType, $move_quantity);

      // 库存变化日志
      $changeLog = array(
        'warehouse_id' => $ibRow['warehouse_id'],
        'product_id' => $ibRow['product_id'],
        'original_shelf_code' => $originalLoactionCode,
        'original_wa_type' => $originalLocationType,
        'to_shelf_code' => $toLocationCode,
        'to_wa_type' => $toLocationType,
        'original_quantity' => $ibRow['ib_quantity'],
        'to_quantity' => $move_quantity,
        'change_reason' => $reason,
        'operator' => Service_User::getUserId(),
        'operation_type' => self::INVENTORY_SHIFTING,
        'operation_time' => date('Y-m-d H:i:s'),
        'operation_ip' => Common_Common::getIP(),
        'operation_remark' => '',
      );
      Service_InventoryChangeProcess::log($changeLog);

      // 提交事务
      $db->commit();
    }
    catch (Exception $e) {
      // 事务回滚
      $db->rollback();
      return $e->getMessage();
    }

    return true;
  }

  /**
   * 移货架时调整批次库存表
   */
  private function resetInventoryBatch($ibRow, $originalType, $toCode, $toType, $move_quantity, $reason) {
    $originalType = (int) $originalType; // 原始货架类型
    $toCode = trim($toCode); // 目标货架
    $toType = (int) $toType; // 目标货架类型
    $move_quantity = (int) $move_quantity; // 移动数量
    $quantity = (int) $ibRow['ib_quantity']; // 原始数量
    $status = $ibRow['ib_status']; // 批次库存状态
    $outboundNum = (int) Service_InventoryBatchOutbound::sumQuantity($ibRow['ib_id']); // 核销表使用数量
    $log = '从货架【' . $ibRow['lc_code'] . '】移到货架【' . $toCode . '】，原因：' . $reason;

    if (self::INVENTORY_NORMAL === $originalType && self::INVENTORY_NORMAL !== $toType) {
      // 良品全部转换为不良品、过期，批次库存状态变为不可用
      $status = 0;
    }
    elseif (self::INVENTORY_NORMAL !== $originalType && self::INVENTORY_NORMAL === $toType) {
      // 不良品、过期转换为良品，批次库存变为可用
      $status = 1;
    }

    if ($quantity === $move_quantity) { // 全部移动
      // 把良品移动到其他类型货架，并核销表有使用数量，抛出异常
      if (self::INVENTORY_NORMAL === $originalType && self::INVENTORY_NORMAL !== $toType && $outboundNum > 0) {
        throw new Exception('移动数量最大值只能为' . ($quantity - $outboundNum));
      }

      $ibUpdate['ib_status'] = $status;
      $ibUpdate['lc_code'] = $toCode;
      $ibUpdate['ib_update_time'] = date('Y-m-d H:i:s');
      Service_InventoryBatch::update($ibUpdate, $ibRow['ib_id']);
      Service_InventoryBatchLog::log($ibRow, $ibRow['ib_quantity'], $ibRow['ib_quantity'], $log);
    }
    else { // 不是全部移动情况
      $allowedQuantity = $outboundNum > 0 ? ($quantity - $outboundNum) : $quantity; // 允许移动的最大数量

      if ($move_quantity > $allowedQuantity) {
        throw new Exception('移动数量最大值只能为' . $allowedQuantity);
      }

      // 新建一条批次库存
      $ibNew = $ibRow;
      unset($ibNew['ib_id']);
      $ibNew['lc_code'] = $toCode;
      $ibNew['ib_status'] = $status;
      $ibNew['ib_quantity'] = $move_quantity;
      $ibNew['ib_add_time'] = date('Y-m-d H:i:s');
      Service_InventoryBatch::add($ibNew);

      $new_quantity = $ibRow['ib_quantity'] - $move_quantity;
      $ibUpdate['ib_quantity'] = $new_quantity;
      $ibUpdate['ib_update_time'] = date('Y-m-d H:i:s');
      Service_InventoryBatch::update($ibUpdate, $ibRow['ib_id']);
      Service_InventoryBatchLog::log($ibRow, $ibRow['ib_quantity'], $new_quantity, $log);
    }
  }

  /**
   * 移货架时调整总库存表
   */
  private function resetProductInventory($ibRow, $originalType, $toType, $toQuantity) {
    $pid = (int) $ibRow['product_id']; // 产品ID
    $wid = (int) $ibRow['warehouse_id']; // 产品所在仓库ID
    $originalType = (int) $originalType; // 原始货架类型
    $toType = (int) $toType; // 目标货架类型
    $toQuantity = (int) $toQuantity; // 调整数量
    $allowedType = array( // 允许调整的货架类型
      self::INVENTORY_NORMAL, // 标准（良品）货架
      self::INVENTORY_REJECT, // 不良品（次品）货架
    );

    // 货架类型相同，不用调整产品总库存表
    if ($originalType === $toType) {
      return ;
    }

    $productInventory = Service_ProductInventory::getByWhProduct($wid, $pid);
    $piRow = Service_ProductInventory::getForUpdate((int) $productInventory['pi_id']); // 锁行

    if (empty($productInventory) || empty($piRow)) {
      throw new Exception('无法获取相关产品总库存信息');
    }

    if (self::INVENTORY_NORMAL === $originalType) { // 原始货架是标准（良品）类型
      if (self::INVENTORY_REJECT === $toType) { // 从良品到不良品，可销售额减少，不可销售额增加
        $piSellable = $piRow['pi_sellable'] - $toQuantity;
        $piUnsellable = $piRow['pi_unsellable'] + $toQuantity;
      }
      else {
        throw new Exception('该货架类型不在处理范围内');
      }
    }
    elseif (self::INVENTORY_REJECT === $originalType) { // 原始货架是不良品（次品）类型
      if (self::INVENTORY_NORMAL === $toType) { // 从不良品到良品，可销售额增加，不可销售额减少
        $piSellable = $piRow['pi_sellable'] + $toQuantity;
        $piUnsellable = $piRow['pi_unsellable'] - $toQuantity;
      }
      else {
        throw new Exception('该货架类型不在处理范围内');
      }
    }
    else {
      throw new Exception('该货架类型不在处理范围内');
    }

    if ($piSellable < 0) {
      throw new Exception('库存异常，调整失败');
    }

    $piUpdate['pi_sellable'] = $piSellable;
    $piUpdate['pi_unsellable'] = $piUnsellable;
    $piUpdate['pi_update_time'] = date('Y-m-d H:i:s');
    Service_ProductInventory::update($piUpdate, $piRow['pi_id']);

    // 产品库存日志
    $piLog['product_id'] = $pid;
    $piLog['product_barcode'] = $ibRow['product_barcode'];
    $piLog['warehouse_id'] = $wid;
    $piLog['reference_code'] = $ibRow['ib_id'];
    $piLog['user_id'] = Service_User::getUserId();
    $piLog['pil_onway'] = $piRow['pi_onway'];
    $piLog['pil_pending'] = $piRow['pi_pending'];
    $piLog['pil_sellable'] = $piSellable;
    $piLog['pil_unsellable'] = $piUnsellable;
    $piLog['pil_reserved'] = $piRow['pi_reserved'];
    $piLog['pil_shipped'] = $piRow['pi_shipped'];
    $piLog['pil_quantity'] = $toQuantity;
    $piLog['pil_add_time'] = date('Y-m-d H:i:s');
    $piLog['pil_ip'] = Common_Common::getIP();
    $piLog['pil_note'] = '批次库存【' . $ibRow['ib_id'] . '】调整，可用库存从' . $piRow['pi_sellable'] . '变为'. $piSellable;
    $piLog['pil_note'] .= '；不良品数量从' . $piRow['pi_unsellable'] . '变为' . $piUnsellable;
    Service_ProductInventoryLog::add($piLog);
  }
}
