<?php
/*
  $Id: _yuupack.php,v 1.2 2006/01/29 14:49:23 ryu Exp $

  YuuPack Shipping Calculator.
  Calculate shipping costs.

  2002/04/23 written by TAMURA Toshihiko (tamura@bitscope.co.jp)
  2003/04/12 modified for ms1
  2004/09/30 料金改定に対応
 */
/*
  $rate = new _YuuPack('yuupack','ゆうパック');
  $rate->SetOrigin('01', 'JP');  // 北海道から
  $rate->SetDest('13', 'JP');    // 東京都まで
  $rate->SetWeight(10);          // 重量(kg)または体積(l)
  $rate->SetSizeTable($size_table);// サイズごとの重量/体積の制限
  $quote = $rate->GetQuote();
  print $quote['type'] . "<br>";
  print $quote['cost'] . "\n";
*/
class _YuuPack {
    var $quote;
    var $OriginZone;
    var $OriginCountryCode = 'JP';
    var $DestZone;
    var $DestCountryCode = 'JP';
    var $Weight = 0;
    var $Length = 0;
    var $Width  = 0;
    var $Height = 0;
    var $SizeList;      // 60〜170 のサイズリスト(３辺計)
    var $SizeTable;     // index,３辺計(cmまで),重量(kgまで)
    var $MaxWeight = 0; // 最大重量(kg):0なら無制限
    var $Items = 1;     // 商品点数
    var $Mode;          // 'weight'/'volume'

    // コンストラクタ
    // $id:   module id
    // $titl: module name
    // $zone: 都道府県コード '01'〜'47'
    // $country: country code
    function _YuuPack($id, $title, $zone = NULL, $country = NULL) {
      $this->quote = array('id' => $id, 'title' => $title);
      if($zone) {
        $this->SetOrigin($zone, $country);
      }
      $this->SizeList = array(60,80,100,120,140,160,170);
      $this->SizeTable = array();
      $this->Mode = MODULE_SHIPPING_YUUPACK_MODE; // 'weight'/'volume'
    }
    // 発送元をセットする
    // $zone: 都道府県コード '01'〜'47'
    // $country: country code
    function SetOrigin($zone, $country = NULL) {
      $this->OriginZone = $zone;
      if($country) {
        $this->OriginCountryCode = $country;
      }
    }
    function SetDest($zone, $country = NULL) {
      $this->DestZone = $zone;
      if($country) {
        $this->DestCountryCode = $country;
      }
    }
    function SetMaxWeight($weight) {
      $this->MaxWeight = $weight;
    }
    function SetWeight($weight) {
      $this->Weight = $weight;
    }
    function SetSize($length = NULL, $width = NULL, $height = NULL) {
      if($length) {
        $this->Length = $length;
      }
      if($width) {
        $this->Width = $width;
      }
      if($height) {
        $this->Height = $height;
      }
    }
    // サイズごとの重量/体積の制限を記録する
    // $a_size_table : array(60=>w1, 80->w2,...)
    //
    function SetSizeTable($a_size_table) {
      $i = 0;
      $this->SizeTable = array();
      foreach ($this->SizeList as $size) {
        $weight = isset($a_size_table[$size]) ? $a_size_table[$size] : 0;
        $this->SizeTable[] = array($i++, $size, $weight);
      }
    }
    // 1パッケージに収められる最大重量または体積を返す
    function GetWeightLimut() {
      $limit = 0;
      foreach ($this->SizeTable as $a_size) {
        $limit = max($limit, $a_size[2]);
      }
      if ( 0 < $this->MaxWeight ) {
        $limit = min($this->MaxWeight, $limit);
      }
      return $limit;
    }
    // サイズ区分(0〜6)を返す
    // 規格外の場合は-1を返す
    //
    // 区分 ３辺計    重量
    // ---------------------------------
    // 0     60cmまで ?kgまで
    // 1     80cmまで ?kgまで
    // 2    100cmまで ?kgまで
    // 3    120cmまで ?kgまで
    // 4    140cmまで ?kgまで
    // 5    160cmまで ?kgまで
    // 6    170cmまで ?kgまで
    //
    function GetSizeClass($weight) {
      foreach ($this->SizeTable as $a_limit) {
        // $a_limit : array(index,３辺計,重量)
        if ($weight <= $a_limit[2]) {
          return $a_limit[0];
        }
      }
      return -1;  // 規格外
    }
    // カートの商品点数を記録する
    //
    function SetItems($items) {
      $this->Items = max($items, 1);
    }
    // 送付元と送付先からキーを作成する
    //
    function GetDistKey() {
        $s_key = '';
        $s_z1 = $this->GetLZone($this->OriginZone);
        $s_z2 = $this->GetLZone($this->DestZone);
        if ( $s_z1 && $s_z2 ) {
            // 地帯コードをアルファベット順に連結する
            $s_key = (ord($s_z1) < ord($s_z2)) ? ($s_z1 . $s_z2) : ($s_z2 . $s_z1);
        }
        return $s_key;
    }
    // 都道府県コードから地帯コードを取得する
    // $zone: 都道府県コード
    function GetLZone($zone) {
        // 都道府県コードを地帯コード('A'〜'K')に変換する
        // 北海道:'A' = 北海道
        // 東北  :'B' = 青森県,岩手県,秋田県,宮城県,山形県,福島県
        // 関東  :'C' = 茨城県,栃木県,群馬県,埼玉県,千葉県,東京都,神奈川県,山梨県
        // 信越  :'D' = 新潟県,長野県
        // 北陸  :'E' = 富山県,石川県,福井県
        // 東海  :'F' = 岐阜県,静岡県,愛知県,三重県
        // 近畿  :'G' = 滋賀県,京都府,大阪府,兵庫県,奈良県,和歌山県
        // 中国  :'H' = 鳥取県,島根県,岡山県,広島県,山口県
        // 四国  :'I' = 徳島県,香川県,愛媛県,高知県
        // 九州  :'J' = 福岡県,佐賀県,長崎県,大分県,熊本県,宮崎県,鹿児島県
        // 沖縄  :'K' = 沖縄県
        $a_zonemap = array(
        '01'=>'A',  // 北海道
        '02'=>'B',  // 青森県
        '03'=>'B',  // 岩手県
        '04'=>'B',  // 宮城県
        '05'=>'B',  // 秋田県
        '06'=>'B',  // 山形県
        '07'=>'B',  // 福島県
        '08'=>'C',  // 茨城県
        '09'=>'C',  // 栃木県
        '10'=>'C',  // 群馬県
        '11'=>'C',  // 埼玉県
        '12'=>'C',  // 千葉県
        '13'=>'C',  // 東京都
        '14'=>'C',  // 神奈川県
        '15'=>'D',  // 新潟県
        '16'=>'E',  // 富山県
        '17'=>'E',  // 石川県
        '18'=>'E',  // 福井県
        '19'=>'C',  // 山梨県
        '20'=>'D',  // 長野県
        '21'=>'F',  // 岐阜県
        '22'=>'F',  // 静岡県
        '23'=>'F',  // 愛知県
        '24'=>'F',  // 三重県
        '25'=>'G',  // 滋賀県
        '26'=>'G',  // 京都府
        '27'=>'G',  // 大阪府
        '28'=>'G',  // 兵庫県
        '29'=>'G',  // 奈良県
        '30'=>'G',  // 和歌山県
        '31'=>'H',  // 鳥取県
        '32'=>'H',  // 島根県
        '33'=>'H',  // 岡山県
        '34'=>'H',  // 広島県
        '35'=>'H',  // 山口県
        '36'=>'I',  // 徳島県
        '37'=>'I',  // 香川県
        '38'=>'I',  // 愛媛県
        '39'=>'I',  // 高知県
        '40'=>'J',  // 福岡県
        '41'=>'J',  // 佐賀県
        '42'=>'J',  // 長崎県
        '43'=>'J',  // 熊本県
        '44'=>'J',  // 大分県
        '45'=>'J',  // 宮崎県
        '46'=>'J',  // 鹿児島県
        '47'=>'K'   // 沖縄県
        );
        return $a_zonemap[$zone];
    }

    function GetQuote() {
        // 地帯・サイズ別の価格: サイズ => 価格(サイズ60,80,100,120,140,160,170)
        $a_pricerank = array(
        'N0'=>array( 600, 800,1000,1200,1400,1600,1700), // 県内
        'N1'=>array( 700, 900,1100,1300,1500,1700,1900), // 第1地帯 近距離
        'N2'=>array( 800,1000,1200,1400,1600,1800,2000), // 第2地帯   ↑
        'N3'=>array( 900,1100,1300,1500,1700,1900,2100), // 第3地帯
        'N4'=>array(1000,1200,1400,1600,1800,2000,2200), // 第4地帯
        'N5'=>array(1100,1300,1500,1700,1900,2100,2300), // 第5地帯
        'N6'=>array(1200,1400,1600,1800,2000,2200,2400), // 第6地帯   ↓
        'N7'=>array(1300,1500,1700,1900,2100,2300,2500), // 第7地帯 遠距離
        );
        // 地帯間の価格ランク
        // (参照) http://www.japanpost.jp/pressrelease/japanese/yubin/040929j201.html
        $a_dist_to_rank = array(
        'AA'=>'N0',
        'AB'=>'N2','BB'=>'N1',
        'AC'=>'N4','BC'=>'N1','CC'=>'N1',
        'AD'=>'N4','BD'=>'N1','CD'=>'N1','DD'=>'N1',
        'AE'=>'N5','BE'=>'N2','CE'=>'N1','DE'=>'N1','EE'=>'N1',
        'AF'=>'N5','BF'=>'N2','CF'=>'N1','DF'=>'N1','EF'=>'N1','FF'=>'N1',
        'AG'=>'N6','BG'=>'N3','CG'=>'N2','DG'=>'N2','EG'=>'N1','FG'=>'N1','GG'=>'N1',
        'AH'=>'N7','BH'=>'N4','CH'=>'N3','DH'=>'N3','EH'=>'N2','FH'=>'N2','GH'=>'N1','HH'=>'N1',
        'AI'=>'N7','BI'=>'N4','CI'=>'N3','DI'=>'N3','EI'=>'N2','FI'=>'N2','GI'=>'N1','HI'=>'N1','II'=>'N1',
        'AJ'=>'N7','BJ'=>'N6','CJ'=>'N5','DJ'=>'N5','EJ'=>'N3','FJ'=>'N3','GJ'=>'N2','HJ'=>'N1','IJ'=>'N2','JJ'=>'N1',
        'AK'=>'N7','BK'=>'N7','CK'=>'N6','DK'=>'N7','EK'=>'N7','FK'=>'N6','GK'=>'N6','HK'=>'N5','IK'=>'N6','JK'=>'N3','KK'=>'N0',
        );

        $s_key = $this->GetDistKey();          // 'AB'などの地帯キーを取得
        if ( $s_key ) {
            if ( ($this->OriginCountryCode == $this->DestCountryCode)
                 && ($this->OriginZone == $this->DestZone)
                 && $this->OriginZone
               ) {
                $s_rank = 'N0';                    // 県内
            } else {
                $s_rank = $a_dist_to_rank[$s_key]; // 'N1'などのランクを取得
            }

            if ( $s_rank ) {
                // 1パッケージに収められる最大重量または体積
                $limit = $this->GetWeightLimut();
                if ($limit < 0.1) {
                    $this->quote['error'] = MODULE_SHIPPING_YUUPACK_TEXT_CONFIG_ERROR;
                } elseif ( ($boxes = $this->GetBoxes($this->Weight, $limit)) < 1 ) {
                    $this->quote['error'] = MODULE_SHIPPING_YUUPACK_TEXT_OVERSIZE;
                } else {
                    $this->quote['boxes'] = $boxes;

                    // 余裕率
                    $room_ratio = ($this->Mode == 'volume' && $boxes < $this->Items)
                          ? max(1.0,MODULE_SHIPPING_YUUPACK_ROOM) : 1.0;

                    // 料金計算に用いる重量または体積(1個)
                    $wv = $this->Weight / $boxes * $room_ratio;

                    // 0〜6 のサイズクラス
                    $n_sizeclass = $this->GetSizeClass($wv);
                    if ($n_sizeclass < 0) {
                        $this->quote['error'] = MODULE_SHIPPING_YUUPACK_TEXT_OVERSIZE;
                    } else {
                        $this->quote['cost'] = $a_pricerank[$s_rank][$n_sizeclass] * $boxes;
                        $this->quote['wv'] = $wv;
                    }
                }
            //  $this->quote['DEBUG'] = ' zone='.$this->OriginZone.'-'.$this->DestZone //DEBUG
            //  . ' cost=' . $a_pricerank[$s_rank][$n_sizeclass]; //DEBUG
            } else {
                $this->quote['error'] = MODULE_SHIPPING_YUUPACK_TEXT_OUT_OF_AREA . '(' . $s_key .')';
            }
        } else {
            $this->quote['error'] = MODULE_SHIPPING_YUUPACK_TEXT_ILLEGAL_ZONE
             . '(' . $this->OriginZone . '=>' . $this->DestZone . ')';
        }

        return $this->quote;
    }
    // パッケージの個数を決定する
    // 戻り値: 正常なら個数を、異常なら-1を返す
    function GetBoxes($weight, $limit) {
        $max_boxes = min(100, $this->Items);
        $num = 0;
        do {
            if ( ++$num > $max_boxes ) {
              return -1;
            }
            // 余裕率
            $room_ratio = ($this->Mode == 'volume' && $num < $this->Items)
                          ? max(1.0,MODULE_SHIPPING_YUUPACK_ROOM) : 1.0;
        } while ($weight / $num * $room_ratio > $limit);

        return $num;
    }
}
?>